Artifact 8d602cf07bda45be44702f2546627744b465f653a1ff432875feb2815a8da01d:
- File
internal/markup/resolver.go
— part of check-in
[420206e59f]
at
2026-08-21 20:40:05
on branch trunk
— markdown composer: Write/Preview tabs, slash commands, described check-in links; forum hashes resolve
Preview runs the production render pipeline (markup.preview op). Check-in refs carry their comment as hover title + visible excerpt. composer.js loads only on composer pages — the base stays one-script. (user: dmestas, size: 4016) [annotate] [blame] [check-ins using]
// resolver.go — zit reference resolution (0002 §2 rework: git-ref hooks // replaced with zit resolution). Runs AFTER sanitization, over text // nodes only — the links it inserts are quarry-built, and code spans, // pre blocks, and existing links are left alone. package markup import ( "bytes" "regexp" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) var ( wikiRef = regexp.MustCompile(`\[\[([^\[\]|]{1,100})\]\]`) hashRef = regexp.MustCompile(`\b[0-9a-f]{10,64}\b`) ) // HashDescriber is an OPTIONAL upgrade on Resolver: a one-line human // description for a resolvable hash (a check-in's comment). Described // links carry it as a hover title and a visible excerpt. type HashDescriber interface { DescribeHash(prefix string) string } // excerpt clips s at n runes on a rune boundary, marking the cut. func excerpt(s string, n int) string { runes := []rune(s) if len(runes) <= n { return s } return string(runes[:n]) + "…" } func resolveRefs(in []byte, r Resolver) []byte { body := &html.Node{Type: html.ElementNode, Data: "body", DataAtom: atom.Body} nodes, err := html.ParseFragment(bytes.NewReader(in), body) if err != nil { return in } var out bytes.Buffer for _, n := range nodes { walkResolve(n, r) if err := html.Render(&out, n); err != nil { return in } } return out.Bytes() } var skipTags = map[string]bool{"a": true, "code": true, "pre": true} func walkResolve(n *html.Node, r Resolver) { if n.Type == html.ElementNode && skipTags[n.Data] { return } for c := n.FirstChild; c != nil; c = c.NextSibling { walkResolve(c, r) } // Collect text children first: splicing replaces nodes mid-walk. var texts []*html.Node for c := n.FirstChild; c != nil; c = c.NextSibling { if c.Type == html.TextNode { texts = append(texts, c) } } for _, t := range texts { splice(n, t, r) } } // splice replaces one text node with text/link/text/... nodes for each // resolvable reference found in it. func splice(parent, text *html.Node, r Resolver) { s := text.Data type ref struct { start, end int label, url, desc string } var refs []ref for _, m := range wikiRef.FindAllStringSubmatchIndex(s, -1) { name := s[m[2]:m[3]] if url := r.ResolveWiki(name); url != "" { refs = append(refs, ref{start: m[0], end: m[1], label: name, url: url}) } } hd, _ := r.(HashDescriber) for _, m := range hashRef.FindAllStringIndex(s, -1) { tok := s[m[0]:m[1]] if url := r.ResolveHash(tok); url != "" { rf := ref{start: m[0], end: m[1], label: tok, url: url} if hd != nil { rf.desc = hd.DescribeHash(tok) } refs = append(refs, rf) } } if len(refs) == 0 { return } // Wiki and hash matches cannot overlap ([[..]] never lexes as a hex // word), so a simple position sort orders the splice. for i := 1; i < len(refs); i++ { for j := i; j > 0 && refs[j-1].start > refs[j].start; j-- { refs[j-1], refs[j] = refs[j], refs[j-1] } } pos := 0 insert := func(nn *html.Node) { parent.InsertBefore(nn, text) } for _, rf := range refs { if rf.start > pos { insert(&html.Node{Type: html.TextNode, Data: s[pos:rf.start]}) } a := &html.Node{Type: html.ElementNode, Data: "a", DataAtom: atom.A, Attr: []html.Attribute{{Key: "href", Val: rf.url}}} a.AppendChild(&html.Node{Type: html.TextNode, Data: rf.label}) // A described reference (a check-in's comment) renders GitHub's // way: the message rides the link — full first line as the // hover title, an excerpt visible beside the hash. if rf.desc != "" { a.Attr = append(a.Attr, html.Attribute{Key: "title", Val: rf.desc}) } insert(a) if rf.desc != "" { span := &html.Node{Type: html.ElementNode, Data: "span", DataAtom: atom.Span, Attr: []html.Attribute{{Key: "class", Val: "cref"}}} span.AppendChild(&html.Node{Type: html.TextNode, Data: " (" + excerpt(rf.desc, 60) + ")"}) insert(span) } pos = rf.end } if pos < len(s) { insert(&html.Node{Type: html.TextNode, Data: s[pos:]}) } parent.RemoveChild(text) }