forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
this repo has no description
fork
Configure Feed
Select the types of activity you want to include in your feed.
1// Package markup is an umbrella package for all markups and their renderers.
2package markup
3
4import (
5 "bytes"
6 "net/url"
7 "path"
8
9 "github.com/yuin/goldmark"
10 "github.com/yuin/goldmark/ast"
11 "github.com/yuin/goldmark/extension"
12 "github.com/yuin/goldmark/parser"
13 "github.com/yuin/goldmark/text"
14 "github.com/yuin/goldmark/util"
15 "tangled.sh/tangled.sh/core/appview/pages/repoinfo"
16)
17
18// RendererType defines the type of renderer to use based on context
19type RendererType int
20
21const (
22 // RendererTypeRepoMarkdown is for repository documentation markdown files
23 RendererTypeRepoMarkdown RendererType = iota
24 // RendererTypeDefault is non-repo markdown, like issues/pulls/comments.
25 RendererTypeDefault
26)
27
28// RenderContext holds the contextual data for rendering markdown.
29// It can be initialized empty, and that'll skip any transformations.
30type RenderContext struct {
31 CamoUrl string
32 CamoSecret string
33 repoinfo.RepoInfo
34 IsDev bool
35 RendererType RendererType
36}
37
38func (rctx *RenderContext) RenderMarkdown(source string) string {
39 md := goldmark.New(
40 goldmark.WithExtensions(extension.GFM),
41 goldmark.WithParserOptions(
42 parser.WithAutoHeadingID(),
43 ),
44 )
45
46 if rctx != nil {
47 var transformers []util.PrioritizedValue
48
49 transformers = append(transformers, util.Prioritized(&MarkdownTransformer{rctx: rctx}, 10000))
50
51 md.Parser().AddOptions(
52 parser.WithASTTransformers(transformers...),
53 )
54 }
55
56 var buf bytes.Buffer
57 if err := md.Convert([]byte(source), &buf); err != nil {
58 return source
59 }
60 return buf.String()
61}
62
63type MarkdownTransformer struct {
64 rctx *RenderContext
65}
66
67func (a *MarkdownTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
68 _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
69 if !entering {
70 return ast.WalkContinue, nil
71 }
72
73 switch a.rctx.RendererType {
74 case RendererTypeRepoMarkdown:
75 switch n.(type) {
76 case *ast.Link:
77 a.rctx.relativeLinkTransformer(n.(*ast.Link))
78 case *ast.Image:
79 a.rctx.imageFromKnotTransformer(n.(*ast.Image))
80 a.rctx.camoImageLinkTransformer(n.(*ast.Image))
81 }
82
83 case RendererTypeDefault:
84 switch n.(type) {
85 case *ast.Image:
86 a.rctx.imageFromKnotTransformer(n.(*ast.Image))
87 a.rctx.camoImageLinkTransformer(n.(*ast.Image))
88 }
89 }
90
91 return ast.WalkContinue, nil
92 })
93}
94
95func (rctx *RenderContext) relativeLinkTransformer(link *ast.Link) {
96 dst := string(link.Destination)
97
98 if isAbsoluteUrl(dst) {
99 return
100 }
101
102 newPath := path.Join("/", rctx.RepoInfo.FullName(), "tree", rctx.RepoInfo.Ref, dst)
103 link.Destination = []byte(newPath)
104}
105
106func (rctx *RenderContext) imageFromKnotTransformer(img *ast.Image) {
107 dst := string(img.Destination)
108
109 if isAbsoluteUrl(dst) {
110 return
111 }
112
113 // strip leading './'
114 if len(dst) >= 2 && dst[0:2] == "./" {
115 dst = dst[2:]
116 }
117
118 scheme := "https"
119 if rctx.IsDev {
120 scheme = "http"
121 }
122 parsedURL := &url.URL{
123 Scheme: scheme,
124 Host: rctx.Knot,
125 Path: path.Join("/",
126 rctx.RepoInfo.OwnerDid,
127 rctx.RepoInfo.Name,
128 "raw",
129 url.PathEscape(rctx.RepoInfo.Ref),
130 dst),
131 }
132 newPath := parsedURL.String()
133 img.Destination = []byte(newPath)
134}
135
136func isAbsoluteUrl(link string) bool {
137 parsed, err := url.Parse(link)
138 if err != nil {
139 return false
140 }
141 return parsed.IsAbs()
142}