forked from tangled.org/core
this repo has no description

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

Changed files
+99 -15
appview
pages
+10 -5
appview/pages/markup/camo.go
···
return fmt.Sprintf("%s/%s/%s", baseURL, signature, hexURL)
}
-
func (rctx *RenderContext) camoImageLinkTransformer(img *ast.Image) {
// don't camo on dev
if rctx.IsDev {
-
return
}
-
dst := string(img.Destination)
-
if rctx.CamoUrl != "" && rctx.CamoSecret != "" {
-
img.Destination = []byte(generateCamoURL(rctx.CamoUrl, rctx.CamoSecret, dst))
}
}
···
return fmt.Sprintf("%s/%s/%s", baseURL, signature, hexURL)
}
+
func (rctx *RenderContext) camoImageLinkTransformer(dst string) string {
// don't camo on dev
if rctx.IsDev {
+
return dst
}
if rctx.CamoUrl != "" && rctx.CamoSecret != "" {
+
return generateCamoURL(rctx.CamoUrl, rctx.CamoSecret, dst)
}
+
+
return dst
+
}
+
+
func (rctx *RenderContext) camoImageLinkAstTransformer(img *ast.Image) {
+
dst := string(img.Destination)
+
img.Destination = []byte(rctx.camoImageLinkTransformer(dst))
}
+89 -10
appview/pages/markup/markdown.go
···
import (
"bytes"
"net/url"
"path"
"github.com/microcosm-cc/bluemonday"
"github.com/yuin/goldmark"
···
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
)
···
if err := md.Convert([]byte(source), &buf); err != nil {
return source
}
-
return buf.String()
}
func (rctx *RenderContext) Sanitize(html string) string {
···
case *ast.Link:
a.rctx.relativeLinkTransformer(n)
case *ast.Image:
-
a.rctx.imageFromKnotTransformer(n)
-
a.rctx.camoImageLinkTransformer(n)
}
case RendererTypeDefault:
switch n := n.(type) {
case *ast.Image:
-
a.rctx.imageFromKnotTransformer(n)
-
a.rctx.camoImageLinkTransformer(n)
}
}
···
link.Destination = []byte(newPath)
}
-
func (rctx *RenderContext) imageFromKnotTransformer(img *ast.Image) {
-
dst := string(img.Destination)
-
if isAbsoluteUrl(dst) {
-
return
}
scheme := "https"
···
actualPath),
}
newPath := parsedURL.String()
-
img.Destination = []byte(newPath)
}
// actualPath decides when to join the file path with the
···
import (
"bytes"
+
"fmt"
+
"io"
"net/url"
"path"
+
"strings"
"github.com/microcosm-cc/bluemonday"
"github.com/yuin/goldmark"
···
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
+
htmlparse "golang.org/x/net/html"
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
)
···
if err := md.Convert([]byte(source), &buf); err != nil {
return source
}
+
+
var processed strings.Builder
+
if err := postProcess(rctx, strings.NewReader(buf.String()), &processed); err != nil {
+
return source
+
}
+
+
return processed.String()
+
}
+
+
func postProcess(ctx *RenderContext, input io.Reader, output io.Writer) error {
+
node, err := htmlparse.Parse(io.MultiReader(
+
strings.NewReader("<html><body>"),
+
input,
+
strings.NewReader("</body></html>"),
+
))
+
if err != nil {
+
return fmt.Errorf("failed to parse html: %w", err)
+
}
+
+
if node.Type == htmlparse.DocumentNode {
+
node = node.FirstChild
+
}
+
+
visitNode(ctx, node)
+
+
newNodes := make([]*htmlparse.Node, 0, 5)
+
+
if node.Data == "html" {
+
node = node.FirstChild
+
for node != nil && node.Data != "body" {
+
node = node.NextSibling
+
}
+
}
+
if node != nil {
+
if node.Data == "body" {
+
child := node.FirstChild
+
for child != nil {
+
newNodes = append(newNodes, child)
+
child = child.NextSibling
+
}
+
} else {
+
newNodes = append(newNodes, node)
+
}
+
}
+
+
for _, node := range newNodes {
+
if err := htmlparse.Render(output, node); err != nil {
+
return fmt.Errorf("failed to render processed html: %w", err)
+
}
+
}
+
+
return nil
+
}
+
+
func visitNode(ctx *RenderContext, node *htmlparse.Node) {
+
switch node.Type {
+
case htmlparse.ElementNode:
+
if node.Data == "img" {
+
for i, attr := range node.Attr {
+
if attr.Key != "src" {
+
continue
+
}
+
attr.Val = ctx.imageFromKnotTransformer(attr.Val)
+
attr.Val = ctx.camoImageLinkTransformer(attr.Val)
+
node.Attr[i] = attr
+
}
+
}
+
+
for n := node.FirstChild; n != nil; n = n.NextSibling {
+
visitNode(ctx, n)
+
}
+
default:
+
}
}
func (rctx *RenderContext) Sanitize(html string) string {
···
case *ast.Link:
a.rctx.relativeLinkTransformer(n)
case *ast.Image:
+
a.rctx.imageFromKnotAstTransformer(n)
+
a.rctx.camoImageLinkAstTransformer(n)
}
case RendererTypeDefault:
switch n := n.(type) {
case *ast.Image:
+
a.rctx.imageFromKnotAstTransformer(n)
+
a.rctx.camoImageLinkAstTransformer(n)
}
}
···
link.Destination = []byte(newPath)
}
+
func (rctx *RenderContext) imageFromKnotTransformer(dst string) string {
if isAbsoluteUrl(dst) {
+
return dst
}
scheme := "https"
···
actualPath),
}
newPath := parsedURL.String()
+
return newPath
+
}
+
+
func (rctx *RenderContext) imageFromKnotAstTransformer(img *ast.Image) {
+
dst := string(img.Destination)
+
img.Destination = []byte(rctx.imageFromKnotTransformer(dst))
}
// actualPath decides when to join the file path with the