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