all repos

mugit @ b7356aa

馃惍 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
Authored at: 2026-02-17 21:35:30 +0200
Change ID: nyvxwyxtmkqkoorkkxlwkpmoluusvlly
Parent: 03f9034
M internal/mdx/relative_link.go
路路路
        1
        1
         package mdx

      
        2
        2
         

      
        3
        3
         import (

      
        
        4
        +	"bytes"

      
        4
        5
         	"fmt"

      
        5
        6
         	"net/url"

      
        6
        7
         	"path"

      
        7
        8
         	"path/filepath"

      
        
        9
        +	"regexp"

      
        8
        10
         	"strings"

      
        9
        11
         

      
        10
        12
         	"github.com/yuin/goldmark"

      
        11
        13
         	"github.com/yuin/goldmark/ast"

      
        12
        14
         	"github.com/yuin/goldmark/parser"

      
        
        15
        +	"github.com/yuin/goldmark/renderer"

      
        13
        16
         	"github.com/yuin/goldmark/text"

      
        14
        17
         	"github.com/yuin/goldmark/util"

      
        15
        18
         )

      路路路
        32
        35
         

      
        33
        36
         func (e *relativeLink) Extend(m goldmark.Markdown) {

      
        34
        37
         	m.Parser().AddOptions(

      
        35
        
        -		parser.WithASTTransformers(

      
        36
        
        -			util.Prioritized(&relinkTransformer{}, 100),

      
        37
        
        -		),

      
        
        38
        +		parser.WithASTTransformers(util.Prioritized(&relinkTransformer{}, 100)),

      
        
        39
        +	)

      
        
        40
        +	m.Renderer().AddOptions(

      
        
        41
        +		renderer.WithNodeRenderers(util.Prioritized(&rawBlockRenderer{}, 100)),

      
        38
        42
         	)

      
        39
        43
         }

      
        40
        44
         

      
        
        45
        +type rawBlock struct {

      
        
        46
        +	ast.BaseBlock

      
        
        47
        +	data []byte

      
        
        48
        +}

      
        
        49
        +

      
        
        50
        +var rawBlockKind = ast.NewNodeKind("RawBlock")

      
        
        51
        +

      
        
        52
        +func (r *rawBlock) Kind() ast.NodeKind   { return rawBlockKind }

      
        
        53
        +func (r *rawBlock) Dump(_ []byte, _ int) {}

      
        
        54
        +

      
        
        55
        +type rawBlockRenderer struct{}

      
        
        56
        +

      
        
        57
        +func (r *rawBlockRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {

      
        
        58
        +	reg.Register(rawBlockKind, func(w util.BufWriter, _ []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {

      
        
        59
        +		if entering {

      
        
        60
        +			w.Write(node.(*rawBlock).data)

      
        
        61
        +		}

      
        
        62
        +		return ast.WalkContinue, nil

      
        
        63
        +	})

      
        
        64
        +}

      
        
        65
        +

      
        41
        66
         type relinkTransformer struct{}

      
        42
        67
         

      
        43
        68
         func (t *relinkTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {

      
        44
        69
         	repoName, _ := pc.Get(repoNameKey).(string)

      
        45
        70
         	baseDir, _ := pc.Get(baseDirKey).(string)

      
        46
        71
         

      
        47
        
        -	if repoName == "" {

      
        48
        
        -		return

      
        49
        
        -	}

      
        50
        
        -

      
        51
        72
         	_ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {

      
        52
        73
         		if !entering {

      
        53
        74
         			return ast.WalkContinue, nil

      
        54
        75
         		}

      
        55
        76
         

      
        56
        
        -		var dest *[]byte

      
        57
        77
         		switch v := n.(type) {

      
        58
        78
         		case *ast.Image:

      
        59
        
        -			dest = &v.Destination

      
        
        79
        +			if rewritten := t.rewriteDest(v.Destination, repoName, baseDir); rewritten != nil {

      
        
        80
        +				v.Destination = rewritten

      
        
        81
        +			}

      
        60
        82
         		case *ast.Link:

      
        61
        
        -			dest = &v.Destination

      
        62
        
        -		default:

      
        63
        
        -			return ast.WalkContinue, nil

      
        
        83
        +			if rewritten := t.rewriteDest(v.Destination, repoName, baseDir); rewritten != nil {

      
        
        84
        +				v.Destination = rewritten

      
        
        85
        +			}

      
        
        86
        +		case *ast.RawHTML:

      
        
        87
        +			var buf []byte

      
        
        88
        +			for i := 0; i < v.Segments.Len(); i++ {

      
        
        89
        +				buf = append(buf, reader.Value(v.Segments.At(i))...)

      
        
        90
        +			}

      
        
        91
        +			updated := t.rewriteSrc(buf, repoName, baseDir)

      
        
        92
        +			if !bytes.Equal(updated, buf) {

      
        
        93
        +				str := ast.NewString(updated)

      
        
        94
        +				str.SetRaw(true)

      
        
        95
        +				n.Parent().ReplaceChild(n.Parent(), n, str)

      
        
        96
        +			}

      
        
        97
        +		case *ast.HTMLBlock:

      
        
        98
        +			var buf []byte

      
        
        99
        +			for i := 0; i < v.Lines().Len(); i++ {

      
        
        100
        +				buf = append(buf, reader.Value(v.Lines().At(i))...)

      
        
        101
        +			}

      
        
        102
        +			updated := t.rewriteSrc(buf, repoName, baseDir)

      
        
        103
        +			if !bytes.Equal(updated, buf) {

      
        
        104
        +				n.Parent().ReplaceChild(n.Parent(), n, &rawBlock{data: updated})

      
        
        105
        +			}

      
        64
        106
         		}

      
        65
        107
         

      
        66
        
        -		urlStr := string(*dest)

      
        67
        
        -

      
        68
        
        -		// skip absolute URLs

      
        69
        
        -		if strings.HasPrefix(urlStr, "http://") ||

      
        70
        
        -			strings.HasPrefix(urlStr, "https://") ||

      
        71
        
        -			strings.HasPrefix(urlStr, "//") ||

      
        72
        
        -			strings.HasPrefix(urlStr, "#") ||

      
        73
        
        -			strings.HasPrefix(urlStr, "mailto:") ||

      
        74
        
        -			strings.HasPrefix(urlStr, "data:") {

      
        75
        
        -			return ast.WalkContinue, nil

      
        76
        
        -		}

      
        
        108
        +		return ast.WalkContinue, nil

      
        
        109
        +	})

      
        
        110
        +}

      
        77
        111
         

      
        78
        
        -		urlStr = strings.TrimPrefix(urlStr, "./")

      
        
        112
        +func (t *relinkTransformer) rewriteDest(dest []byte, repoName, baseDir string) []byte {

      
        
        113
        +	urlStr := string(dest)

      
        
        114
        +	if strings.HasPrefix(urlStr, "http://") ||

      
        
        115
        +		strings.HasPrefix(urlStr, "https://") ||

      
        
        116
        +		strings.HasPrefix(urlStr, "//") ||

      
        
        117
        +		strings.HasPrefix(urlStr, "#") ||

      
        
        118
        +		strings.HasPrefix(urlStr, "mailto:") ||

      
        
        119
        +		strings.HasPrefix(urlStr, "data:") {

      
        
        120
        +		return nil

      
        
        121
        +	}

      
        
        122
        +	urlStr = strings.TrimPrefix(urlStr, "./")

      
        79
        123
         

      
        80
        
        -		var absPath string

      
        81
        
        -		if after, ok := strings.CutPrefix(urlStr, "/"); ok {

      
        82
        
        -			absPath = after // abs from repo root

      
        
        124
        +	var absPath string

      
        
        125
        +	if after, ok := strings.CutPrefix(urlStr, "/"); ok {

      
        
        126
        +		absPath = after

      
        
        127
        +	} else {

      
        
        128
        +		if baseDir == "" || baseDir == "." {

      
        
        129
        +			absPath = urlStr

      
        83
        130
         		} else {

      
        84
        
        -			// relative to repo location

      
        85
        
        -			if baseDir == "" || baseDir == "." {

      
        86
        
        -				absPath = urlStr

      
        87
        
        -			} else {

      
        88
        
        -				absPath = path.Join(baseDir, urlStr)

      
        89
        
        -			}

      
        
        131
        +			absPath = path.Join(baseDir, urlStr)

      
        90
        132
         		}

      
        
        133
        +	}

      
        91
        134
         

      
        92
        
        -		absPath = path.Clean(absPath)

      
        93
        
        -		absPath = strings.TrimPrefix(absPath, "/")

      
        
        135
        +	absPath = strings.TrimPrefix(path.Clean(absPath), "/")

      
        94
        136
         

      
        95
        
        -		// FIXME:hardcoded link

      
        96
        
        -		*dest = fmt.Appendf(nil, "/%s/blob/HEAD/%s?raw=true",

      
        97
        
        -			url.PathEscape(repoName), absPath)

      
        
        137
        +	// FIXME: hardcoded ref and link

      
        
        138
        +	return fmt.Appendf(nil, "/%s/blob/HEAD/%s?raw=true", url.PathEscape(repoName), absPath)

      
        
        139
        +}

      
        98
        140
         

      
        99
        
        -		return ast.WalkContinue, nil

      
        
        141
        +var imgSrcRe = regexp.MustCompile(`src="([^"]*)"`)

      
        
        142
        +

      
        
        143
        +func (t *relinkTransformer) rewriteSrc(buf []byte, repoName, baseDir string) []byte {

      
        
        144
        +	return imgSrcRe.ReplaceAllFunc(buf, func(match []byte) []byte {

      
        
        145
        +		src := imgSrcRe.FindSubmatch(match)[1]

      
        
        146
        +		rewritten := t.rewriteDest(src, repoName, baseDir)

      
        
        147
        +		if rewritten == nil {

      
        
        148
        +			return match

      
        
        149
        +		}

      
        
        150
        +		return append([]byte(`src="`), append(rewritten, '"')...)

      
        100
        151
         	})

      
        101
        152
         }