all repos

mugit @ 6e228d9192b98e484881b8e702734ae920511538

馃惍 git server that your cow will love
2 files changed, 108 insertions(+), 3 deletions(-)
ui: render images with relative links
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-02-15 16:46:22 +0200
Authored at: 2026-02-15 16:44:09 +0200
Change ID: xzzqlkskrvnsqxzwrzotyxsymzpspqvu
Parent: 850c26a
M internal/handlers/repo.go
路路路
        19
        19
         	"github.com/yuin/goldmark/extension"

      
        20
        20
         	"github.com/yuin/goldmark/renderer/html"

      
        21
        21
         	"olexsmir.xyz/mugit/internal/git"

      
        
        22
        +	"olexsmir.xyz/mugit/internal/mdx"

      
        22
        23
         )

      
        23
        24
         

      
        24
        25
         func (h *handlers) indexHandler(w http.ResponseWriter, r *http.Request) {

      路路路
        408
        409
         	goldmark.WithExtensions(

      
        409
        410
         		extension.GFM,

      
        410
        411
         		extension.Linkify,

      
        
        412
        +		mdx.RelativeLink,

      
        411
        413
         	))

      
        412
        414
         

      
        413
        415
         func (h *handlers) renderReadme(r *git.Repo) (template.HTML, error) {

      
        414
        
        -	if v, found := h.readmeCache.Get(r.Name()); found {

      
        
        416
        +	name := r.Name()

      
        
        417
        +	if v, found := h.readmeCache.Get(name); found {

      
        415
        418
         		return v, nil

      
        416
        419
         	}

      
        417
        420
         

      路路路
        432
        435
         			switch ext {

      
        433
        436
         			case ".md", ".markdown", ".mkd":

      
        434
        437
         				var buf bytes.Buffer

      
        435
        
        -				if cerr := markdown.Convert([]byte(content), &buf); cerr != nil {

      
        
        438
        +				if cerr := markdown.Convert([]byte(content), &buf,

      
        
        439
        +					mdx.NewRelativeLinkCtx(name, readme)); cerr != nil {

      
        436
        440
         					return "", cerr

      
        437
        441
         				}

      
        438
        442
         				readmeContents = template.HTML(buf.String())

      路路路
        443
        447
         		}

      
        444
        448
         	}

      
        445
        449
         

      
        446
        
        -	h.readmeCache.Set(r.Name(), readmeContents)

      
        
        450
        +	h.readmeCache.Set(name, readmeContents)

      
        447
        451
         	return readmeContents, nil

      
        448
        452
         }

      
A internal/mdx/relative_link.go
路路路
        
        1
        +package mdx

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"fmt"

      
        
        5
        +	"net/url"

      
        
        6
        +	"path"

      
        
        7
        +	"path/filepath"

      
        
        8
        +	"strings"

      
        
        9
        +

      
        
        10
        +	"github.com/yuin/goldmark"

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

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

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

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

      
        
        15
        +)

      
        
        16
        +

      
        
        17
        +var (

      
        
        18
        +	repoNameKey = parser.NewContextKey()

      
        
        19
        +	baseDirKey  = parser.NewContextKey()

      
        
        20
        +)

      
        
        21
        +

      
        
        22
        +func NewRelativeLinkCtx(repoName, readmePath string) parser.ParseOption {

      
        
        23
        +	ctx := parser.NewContext()

      
        
        24
        +	ctx.Set(repoNameKey, repoName)

      
        
        25
        +	ctx.Set(baseDirKey, filepath.Dir(readmePath))

      
        
        26
        +	return parser.WithContext(ctx)

      
        
        27
        +}

      
        
        28
        +

      
        
        29
        +var RelativeLink = &relativeLink{}

      
        
        30
        +

      
        
        31
        +type relativeLink struct{}

      
        
        32
        +

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

      
        
        34
        +	m.Parser().AddOptions(

      
        
        35
        +		parser.WithASTTransformers(

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

      
        
        37
        +		),

      
        
        38
        +	)

      
        
        39
        +}

      
        
        40
        +

      
        
        41
        +type relinkTransformer struct{}

      
        
        42
        +

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

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

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

      
        
        46
        +

      
        
        47
        +	if repoName == "" {

      
        
        48
        +		return

      
        
        49
        +	}

      
        
        50
        +

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

      
        
        52
        +		if !entering {

      
        
        53
        +			return ast.WalkContinue, nil

      
        
        54
        +		}

      
        
        55
        +

      
        
        56
        +		var dest *[]byte

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

      
        
        58
        +		case *ast.Image:

      
        
        59
        +			dest = &v.Destination

      
        
        60
        +		case *ast.Link:

      
        
        61
        +			dest = &v.Destination

      
        
        62
        +		default:

      
        
        63
        +			return ast.WalkContinue, nil

      
        
        64
        +		}

      
        
        65
        +

      
        
        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
        +		}

      
        
        77
        +

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

      
        
        79
        +

      
        
        80
        +		var absPath string

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

      
        
        82
        +			absPath = after // abs from repo root

      
        
        83
        +		} else {

      
        
        84
        +			// relative to repo location

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

      
        
        86
        +				absPath = urlStr

      
        
        87
        +			} else {

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

      
        
        89
        +			}

      
        
        90
        +		}

      
        
        91
        +

      
        
        92
        +		absPath = path.Clean(absPath)

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

      
        
        94
        +

      
        
        95
        +		// FIXME:hardcoded link

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

      
        
        97
        +			url.PathEscape(repoName), absPath)

      
        
        98
        +

      
        
        99
        +		return ast.WalkContinue, nil

      
        
        100
        +	})

      
        
        101
        +}