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: markdown: introduce post-processor step to transform image links

This adds a post-processor step to the markdown parser to parse raw
html images `<img>` and resolve their `src` attributes if needed

authored by

BrookJeynes and committed by
Tangled
6dbaf7f9 9436c543

+99 -15
+10 -5
appview/pages/markup/camo.go
··· 17 17 return fmt.Sprintf("%s/%s/%s", baseURL, signature, hexURL) 18 18 } 19 19 20 - func (rctx *RenderContext) camoImageLinkTransformer(img *ast.Image) { 20 + func (rctx *RenderContext) camoImageLinkTransformer(dst string) string { 21 21 // don't camo on dev 22 22 if rctx.IsDev { 23 - return 23 + return dst 24 24 } 25 - 26 - dst := string(img.Destination) 27 25 28 26 if rctx.CamoUrl != "" && rctx.CamoSecret != "" { 29 - img.Destination = []byte(generateCamoURL(rctx.CamoUrl, rctx.CamoSecret, dst)) 27 + return generateCamoURL(rctx.CamoUrl, rctx.CamoSecret, dst) 30 28 } 29 + 30 + return dst 31 + } 32 + 33 + func (rctx *RenderContext) camoImageLinkAstTransformer(img *ast.Image) { 34 + dst := string(img.Destination) 35 + img.Destination = []byte(rctx.camoImageLinkTransformer(dst)) 31 36 }
+89 -10
appview/pages/markup/markdown.go
··· 3 3 4 4 import ( 5 5 "bytes" 6 + "fmt" 7 + "io" 6 8 "net/url" 7 9 "path" 10 + "strings" 8 11 9 12 "github.com/microcosm-cc/bluemonday" 10 13 "github.com/yuin/goldmark" ··· 17 14 "github.com/yuin/goldmark/renderer/html" 18 15 "github.com/yuin/goldmark/text" 19 16 "github.com/yuin/goldmark/util" 17 + htmlparse "golang.org/x/net/html" 20 18 21 19 "tangled.sh/tangled.sh/core/appview/pages/repoinfo" 22 20 ) ··· 65 61 if err := md.Convert([]byte(source), &buf); err != nil { 66 62 return source 67 63 } 68 - return buf.String() 64 + 65 + var processed strings.Builder 66 + if err := postProcess(rctx, strings.NewReader(buf.String()), &processed); err != nil { 67 + return source 68 + } 69 + 70 + return processed.String() 71 + } 72 + 73 + func postProcess(ctx *RenderContext, input io.Reader, output io.Writer) error { 74 + node, err := htmlparse.Parse(io.MultiReader( 75 + strings.NewReader("<html><body>"), 76 + input, 77 + strings.NewReader("</body></html>"), 78 + )) 79 + if err != nil { 80 + return fmt.Errorf("failed to parse html: %w", err) 81 + } 82 + 83 + if node.Type == htmlparse.DocumentNode { 84 + node = node.FirstChild 85 + } 86 + 87 + visitNode(ctx, node) 88 + 89 + newNodes := make([]*htmlparse.Node, 0, 5) 90 + 91 + if node.Data == "html" { 92 + node = node.FirstChild 93 + for node != nil && node.Data != "body" { 94 + node = node.NextSibling 95 + } 96 + } 97 + if node != nil { 98 + if node.Data == "body" { 99 + child := node.FirstChild 100 + for child != nil { 101 + newNodes = append(newNodes, child) 102 + child = child.NextSibling 103 + } 104 + } else { 105 + newNodes = append(newNodes, node) 106 + } 107 + } 108 + 109 + for _, node := range newNodes { 110 + if err := htmlparse.Render(output, node); err != nil { 111 + return fmt.Errorf("failed to render processed html: %w", err) 112 + } 113 + } 114 + 115 + return nil 116 + } 117 + 118 + func visitNode(ctx *RenderContext, node *htmlparse.Node) { 119 + switch node.Type { 120 + case htmlparse.ElementNode: 121 + if node.Data == "img" { 122 + for i, attr := range node.Attr { 123 + if attr.Key != "src" { 124 + continue 125 + } 126 + attr.Val = ctx.imageFromKnotTransformer(attr.Val) 127 + attr.Val = ctx.camoImageLinkTransformer(attr.Val) 128 + node.Attr[i] = attr 129 + } 130 + } 131 + 132 + for n := node.FirstChild; n != nil; n = n.NextSibling { 133 + visitNode(ctx, n) 134 + } 135 + default: 136 + } 69 137 } 70 138 71 139 func (rctx *RenderContext) Sanitize(html string) string { ··· 177 101 case *ast.Link: 178 102 a.rctx.relativeLinkTransformer(n) 179 103 case *ast.Image: 180 - a.rctx.imageFromKnotTransformer(n) 181 - a.rctx.camoImageLinkTransformer(n) 104 + a.rctx.imageFromKnotAstTransformer(n) 105 + a.rctx.camoImageLinkAstTransformer(n) 182 106 } 183 107 case RendererTypeDefault: 184 108 switch n := n.(type) { 185 109 case *ast.Image: 186 - a.rctx.imageFromKnotTransformer(n) 187 - a.rctx.camoImageLinkTransformer(n) 110 + a.rctx.imageFromKnotAstTransformer(n) 111 + a.rctx.camoImageLinkAstTransformer(n) 188 112 } 189 113 } 190 114 ··· 206 130 link.Destination = []byte(newPath) 207 131 } 208 132 209 - func (rctx *RenderContext) imageFromKnotTransformer(img *ast.Image) { 210 - dst := string(img.Destination) 211 - 133 + func (rctx *RenderContext) imageFromKnotTransformer(dst string) string { 212 134 if isAbsoluteUrl(dst) { 213 - return 135 + return dst 214 136 } 215 137 216 138 scheme := "https" ··· 229 155 actualPath), 230 156 } 231 157 newPath := parsedURL.String() 232 - img.Destination = []byte(newPath) 158 + return newPath 159 + } 160 + 161 + func (rctx *RenderContext) imageFromKnotAstTransformer(img *ast.Image) { 162 + dst := string(img.Destination) 163 + img.Destination = []byte(rctx.imageFromKnotTransformer(dst)) 233 164 } 234 165 235 166 // actualPath decides when to join the file path with the