metas := map[string]string{
"user": repo.OwnerName,
"repo": repo.Name,
- "mode": "comment",
}
unit, err := repo.GetUnit(ctx, unit.TypeExternalTracker)
for k, v := range repo.ComposeMetas(ctx) {
metas[k] = v
}
- metas["mode"] = "document"
repo.DocumentRenderingMetas = metas
}
return repo.DocumentRenderingMetas
"io"
"path/filepath"
"regexp"
- "strings"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"github.com/go-enry/go-enry/v2"
)
-// MarkupName describes markup's name
-var MarkupName = "console"
-
func init() {
markup.RegisterRenderer(Renderer{})
}
// Name implements markup.Renderer
func (Renderer) Name() string {
- return MarkupName
+ return "console"
}
// Extensions implements markup.Renderer
_, 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
-}
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})
}
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/markup/markdown"
"github.com/stretchr/testify/assert"
)
})
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))
"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"
}
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"
}
expectedNum := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{
- Ctx: git.DefaultContext,
- Metas: numericMetas,
+ Ctx: git.DefaultContext,
+ Metas: numericMetas,
+ ContentMode: RenderContentAsComment,
})
}
"user": "someUser",
"repo": "someRepo",
"style": IssueNameStyleNumeric,
- "mode": "document",
}
testRenderIssueIndexPattern(t, "#1", "#1", &RenderContext{
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()))
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{
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")
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
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()
}
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 == "" {
}
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,
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
"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"
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,
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(
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,
},
}, 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
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,
},
}, 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
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
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)))
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))
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
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.
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)
// 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)
}
"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"
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)))
}
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{
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,
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{}
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)
},
}
+ 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)
}
}
// 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), "/"),
))
}
}
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)
"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"
)
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))
import (
"context"
- "errors"
"fmt"
"io"
"net/url"
- "path/filepath"
"strings"
"sync"
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®exp (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
// 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
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 {
//
// in: body
Text string
- // Mode to render (comment, gfm, markdown, file)
+ // Mode to render (markdown, comment, wiki, file)
//
// in: body
Mode string
//
// 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
//
// in: body
Text string
- // Mode to render (comment, gfm, markdown)
+ // Mode to render (markdown, comment, wiki, file)
//
// in: body
Mode string
//
// in: body
Context string
- // Is it a wiki page ?
+ // Is it a wiki page? (use mode=wiki instead)
//
+ // Deprecated: true
// in: body
Wiki bool
}
}
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)
// 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)
"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"
}
func TestRenderCommitBody(t *testing.T) {
+ defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
type args struct {
msg string
metas map[string]string
<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
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>
#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) {
"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"
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
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
"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"
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))
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 {
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()
}
<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{
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
// 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
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())
Links: markup.Links{
Base: act.GetRepoLink(ctx),
},
- Type: markdown.MarkupName,
Metas: map[string]string{
"user": act.GetRepoUserName(ctx),
"repo": act.GetRepoName(ctx),
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"
// 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)
}
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,
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,
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,
}
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{}
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 {
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)
"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"
}
},
"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"
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)
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))
}
previewUrl: string;
previewContext: string;
previewMode: string;
- previewWiki: boolean;
constructor(container, options = {}) {
container._giteaComboMarkdownEditor = this;
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);
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();
// 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', '|',