Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

appview: pages/markup: resolve link destinations against the current dir

Fixes a bug reported on Discord with relative links inside a
repository's subdir would resolve incorrectly since we were naively
"absoluting" the link destination.

Now, we resolve it against the current (parent) directory. For example,
if lol/x.md has a link

[foo](./some.png) => /lol/some.png (instead of just /some.png)

authored by

Anirudh Oppiliappan and committed by
Tangled
cd60c898 3f274571

+87 -17
+50 -15
appview/pages/markup/markdown.go
··· 6 6 "net/url" 7 7 "path" 8 8 9 + "github.com/microcosm-cc/bluemonday" 9 10 "github.com/yuin/goldmark" 10 11 "github.com/yuin/goldmark/ast" 11 12 "github.com/yuin/goldmark/extension" ··· 14 13 "github.com/yuin/goldmark/renderer/html" 15 14 "github.com/yuin/goldmark/text" 16 15 "github.com/yuin/goldmark/util" 16 + 17 17 "tangled.sh/tangled.sh/core/appview/pages/repoinfo" 18 18 ) 19 19 ··· 64 62 return buf.String() 65 63 } 66 64 65 + func (rctx *RenderContext) Sanitize(html string) string { 66 + policy := bluemonday.UGCPolicy() 67 + policy.AllowAttrs("align", "style").Globally() 68 + policy.AllowStyles( 69 + "margin", 70 + "padding", 71 + "text-align", 72 + "font-weight", 73 + "text-decoration", 74 + "padding-left", 75 + "padding-right", 76 + "padding-top", 77 + "padding-bottom", 78 + "margin-left", 79 + "margin-right", 80 + "margin-top", 81 + "margin-bottom", 82 + ) 83 + return policy.Sanitize(html) 84 + } 85 + 67 86 type MarkdownTransformer struct { 68 87 rctx *RenderContext 69 88 } ··· 97 74 98 75 switch a.rctx.RendererType { 99 76 case RendererTypeRepoMarkdown: 100 - switch n.(type) { 77 + switch n := n.(type) { 101 78 case *ast.Link: 102 - a.rctx.relativeLinkTransformer(n.(*ast.Link)) 79 + a.rctx.relativeLinkTransformer(n) 103 80 case *ast.Image: 104 - a.rctx.imageFromKnotTransformer(n.(*ast.Image)) 105 - a.rctx.camoImageLinkTransformer(n.(*ast.Image)) 81 + a.rctx.imageFromKnotTransformer(n) 82 + a.rctx.camoImageLinkTransformer(n) 106 83 } 107 - 108 84 case RendererTypeDefault: 109 - switch n.(type) { 85 + switch n := n.(type) { 110 86 case *ast.Image: 111 - a.rctx.imageFromKnotTransformer(n.(*ast.Image)) 112 - a.rctx.camoImageLinkTransformer(n.(*ast.Image)) 87 + a.rctx.imageFromKnotTransformer(n) 88 + a.rctx.camoImageLinkTransformer(n) 113 89 } 114 90 } 115 91 ··· 117 95 } 118 96 119 97 func (rctx *RenderContext) relativeLinkTransformer(link *ast.Link) { 98 + 120 99 dst := string(link.Destination) 121 100 122 101 if isAbsoluteUrl(dst) { 123 102 return 124 103 } 125 104 126 - newPath := path.Join("/", rctx.RepoInfo.FullName(), "tree", rctx.RepoInfo.Ref, dst) 105 + actualPath := rctx.actualPath(dst) 106 + 107 + newPath := path.Join("/", rctx.RepoInfo.FullName(), "tree", rctx.RepoInfo.Ref, actualPath) 127 108 link.Destination = []byte(newPath) 128 109 } 129 110 ··· 137 112 return 138 113 } 139 114 140 - // strip leading './' 141 - if len(dst) >= 2 && dst[0:2] == "./" { 142 - dst = dst[2:] 143 - } 144 - 145 115 scheme := "https" 146 116 if rctx.IsDev { 147 117 scheme = "http" 148 118 } 119 + 120 + actualPath := rctx.actualPath(dst) 121 + 149 122 parsedURL := &url.URL{ 150 123 Scheme: scheme, 151 124 Host: rctx.Knot, ··· 152 129 rctx.RepoInfo.Name, 153 130 "raw", 154 131 url.PathEscape(rctx.RepoInfo.Ref), 155 - dst), 132 + actualPath), 156 133 } 157 134 newPath := parsedURL.String() 158 135 img.Destination = []byte(newPath) 136 + } 137 + 138 + // actualPath decides when to join the file path with the 139 + // current repository directory (essentially only when the link 140 + // destination is relative. if it's absolute then we assume the 141 + // user knows what they're doing.) 142 + func (rctx *RenderContext) actualPath(dst string) string { 143 + if path.IsAbs(dst) { 144 + return dst 145 + } 146 + 147 + return path.Join(rctx.CurrentDir, dst) 159 148 } 160 149 161 150 func isAbsoluteUrl(link string) bool {
+3 -2
appview/pages/pages.go
··· 432 432 case ".md", ".markdown", ".mdown", ".mkdn", ".mkd": 433 433 htmlString = p.rctx.RenderMarkdown(params.Readme) 434 434 params.Raw = false 435 - params.HTMLReadme = template.HTML(bluemonday.UGCPolicy().Sanitize(htmlString)) 435 + params.HTMLReadme = template.HTML(p.rctx.Sanitize(htmlString)) 436 436 default: 437 437 htmlString = string(params.Readme) 438 438 params.Raw = true ··· 562 562 case markup.FormatMarkdown: 563 563 p.rctx.RepoInfo = params.RepoInfo 564 564 p.rctx.RendererType = markup.RendererTypeRepoMarkdown 565 - params.RenderedContents = template.HTML(bluemonday.UGCPolicy().Sanitize(p.rctx.RenderMarkdown(params.Contents))) 565 + htmlString := p.rctx.RenderMarkdown(params.Contents) 566 + params.RenderedContents = template.HTML(p.rctx.Sanitize(htmlString)) 566 567 } 567 568 } 568 569
+1
appview/pages/repoinfo/repoinfo.go
··· 63 63 SourceHandle string 64 64 Ref string 65 65 DisableFork bool 66 + CurrentDir string 66 67 } 67 68 68 69 // each tab on a repo could have some metadata:
+2
appview/state/repo.go
··· 946 946 Description string 947 947 CreatedAt string 948 948 Ref string 949 + CurrentDir string 949 950 } 950 951 951 952 func (f *FullyResolvedRepo) OwnerDid() string { ··· 1105 1104 PullCount: pullCount, 1106 1105 }, 1107 1106 DisableFork: disableFork, 1107 + CurrentDir: f.CurrentDir, 1108 1108 } 1109 1109 1110 1110 if sourceRepo != nil {
+31
appview/state/repo_util.go
··· 7 7 "log" 8 8 "math/big" 9 9 "net/http" 10 + "net/url" 11 + "path" 12 + "strings" 10 13 11 14 "github.com/bluesky-social/indigo/atproto/identity" 12 15 "github.com/bluesky-social/indigo/atproto/syntax" ··· 62 59 ref = defaultBranch.Branch 63 60 } 64 61 62 + currentDir := path.Dir(extractPathAfterRef(r.URL.EscapedPath(), ref)) 63 + 65 64 // pass through values from the middleware 66 65 description, ok := r.Context().Value("repoDescription").(string) 67 66 addedAt, ok := r.Context().Value("repoAddedAt").(string) ··· 76 71 Description: description, 77 72 CreatedAt: addedAt, 78 73 Ref: ref, 74 + CurrentDir: currentDir, 79 75 }, nil 80 76 } 81 77 ··· 87 81 } else { 88 82 return repoinfo.RolesInRepo{} 89 83 } 84 + } 85 + 86 + // extractPathAfterRef gets the actual repository path 87 + // after the ref. for example: 88 + // 89 + // /@icyphox.sh/foorepo/blob/main/abc/xyz/ => abc/xyz/ 90 + func extractPathAfterRef(fullPath, ref string) string { 91 + fullPath = strings.TrimPrefix(fullPath, "/") 92 + 93 + ref = url.PathEscape(ref) 94 + 95 + prefixes := []string{ 96 + fmt.Sprintf("blob/%s/", ref), 97 + fmt.Sprintf("tree/%s/", ref), 98 + fmt.Sprintf("raw/%s/", ref), 99 + } 100 + 101 + for _, prefix := range prefixes { 102 + idx := strings.Index(fullPath, prefix) 103 + if idx != -1 { 104 + return fullPath[idx+len(prefix):] 105 + } 106 + } 107 + 108 + return "" 90 109 } 91 110 92 111 func uniqueEmails(commits []*object.Commit) []string {