all repos

mugit @ b7356aab67e27e0bcf94ab1bdfa9c336aacc39d7

🐮 git server that your cow will love
1 files changed, 90 insertions(+), 39 deletions(-)
mdx: fix raw <img> sources
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-02-17 21:37:40 +0200
Change ID: nyvxwyxtmkqkoorkkxlwkpmoluusvlly
Parent: 03f9034
M internal/mdx/relative_link.go

@@ -1,15 +1,18 @@

package mdx import ( + "bytes" "fmt" "net/url" "path" "path/filepath" + "regexp" "strings" "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/renderer" "github.com/yuin/goldmark/text" "github.com/yuin/goldmark/util" )

@@ -32,70 +35,118 @@ type relativeLink struct{}

func (e *relativeLink) Extend(m goldmark.Markdown) { m.Parser().AddOptions( - parser.WithASTTransformers( - util.Prioritized(&relinkTransformer{}, 100), - ), + parser.WithASTTransformers(util.Prioritized(&relinkTransformer{}, 100)), + ) + m.Renderer().AddOptions( + renderer.WithNodeRenderers(util.Prioritized(&rawBlockRenderer{}, 100)), ) } +type rawBlock struct { + ast.BaseBlock + data []byte +} + +var rawBlockKind = ast.NewNodeKind("RawBlock") + +func (r *rawBlock) Kind() ast.NodeKind { return rawBlockKind } +func (r *rawBlock) Dump(_ []byte, _ int) {} + +type rawBlockRenderer struct{} + +func (r *rawBlockRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { + reg.Register(rawBlockKind, func(w util.BufWriter, _ []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + if entering { + w.Write(node.(*rawBlock).data) + } + return ast.WalkContinue, nil + }) +} + type relinkTransformer struct{} func (t *relinkTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) { repoName, _ := pc.Get(repoNameKey).(string) baseDir, _ := pc.Get(baseDirKey).(string) - if repoName == "" { - return - } - _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { return ast.WalkContinue, nil } - var dest *[]byte switch v := n.(type) { case *ast.Image: - dest = &v.Destination + if rewritten := t.rewriteDest(v.Destination, repoName, baseDir); rewritten != nil { + v.Destination = rewritten + } case *ast.Link: - dest = &v.Destination - default: - return ast.WalkContinue, nil + if rewritten := t.rewriteDest(v.Destination, repoName, baseDir); rewritten != nil { + v.Destination = rewritten + } + case *ast.RawHTML: + var buf []byte + for i := 0; i < v.Segments.Len(); i++ { + buf = append(buf, reader.Value(v.Segments.At(i))...) + } + updated := t.rewriteSrc(buf, repoName, baseDir) + if !bytes.Equal(updated, buf) { + str := ast.NewString(updated) + str.SetRaw(true) + n.Parent().ReplaceChild(n.Parent(), n, str) + } + case *ast.HTMLBlock: + var buf []byte + for i := 0; i < v.Lines().Len(); i++ { + buf = append(buf, reader.Value(v.Lines().At(i))...) + } + updated := t.rewriteSrc(buf, repoName, baseDir) + if !bytes.Equal(updated, buf) { + n.Parent().ReplaceChild(n.Parent(), n, &rawBlock{data: updated}) + } } - urlStr := string(*dest) - - // skip absolute URLs - if strings.HasPrefix(urlStr, "http://") || - strings.HasPrefix(urlStr, "https://") || - strings.HasPrefix(urlStr, "//") || - strings.HasPrefix(urlStr, "#") || - strings.HasPrefix(urlStr, "mailto:") || - strings.HasPrefix(urlStr, "data:") { - return ast.WalkContinue, nil - } + return ast.WalkContinue, nil + }) +} - urlStr = strings.TrimPrefix(urlStr, "./") +func (t *relinkTransformer) rewriteDest(dest []byte, repoName, baseDir string) []byte { + urlStr := string(dest) + if strings.HasPrefix(urlStr, "http://") || + strings.HasPrefix(urlStr, "https://") || + strings.HasPrefix(urlStr, "//") || + strings.HasPrefix(urlStr, "#") || + strings.HasPrefix(urlStr, "mailto:") || + strings.HasPrefix(urlStr, "data:") { + return nil + } + urlStr = strings.TrimPrefix(urlStr, "./") - var absPath string - if after, ok := strings.CutPrefix(urlStr, "/"); ok { - absPath = after // abs from repo root + var absPath string + if after, ok := strings.CutPrefix(urlStr, "/"); ok { + absPath = after + } else { + if baseDir == "" || baseDir == "." { + absPath = urlStr } else { - // relative to repo location - if baseDir == "" || baseDir == "." { - absPath = urlStr - } else { - absPath = path.Join(baseDir, urlStr) - } + absPath = path.Join(baseDir, urlStr) } + } - absPath = path.Clean(absPath) - absPath = strings.TrimPrefix(absPath, "/") + absPath = strings.TrimPrefix(path.Clean(absPath), "/") - // FIXME:hardcoded link - *dest = fmt.Appendf(nil, "/%s/blob/HEAD/%s?raw=true", - url.PathEscape(repoName), absPath) + // FIXME: hardcoded ref and link + return fmt.Appendf(nil, "/%s/blob/HEAD/%s?raw=true", url.PathEscape(repoName), absPath) +} - return ast.WalkContinue, nil +var imgSrcRe = regexp.MustCompile(`src="([^"]*)"`) + +func (t *relinkTransformer) rewriteSrc(buf []byte, repoName, baseDir string) []byte { + return imgSrcRe.ReplaceAllFunc(buf, func(match []byte) []byte { + src := imgSrcRe.FindSubmatch(match)[1] + rewritten := t.rewriteDest(src, repoName, baseDir) + if rewritten == nil { + return match + } + return append([]byte(`src="`), append(rewritten, '"')...) }) }