blob: 72ac25592234a066888f87ae72674fac711815bd [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package site
import (
"bytes"
"regexp"
"strings"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
"golang.org/x/go.dev/cmd/internal/html/template"
"golang.org/x/go.dev/cmd/internal/tmplfunc"
)
// markdownToHTML converts markdown to HTML using the renderer and settings that Hugo uses.
func markdownToHTML(markdown string) (template.HTML, error) {
// parser.WithHeadingAttribute allows custom ids on headings.
// html.WithUnsafe allows use of raw HTML, which we need for tables.
md := goldmark.New(
goldmark.WithParserOptions(
parser.WithHeadingAttribute(),
parser.WithAutoHeadingID(),
parser.WithASTTransformers(util.Prioritized(mdTransformFunc(mdLink), 1)),
),
goldmark.WithRendererOptions(html.WithUnsafe()),
goldmark.WithExtensions(
extension.NewTypographer(),
extension.NewLinkify(
extension.WithLinkifyAllowedProtocols([][]byte{[]byte("http"), []byte("https")}),
extension.WithLinkifyEmailRegexp(regexp.MustCompile(`[^\x00-\x{10FFFF}]`)), // impossible
),
),
)
var buf bytes.Buffer
if err := md.Convert([]byte(markdown), &buf); err != nil {
return "", err
}
return template.HTML(buf.Bytes()), nil
}
// mdTransformFunc is a func implementing parser.ASTTransformer.
type mdTransformFunc func(*ast.Document, text.Reader, parser.Context)
func (f mdTransformFunc) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
f(node, reader, pc)
}
// mdLink walks doc, adding rel=noreferrer target=_blank to non-relative links.
func mdLink(doc *ast.Document, _ text.Reader, _ parser.Context) {
mdLinkWalk(doc)
}
func mdLinkWalk(n ast.Node) {
switch n := n.(type) {
case *ast.Link:
dest := string(n.Destination)
if strings.HasPrefix(dest, "https://") || strings.HasPrefix(dest, "http://") {
n.SetAttributeString("rel", []byte("noreferrer"))
n.SetAttributeString("target", []byte("_blank"))
}
return
case *ast.AutoLink:
// All autolinks are non-relative.
n.SetAttributeString("rel", []byte("noreferrer"))
n.SetAttributeString("target", []byte("_blank"))
return
}
for child := n.FirstChild(); child != nil; child = child.NextSibling() {
mdLinkWalk(child)
}
}
// markdownTemplateToHTML converts a markdown template to HTML,
// first applying the template execution engine and then interpreting
// the result as markdown to be converted to HTML.
// This is the same logic used by the Go web site.
func (site *Site) markdownTemplateToHTML(markdown string, p *page) (template.HTML, error) {
t := site.clone().New(p.file)
if err := tmplfunc.Parse(t, string(p.data)); err != nil {
return "", err
}
var buf bytes.Buffer
if err := t.Execute(&buf, p.params); err != nil {
return "", err
}
return markdownToHTML(buf.String())
}