Skip to content

Commit 597d1da

Browse files
brechtvlwxiaoguang
andauthored
Fix missing images in editor preview due to wrong links (#31299)
Parse base path and tree path so that media links can be correctly created with /media/. Resolves #31294 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
1 parent f5dfd7d commit 597d1da

File tree

5 files changed

+103
-78
lines changed

5 files changed

+103
-78
lines changed

modules/markup/renderer.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ type RenderContext struct {
8686
}
8787

8888
type Links struct {
89-
AbsolutePrefix bool
90-
Base string
91-
BranchPath string
92-
TreePath string
89+
AbsolutePrefix bool // add absolute URL prefix to auto-resolved links like "#issue", but not for pre-provided links and medias
90+
Base string // base prefix for pre-provided links and medias (images, videos)
91+
BranchPath string // actually it is the ref path, eg: "branch/features/feat-12", "tag/v1.0"
92+
TreePath string // the dir of the file, eg: "doc" if the file "doc/CHANGE.md" is being rendered
9393
}
9494

9595
func (l *Links) Prefix() string {

modules/structs/miscellaneous.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ type MarkupOption struct {
2525
//
2626
// in: body
2727
Mode string
28-
// Context to render
28+
// URL path for rendering issue, media and file links
29+
// Expected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}
2930
//
3031
// in: body
3132
Context string
@@ -53,7 +54,8 @@ type MarkdownOption struct {
5354
//
5455
// in: body
5556
Mode string
56-
// Context to render
57+
// URL path for rendering issue, media and file links
58+
// Expected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}
5759
//
5860
// in: body
5961
Context string

routers/api/v1/misc/markup_test.go

+61-39
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
go_context "context"
88
"io"
99
"net/http"
10+
"path"
1011
"strings"
1112
"testing"
1213

@@ -19,36 +20,40 @@ import (
1920
"github.com/stretchr/testify/assert"
2021
)
2122

22-
const (
23-
AppURL = "http://localhost:3000/"
24-
Repo = "gogits/gogs"
25-
FullURL = AppURL + Repo + "/"
26-
)
23+
const AppURL = "http://localhost:3000/"
2724

28-
func testRenderMarkup(t *testing.T, mode, filePath, text, responseBody string, responseCode int) {
25+
func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expectedBody string, expectedCode int) {
2926
setting.AppURL = AppURL
27+
context := "/gogits/gogs"
28+
if !wiki {
29+
context += path.Join("/src/branch/main", path.Dir(filePath))
30+
}
3031
options := api.MarkupOption{
3132
Mode: mode,
3233
Text: text,
33-
Context: Repo,
34-
Wiki: true,
34+
Context: context,
35+
Wiki: wiki,
3536
FilePath: filePath,
3637
}
3738
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
3839
web.SetForm(ctx, &options)
3940
Markup(ctx)
40-
assert.Equal(t, responseBody, resp.Body.String())
41-
assert.Equal(t, responseCode, resp.Code)
41+
assert.Equal(t, expectedBody, resp.Body.String())
42+
assert.Equal(t, expectedCode, resp.Code)
4243
resp.Body.Reset()
4344
}
4445

45-
func testRenderMarkdown(t *testing.T, mode, text, responseBody string, responseCode int) {
46+
func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody string, responseCode int) {
4647
setting.AppURL = AppURL
48+
context := "/gogits/gogs"
49+
if !wiki {
50+
context += "/src/branch/main"
51+
}
4752
options := api.MarkdownOption{
4853
Mode: mode,
4954
Text: text,
50-
Context: Repo,
51-
Wiki: true,
55+
Context: context,
56+
Wiki: wiki,
5257
}
5358
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
5459
web.SetForm(ctx, &options)
@@ -65,7 +70,7 @@ func TestAPI_RenderGFM(t *testing.T) {
6570
},
6671
})
6772

68-
testCasesCommon := []string{
73+
testCasesWiki := []string{
6974
// dear imgui wiki markdown extract: special wiki syntax
7075
`Wiki! Enjoy :)
7176
- [[Links, Language bindings, Engine bindings|Links]]
@@ -74,28 +79,28 @@ func TestAPI_RenderGFM(t *testing.T) {
7479
// rendered
7580
`<p>Wiki! Enjoy :)</p>
7681
<ul>
77-
<li><a href="` + FullURL + `wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
78-
<li><a href="` + FullURL + `wiki/Tips" rel="nofollow">Tips</a></li>
79-
<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li>
82+
<li><a href="http://localhost:3000/gogits/gogs/wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
83+
<li><a href="http://localhost:3000/gogits/gogs/wiki/Tips" rel="nofollow">Tips</a></li>
84+
<li>Bezier widget (by <a href="http://localhost:3000/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li>
8085
</ul>
8186
`,
8287
// Guard wiki sidebar: special syntax
8388
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
8489
// rendered
85-
`<p><a href="` + FullURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
90+
`<p><a href="http://localhost:3000/gogits/gogs/wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
8691
`,
8792
// special syntax
8893
`[[Name|Link]]`,
8994
// rendered
90-
`<p><a href="` + FullURL + `wiki/Link" rel="nofollow">Name</a></p>
95+
`<p><a href="http://localhost:3000/gogits/gogs/wiki/Link" rel="nofollow">Name</a></p>
9196
`,
9297
// empty
9398
``,
9499
// rendered
95100
``,
96101
}
97102

98-
testCasesDocument := []string{
103+
testCasesWikiDocument := []string{
99104
// wine-staging wiki home extract: special wiki syntax, images
100105
`## What is Wine Staging?
101106
**Wine Staging** on website [wine-staging.com](http://wine-staging.com).
@@ -111,31 +116,48 @@ Here are some links to the most important topics. You can find the full list of
111116
<p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
112117
<h2 id="user-content-quick-links">Quick Links</h2>
113118
<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
114-
<p><a href="` + FullURL + `wiki/Configuration" rel="nofollow">Configuration</a>
115-
<a href="` + FullURL + `wiki/raw/images/icon-bug.png" rel="nofollow"><img src="` + FullURL + `wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
119+
<p><a href="http://localhost:3000/gogits/gogs/wiki/Configuration" rel="nofollow">Configuration</a>
120+
<a href="http://localhost:3000/gogits/gogs/wiki/raw/images/icon-bug.png" rel="nofollow"><img src="http://localhost:3000/gogits/gogs/wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
116121
`,
117122
}
118123

119-
for i := 0; i < len(testCasesCommon); i += 2 {
120-
text := testCasesCommon[i]
121-
response := testCasesCommon[i+1]
122-
testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
123-
testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
124-
testRenderMarkdown(t, "comment", text, response, http.StatusOK)
125-
testRenderMarkup(t, "comment", "", text, response, http.StatusOK)
126-
testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
124+
for i := 0; i < len(testCasesWiki); i += 2 {
125+
text := testCasesWiki[i]
126+
response := testCasesWiki[i+1]
127+
testRenderMarkdown(t, "gfm", true, text, response, http.StatusOK)
128+
testRenderMarkup(t, "gfm", true, "", text, response, http.StatusOK)
129+
testRenderMarkdown(t, "comment", true, text, response, http.StatusOK)
130+
testRenderMarkup(t, "comment", true, "", text, response, http.StatusOK)
131+
testRenderMarkup(t, "file", true, "path/test.md", text, response, http.StatusOK)
127132
}
128133

129-
for i := 0; i < len(testCasesDocument); i += 2 {
130-
text := testCasesDocument[i]
131-
response := testCasesDocument[i+1]
132-
testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
133-
testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
134-
testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
134+
for i := 0; i < len(testCasesWikiDocument); i += 2 {
135+
text := testCasesWikiDocument[i]
136+
response := testCasesWikiDocument[i+1]
137+
testRenderMarkdown(t, "gfm", true, text, response, http.StatusOK)
138+
testRenderMarkup(t, "gfm", true, "", text, response, http.StatusOK)
139+
testRenderMarkup(t, "file", true, "path/test.md", text, response, http.StatusOK)
135140
}
136141

137-
testRenderMarkup(t, "file", "path/test.unknown", "## Test", "Unsupported render extension: .unknown\n", http.StatusUnprocessableEntity)
138-
testRenderMarkup(t, "unknown", "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
142+
input := "[Link](test.md)\n![Image](image.png)"
143+
testRenderMarkdown(t, "gfm", false, input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
144+
<a href="http://localhost:3000/gogits/gogs/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/image.png" alt="Image"/></a></p>
145+
`, http.StatusOK)
146+
147+
testRenderMarkdown(t, "gfm", false, input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
148+
<a href="http://localhost:3000/gogits/gogs/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/image.png" alt="Image"/></a></p>
149+
`, http.StatusOK)
150+
151+
testRenderMarkup(t, "gfm", false, "", input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
152+
<a href="http://localhost:3000/gogits/gogs/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/image.png" alt="Image"/></a></p>
153+
`, http.StatusOK)
154+
155+
testRenderMarkup(t, "file", false, "path/new-file.md", input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/path/test.md" rel="nofollow">Link</a>
156+
<a href="http://localhost:3000/gogits/gogs/media/branch/main/path/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/path/image.png" alt="Image"/></a></p>
157+
`, http.StatusOK)
158+
159+
testRenderMarkup(t, "file", true, "path/test.unknown", "## Test", "Unsupported render extension: .unknown\n", http.StatusUnprocessableEntity)
160+
testRenderMarkup(t, "unknown", true, "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
139161
}
140162

141163
var simpleCases = []string{
@@ -160,7 +182,7 @@ func TestAPI_RenderSimple(t *testing.T) {
160182
options := api.MarkdownOption{
161183
Mode: "markdown",
162184
Text: "",
163-
Context: Repo,
185+
Context: "/gogits/gogs",
164186
}
165187
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
166188
for i := 0; i < len(simpleCases); i += 2 {

routers/common/markup.go

+32-31
Original file line numberDiff line numberDiff line change
@@ -7,63 +7,67 @@ package common
77
import (
88
"fmt"
99
"net/http"
10+
"path"
1011
"strings"
1112

1213
repo_model "code.gitea.io/gitea/models/repo"
14+
"code.gitea.io/gitea/modules/httplib"
1315
"code.gitea.io/gitea/modules/markup"
1416
"code.gitea.io/gitea/modules/markup/markdown"
1517
"code.gitea.io/gitea/modules/setting"
16-
"code.gitea.io/gitea/modules/util"
1718
"code.gitea.io/gitea/services/context"
18-
19-
"mvdan.cc/xurls/v2"
2019
)
2120

2221
// RenderMarkup renders markup text for the /markup and /markdown endpoints
23-
func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPrefix, filePath string, wiki bool) {
24-
var markupType string
25-
relativePath := ""
22+
func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPathContext, filePath string, wiki bool) {
23+
// urlPathContext format is "/subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}"
24+
// filePath is the path of the file to render if the end user is trying to preview a repo file (mode == "file")
25+
// filePath will be used as RenderContext.RelativePath
2626

27-
if len(text) == 0 {
28-
_, _ = ctx.Write([]byte(""))
29-
return
27+
// for example, when previewing file "/gitea/owner/repo/src/branch/features/feat-123/doc/CHANGE.md", then filePath is "doc/CHANGE.md"
28+
// and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc"
29+
30+
var markupType, relativePath string
31+
32+
links := markup.Links{AbsolutePrefix: true}
33+
if urlPathContext != "" {
34+
links.Base = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext)
3035
}
3136

3237
switch mode {
3338
case "markdown":
3439
// Raw markdown
3540
if err := markdown.RenderRaw(&markup.RenderContext{
36-
Ctx: ctx,
37-
Links: markup.Links{
38-
AbsolutePrefix: true,
39-
Base: urlPrefix,
40-
},
41+
Ctx: ctx,
42+
Links: links,
4143
}, strings.NewReader(text), ctx.Resp); err != nil {
4244
ctx.Error(http.StatusInternalServerError, err.Error())
4345
}
4446
return
4547
case "comment":
46-
// Comment as markdown
48+
// Issue & comment content
4749
markupType = markdown.MarkupName
4850
case "gfm":
49-
// Github Flavored Markdown as document
51+
// GitHub Flavored Markdown
5052
markupType = markdown.MarkupName
5153
case "file":
52-
// File as document based on file extension
53-
markupType = ""
54+
markupType = "" // render the repo file content by its extension
5455
relativePath = filePath
5556
default:
5657
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
5758
return
5859
}
5960

60-
if !strings.HasPrefix(setting.AppSubURL+"/", urlPrefix) {
61-
// check if urlPrefix is already set to a URL
62-
linkRegex, _ := xurls.StrictMatchingScheme("https?://")
63-
m := linkRegex.FindStringIndex(urlPrefix)
64-
if m == nil {
65-
urlPrefix = util.URLJoin(setting.AppURL, urlPrefix)
66-
}
61+
fields := strings.SplitN(strings.TrimPrefix(urlPathContext, setting.AppSubURL+"/"), "/", 5)
62+
if len(fields) == 5 && fields[2] == "src" && (fields[3] == "branch" || fields[3] == "commit" || fields[3] == "tag") {
63+
// absolute base prefix is something like "https://host/subpath/{user}/{repo}"
64+
absoluteBasePrefix := fmt.Sprintf("%s%s/%s", httplib.GuessCurrentAppURL(ctx), fields[0], fields[1])
65+
66+
fileDir := path.Dir(filePath) // it is "doc" if filePath is "doc/CHANGE.md"
67+
refPath := strings.Join(fields[3:], "/") // it is "branch/features/feat-12/doc"
68+
refPath = strings.TrimSuffix(refPath, "/"+fileDir) // now we get the correct branch path: "branch/features/feat-12"
69+
70+
links = markup.Links{AbsolutePrefix: true, Base: absoluteBasePrefix, BranchPath: refPath, TreePath: fileDir}
6771
}
6872

6973
meta := map[string]string{}
@@ -81,12 +85,9 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
8185
}
8286

8387
if err := markup.Render(&markup.RenderContext{
84-
Ctx: ctx,
85-
Repo: repoCtx,
86-
Links: markup.Links{
87-
AbsolutePrefix: true,
88-
Base: urlPrefix,
89-
},
88+
Ctx: ctx,
89+
Repo: repoCtx,
90+
Links: links,
9091
Metas: meta,
9192
IsWiki: wiki,
9293
Type: markupType,

templates/swagger/v1_json.tmpl

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)