Merge pull request '[v7.0/forgejo] Fix some edge cases with permalink rendering' (#3341) from bp-v7.0/forgejo-acfae43-e9eacde-5b6b3f3 into v7.0/forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3341
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
This commit is contained in:
Earl Warren 2024-04-20 11:09:20 +00:00
commit b349f82a62
3 changed files with 351 additions and 108 deletions

View file

@ -35,32 +35,44 @@ type FilePreview struct {
isTruncated bool isTruncated bool
} }
func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Locale) *FilePreview { func NewFilePreviews(ctx *RenderContext, node *html.Node, locale translation.Locale) []*FilePreview {
if setting.FilePreviewMaxLines == 0 { if setting.FilePreviewMaxLines == 0 {
// Feature is disabled // Feature is disabled
return nil return nil
} }
mAll := filePreviewPattern.FindAllStringSubmatchIndex(node.Data, -1)
if mAll == nil {
return nil
}
result := make([]*FilePreview, 0)
for _, m := range mAll {
if slices.Contains(m, -1) {
continue
}
preview := newFilePreview(ctx, node, locale, m)
if preview != nil {
result = append(result, preview)
}
}
return result
}
func newFilePreview(ctx *RenderContext, node *html.Node, locale translation.Locale, m []int) *FilePreview {
preview := &FilePreview{} preview := &FilePreview{}
m := filePreviewPattern.FindStringSubmatchIndex(node.Data)
if m == nil {
return nil
}
// Ensure that every group has a match
if slices.Contains(m, -1) {
return nil
}
urlFull := node.Data[m[0]:m[1]] urlFull := node.Data[m[0]:m[1]]
// Ensure that we only use links to local repositories // Ensure that we only use links to local repositories
if !strings.HasPrefix(urlFull, setting.AppURL+setting.AppSubURL) { if !strings.HasPrefix(urlFull, setting.AppURL) {
return nil return nil
} }
projPath := strings.TrimSuffix(node.Data[m[2]:m[3]], "/") projPath := strings.TrimPrefix(strings.TrimSuffix(node.Data[m[0]:m[3]], "/"), setting.AppURL)
commitSha := node.Data[m[4]:m[5]] commitSha := node.Data[m[4]:m[5]]
filePath := node.Data[m[6]:m[7]] filePath := node.Data[m[6]:m[7]]
@ -70,6 +82,10 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
preview.end = m[1] preview.end = m[1]
projPathSegments := strings.Split(projPath, "/") projPathSegments := strings.Split(projPath, "/")
if len(projPathSegments) != 2 {
return nil
}
ownerName := projPathSegments[len(projPathSegments)-2] ownerName := projPathSegments[len(projPathSegments)-2]
repoName := projPathSegments[len(projPathSegments)-1] repoName := projPathSegments[len(projPathSegments)-1]

View file

@ -1063,38 +1063,46 @@ func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
return return
} }
locale := translation.NewLocale("en-US")
if ctx.Ctx != nil {
ctxLocale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale)
if ok {
locale = ctxLocale
}
}
next := node.NextSibling next := node.NextSibling
for node != nil && node != next { for node != nil && node != next {
locale := translation.NewLocale("en-US") previews := NewFilePreviews(ctx, node, locale)
if ctx.Ctx != nil { if previews == nil {
ctxLocale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale) node = node.NextSibling
if ok { continue
locale = ctxLocale }
offset := 0
for _, preview := range previews {
previewNode := preview.CreateHTML(locale)
// Specialized version of replaceContent, so the parent paragraph element is not destroyed from our div
before := node.Data[:(preview.start - offset)]
after := node.Data[(preview.end - offset):]
afterPrefix := "<p>"
offset = preview.end - len(afterPrefix)
node.Data = before
nextSibling := node.NextSibling
node.Parent.InsertBefore(&html.Node{
Type: html.RawNode,
Data: "</p>",
}, nextSibling)
node.Parent.InsertBefore(previewNode, nextSibling)
afterNode := &html.Node{
Type: html.RawNode,
Data: afterPrefix + after,
} }
node.Parent.InsertBefore(afterNode, nextSibling)
node = afterNode
} }
preview := NewFilePreview(ctx, node, locale)
if preview == nil {
return
}
previewNode := preview.CreateHTML(locale)
// Specialized version of replaceContent, so the parent paragraph element is not destroyed from our div
before := node.Data[:preview.start]
after := node.Data[preview.end:]
node.Data = before
nextSibling := node.NextSibling
node.Parent.InsertBefore(&html.Node{
Type: html.RawNode,
Data: "</p>",
}, nextSibling)
node.Parent.InsertBefore(previewNode, nextSibling)
node.Parent.InsertBefore(&html.Node{
Type: html.RawNode,
Data: "<p>" + after,
}, nextSibling)
node = node.NextSibling node = node.NextSibling
} }
} }

View file

@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -680,9 +681,9 @@ func TestIssue18471(t *testing.T) {
} }
func TestRender_FilePreview(t *testing.T) { func TestRender_FilePreview(t *testing.T) {
setting.StaticRootPath = "../../" defer test.MockVariableValue(&setting.StaticRootPath, "../../")()
setting.Names = []string{"english"} defer test.MockVariableValue(&setting.Names, []string{"english"})()
setting.Langs = []string{"en-US"} defer test.MockVariableValue(&setting.Langs, []string{"en-US"})()
translation.InitLocales(context.Background()) translation.InitLocales(context.Background())
setting.AppURL = markup.TestAppURL setting.AppURL = markup.TestAppURL
@ -705,7 +706,7 @@ func TestRender_FilePreview(t *testing.T) {
sha := "190d9492934af498c3f669d6a2431dc5459e5b20" sha := "190d9492934af498c3f669d6a2431dc5459e5b20"
commitFilePreview := util.URLJoin(markup.TestRepoURL, "src", "commit", sha, "path", "to", "file.go") + "#L2-L3" commitFilePreview := util.URLJoin(markup.TestRepoURL, "src", "commit", sha, "path", "to", "file.go") + "#L2-L3"
test := func(input, expected string, metas map[string]string) { testRender := func(input, expected string, metas map[string]string) {
buffer, err := markup.RenderString(&markup.RenderContext{ buffer, err := markup.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext, Ctx: git.DefaultContext,
RelativePath: ".md", RelativePath: ".md",
@ -715,69 +716,287 @@ func TestRender_FilePreview(t *testing.T) {
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
} }
test( t.Run("single", func(t *testing.T) {
commitFilePreview, testRender(
`<p></p>`+ commitFilePreview,
`<div class="file-preview-box">`+ `<p></p>`+
`<div class="header">`+ `<div class="file-preview-box">`+
`<div>`+ `<div class="header">`+
`<a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" class="muted" rel="nofollow">path/to/file.go</a>`+ `<div>`+
`</div>`+ `<a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" class="muted" rel="nofollow">path/to/file.go</a>`+
`<span class="text small grey">`+ `</div>`+
`Lines 2 to 3 in <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" class="text black" rel="nofollow">190d949</a>`+ `<span class="text small grey">`+
`</span>`+ `Lines 2 to 3 in <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" class="text black" rel="nofollow">190d949</a>`+
`</div>`+ `</span>`+
`<div class="ui table">`+ `</div>`+
`<table class="file-preview">`+ `<div class="ui table">`+
`<tbody>`+ `<table class="file-preview">`+
`<tr>`+ `<tbody>`+
`<td class="lines-num"><span data-line-number="2"></span></td>`+ `<tr>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">B</span>`+"\n"+`</code></td>`+ `<td class="lines-num"><span data-line-number="2"></span></td>`+
`</tr>`+ `<td class="lines-code chroma"><code class="code-inner"><span class="nx">B</span>`+"\n"+`</code></td>`+
`<tr>`+ `</tr>`+
`<td class="lines-num"><span data-line-number="3"></span></td>`+ `<tr>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">C</span>`+"\n"+`</code></td>`+ `<td class="lines-num"><span data-line-number="3"></span></td>`+
`</tr>`+ `<td class="lines-code chroma"><code class="code-inner"><span class="nx">C</span>`+"\n"+`</code></td>`+
`</tbody>`+ `</tr>`+
`</table>`+ `</tbody>`+
`</div>`+ `</table>`+
`</div>`+ `</div>`+
`<p></p>`, `</div>`+
localMetas, `<p></p>`,
) localMetas,
)
})
test( t.Run("cross-repo", func(t *testing.T) {
commitFilePreview, testRender(
`<p></p>`+ commitFilePreview,
`<div class="file-preview-box">`+ `<p></p>`+
`<div class="header">`+ `<div class="file-preview-box">`+
`<div>`+ `<div class="header">`+
`<a href="http://localhost:3000/gogits/gogs/" rel="nofollow">gogits/gogs</a> `+ `<div>`+
`<a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" class="muted" rel="nofollow">path/to/file.go</a>`+ `<a href="http://localhost:3000/gogits/gogs/" rel="nofollow">gogits/gogs</a> `+
`</div>`+ `<a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" class="muted" rel="nofollow">path/to/file.go</a>`+
`<span class="text small grey">`+ `</div>`+
`Lines 2 to 3 in <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" class="text black" rel="nofollow">gogits/gogs@190d949</a>`+ `<span class="text small grey">`+
`</span>`+ `Lines 2 to 3 in <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" class="text black" rel="nofollow">gogits/gogs@190d949</a>`+
`</div>`+ `</span>`+
`<div class="ui table">`+ `</div>`+
`<table class="file-preview">`+ `<div class="ui table">`+
`<tbody>`+ `<table class="file-preview">`+
`<tr>`+ `<tbody>`+
`<td class="lines-num"><span data-line-number="2"></span></td>`+ `<tr>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">B</span>`+"\n"+`</code></td>`+ `<td class="lines-num"><span data-line-number="2"></span></td>`+
`</tr>`+ `<td class="lines-code chroma"><code class="code-inner"><span class="nx">B</span>`+"\n"+`</code></td>`+
`<tr>`+ `</tr>`+
`<td class="lines-num"><span data-line-number="3"></span></td>`+ `<tr>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">C</span>`+"\n"+`</code></td>`+ `<td class="lines-num"><span data-line-number="3"></span></td>`+
`</tr>`+ `<td class="lines-code chroma"><code class="code-inner"><span class="nx">C</span>`+"\n"+`</code></td>`+
`</tbody>`+ `</tr>`+
`</table>`+ `</tbody>`+
`</div>`+ `</table>`+
`</div>`+ `</div>`+
`<p></p>`, `</div>`+
map[string]string{ `<p></p>`,
"user": "gogits", map[string]string{
"repo": "gogs2", "user": "gogits",
}, "repo": "gogs2",
) },
)
})
t.Run("AppSubURL", func(t *testing.T) {
urlWithSub := util.URLJoin(markup.TestAppURL, "sub", markup.TestOrgRepo, "src", "commit", sha, "path", "to", "file.go") + "#L2-L3"
testRender(
urlWithSub,
`<p><a href="http://localhost:3000/sub/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" rel="nofollow"><code>190d949293/path/to/file.go (L2-L3)</code></a></p>`,
localMetas,
)
defer test.MockVariableValue(&setting.AppURL, markup.TestAppURL+"sub/")()
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
testRender(
urlWithSub,
`<p></p>`+
`<div class="file-preview-box">`+
`<div class="header">`+
`<div>`+
`<a href="http://localhost:3000/sub/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" class="muted" rel="nofollow">path/to/file.go</a>`+
`</div>`+
`<span class="text small grey">`+
`Lines 2 to 3 in <a href="http://localhost:3000/sub/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" class="text black" rel="nofollow">190d949</a>`+
`</span>`+
`</div>`+
`<div class="ui table">`+
`<table class="file-preview">`+
`<tbody>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="2"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">B</span>`+"\n"+`</code></td>`+
`</tr>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="3"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">C</span>`+"\n"+`</code></td>`+
`</tr>`+
`</tbody>`+
`</table>`+
`</div>`+
`</div>`+
`<p></p>`,
localMetas,
)
testRender(
"first without sub "+commitFilePreview+" second "+urlWithSub,
`<p>first without sub <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" rel="nofollow"><code>190d949293/path/to/file.go (L2-L3)</code></a> second </p>`+
`<div class="file-preview-box">`+
`<div class="header">`+
`<div>`+
`<a href="http://localhost:3000/sub/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" class="muted" rel="nofollow">path/to/file.go</a>`+
`</div>`+
`<span class="text small grey">`+
`Lines 2 to 3 in <a href="http://localhost:3000/sub/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" class="text black" rel="nofollow">190d949</a>`+
`</span>`+
`</div>`+
`<div class="ui table">`+
`<table class="file-preview">`+
`<tbody>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="2"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">B</span>`+"\n"+`</code></td>`+
`</tr>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="3"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">C</span>`+"\n"+`</code></td>`+
`</tr>`+
`</tbody>`+
`</table>`+
`</div>`+
`</div>`+
`<p></p>`,
localMetas,
)
})
t.Run("multiples", func(t *testing.T) {
testRender(
"first "+commitFilePreview+" second "+commitFilePreview,
`<p>first </p>`+
`<div class="file-preview-box">`+
`<div class="header">`+
`<div>`+
`<a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" class="muted" rel="nofollow">path/to/file.go</a>`+
`</div>`+
`<span class="text small grey">`+
`Lines 2 to 3 in <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" class="text black" rel="nofollow">190d949</a>`+
`</span>`+
`</div>`+
`<div class="ui table">`+
`<table class="file-preview">`+
`<tbody>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="2"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">B</span>`+"\n"+`</code></td>`+
`</tr>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="3"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">C</span>`+"\n"+`</code></td>`+
`</tr>`+
`</tbody>`+
`</table>`+
`</div>`+
`</div>`+
`<p> second </p>`+
`<div class="file-preview-box">`+
`<div class="header">`+
`<div>`+
`<a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" class="muted" rel="nofollow">path/to/file.go</a>`+
`</div>`+
`<span class="text small grey">`+
`Lines 2 to 3 in <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" class="text black" rel="nofollow">190d949</a>`+
`</span>`+
`</div>`+
`<div class="ui table">`+
`<table class="file-preview">`+
`<tbody>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="2"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">B</span>`+"\n"+`</code></td>`+
`</tr>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="3"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">C</span>`+"\n"+`</code></td>`+
`</tr>`+
`</tbody>`+
`</table>`+
`</div>`+
`</div>`+
`<p></p>`,
localMetas,
)
testRender(
"first "+commitFilePreview+" second "+commitFilePreview+" third "+commitFilePreview,
`<p>first </p>`+
`<div class="file-preview-box">`+
`<div class="header">`+
`<div>`+
`<a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" class="muted" rel="nofollow">path/to/file.go</a>`+
`</div>`+
`<span class="text small grey">`+
`Lines 2 to 3 in <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" class="text black" rel="nofollow">190d949</a>`+
`</span>`+
`</div>`+
`<div class="ui table">`+
`<table class="file-preview">`+
`<tbody>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="2"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">B</span>`+"\n"+`</code></td>`+
`</tr>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="3"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">C</span>`+"\n"+`</code></td>`+
`</tr>`+
`</tbody>`+
`</table>`+
`</div>`+
`</div>`+
`<p> second </p>`+
`<div class="file-preview-box">`+
`<div class="header">`+
`<div>`+
`<a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" class="muted" rel="nofollow">path/to/file.go</a>`+
`</div>`+
`<span class="text small grey">`+
`Lines 2 to 3 in <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" class="text black" rel="nofollow">190d949</a>`+
`</span>`+
`</div>`+
`<div class="ui table">`+
`<table class="file-preview">`+
`<tbody>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="2"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">B</span>`+"\n"+`</code></td>`+
`</tr>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="3"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">C</span>`+"\n"+`</code></td>`+
`</tr>`+
`</tbody>`+
`</table>`+
`</div>`+
`</div>`+
`<p> third </p>`+
`<div class="file-preview-box">`+
`<div class="header">`+
`<div>`+
`<a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" class="muted" rel="nofollow">path/to/file.go</a>`+
`</div>`+
`<span class="text small grey">`+
`Lines 2 to 3 in <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" class="text black" rel="nofollow">190d949</a>`+
`</span>`+
`</div>`+
`<div class="ui table">`+
`<table class="file-preview">`+
`<tbody>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="2"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">B</span>`+"\n"+`</code></td>`+
`</tr>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="3"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">C</span>`+"\n"+`</code></td>`+
`</tr>`+
`</tbody>`+
`</table>`+
`</div>`+
`</div>`+
`<p></p>`,
localMetas,
)
})
} }