From 1d8dfdb1da128406a07d0d9ecffb4b08b7b50a96 Mon Sep 17 00:00:00 2001 From: luxick Date: Wed, 29 Apr 2026 13:17:42 +0200 Subject: [PATCH] Force external links into new tabs --- extlinks.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++ render.go | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 extlinks.go diff --git a/extlinks.go b/extlinks.go new file mode 100644 index 0000000..e99a1f4 --- /dev/null +++ b/extlinks.go @@ -0,0 +1,57 @@ +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), + )) +} diff --git a/render.go b/render.go index 9170988..fc4aa53 100644 --- a/render.go +++ b/render.go @@ -22,7 +22,7 @@ var md goldmark.Markdown // targets against the filesystem. func initMarkdown(root string) { md = goldmark.New( - goldmark.WithExtensions(extension.GFM, extension.Table, newWikiLinkExt(root)), + goldmark.WithExtensions(extension.GFM, extension.Table, newWikiLinkExt(root), newExtLinksExt()), goldmark.WithParserOptions(parser.WithAutoHeadingID()), goldmark.WithRendererOptions(html.WithUnsafe()), )