package main import ( "strings" "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/text" "github.com/yuin/goldmark/util" ) type extLinksTransformer struct{} func (extLinksTransformer) Transform(doc *ast.Document, reader text.Reader, pc parser.Context) { ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { return ast.WalkContinue, nil } link, ok := n.(*ast.Link) if !ok { return ast.WalkContinue, nil } if isExternalURL(string(link.Destination)) { link.SetAttribute([]byte("target"), []byte("_blank")) link.SetAttribute([]byte("rel"), []byte("noopener noreferrer")) } return ast.WalkContinue, nil }) } func isExternalURL(dest string) bool { if strings.HasPrefix(dest, "//") { return true } i := strings.Index(dest, ":") if i <= 0 { return false } for _, c := range dest[:i] { if !(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') && !(c >= '0' && c <= '9') && c != '+' && c != '-' && c != '.' { return false } } return true } type extLinksExt struct{} func newExtLinksExt() goldmark.Extender { return &extLinksExt{} } func (e *extLinksExt) Extend(m goldmark.Markdown) { m.Parser().AddOptions(parser.WithASTTransformers( util.Prioritized(extLinksTransformer{}, 999), )) }