]> source.dussan.org Git - gitea.git/commitdiff
Refactor render system (#32492)
authorwxiaoguang <wxiaoguang@gmail.com>
Thu, 14 Nov 2024 05:02:11 +0000 (13:02 +0800)
committerGitHub <noreply@github.com>
Thu, 14 Nov 2024 05:02:11 +0000 (05:02 +0000)
There were too many patches to the Render system, it's really difficult
to make further improvements.

This PR clears the legacy problems and fix TODOs.

1. Rename `RenderContext.Type` to `RenderContext.MarkupType` to clarify
its usage.
2. Use `ContentMode` to replace `meta["mode"]` and `IsWiki`, to clarify
the rendering behaviors.
3. Use "wiki" mode instead of "mode=gfm + wiki=true"
4. Merge `renderByType` and `renderByFile`
5. Add more comments

----

The problem of "mode=document": in many cases it is not set, so many
non-comment places use comment's hard line break incorrectly

32 files changed:
models/repo/repo.go
modules/markup/console/console.go
modules/markup/html.go
modules/markup/html_codepreview_test.go
modules/markup/html_internal_test.go
modules/markup/html_issue.go
modules/markup/html_link.go
modules/markup/html_node.go
modules/markup/html_test.go
modules/markup/markdown/goldmark.go
modules/markup/markdown/markdown.go
modules/markup/markdown/markdown_test.go
modules/markup/markdown/transform_image.go
modules/markup/orgmode/orgmode.go
modules/markup/orgmode/orgmode_test.go
modules/markup/render.go
modules/structs/miscellaneous.go
modules/templates/util_render.go
modules/templates/util_render_test.go
routers/api/v1/misc/markup.go
routers/api/v1/misc/markup_test.go
routers/common/markup.go
routers/web/feed/convert.go
routers/web/misc/markup.go
routers/web/repo/view.go
routers/web/repo/wiki.go
routers/web/user/profile.go
services/context/org.go
templates/swagger/v1_json.tmpl
tests/integration/markup_external_test.go
web_src/js/features/comp/ComboMarkdownEditor.ts
web_src/js/features/repo-wiki.ts

index 68f8e16a21d5853ff22b2964841c4c30840523f1..4776ff0b9ca25c8216693cad955cf115d9e0cdb3 100644 (file)
@@ -479,7 +479,6 @@ func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string {
                metas := map[string]string{
                        "user": repo.OwnerName,
                        "repo": repo.Name,
-                       "mode": "comment",
                }
 
                unit, err := repo.GetUnit(ctx, unit.TypeExternalTracker)
@@ -521,7 +520,6 @@ func (repo *Repository) ComposeDocumentMetas(ctx context.Context) map[string]str
                for k, v := range repo.ComposeMetas(ctx) {
                        metas[k] = v
                }
-               metas["mode"] = "document"
                repo.DocumentRenderingMetas = metas
        }
        return repo.DocumentRenderingMetas
index 01653565fe2972111359a3c4e042cb7a02d99bdd..d991527b80f59ea6faf1c1e55540acbf634ecd80 100644 (file)
@@ -8,7 +8,6 @@ import (
        "io"
        "path/filepath"
        "regexp"
-       "strings"
 
        "code.gitea.io/gitea/modules/markup"
        "code.gitea.io/gitea/modules/setting"
@@ -17,9 +16,6 @@ import (
        "github.com/go-enry/go-enry/v2"
 )
 
-// MarkupName describes markup's name
-var MarkupName = "console"
-
 func init() {
        markup.RegisterRenderer(Renderer{})
 }
@@ -29,7 +25,7 @@ type Renderer struct{}
 
 // Name implements markup.Renderer
 func (Renderer) Name() string {
-       return MarkupName
+       return "console"
 }
 
 // Extensions implements markup.Renderer
@@ -67,20 +63,3 @@ func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Wri
        _, err = output.Write(buf)
        return err
 }
-
-// Render renders terminal colors to HTML with all specific handling stuff.
-func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
-       if ctx.Type == "" {
-               ctx.Type = MarkupName
-       }
-       return markup.Render(ctx, input, output)
-}
-
-// RenderString renders terminal colors in string to HTML with all specific handling stuff and return string
-func RenderString(ctx *markup.RenderContext, content string) (string, error) {
-       var buf strings.Builder
-       if err := Render(ctx, strings.NewReader(content), &buf); err != nil {
-               return "", err
-       }
-       return buf.String(), nil
-}
index e2eefefc4bafd7a9dcfddec306dc86517c2d79d4..54c65c95d27239513a245ca4ad85118b2493cb1c 100644 (file)
@@ -442,12 +442,11 @@ func createLink(href, content, class string) *html.Node {
        a := &html.Node{
                Type: html.ElementNode,
                Data: atom.A.String(),
-               Attr: []html.Attribute{
-                       {Key: "href", Val: href},
-                       {Key: "data-markdown-generated-content"},
-               },
+               Attr: []html.Attribute{{Key: "href", Val: href}},
+       }
+       if !RenderBehaviorForTesting.DisableInternalAttributes {
+               a.Attr = append(a.Attr, html.Attribute{Key: "data-markdown-generated-content"})
        }
-
        if class != "" {
                a.Attr = append(a.Attr, html.Attribute{Key: "class", Val: class})
        }
index a90de278f57ff7033f139771e756da3cc060da05..5054627dde6c10cecf0856794f258bcbe3b0e6e7 100644 (file)
@@ -11,6 +11,7 @@ import (
 
        "code.gitea.io/gitea/modules/git"
        "code.gitea.io/gitea/modules/markup"
+       "code.gitea.io/gitea/modules/markup/markdown"
 
        "github.com/stretchr/testify/assert"
 )
@@ -23,8 +24,8 @@ func TestRenderCodePreview(t *testing.T) {
        })
        test := func(input, expected string) {
                buffer, err := markup.RenderString(&markup.RenderContext{
-                       Ctx:  git.DefaultContext,
-                       Type: "markdown",
+                       Ctx:        git.DefaultContext,
+                       MarkupType: markdown.MarkupName,
                }, input)
                assert.NoError(t, err)
                assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
index 8f516751b08b811bdbf70aeee6e3f5c9a836477e..2fb657f56b6ff5782a801015c1e27d3877a56c57 100644 (file)
@@ -11,6 +11,7 @@ import (
 
        "code.gitea.io/gitea/modules/git"
        "code.gitea.io/gitea/modules/setting"
+       testModule "code.gitea.io/gitea/modules/test"
        "code.gitea.io/gitea/modules/util"
 
        "github.com/stretchr/testify/assert"
@@ -123,8 +124,9 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
                }
                expectedNil := fmt.Sprintf(expectedFmt, links...)
                testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{
-                       Ctx:   git.DefaultContext,
-                       Metas: localMetas,
+                       Ctx:         git.DefaultContext,
+                       Metas:       localMetas,
+                       ContentMode: RenderContentAsComment,
                })
 
                class := "ref-issue"
@@ -137,8 +139,9 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
                }
                expectedNum := fmt.Sprintf(expectedFmt, links...)
                testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{
-                       Ctx:   git.DefaultContext,
-                       Metas: numericMetas,
+                       Ctx:         git.DefaultContext,
+                       Metas:       numericMetas,
+                       ContentMode: RenderContentAsComment,
                })
        }
 
@@ -266,7 +269,6 @@ func TestRender_IssueIndexPattern_Document(t *testing.T) {
                "user":   "someUser",
                "repo":   "someRepo",
                "style":  IssueNameStyleNumeric,
-               "mode":   "document",
        }
 
        testRenderIssueIndexPattern(t, "#1", "#1", &RenderContext{
@@ -316,8 +318,8 @@ func TestRender_AutoLink(t *testing.T) {
                        Links: Links{
                                Base: TestRepoURL,
                        },
-                       Metas:  localMetas,
-                       IsWiki: true,
+                       Metas:       localMetas,
+                       ContentMode: RenderContentAsWiki,
                }, strings.NewReader(input), &buffer)
                assert.Equal(t, err, nil)
                assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
@@ -340,7 +342,7 @@ func TestRender_AutoLink(t *testing.T) {
 
 func TestRender_FullIssueURLs(t *testing.T) {
        setting.AppURL = TestAppURL
-
+       defer testModule.MockVariableValue(&RenderBehaviorForTesting.DisableInternalAttributes, true)()
        test := func(input, expected string) {
                var result strings.Builder
                err := postProcess(&RenderContext{
@@ -351,9 +353,7 @@ func TestRender_FullIssueURLs(t *testing.T) {
                        Metas: localMetas,
                }, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result)
                assert.NoError(t, err)
-               actual := result.String()
-               actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
-               assert.Equal(t, expected, actual)
+               assert.Equal(t, expected, result.String())
        }
        test("Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6",
                "Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6")
index b6d4ed6a8e2a3582ea42c81c459b984ba1e797d3..fa630656cef41a3effded84389d926e36e4f95cd 100644 (file)
@@ -67,9 +67,8 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
                return
        }
 
-       // FIXME: the use of "mode" is quite dirty and hacky, for example: what is a "document"? how should it be rendered?
-       // The "mode" approach should be refactored to some other more clear&reliable way.
-       crossLinkOnly := ctx.Metas["mode"] == "document" && !ctx.IsWiki
+       // crossLinkOnly if not comment and not wiki
+       crossLinkOnly := ctx.ContentMode != RenderContentAsTitle && ctx.ContentMode != RenderContentAsComment && ctx.ContentMode != RenderContentAsWiki
 
        var (
                found bool
index 93506345683179b49e19be224ee4acd55d591be6..30564da548a66c213cc5c029a1acf9acf74df4da 100644 (file)
@@ -20,7 +20,7 @@ func ResolveLink(ctx *RenderContext, link, userContentAnchorPrefix string) (resu
        isAnchorFragment := link != "" && link[0] == '#'
        if !isAnchorFragment && !IsFullURLString(link) {
                linkBase := ctx.Links.Base
-               if ctx.IsWiki {
+               if ctx.ContentMode == RenderContentAsWiki {
                        // no need to check if the link should be resolved as a wiki link or a wiki raw link
                        // just use wiki link here and it will be redirected to a wiki raw link if necessary
                        linkBase = ctx.Links.WikiLink()
@@ -147,7 +147,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
                }
                if image {
                        if !absoluteLink {
-                               link = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), link)
+                               link = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.ContentMode == RenderContentAsWiki), link)
                        }
                        title := props["title"]
                        if title == "" {
index 6d784975b9849535c6d14c33766b2bc04b6bace6..c499854053fa225b57deaf1bc9b1f2757dee820e 100644 (file)
@@ -17,7 +17,7 @@ func visitNodeImg(ctx *RenderContext, img *html.Node) (next *html.Node) {
                }
 
                if IsNonEmptyRelativePath(attr.Val) {
-                       attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), attr.Val)
+                       attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.ContentMode == RenderContentAsWiki), attr.Val)
 
                        // By default, the "<img>" tag should also be clickable,
                        // because frontend use `<img>` to paste the re-scaled image into the markdown,
@@ -53,7 +53,7 @@ func visitNodeVideo(ctx *RenderContext, node *html.Node) (next *html.Node) {
                        continue
                }
                if IsNonEmptyRelativePath(attr.Val) {
-                       attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), attr.Val)
+                       attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.ContentMode == RenderContentAsWiki), attr.Val)
                }
                attr.Val = camoHandleLink(attr.Val)
                node.Attr[i] = attr
index 82aded4407c2ae281507c2064d43352678e550d3..262d0fc4dd5d14b524a4eeb224dcd58237445a95 100644 (file)
@@ -14,6 +14,7 @@ import (
        "code.gitea.io/gitea/modules/markup"
        "code.gitea.io/gitea/modules/markup/markdown"
        "code.gitea.io/gitea/modules/setting"
+       testModule "code.gitea.io/gitea/modules/test"
        "code.gitea.io/gitea/modules/util"
 
        "github.com/stretchr/testify/assert"
@@ -104,7 +105,7 @@ func TestRender_Commits(t *testing.T) {
 
 func TestRender_CrossReferences(t *testing.T) {
        setting.AppURL = markup.TestAppURL
-
+       defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
        test := func(input, expected string) {
                buffer, err := markup.RenderString(&markup.RenderContext{
                        Ctx:          git.DefaultContext,
@@ -116,9 +117,7 @@ func TestRender_CrossReferences(t *testing.T) {
                        Metas: localMetas,
                }, input)
                assert.NoError(t, err)
-               actual := strings.TrimSpace(buffer)
-               actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
-               assert.Equal(t, strings.TrimSpace(expected), actual)
+               assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
        }
 
        test(
@@ -148,7 +147,7 @@ func TestRender_CrossReferences(t *testing.T) {
 
 func TestRender_links(t *testing.T) {
        setting.AppURL = markup.TestAppURL
-
+       defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
        test := func(input, expected string) {
                buffer, err := markup.RenderString(&markup.RenderContext{
                        Ctx:          git.DefaultContext,
@@ -158,9 +157,7 @@ func TestRender_links(t *testing.T) {
                        },
                }, input)
                assert.NoError(t, err)
-               actual := strings.TrimSpace(buffer)
-               actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
-               assert.Equal(t, strings.TrimSpace(expected), actual)
+               assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
        }
 
        oldCustomURLSchemes := setting.Markdown.CustomURLSchemes
@@ -261,7 +258,7 @@ func TestRender_links(t *testing.T) {
 
 func TestRender_email(t *testing.T) {
        setting.AppURL = markup.TestAppURL
-
+       defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
        test := func(input, expected string) {
                res, err := markup.RenderString(&markup.RenderContext{
                        Ctx:          git.DefaultContext,
@@ -271,9 +268,7 @@ func TestRender_email(t *testing.T) {
                        },
                }, input)
                assert.NoError(t, err)
-               actual := strings.TrimSpace(res)
-               actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
-               assert.Equal(t, strings.TrimSpace(expected), actual)
+               assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res))
        }
        // Text that should be turned into email link
 
@@ -302,10 +297,10 @@ func TestRender_email(t *testing.T) {
        j.doe@example.com;
        j.doe@example.com?
        j.doe@example.com!`,
-               `<p><a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>,<br/>
-<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>.<br/>
-<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>;<br/>
-<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>?<br/>
+               `<p><a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>,
+<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>.
+<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>;
+<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>?
 <a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>!</p>`)
 
        // Test that should *not* be turned into email links
@@ -418,8 +413,8 @@ func TestRender_ShortLinks(t *testing.T) {
                        Links: markup.Links{
                                Base: markup.TestRepoURL,
                        },
-                       Metas:  localMetas,
-                       IsWiki: true,
+                       Metas:       localMetas,
+                       ContentMode: markup.RenderContentAsWiki,
                }, input)
                assert.NoError(t, err)
                assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
@@ -531,10 +526,10 @@ func TestRender_ShortLinks(t *testing.T) {
 func TestRender_RelativeMedias(t *testing.T) {
        render := func(input string, isWiki bool, links markup.Links) string {
                buffer, err := markdown.RenderString(&markup.RenderContext{
-                       Ctx:    git.DefaultContext,
-                       Links:  links,
-                       Metas:  localMetas,
-                       IsWiki: isWiki,
+                       Ctx:         git.DefaultContext,
+                       Links:       links,
+                       Metas:       localMetas,
+                       ContentMode: util.Iif(isWiki, markup.RenderContentAsWiki, markup.RenderContentAsComment),
                }, input)
                assert.NoError(t, err)
                return strings.TrimSpace(string(buffer))
@@ -604,12 +599,7 @@ func Test_ParseClusterFuzz(t *testing.T) {
 func TestPostProcess_RenderDocument(t *testing.T) {
        setting.AppURL = markup.TestAppURL
        setting.StaticURLPrefix = markup.TestAppURL // can't run standalone
-
-       localMetas := map[string]string{
-               "user": "go-gitea",
-               "repo": "gitea",
-               "mode": "document",
-       }
+       defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
 
        test := func(input, expected string) {
                var res strings.Builder
@@ -619,12 +609,10 @@ func TestPostProcess_RenderDocument(t *testing.T) {
                                AbsolutePrefix: true,
                                Base:           "https://example.com",
                        },
-                       Metas: localMetas,
+                       Metas: map[string]string{"user": "go-gitea", "repo": "gitea"},
                }, strings.NewReader(input), &res)
                assert.NoError(t, err)
-               actual := strings.TrimSpace(res.String())
-               actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
-               assert.Equal(t, strings.TrimSpace(expected), actual)
+               assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String()))
        }
 
        // Issue index shouldn't be post processing in a document.
index 0cd9dc5f30c61cdd38e8fb9c99e84110b03b04b3..c8488cfb50c375c8eea1956c9b7c1a920e35bf0c 100644 (file)
@@ -72,7 +72,12 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
                        g.transformList(ctx, v, rc)
                case *ast.Text:
                        if v.SoftLineBreak() && !v.HardLineBreak() {
-                               if ctx.Metas["mode"] != "document" {
+                               // TODO: this was a quite unclear part, old code: `if metas["mode"] != "document" { use comment link break setting }`
+                               // many places render non-comment contents with no mode=document, then these contents also use comment's hard line break setting
+                               // especially in many tests.
+                               if markup.RenderBehaviorForTesting.ForceHardLineBreak {
+                                       v.SetHardLineBreak(true)
+                               } else if ctx.ContentMode == markup.RenderContentAsComment {
                                        v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInComments)
                                } else {
                                        v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInDocuments)
index db4e5706f6dda1f549b7d226fb68d8d9cbe252cf..6af0deb27bb2bd00865e13f3392db6ec882a281f 100644 (file)
@@ -257,9 +257,7 @@ func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Wri
 
 // Render renders Markdown to HTML with all specific handling stuff.
 func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
-       if ctx.Type == "" {
-               ctx.Type = MarkupName
-       }
+       ctx.MarkupType = MarkupName
        return markup.Render(ctx, input, output)
 }
 
index ad38e7a088fbf0aa5d41de590fc0cc7a99286d37..315eed2e62e7cca5a19467c204c9c45e2c5c59cc 100644 (file)
@@ -16,6 +16,7 @@ import (
        "code.gitea.io/gitea/modules/markup/markdown"
        "code.gitea.io/gitea/modules/setting"
        "code.gitea.io/gitea/modules/svg"
+       "code.gitea.io/gitea/modules/test"
        "code.gitea.io/gitea/modules/util"
 
        "github.com/stretchr/testify/assert"
@@ -74,7 +75,7 @@ func TestRender_StandardLinks(t *testing.T) {
                        Links: markup.Links{
                                Base: FullURL,
                        },
-                       IsWiki: true,
+                       ContentMode: markup.RenderContentAsWiki,
                }, input)
                assert.NoError(t, err)
                assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
@@ -296,23 +297,22 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno
 }
 
 func TestTotal_RenderWiki(t *testing.T) {
+       defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)()
+       defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
        setting.AppURL = AppURL
-
        answers := testAnswers(util.URLJoin(FullURL, "wiki"), util.URLJoin(FullURL, "wiki", "raw"))
-
        for i := 0; i < len(sameCases); i++ {
                line, err := markdown.RenderString(&markup.RenderContext{
                        Ctx: git.DefaultContext,
                        Links: markup.Links{
                                Base: FullURL,
                        },
-                       Repo:   newMockRepo(testRepoOwnerName, testRepoName),
-                       Metas:  localMetas,
-                       IsWiki: true,
+                       Repo:        newMockRepo(testRepoOwnerName, testRepoName),
+                       Metas:       localMetas,
+                       ContentMode: markup.RenderContentAsWiki,
                }, sameCases[i])
                assert.NoError(t, err)
-               actual := strings.ReplaceAll(string(line), ` data-markdown-generated-content=""`, "")
-               assert.Equal(t, answers[i], actual)
+               assert.Equal(t, answers[i], string(line))
        }
 
        testCases := []string{
@@ -334,19 +334,18 @@ func TestTotal_RenderWiki(t *testing.T) {
                        Links: markup.Links{
                                Base: FullURL,
                        },
-                       IsWiki: true,
+                       ContentMode: markup.RenderContentAsWiki,
                }, testCases[i])
                assert.NoError(t, err)
-               actual := strings.ReplaceAll(string(line), ` data-markdown-generated-content=""`, "")
-               assert.EqualValues(t, testCases[i+1], actual)
+               assert.EqualValues(t, testCases[i+1], string(line))
        }
 }
 
 func TestTotal_RenderString(t *testing.T) {
+       defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)()
+       defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
        setting.AppURL = AppURL
-
        answers := testAnswers(util.URLJoin(FullURL, "src", "master"), util.URLJoin(FullURL, "media", "master"))
-
        for i := 0; i < len(sameCases); i++ {
                line, err := markdown.RenderString(&markup.RenderContext{
                        Ctx: git.DefaultContext,
@@ -358,8 +357,7 @@ func TestTotal_RenderString(t *testing.T) {
                        Metas: localMetas,
                }, sameCases[i])
                assert.NoError(t, err)
-               actual := strings.ReplaceAll(string(line), ` data-markdown-generated-content=""`, "")
-               assert.Equal(t, answers[i], actual)
+               assert.Equal(t, answers[i], string(line))
        }
 
        testCases := []string{}
@@ -428,6 +426,7 @@ func TestRenderSiblingImages_Issue12925(t *testing.T) {
        expected := `<p><a href="/image1" target="_blank" rel="nofollow noopener"><img src="/image1" alt="image1"></a><br>
 <a href="/image2" target="_blank" rel="nofollow noopener"><img src="/image2" alt="image2"></a></p>
 `
+       defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)()
        res, err := markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase)
        assert.NoError(t, err)
        assert.Equal(t, expected, res)
@@ -996,11 +995,16 @@ space</p>
                },
        }
 
+       defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)()
+       defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
        for i, c := range cases {
-               result, err := markdown.RenderString(&markup.RenderContext{Ctx: context.Background(), Links: c.Links, IsWiki: c.IsWiki}, input)
+               result, err := markdown.RenderString(&markup.RenderContext{
+                       Ctx:         context.Background(),
+                       Links:       c.Links,
+                       ContentMode: util.Iif(c.IsWiki, markup.RenderContentAsWiki, markup.RenderContentAsDefault),
+               }, input)
                assert.NoError(t, err, "Unexpected error in testcase: %v", i)
-               actual := strings.ReplaceAll(string(result), ` data-markdown-generated-content=""`, "")
-               assert.Equal(t, c.Expected, actual, "Unexpected result in testcase %v", i)
+               assert.Equal(t, c.Expected, string(result), "Unexpected result in testcase %v", i)
        }
 }
 
index 812e24f0a2bb49c2c7374c9775c49c52b3d76633..4ed4118854386b66e40efed06d39643ae1199bb8 100644 (file)
@@ -21,7 +21,7 @@ func (g *ASTTransformer) transformImage(ctx *markup.RenderContext, v *ast.Image)
        // Check if the destination is a real link
        if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) {
                v.Destination = []byte(giteautil.URLJoin(
-                       ctx.Links.ResolveMediaLink(ctx.IsWiki),
+                       ctx.Links.ResolveMediaLink(ctx.ContentMode == markup.RenderContentAsWiki),
                        strings.TrimLeft(string(v.Destination), "/"),
                ))
        }
index 25f8d15ef4739e94d1d962e039e4b01849e3e354..6b9c9631575beb0a16cf57a0a2b450ed194ec78c 100644 (file)
@@ -144,14 +144,15 @@ func (r *Writer) resolveLink(kind, link string) string {
                }
 
                base := r.Ctx.Links.Base
-               if r.Ctx.IsWiki {
+               isWiki := r.Ctx.ContentMode == markup.RenderContentAsWiki
+               if isWiki {
                        base = r.Ctx.Links.WikiLink()
                } else if r.Ctx.Links.HasBranchInfo() {
                        base = r.Ctx.Links.SrcLink()
                }
 
                if kind == "image" || kind == "video" {
-                       base = r.Ctx.Links.ResolveMediaLink(r.Ctx.IsWiki)
+                       base = r.Ctx.Links.ResolveMediaLink(isWiki)
                }
 
                link = util.URLJoin(base, link)
index 75b60ed81f004825067f63be61d8d44b82a37b60..b882678c7e20ed6eac1512ada9b798bdec1e12dc 100644 (file)
@@ -10,6 +10,7 @@ import (
        "code.gitea.io/gitea/modules/git"
        "code.gitea.io/gitea/modules/markup"
        "code.gitea.io/gitea/modules/setting"
+       "code.gitea.io/gitea/modules/util"
 
        "github.com/stretchr/testify/assert"
 )
@@ -26,7 +27,7 @@ func TestRender_StandardLinks(t *testing.T) {
                                Base:       "/relative-path",
                                BranchPath: "branch/main",
                        },
-                       IsWiki: isWiki,
+                       ContentMode: util.Iif(isWiki, markup.RenderContentAsWiki, markup.RenderContentAsDefault),
                }, input)
                assert.NoError(t, err)
                assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
index f2ce9229af64d4682e4a28d772ac87ee632c6d18..add50f438272a14630bb688d9eca0e392e5e3ce0 100644 (file)
@@ -5,11 +5,9 @@ package markup
 
 import (
        "context"
-       "errors"
        "fmt"
        "io"
        "net/url"
-       "path/filepath"
        "strings"
        "sync"
 
@@ -29,15 +27,44 @@ const (
        RenderMetaAsTable   RenderMetaMode = "table"
 )
 
+type RenderContentMode string
+
+const (
+       RenderContentAsDefault RenderContentMode = "" // empty means "default", no special handling, maybe just a simple "document"
+       RenderContentAsComment RenderContentMode = "comment"
+       RenderContentAsTitle   RenderContentMode = "title"
+       RenderContentAsWiki    RenderContentMode = "wiki"
+)
+
+var RenderBehaviorForTesting struct {
+       // Markdown line break rendering has 2 default behaviors:
+       // * Use hard: replace "\n" with "<br>" for comments, setting.Markdown.EnableHardLineBreakInComments=true
+       // * Keep soft: "\n" for non-comments (a.k.a. documents), setting.Markdown.EnableHardLineBreakInDocuments=false
+       // In history, there was a mess:
+       // * The behavior was controlled by `Metas["mode"] != "document",
+       // * However, many places render the content without setting "mode" in Metas, all these places used comment line break setting incorrectly
+       ForceHardLineBreak bool
+
+       // Gitea will emit some internal attributes for various purposes, these attributes don't affect rendering.
+       // But there are too many hard-coded test cases, to avoid changing all of them again and again, we can disable emitting these internal attributes.
+       DisableInternalAttributes bool
+}
+
 // RenderContext represents a render context
 type RenderContext struct {
-       Ctx              context.Context
-       RelativePath     string // relative path from tree root of the branch
-       Type             string
-       IsWiki           bool
-       Links            Links
-       Metas            map[string]string // user, repo, mode(comment/document)
-       DefaultLink      string
+       Ctx          context.Context
+       RelativePath string // relative path from tree root of the branch
+
+       // eg: "orgmode", "asciicast", "console"
+       // for file mode, it could be left as empty, and will be detected by file extension in RelativePath
+       MarkupType string
+
+       // what the content will be used for: eg: for comment or for wiki? or just render a file?
+       ContentMode RenderContentMode
+
+       Links            Links             // special link references for rendering, especially when there is a branch/tree path
+       Metas            map[string]string // user&repo, format&style&regexp (for external issue pattern), teams&org (for mention), BranchNameSubURL(for iframe&asciicast)
+       DefaultLink      string            // TODO: need to figure out
        GitRepo          *git.Repository
        Repo             gitrepo.Repository
        ShaExistCache    map[string]bool
@@ -77,12 +104,29 @@ func (ctx *RenderContext) AddCancel(fn func()) {
 
 // Render renders markup file to HTML with all specific handling stuff.
 func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
-       if ctx.Type != "" {
-               return renderByType(ctx, input, output)
-       } else if ctx.RelativePath != "" {
-               return renderFile(ctx, input, output)
+       if ctx.MarkupType == "" && ctx.RelativePath != "" {
+               ctx.MarkupType = DetectMarkupTypeByFileName(ctx.RelativePath)
+               if ctx.MarkupType == "" {
+                       return util.NewInvalidArgumentErrorf("unsupported file to render: %q", ctx.RelativePath)
+               }
+       }
+
+       renderer := renderers[ctx.MarkupType]
+       if renderer == nil {
+               return util.NewInvalidArgumentErrorf("unsupported markup type: %q", ctx.MarkupType)
+       }
+
+       if ctx.RelativePath != "" {
+               if externalRender, ok := renderer.(ExternalRenderer); ok && externalRender.DisplayInIFrame() {
+                       if !ctx.InStandalonePage {
+                               // for an external "DisplayInIFrame" render, it could only output its content in a standalone page
+                               // otherwise, a <iframe> should be outputted to embed the external rendered page
+                               return renderIFrame(ctx, output)
+                       }
+               }
        }
-       return errors.New("render options both filename and type missing")
+
+       return render(ctx, renderer, input, output)
 }
 
 // RenderString renders Markup string to HTML with all specific handling stuff and return string
@@ -170,42 +214,6 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr
        return err
 }
 
-func renderByType(ctx *RenderContext, input io.Reader, output io.Writer) error {
-       if renderer, ok := renderers[ctx.Type]; ok {
-               return render(ctx, renderer, input, output)
-       }
-       return fmt.Errorf("unsupported render type: %s", ctx.Type)
-}
-
-// ErrUnsupportedRenderExtension represents the error when extension doesn't supported to render
-type ErrUnsupportedRenderExtension struct {
-       Extension string
-}
-
-func IsErrUnsupportedRenderExtension(err error) bool {
-       _, ok := err.(ErrUnsupportedRenderExtension)
-       return ok
-}
-
-func (err ErrUnsupportedRenderExtension) Error() string {
-       return fmt.Sprintf("Unsupported render extension: %s", err.Extension)
-}
-
-func renderFile(ctx *RenderContext, input io.Reader, output io.Writer) error {
-       extension := strings.ToLower(filepath.Ext(ctx.RelativePath))
-       if renderer, ok := extRenderers[extension]; ok {
-               if r, ok := renderer.(ExternalRenderer); ok && r.DisplayInIFrame() {
-                       if !ctx.InStandalonePage {
-                               // for an external render, it could only output its content in a standalone page
-                               // otherwise, a <iframe> should be outputted to embed the external rendered page
-                               return renderIFrame(ctx, output)
-                       }
-               }
-               return render(ctx, renderer, input, output)
-       }
-       return ErrUnsupportedRenderExtension{extension}
-}
-
 // Init initializes the render global variables
 func Init(ph *ProcessorHelper) {
        if ph != nil {
index 3b206c1dd780235008a668f28b7dfa4bcfc1cd97..cfdb6db96c3c74fe9136e5c414898c12ff6af1f8 100644 (file)
@@ -21,7 +21,7 @@ type MarkupOption struct {
        //
        // in: body
        Text string
-       // Mode to render (comment, gfm, markdown, file)
+       // Mode to render (markdown, comment, wiki, file)
        //
        // in: body
        Mode string
@@ -30,8 +30,9 @@ type MarkupOption struct {
        //
        // in: body
        Context string
-       // Is it a wiki page ?
+       // Is it a wiki page? (use mode=wiki instead)
        //
+       // Deprecated: true
        // in: body
        Wiki bool
        // File path for detecting extension in file mode
@@ -50,7 +51,7 @@ type MarkdownOption struct {
        //
        // in: body
        Text string
-       // Mode to render (comment, gfm, markdown)
+       // Mode to render (markdown, comment, wiki, file)
        //
        // in: body
        Mode string
@@ -59,8 +60,9 @@ type MarkdownOption struct {
        //
        // in: body
        Context string
-       // Is it a wiki page ?
+       // Is it a wiki page? (use mode=wiki instead)
        //
+       // Deprecated: true
        // in: body
        Wiki bool
 }
index 120182834571528adc65942db42067af2a747db2..1db1e4a111919c29b7e1472949d85d1ca7d50216 100644 (file)
@@ -94,8 +94,9 @@ func (ut *RenderUtils) RenderCommitBody(msg string, metas map[string]string) tem
        }
 
        renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
-               Ctx:   ut.ctx,
-               Metas: metas,
+               Ctx:         ut.ctx,
+               Metas:       metas,
+               ContentMode: markup.RenderContentAsComment,
        }, template.HTMLEscapeString(msgLine))
        if err != nil {
                log.Error("RenderCommitMessage: %v", err)
@@ -116,8 +117,9 @@ func renderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
 // RenderIssueTitle renders issue/pull title with defined post processors
 func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML {
        renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{
-               Ctx:   ut.ctx,
-               Metas: metas,
+               Ctx:         ut.ctx,
+               ContentMode: markup.RenderContentAsTitle,
+               Metas:       metas,
        }, template.HTMLEscapeString(text))
        if err != nil {
                log.Error("RenderIssueTitle: %v", err)
index 0a6e89c5f2edc09156fa6a6cfbeb4548d5f75130..2d331b83171970b9c3355ebd8adb6e7bbc39afe2 100644 (file)
@@ -15,6 +15,7 @@ import (
        "code.gitea.io/gitea/modules/git"
        "code.gitea.io/gitea/modules/log"
        "code.gitea.io/gitea/modules/markup"
+       "code.gitea.io/gitea/modules/test"
        "code.gitea.io/gitea/modules/translation"
 
        "github.com/stretchr/testify/assert"
@@ -72,6 +73,7 @@ func newTestRenderUtils() *RenderUtils {
 }
 
 func TestRenderCommitBody(t *testing.T) {
+       defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
        type args struct {
                msg   string
                metas map[string]string
@@ -129,23 +131,21 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
 <a href="/mention-user" class="mention">@mention-user</a> test
 <a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
   space`
-       actual := strings.ReplaceAll(string(newTestRenderUtils().RenderCommitBody(testInput(), testMetas)), ` data-markdown-generated-content=""`, "")
-       assert.EqualValues(t, expected, actual)
+       assert.EqualValues(t, expected, string(newTestRenderUtils().RenderCommitBody(testInput(), testMetas)))
 }
 
 func TestRenderCommitMessage(t *testing.T) {
        expected := `space <a href="/mention-user" data-markdown-generated-content="" class="mention">@mention-user</a>  `
-
        assert.EqualValues(t, expected, newTestRenderUtils().RenderCommitMessage(testInput(), testMetas))
 }
 
 func TestRenderCommitMessageLinkSubject(t *testing.T) {
        expected := `<a href="https://example.com/link" class="default-link muted">space </a><a href="/mention-user" data-markdown-generated-content="" class="mention">@mention-user</a>`
-
        assert.EqualValues(t, expected, newTestRenderUtils().RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", testMetas))
 }
 
 func TestRenderIssueTitle(t *testing.T) {
+       defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
        expected := `  space @mention-user<SPACE><SPACE>
 /just/a/path.bin
 https://example.com/file.bin
@@ -168,11 +168,11 @@ mail@domain.com
   space<SPACE><SPACE>
 `
        expected = strings.ReplaceAll(expected, "<SPACE>", " ")
-       actual := strings.ReplaceAll(string(newTestRenderUtils().RenderIssueTitle(testInput(), testMetas)), ` data-markdown-generated-content=""`, "")
-       assert.EqualValues(t, expected, actual)
+       assert.EqualValues(t, expected, string(newTestRenderUtils().RenderIssueTitle(testInput(), testMetas)))
 }
 
 func TestRenderMarkdownToHtml(t *testing.T) {
+       defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
        expected := `<p>space <a href="/mention-user" rel="nofollow">@mention-user</a><br/>
 /just/a/path.bin
 <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a>
@@ -194,8 +194,7 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
 #123
 space</p>
 `
-       actual := strings.ReplaceAll(string(newTestRenderUtils().MarkdownToHtml(testInput())), ` data-markdown-generated-content=""`, "")
-       assert.Equal(t, expected, actual)
+       assert.Equal(t, expected, string(newTestRenderUtils().MarkdownToHtml(testInput())))
 }
 
 func TestRenderLabels(t *testing.T) {
index 9699c79368a2cecf8e43ab6c95c96632b9e32627..868ed925190894d895951429a1701fc52fa24b82 100644 (file)
@@ -9,6 +9,7 @@ import (
        "code.gitea.io/gitea/modules/markup"
        "code.gitea.io/gitea/modules/markup/markdown"
        api "code.gitea.io/gitea/modules/structs"
+       "code.gitea.io/gitea/modules/util"
        "code.gitea.io/gitea/modules/web"
        "code.gitea.io/gitea/routers/common"
        "code.gitea.io/gitea/services/context"
@@ -41,7 +42,8 @@ func Markup(ctx *context.APIContext) {
                return
        }
 
-       common.RenderMarkup(ctx.Base, ctx.Repo, form.Mode, form.Text, form.Context, form.FilePath, form.Wiki)
+       mode := util.Iif(form.Wiki, "wiki", form.Mode) //nolint:staticcheck
+       common.RenderMarkup(ctx.Base, ctx.Repo, mode, form.Text, form.Context, form.FilePath)
 }
 
 // Markdown render markdown document to HTML
@@ -71,12 +73,8 @@ func Markdown(ctx *context.APIContext) {
                return
        }
 
-       mode := "markdown"
-       if form.Mode == "comment" || form.Mode == "gfm" {
-               mode = form.Mode
-       }
-
-       common.RenderMarkup(ctx.Base, ctx.Repo, mode, form.Text, form.Context, "", form.Wiki)
+       mode := util.Iif(form.Wiki, "wiki", form.Mode) //nolint:staticcheck
+       common.RenderMarkup(ctx.Base, ctx.Repo, mode, form.Text, form.Context, "")
 }
 
 // MarkdownRaw render raw markdown HTML
index abffdf351614aca50f89c92b0d352930c17833f8..6b8c09034ac72e0fac4c1701977f5b18707c7951 100644 (file)
@@ -14,6 +14,7 @@ import (
        "code.gitea.io/gitea/modules/markup"
        "code.gitea.io/gitea/modules/setting"
        api "code.gitea.io/gitea/modules/structs"
+       "code.gitea.io/gitea/modules/test"
        "code.gitea.io/gitea/modules/web"
        "code.gitea.io/gitea/services/contexttest"
 
@@ -24,6 +25,7 @@ const AppURL = "http://localhost:3000/"
 
 func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expectedBody string, expectedCode int) {
        setting.AppURL = AppURL
+       defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
        context := "/gogits/gogs"
        if !wiki {
                context += path.Join("/src/branch/main", path.Dir(filePath))
@@ -38,13 +40,13 @@ func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expe
        ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
        web.SetForm(ctx, &options)
        Markup(ctx)
-       actual := strings.ReplaceAll(resp.Body.String(), ` data-markdown-generated-content=""`, "")
-       assert.Equal(t, expectedBody, actual)
+       assert.Equal(t, expectedBody, resp.Body.String())
        assert.Equal(t, expectedCode, resp.Code)
        resp.Body.Reset()
 }
 
 func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody string, responseCode int) {
+       defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
        setting.AppURL = AppURL
        context := "/gogits/gogs"
        if !wiki {
@@ -59,8 +61,7 @@ func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody
        ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
        web.SetForm(ctx, &options)
        Markdown(ctx)
-       actual := strings.ReplaceAll(resp.Body.String(), ` data-markdown-generated-content=""`, "")
-       assert.Equal(t, responseBody, actual)
+       assert.Equal(t, responseBody, resp.Body.String())
        assert.Equal(t, responseCode, resp.Code)
        resp.Body.Reset()
 }
@@ -158,8 +159,8 @@ Here are some links to the most important topics. You can find the full list of
 <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)
+       testRenderMarkup(t, "file", false, "path/test.unknown", "## Test", "unsupported file to render: \"path/test.unknown\"\n", http.StatusUnprocessableEntity)
+       testRenderMarkup(t, "unknown", false, "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
 }
 
 var simpleCases = []string{
index 0a00eac7d40d6ec089a0103c65863767e9dcc1a9..c8cc1a5ff12719ff6f8532e73c0db1c9f0791618 100644 (file)
@@ -5,21 +5,22 @@
 package common
 
 import (
+       "errors"
        "fmt"
        "net/http"
        "path"
        "strings"
 
-       repo_model "code.gitea.io/gitea/models/repo"
        "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"
 )
 
 // RenderMarkup renders markup text for the /markup and /markdown endpoints
-func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPathContext, filePath string, wiki bool) {
+func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPathContext, filePath string) {
        // 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
@@ -27,32 +28,33 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPa
        // 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}
+       renderCtx := &markup.RenderContext{
+               Ctx:        ctx,
+               Links:      markup.Links{AbsolutePrefix: true},
+               MarkupType: markdown.MarkupName,
+       }
        if urlPathContext != "" {
-               links.Base = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext)
+               renderCtx.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: links,
-               }, strings.NewReader(text), ctx.Resp); err != nil {
+       if mode == "" || mode == "markdown" {
+               // raw markdown doesn't need any special handling
+               if err := markdown.RenderRaw(renderCtx, strings.NewReader(text), ctx.Resp); err != nil {
                        ctx.Error(http.StatusInternalServerError, err.Error())
                }
                return
+       }
+       switch mode {
+       case "gfm": // legacy mode, do nothing
        case "comment":
-               // Issue & comment content
-               markupType = markdown.MarkupName
-       case "gfm":
-               // GitHub Flavored Markdown
-               markupType = markdown.MarkupName
+               renderCtx.ContentMode = markup.RenderContentAsComment
+       case "wiki":
+               renderCtx.ContentMode = markup.RenderContentAsWiki
        case "file":
-               markupType = "" // render the repo file content by its extension
-               relativePath = filePath
+               // render the repo file content by its extension
+               renderCtx.MarkupType = ""
+               renderCtx.RelativePath = filePath
+               renderCtx.InStandalonePage = true
        default:
                ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
                return
@@ -67,33 +69,19 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPa
                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}
+               renderCtx.Links = markup.Links{AbsolutePrefix: true, Base: absoluteBasePrefix, BranchPath: refPath, TreePath: fileDir}
        }
 
-       meta := map[string]string{}
-       var repoCtx *repo_model.Repository
        if repo != nil && repo.Repository != nil {
-               repoCtx = repo.Repository
-               if mode == "comment" {
-                       meta = repo.Repository.ComposeMetas(ctx)
+               renderCtx.Repo = repo.Repository
+               if renderCtx.ContentMode == markup.RenderContentAsComment {
+                       renderCtx.Metas = repo.Repository.ComposeMetas(ctx)
                } else {
-                       meta = repo.Repository.ComposeDocumentMetas(ctx)
+                       renderCtx.Metas = repo.Repository.ComposeDocumentMetas(ctx)
                }
        }
-       if mode != "comment" {
-               meta["mode"] = "document"
-       }
-
-       if err := markup.Render(&markup.RenderContext{
-               Ctx:          ctx,
-               Repo:         repoCtx,
-               Links:        links,
-               Metas:        meta,
-               IsWiki:       wiki,
-               Type:         markupType,
-               RelativePath: relativePath,
-       }, strings.NewReader(text), ctx.Resp); err != nil {
-               if markup.IsErrUnsupportedRenderExtension(err) {
+       if err := markup.Render(renderCtx, strings.NewReader(text), ctx.Resp); err != nil {
+               if errors.Is(err, util.ErrInvalidArgument) {
                        ctx.Error(http.StatusUnprocessableEntity, err.Error())
                } else {
                        ctx.Error(http.StatusInternalServerError, err.Error())
index 3f33e9933a2ea4a12c0424ced639d0ffb6d5ca5c..a273515c8a3b0f5b2284c12334d8029c340894f0 100644 (file)
@@ -56,7 +56,6 @@ func renderMarkdown(ctx *context.Context, act *activities_model.Action, content
                Links: markup.Links{
                        Base: act.GetRepoLink(ctx),
                },
-               Type: markdown.MarkupName,
                Metas: map[string]string{
                        "user": act.GetRepoUserName(ctx),
                        "repo": act.GetRepoName(ctx),
index 2dbbd6fc097dca3e9772988ebcc7bf3dbba6131c..0c7ec6c2eb64f22767d7b82d322610bd35736861 100644 (file)
@@ -6,6 +6,7 @@ package misc
 
 import (
        api "code.gitea.io/gitea/modules/structs"
+       "code.gitea.io/gitea/modules/util"
        "code.gitea.io/gitea/modules/web"
        "code.gitea.io/gitea/routers/common"
        "code.gitea.io/gitea/services/context"
@@ -14,5 +15,6 @@ import (
 // Markup render markup document to HTML
 func Markup(ctx *context.Context) {
        form := web.GetForm(ctx).(*api.MarkupOption)
-       common.RenderMarkup(ctx.Base, ctx.Repo, form.Mode, form.Text, form.Context, form.FilePath, form.Wiki)
+       mode := util.Iif(form.Wiki, "wiki", form.Mode) //nolint:staticcheck
+       common.RenderMarkup(ctx.Base, ctx.Repo, mode, form.Text, form.Context, form.FilePath)
 }
index 7d9281b3973875b6d0a5f9c191f38658d80bfdfa..7030f6d8a982adc0383a7e2de8c283a88966c465 100644 (file)
@@ -312,6 +312,7 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
 
                ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{
                        Ctx:          ctx,
+                       MarkupType:   markupType,
                        RelativePath: path.Join(ctx.Repo.TreePath, readmeFile.Name()), // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path).
                        Links: markup.Links{
                                Base:       ctx.Repo.RepoLink,
@@ -502,28 +503,20 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
                ctx.Data["ReadmeExist"] = readmeExist
 
                markupType := markup.DetectMarkupTypeByFileName(blob.Name())
-               // If the markup is detected by custom markup renderer it should not be reset later on
-               // to not pass it down to the render context.
-               detected := false
                if markupType == "" {
-                       detected = true
                        markupType = markup.DetectRendererType(blob.Name(), bytes.NewReader(buf))
                }
                if markupType != "" {
                        ctx.Data["HasSourceRenderedToggle"] = true
                }
-
                if markupType != "" && !shouldRenderSource {
                        ctx.Data["IsMarkup"] = true
                        ctx.Data["MarkupType"] = markupType
-                       if !detected {
-                               markupType = ""
-                       }
                        metas := ctx.Repo.Repository.ComposeDocumentMetas(ctx)
                        metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
                        ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{
                                Ctx:          ctx,
-                               Type:         markupType,
+                               MarkupType:   markupType,
                                RelativePath: ctx.Repo.TreePath,
                                Links: markup.Links{
                                        Base:       ctx.Repo.RepoLink,
@@ -615,6 +608,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
                        ctx.Data["MarkupType"] = markupType
                        ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{
                                Ctx:          ctx,
+                               MarkupType:   markupType,
                                RelativePath: ctx.Repo.TreePath,
                                Links: markup.Links{
                                        Base:       ctx.Repo.RepoLink,
index d2056353d886d767640d0af8ea60a570e663a812..2b8312f10acdfb0cd1111eaadd55b7e7959ed1e2 100644 (file)
@@ -289,12 +289,12 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
        }
 
        rctx := &markup.RenderContext{
-               Ctx:   ctx,
-               Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
+               Ctx:         ctx,
+               ContentMode: markup.RenderContentAsWiki,
+               Metas:       ctx.Repo.Repository.ComposeDocumentMetas(ctx),
                Links: markup.Links{
                        Base: ctx.Repo.RepoLink,
                },
-               IsWiki: true,
        }
        buf := &strings.Builder{}
 
index d0abf603c31ba38ca1105e8e160d99c6d9dd1795..4fbfc2bd175fbcb9aee9783e0a61c4da9ee6b580 100644 (file)
@@ -258,7 +258,6 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
                                        Base:       profileDbRepo.Link(),
                                        BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
                                },
-                               Metas: map[string]string{"mode": "document"},
                        }, bytes); err != nil {
                                log.Error("failed to RenderString: %v", err)
                        } else {
index e4206293724eb1dc7178401380164ac631f7c3ae..132ce19a31b90b748f2980dd2d9643b7271f1461 100644 (file)
@@ -260,8 +260,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
        ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
        if len(ctx.ContextUser.Description) != 0 {
                content, err := markdown.RenderString(&markup.RenderContext{
-                       Metas: map[string]string{"mode": "document"},
-                       Ctx:   ctx,
+                       Ctx: ctx,
                }, ctx.ContextUser.Description)
                if err != nil {
                        ctx.ServerError("RenderString", err)
index 8fed15b516fbca2ba74aea75ab504f266cb10851..a6dcba4f191ef54ee0639d4a4e44874607ddd7e3 100644 (file)
           "type": "string"
         },
         "Mode": {
-          "description": "Mode to render (comment, gfm, markdown)\n\nin: body",
+          "description": "Mode to render (markdown, comment, wiki, file)\n\nin: body",
           "type": "string"
         },
         "Text": {
           "type": "string"
         },
         "Wiki": {
-          "description": "Is it a wiki page ?\n\nin: body",
+          "description": "Is it a wiki page? (use mode=wiki instead)\n\nDeprecated: true\nin: body",
           "type": "boolean"
         }
       },
           "type": "string"
         },
         "Mode": {
-          "description": "Mode to render (comment, gfm, markdown, file)\n\nin: body",
+          "description": "Mode to render (markdown, comment, wiki, file)\n\nin: body",
           "type": "string"
         },
         "Text": {
           "type": "string"
         },
         "Wiki": {
-          "description": "Is it a wiki page ?\n\nin: body",
+          "description": "Is it a wiki page? (use mode=wiki instead)\n\nDeprecated: true\nin: body",
           "type": "boolean"
         }
       },
index e50f5c1356e22d0ae355f6b49e9b646a59f2ab97..2d713b0eb9d5abbee5feec54916a4740ef60a875 100644 (file)
@@ -10,6 +10,8 @@ import (
        "strings"
        "testing"
 
+       "code.gitea.io/gitea/modules/markup"
+       "code.gitea.io/gitea/modules/markup/external"
        "code.gitea.io/gitea/modules/setting"
        "code.gitea.io/gitea/tests"
 
@@ -23,10 +25,9 @@ func TestExternalMarkupRenderer(t *testing.T) {
                return
        }
 
-       const repoURL = "user30/renderer"
-       req := NewRequest(t, "GET", repoURL+"/src/branch/master/README.html")
+       req := NewRequest(t, "GET", "/user30/renderer/src/branch/master/README.html")
        resp := MakeRequest(t, req, http.StatusOK)
-       assert.EqualValues(t, "text/html; charset=utf-8", resp.Header()["Content-Type"][0])
+       assert.EqualValues(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type"))
 
        bs, err := io.ReadAll(resp.Body)
        assert.NoError(t, err)
@@ -36,4 +37,24 @@ func TestExternalMarkupRenderer(t *testing.T) {
        data, err := div.Html()
        assert.NoError(t, err)
        assert.EqualValues(t, "<div>\n\ttest external renderer\n</div>", strings.TrimSpace(data))
+
+       r := markup.GetRendererByFileName("a.html").(*external.Renderer)
+       r.RenderContentMode = setting.RenderContentModeIframe
+
+       req = NewRequest(t, "GET", "/user30/renderer/src/branch/master/README.html")
+       resp = MakeRequest(t, req, http.StatusOK)
+       assert.EqualValues(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type"))
+       bs, err = io.ReadAll(resp.Body)
+       assert.NoError(t, err)
+       doc = NewHTMLParser(t, bytes.NewBuffer(bs))
+       iframe := doc.Find("iframe")
+       assert.EqualValues(t, "/user30/renderer/render/branch/master/README.html", iframe.AttrOr("src", ""))
+
+       req = NewRequest(t, "GET", "/user30/renderer/render/branch/master/README.html")
+       resp = MakeRequest(t, req, http.StatusOK)
+       assert.EqualValues(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type"))
+       bs, err = io.ReadAll(resp.Body)
+       assert.NoError(t, err)
+       assert.EqualValues(t, "frame-src 'self'; sandbox allow-scripts", resp.Header().Get("Content-Security-Policy"))
+       assert.EqualValues(t, "<div>\n\ttest external renderer\n</div>\n", string(bs))
 }
index 576c1bccd697d4c8ae50d9045b8757c4e9a2776a..7117952fa383c0d92c483b2e0280074134208dcd 100644 (file)
@@ -74,7 +74,6 @@ export class ComboMarkdownEditor {
   previewUrl: string;
   previewContext: string;
   previewMode: string;
-  previewWiki: boolean;
 
   constructor(container, options = {}) {
     container._giteaComboMarkdownEditor = this;
@@ -213,13 +212,11 @@ export class ComboMarkdownEditor {
     this.previewUrl = this.tabPreviewer.getAttribute('data-preview-url');
     this.previewContext = this.tabPreviewer.getAttribute('data-preview-context');
     this.previewMode = this.options.previewMode ?? 'comment';
-    this.previewWiki = this.options.previewWiki ?? false;
     this.tabPreviewer.addEventListener('click', async () => {
       const formData = new FormData();
       formData.append('mode', this.previewMode);
       formData.append('context', this.previewContext);
       formData.append('text', this.value());
-      formData.append('wiki', String(this.previewWiki));
       const response = await POST(this.previewUrl, {data: formData});
       const data = await response.text();
       renderPreviewPanelContent($(panelPreviewer), data);
index 1a62427b731c25c5b4e586492ae6b11a145e3a7c..0e72b8710982e9c5806e6a8b033ae5e47f40f144 100644 (file)
@@ -26,7 +26,6 @@ async function initRepoWikiFormEditor() {
       formData.append('mode', editor.previewMode);
       formData.append('context', editor.previewContext);
       formData.append('text', newContent);
-      formData.append('wiki', editor.previewWiki);
       try {
         const response = await POST(editor.previewUrl, {data: formData});
         const data = await response.text();
@@ -51,8 +50,7 @@ async function initRepoWikiFormEditor() {
     // And another benefit is that we only need to write the style once for both editors.
     // TODO: Move height style to CSS after EasyMDE removal.
     editorHeights: {minHeight: '300px', height: 'calc(100vh - 600px)'},
-    previewMode: 'gfm',
-    previewWiki: true,
+    previewMode: 'wiki',
     easyMDEOptions: {
       previewRender: (_content, previewTarget) => previewTarget.innerHTML, // disable builtin preview render
       toolbar: ['bold', 'italic', 'strikethrough', '|',