forked from tangled.org/core
this repo has no description

appview/pages/markup: smart commit autolink renderer

Implemented tangled link extension. This can be extended for other link
types like issue/pull references in future.

The `tangled.org` host is hardcoded right now.

Close: <https://tangled.org/tangled.org/core/issues/382>

Signed-off-by: Seongmin Lee <git@boltless.me>

authored by boltless.me and committed by tangled.org 87ef8dae d4c067fd

+202
+149
appview/pages/markup/extension/tangledlink.go
··· 1 + package extension 2 + 3 + import ( 4 + "net/url" 5 + "strings" 6 + 7 + "github.com/yuin/goldmark" 8 + "github.com/yuin/goldmark/ast" 9 + "github.com/yuin/goldmark/parser" 10 + "github.com/yuin/goldmark/renderer" 11 + "github.com/yuin/goldmark/text" 12 + "github.com/yuin/goldmark/util" 13 + ) 14 + 15 + // KindTangledLink is a NodeKind of the TangledLink node. 16 + var KindTangledLink = ast.NewNodeKind("TangledLink") 17 + 18 + type TangledLinkNode struct { 19 + ast.BaseInline 20 + Destination string 21 + Commit *TangledCommitLink 22 + // TODO: add more Tangled-link types 23 + } 24 + 25 + type TangledCommitLink struct { 26 + Sha string 27 + } 28 + 29 + var _ ast.Node = new(TangledLinkNode) 30 + 31 + // Dump implements [ast.Node]. 32 + func (n *TangledLinkNode) Dump(source []byte, level int) { 33 + ast.DumpHelper(n, source, level, nil, nil) 34 + } 35 + 36 + // Kind implements [ast.Node]. 37 + func (n *TangledLinkNode) Kind() ast.NodeKind { 38 + return KindTangledLink 39 + } 40 + 41 + type tangledLinkTransformer struct { 42 + host string 43 + } 44 + 45 + var _ parser.ASTTransformer = new(tangledLinkTransformer) 46 + 47 + // Transform implements [parser.ASTTransformer]. 48 + func (t *tangledLinkTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) { 49 + ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) { 50 + if !entering { 51 + return ast.WalkContinue, nil 52 + } 53 + 54 + var dest string 55 + 56 + switch n := n.(type) { 57 + case *ast.AutoLink: 58 + dest = string(n.URL(reader.Source())) 59 + case *ast.Link: 60 + // maybe..? not sure 61 + default: 62 + return ast.WalkContinue, nil 63 + } 64 + 65 + if sha := t.parseLinkCommitSha(dest); sha != "" { 66 + newLink := &TangledLinkNode{ 67 + Destination: dest, 68 + Commit: &TangledCommitLink{ 69 + Sha: sha, 70 + }, 71 + } 72 + n.Parent().ReplaceChild(n.Parent(), n, newLink) 73 + } 74 + 75 + return ast.WalkContinue, nil 76 + }) 77 + } 78 + 79 + func (t *tangledLinkTransformer) parseLinkCommitSha(raw string) string { 80 + u, err := url.Parse(raw) 81 + if err != nil || u.Host != "tangled.org" { 82 + return "" 83 + } 84 + 85 + // /{owner}/{repo}/commit/<sha> 86 + parts := strings.Split(strings.Trim(u.Path, "/"), "/") 87 + if len(parts) != 4 || parts[2] != "commit" { 88 + return "" 89 + } 90 + 91 + sha := parts[3] 92 + 93 + // basic sha validation 94 + if len(sha) < 7 { 95 + return "" 96 + } 97 + for _, c := range sha { 98 + if !strings.ContainsRune("0123456789abcdef", c) { 99 + return "" 100 + } 101 + } 102 + 103 + return sha[:8] 104 + } 105 + 106 + type tangledLinkRenderer struct{} 107 + 108 + var _ renderer.NodeRenderer = new(tangledLinkRenderer) 109 + 110 + // RegisterFuncs implements [renderer.NodeRenderer]. 111 + func (r *tangledLinkRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { 112 + reg.Register(KindTangledLink, r.renderTangledLink) 113 + } 114 + 115 + func (r *tangledLinkRenderer) renderTangledLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { 116 + link := node.(*TangledLinkNode) 117 + 118 + if link.Commit != nil { 119 + if entering { 120 + w.WriteString(`<a href="`) 121 + w.WriteString(link.Destination) 122 + w.WriteString(`"><code>`) 123 + w.WriteString(link.Commit.Sha) 124 + } else { 125 + w.WriteString(`</code></a>`) 126 + } 127 + } 128 + 129 + return ast.WalkContinue, nil 130 + } 131 + 132 + type tangledLinkExt struct { 133 + host string 134 + } 135 + 136 + var _ goldmark.Extender = new(tangledLinkExt) 137 + 138 + func (e *tangledLinkExt) Extend(m goldmark.Markdown) { 139 + m.Parser().AddOptions(parser.WithASTTransformers( 140 + util.Prioritized(&tangledLinkTransformer{host: e.host}, 500), 141 + )) 142 + m.Renderer().AddOptions(renderer.WithNodeRenderers( 143 + util.Prioritized(&tangledLinkRenderer{}, 500), 144 + )) 145 + } 146 + 147 + func NewTangledLinkExt(host string) goldmark.Extender { 148 + return &tangledLinkExt{host} 149 + }
+1
appview/pages/markup/markdown.go
··· 67 67 ), 68 68 callout.CalloutExtention, 69 69 textension.AtExt, 70 + textension.NewTangledLinkExt("tangled.org"), 70 71 emoji.Emoji, 71 72 ), 72 73 goldmark.WithParserOptions(