aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwxiaoguang <wxiaoguang@gmail.com>2024-06-17 15:07:21 +0800
committerGitHub <noreply@github.com>2024-06-17 15:07:21 +0800
commitfa307167f97a185fefd58f016a96ccdf55783b1c (patch)
tree83c3b6d9f0f92da78c648fa8dbd77b8af466a97c
parent3f44844244dd5e6329bbfaf29a3d5b4b244ab84f (diff)
downloadgitea-fa307167f97a185fefd58f016a96ccdf55783b1c.tar.gz
gitea-fa307167f97a185fefd58f016a96ccdf55783b1c.zip
Fix missing images in editor preview due to wrong links (#31299) (#31393)
Backport #31299 Parse base path and tree path so that media links can be correctly created with /media/. Resolves #31294 --------- Co-authored-by: Brecht Van Lommel <brecht@blender.org>
-rw-r--r--modules/markup/renderer.go8
-rw-r--r--modules/structs/miscellaneous.go6
-rw-r--r--routers/api/v1/misc/markup_test.go100
-rw-r--r--routers/common/markup.go61
-rw-r--r--templates/swagger/v1_json.tmpl4
5 files changed, 102 insertions, 77 deletions
diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go
index f372dcd5b7..3284a8194e 100644
--- a/modules/markup/renderer.go
+++ b/modules/markup/renderer.go
@@ -84,10 +84,10 @@ type RenderContext struct {
}
type Links struct {
- AbsolutePrefix bool
- Base string
- BranchPath string
- TreePath string
+ AbsolutePrefix bool // add absolute URL prefix to auto-resolved links like "#issue", but not for pre-provided links and medias
+ Base string // base prefix for pre-provided links and medias (images, videos)
+ BranchPath string // actually it is the ref path, eg: "branch/features/feat-12", "tag/v1.0"
+ TreePath string // the dir of the file, eg: "doc" if the file "doc/CHANGE.md" is being rendered
}
func (l *Links) Prefix() string {
diff --git a/modules/structs/miscellaneous.go b/modules/structs/miscellaneous.go
index bff10f95b7..3b206c1dd7 100644
--- a/modules/structs/miscellaneous.go
+++ b/modules/structs/miscellaneous.go
@@ -25,7 +25,8 @@ type MarkupOption struct {
//
// in: body
Mode string
- // Context to render
+ // URL path for rendering issue, media and file links
+ // Expected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}
//
// in: body
Context string
@@ -53,7 +54,8 @@ type MarkdownOption struct {
//
// in: body
Mode string
- // Context to render
+ // URL path for rendering issue, media and file links
+ // Expected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}
//
// in: body
Context string
diff --git a/routers/api/v1/misc/markup_test.go b/routers/api/v1/misc/markup_test.go
index 5236fd06ae..e2ab7141b7 100644
--- a/routers/api/v1/misc/markup_test.go
+++ b/routers/api/v1/misc/markup_test.go
@@ -7,6 +7,7 @@ import (
go_context "context"
"io"
"net/http"
+ "path"
"strings"
"testing"
@@ -19,36 +20,40 @@ import (
"github.com/stretchr/testify/assert"
)
-const (
- AppURL = "http://localhost:3000/"
- Repo = "gogits/gogs"
- FullURL = AppURL + Repo + "/"
-)
+const AppURL = "http://localhost:3000/"
-func testRenderMarkup(t *testing.T, mode, filePath, text, responseBody string, responseCode int) {
+func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expectedBody string, expectedCode int) {
setting.AppURL = AppURL
+ context := "/gogits/gogs"
+ if !wiki {
+ context += path.Join("/src/branch/main", path.Dir(filePath))
+ }
options := api.MarkupOption{
Mode: mode,
Text: text,
- Context: Repo,
- Wiki: true,
+ Context: context,
+ Wiki: wiki,
FilePath: filePath,
}
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
web.SetForm(ctx, &options)
Markup(ctx)
- assert.Equal(t, responseBody, resp.Body.String())
- assert.Equal(t, responseCode, resp.Code)
+ assert.Equal(t, expectedBody, resp.Body.String())
+ assert.Equal(t, expectedCode, resp.Code)
resp.Body.Reset()
}
-func testRenderMarkdown(t *testing.T, mode, text, responseBody string, responseCode int) {
+func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody string, responseCode int) {
setting.AppURL = AppURL
+ context := "/gogits/gogs"
+ if !wiki {
+ context += "/src/branch/main"
+ }
options := api.MarkdownOption{
Mode: mode,
Text: text,
- Context: Repo,
- Wiki: true,
+ Context: context,
+ Wiki: wiki,
}
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
web.SetForm(ctx, &options)
@@ -65,7 +70,7 @@ func TestAPI_RenderGFM(t *testing.T) {
},
})
- testCasesCommon := []string{
+ testCasesWiki := []string{
// dear imgui wiki markdown extract: special wiki syntax
`Wiki! Enjoy :)
- [[Links, Language bindings, Engine bindings|Links]]
@@ -74,20 +79,20 @@ func TestAPI_RenderGFM(t *testing.T) {
// rendered
`<p>Wiki! Enjoy :)</p>
<ul>
-<li><a href="` + FullURL + `wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
-<li><a href="` + FullURL + `wiki/Tips" rel="nofollow">Tips</a></li>
-<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>
+<li><a href="http://localhost:3000/gogits/gogs/wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
+<li><a href="http://localhost:3000/gogits/gogs/wiki/Tips" rel="nofollow">Tips</a></li>
+<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>
</ul>
`,
// Guard wiki sidebar: special syntax
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
// rendered
- `<p><a href="` + FullURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
+ `<p><a href="http://localhost:3000/gogits/gogs/wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
`,
// special syntax
`[[Name|Link]]`,
// rendered
- `<p><a href="` + FullURL + `wiki/Link" rel="nofollow">Name</a></p>
+ `<p><a href="http://localhost:3000/gogits/gogs/wiki/Link" rel="nofollow">Name</a></p>
`,
// empty
``,
@@ -95,7 +100,7 @@ func TestAPI_RenderGFM(t *testing.T) {
``,
}
- testCasesDocument := []string{
+ testCasesWikiDocument := []string{
// wine-staging wiki home extract: special wiki syntax, images
`## What is Wine Staging?
**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
<p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
<h2 id="user-content-quick-links">Quick Links</h2>
<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
-<p><a href="` + FullURL + `wiki/Configuration" rel="nofollow">Configuration</a>
-<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>
+<p><a href="http://localhost:3000/gogits/gogs/wiki/Configuration" rel="nofollow">Configuration</a>
+<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>
`,
}
- for i := 0; i < len(testCasesCommon); i += 2 {
- text := testCasesCommon[i]
- response := testCasesCommon[i+1]
- testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
- testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
- testRenderMarkdown(t, "comment", text, response, http.StatusOK)
- testRenderMarkup(t, "comment", "", text, response, http.StatusOK)
- testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
+ for i := 0; i < len(testCasesWiki); i += 2 {
+ text := testCasesWiki[i]
+ response := testCasesWiki[i+1]
+ testRenderMarkdown(t, "gfm", true, text, response, http.StatusOK)
+ testRenderMarkup(t, "gfm", true, "", text, response, http.StatusOK)
+ testRenderMarkdown(t, "comment", true, text, response, http.StatusOK)
+ testRenderMarkup(t, "comment", true, "", text, response, http.StatusOK)
+ testRenderMarkup(t, "file", true, "path/test.md", text, response, http.StatusOK)
}
- for i := 0; i < len(testCasesDocument); i += 2 {
- text := testCasesDocument[i]
- response := testCasesDocument[i+1]
- testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
- testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
- testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
+ for i := 0; i < len(testCasesWikiDocument); i += 2 {
+ text := testCasesWikiDocument[i]
+ response := testCasesWikiDocument[i+1]
+ testRenderMarkdown(t, "gfm", true, text, response, http.StatusOK)
+ testRenderMarkup(t, "gfm", true, "", text, response, http.StatusOK)
+ testRenderMarkup(t, "file", true, "path/test.md", text, response, http.StatusOK)
}
- testRenderMarkup(t, "file", "path/test.unknown", "## Test", "Unsupported render extension: .unknown\n", http.StatusUnprocessableEntity)
- testRenderMarkup(t, "unknown", "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
+ input := "[Link](test.md)\n![Image](image.png)"
+ testRenderMarkdown(t, "gfm", false, input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
+<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>
+`, http.StatusOK)
+
+ testRenderMarkdown(t, "gfm", false, input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
+<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>
+`, http.StatusOK)
+
+ testRenderMarkup(t, "gfm", false, "", input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
+<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>
+`, http.StatusOK)
+
+ 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>
+<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>
+`, http.StatusOK)
+
+ testRenderMarkup(t, "file", true, "path/test.unknown", "## Test", "Unsupported render extension: .unknown\n", http.StatusUnprocessableEntity)
+ testRenderMarkup(t, "unknown", true, "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
}
var simpleCases = []string{
@@ -160,7 +182,7 @@ func TestAPI_RenderSimple(t *testing.T) {
options := api.MarkdownOption{
Mode: "markdown",
Text: "",
- Context: Repo,
+ Context: "/gogits/gogs",
}
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
for i := 0; i < len(simpleCases); i += 2 {
diff --git a/routers/common/markup.go b/routers/common/markup.go
index 2d5638ef61..242e9a3754 100644
--- a/routers/common/markup.go
+++ b/routers/common/markup.go
@@ -7,62 +7,66 @@ package common
import (
"fmt"
"net/http"
+ "path"
"strings"
+ "code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
-
- "mvdan.cc/xurls/v2"
)
// RenderMarkup renders markup text for the /markup and /markdown endpoints
-func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPrefix, filePath string, wiki bool) {
- var markupType string
- relativePath := ""
+func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPathContext, filePath string, wiki bool) {
+ // urlPathContext format is "/subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}"
+ // filePath is the path of the file to render if the end user is trying to preview a repo file (mode == "file")
+ // filePath will be used as RenderContext.RelativePath
- if len(text) == 0 {
- _, _ = ctx.Write([]byte(""))
- return
+ // for example, when previewing file "/gitea/owner/repo/src/branch/features/feat-123/doc/CHANGE.md", then filePath is "doc/CHANGE.md"
+ // and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc"
+
+ var markupType, relativePath string
+
+ links := markup.Links{AbsolutePrefix: true}
+ if urlPathContext != "" {
+ links.Base = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext)
}
switch mode {
case "markdown":
// Raw markdown
if err := markdown.RenderRaw(&markup.RenderContext{
- Ctx: ctx,
- Links: markup.Links{
- AbsolutePrefix: true,
- Base: urlPrefix,
- },
+ Ctx: ctx,
+ Links: links,
}, strings.NewReader(text), ctx.Resp); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
}
return
case "comment":
- // Comment as markdown
+ // Issue & comment content
markupType = markdown.MarkupName
case "gfm":
- // Github Flavored Markdown as document
+ // GitHub Flavored Markdown
markupType = markdown.MarkupName
case "file":
- // File as document based on file extension
- markupType = ""
+ markupType = "" // render the repo file content by its extension
relativePath = filePath
default:
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
return
}
- if !strings.HasPrefix(setting.AppSubURL+"/", urlPrefix) {
- // check if urlPrefix is already set to a URL
- linkRegex, _ := xurls.StrictMatchingScheme("https?://")
- m := linkRegex.FindStringIndex(urlPrefix)
- if m == nil {
- urlPrefix = util.URLJoin(setting.AppURL, urlPrefix)
- }
+ fields := strings.SplitN(strings.TrimPrefix(urlPathContext, setting.AppSubURL+"/"), "/", 5)
+ if len(fields) == 5 && fields[2] == "src" && (fields[3] == "branch" || fields[3] == "commit" || fields[3] == "tag") {
+ // absolute base prefix is something like "https://host/subpath/{user}/{repo}"
+ absoluteBasePrefix := fmt.Sprintf("%s%s/%s", httplib.GuessCurrentAppURL(ctx), fields[0], fields[1])
+
+ fileDir := path.Dir(filePath) // it is "doc" if filePath is "doc/CHANGE.md"
+ refPath := strings.Join(fields[3:], "/") // it is "branch/features/feat-12/doc"
+ refPath = strings.TrimSuffix(refPath, "/"+fileDir) // now we get the correct branch path: "branch/features/feat-12"
+
+ links = markup.Links{AbsolutePrefix: true, Base: absoluteBasePrefix, BranchPath: refPath, TreePath: fileDir}
}
meta := map[string]string{}
@@ -78,11 +82,8 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
}
if err := markup.Render(&markup.RenderContext{
- Ctx: ctx,
- Links: markup.Links{
- AbsolutePrefix: true,
- Base: urlPrefix,
- },
+ Ctx: ctx,
+ Links: links,
Metas: meta,
IsWiki: wiki,
Type: markupType,
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 9ea6763a1a..fb117ea6cc 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -21954,7 +21954,7 @@
"type": "object",
"properties": {
"Context": {
- "description": "Context to render\n\nin: body",
+ "description": "URL path for rendering issue, media and file links\nExpected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}\n\nin: body",
"type": "string"
},
"Mode": {
@@ -21977,7 +21977,7 @@
"type": "object",
"properties": {
"Context": {
- "description": "Context to render\n\nin: body",
+ "description": "URL path for rendering issue, media and file links\nExpected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}\n\nin: body",
"type": "string"
},
"FilePath": {