Monorepo for Tangled tangled.org

appview/pages/markup: smart commit autolink renderer #1003

merged opened by boltless.me targeting master from sl/uupvwnkzxzom
Labels

None yet.

assignee

None yet.

Participants 3
AT URI
at://did:plc:xasnlahkri4ewmbuzly2rlc5/sh.tangled.repo.pull/3mcufej76qu22
+166
Diff #0
+118
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 + const ( 16 + tangledCommitLinkAttr = "tangled-commit-link" 17 + ) 18 + 19 + type tangledLinkTransformer struct { 20 + host string 21 + } 22 + 23 + var _ parser.ASTTransformer = new(tangledLinkTransformer) 24 + 25 + // Transform implements [parser.ASTTransformer]. 26 + func (t *tangledLinkTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) { 27 + ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) { 28 + if !entering { 29 + return ast.WalkContinue, nil 30 + } 31 + 32 + link, ok := n.(*ast.AutoLink) 33 + if !ok { 34 + return ast.WalkContinue, nil 35 + } 36 + 37 + dest := string(link.URL(reader.Source())) 38 + if sha := t.parseLinkCommitSha(dest); sha != "" { 39 + link.SetAttributeString(tangledCommitLinkAttr, []byte(sha)) 40 + } 41 + 42 + return ast.WalkContinue, nil 43 + }) 44 + } 45 + 46 + func (t *tangledLinkTransformer) parseLinkCommitSha(raw string) string { 47 + u, err := url.Parse(raw) 48 + if err != nil || u.Host != "tangled.org" { 49 + return "" 50 + } 51 + 52 + // /{owner}/{repo}/commit/<sha> 53 + parts := strings.Split(strings.Trim(u.Path, "/"), "/") 54 + if len(parts) != 4 || parts[2] != "commit" { 55 + return "" 56 + } 57 + 58 + sha := parts[3] 59 + 60 + // basic sha validation 61 + if len(sha) < 7 { 62 + return "" 63 + } 64 + for _, c := range sha { 65 + if !strings.ContainsRune("0123456789abcdef", c) { 66 + return "" 67 + } 68 + } 69 + 70 + return sha[:8] 71 + } 72 + 73 + type tangledLinkRenderer struct{} 74 + 75 + var _ renderer.NodeRenderer = new(tangledLinkRenderer) 76 + 77 + // RegisterFuncs implements [renderer.NodeRenderer]. 78 + func (r *tangledLinkRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { 79 + reg.Register(ast.KindAutoLink, r.renderAutoLink) 80 + } 81 + 82 + func (r *tangledLinkRenderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { 83 + link := node.(*ast.AutoLink) 84 + sha, ok := link.AttributeString(tangledCommitLinkAttr) 85 + if !ok { 86 + return ast.WalkContinue, nil 87 + } 88 + 89 + if entering { 90 + w.WriteString(`<a href="`) 91 + w.Write(link.URL(source)) 92 + w.WriteString(`"><code>`) 93 + w.Write(sha.([]byte)) 94 + return ast.WalkSkipChildren, nil 95 + } else { 96 + w.WriteString("</code></a>") 97 + } 98 + return ast.WalkContinue, nil 99 + } 100 + 101 + type tangledLinkExt struct { 102 + host string 103 + } 104 + 105 + var _ goldmark.Extender = new(tangledLinkExt) 106 + 107 + func (e *tangledLinkExt) Extend(m goldmark.Markdown) { 108 + m.Parser().AddOptions(parser.WithASTTransformers( 109 + util.Prioritized(&tangledLinkTransformer{host: e.host}, 500), 110 + )) 111 + m.Renderer().AddOptions(renderer.WithNodeRenderers( 112 + util.Prioritized(&tangledLinkRenderer{}, 500), 113 + )) 114 + } 115 + 116 + func NewTangledLinkExt(host string) goldmark.Extender { 117 + return &tangledLinkExt{host} 118 + }
+1
appview/pages/markup/markdown.go
··· 67 ), 68 callout.CalloutExtention, 69 textension.AtExt, 70 emoji.Emoji, 71 ), 72 goldmark.WithParserOptions(
··· 67 ), 68 callout.CalloutExtention, 69 textension.AtExt, 70 + textension.NewTangledLinkExt("tangled.org"), 71 emoji.Emoji, 72 ), 73 goldmark.WithParserOptions(

History

5 rounds 11 comments
sign up or login to add to the discussion
2 commits
expand
appview/pages/markup: smart commit autolink renderer
appview: strip scheme from CoreConfig.AppviewHost
3/3 success
expand
expand 2 comments

@oppi.li makes sense. I completely forgot the indigo oauth behavior. I reverted the change.

lgtm, this bug is still present:

next, in appview/config/config.go, the default value for AppivewHost should be set to tangled.org

but should be a quick fix, i can apply that on master!

pull request successfully merged
2 commits
expand
appview/pages/markup: smart commit autolink renderer
appview: strip scheme from CoreConfig.AppviewHost
expand 3 comments

when using dev, the callback URL should always be http://127.0.0.1/oauth/callback (or http://localhost), regardless of what the AppviewHost is set to. the port is ignored iirc.

next, in appview/config/config.go, the default value for AppivewHost should be set to tangled.org (without the scheme).

i think BaseUrl is good naming choice. happy with that!

when using dev, the callback URL should always be http://127.0.0.1/oauth/callback (or http://localhost), regardless of what the AppviewHost is set to. the port is ignored iirc.

If someone set AppviewHost, I suspect they are testing on that host. I used to test on my own domain long ago. http://127.0.0.1:3000/oauth/callback is what was used in original code.

next, in appview/config/config.go, the default value for AppivewHost should be set to tangled.org

will do!

if you are using oauth.NewLocalhostConfig (as we do in dev), the redirect URL's host cannot be anything other than localhost or 127.0.0.1, if it is something else, you will get an error when performing the PAR request:

URL must use \"localhost\", \"127.0.0.1\" or \"[::1]\" as hostname at body.redirect_uri]"

when in dev, we should just ignore AppviewHost and use one of the predefined hosts. in the original code, the host is 127.0.0.1 which is one of the predefined hosts. the port is ignored anyway.

2 commits
expand
appview/pages/markup: smart commit autolink renderer
appview: strip scheme from CoreConfig.AppviewHost
3/3 success
expand
expand 5 comments
  • here, i think Url is a bit ambiguous to have on config, could use a better name here

rest of the changeset lgtm, will give this a test, thanks!

after some local testing, this seems to oauth, i am unable to login!

2026/01/21 06:25:06 WARN auth server request failed request=PAR statusCode=400 body="map[error:invalid_request error_description:Invalid authorization request: URL must use \"localhost\", \"127.0.0.1\" or \"[::1]\" as hostname at body.redirect_uri]"
2026/01/21 06:25:06 ERRO appview: failed to start auth handler=Login err="auth request failed: PAR request failed (HTTP 400): invalid_request"

@oppi.li I can't reproduce the oauth issue. Have you tried after setting TANGLED_APPVIEWHOST=127.0.0.1? you should remove the scheme now.

for Url() naming, would BaseUrl() be fine enough?

Also naming/consistency nit: tangledlink.gotangled_link.go? Keeping in line with reference_link.go. Rest looks OK!

@anirudh.fi I'm matching with extension/atlink.go. Doesn't reference_link.go violates the golang conventions? yes, I'm the one who wrote both... inconsistencies all over the place 🙈

1 commit
expand
appview/pages/markup: smart commit autolink renderer
3/3 success
expand
expand 1 comment

would be good to avoid hardcoding the appview host here!

boltless.me submitted #0
1 commit
expand
appview/pages/markup: smart commit autolink renderer
1/3 failed, 2/3 success
expand
expand 0 comments