Refactor markup render system (#32589)

This PR mainly moves some code and introduces `RenderContext.WithXxx`
functions
This commit is contained in:
wxiaoguang 2024-11-22 13:48:09 +08:00 committed by GitHub
parent 81ac8d914c
commit c4e27cb27b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 486 additions and 626 deletions

View File

@ -112,14 +112,12 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
} }
var err error var err error
if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ rctx := markup.NewRenderContext(ctx).
Ctx: ctx, WithRepoFacade(issue.Repo).
Repo: issue.Repo, WithLinks(markup.Links{Base: issue.Repo.Link()}).
Links: markup.Links{ WithMetas(issue.Repo.ComposeMetas(ctx))
Base: issue.Repo.Link(), if comment.RenderedContent, err = markdown.RenderString(rctx,
}, comment.Content); err != nil {
Metas: issue.Repo.ComposeMetas(ctx),
}, comment.Content); err != nil {
return nil, err return nil, err
} }
} }

View File

@ -617,10 +617,7 @@ func (repo *Repository) CanEnableEditor() bool {
// DescriptionHTML does special handles to description and return HTML string. // DescriptionHTML does special handles to description and return HTML string.
func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML { func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
desc, err := markup.RenderDescriptionHTML(&markup.RenderContext{ desc, err := markup.RenderDescriptionHTML(markup.NewRenderContext(ctx), repo.Description)
Ctx: ctx,
// Don't use Metas to speedup requests
}, repo.Description)
if err != nil { if err != nil {
log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err) log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
return template.HTML(markup.SanitizeDescription(repo.Description)) return template.HTML(markup.SanitizeDescription(repo.Description))

View File

@ -7,7 +7,7 @@ import (
"bytes" "bytes"
stdcsv "encoding/csv" stdcsv "encoding/csv"
"io" "io"
"path/filepath" "path"
"regexp" "regexp"
"strings" "strings"
@ -53,7 +53,7 @@ func CreateReaderAndDetermineDelimiter(ctx *markup.RenderContext, rd io.Reader)
func determineDelimiter(ctx *markup.RenderContext, data []byte) rune { func determineDelimiter(ctx *markup.RenderContext, data []byte) rune {
extension := ".csv" extension := ".csv"
if ctx != nil { if ctx != nil {
extension = strings.ToLower(filepath.Ext(ctx.RelativePath)) extension = strings.ToLower(path.Ext(ctx.RenderOptions.RelativePath))
} }
var delimiter rune var delimiter rune

View File

@ -5,13 +5,13 @@ package csv
import ( import (
"bytes" "bytes"
"context"
"encoding/csv" "encoding/csv"
"io" "io"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
@ -231,10 +231,7 @@ John Doe john@doe.com This,note,had,a,lot,of,commas,to,test,delimiters`,
} }
for n, c := range cases { for n, c := range cases {
delimiter := determineDelimiter(&markup.RenderContext{ delimiter := determineDelimiter(markup.NewRenderContext(context.Background()).WithRelativePath(c.filename), []byte(decodeSlashes(t, c.csv)))
Ctx: git.DefaultContext,
RelativePath: c.filename,
}, []byte(decodeSlashes(t, c.csv)))
assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter) assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter)
} }
} }

View File

@ -44,10 +44,10 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) error { func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) error {
rawURL := fmt.Sprintf("%s/%s/%s/raw/%s/%s", rawURL := fmt.Sprintf("%s/%s/%s/raw/%s/%s",
setting.AppSubURL, setting.AppSubURL,
url.PathEscape(ctx.Metas["user"]), url.PathEscape(ctx.RenderOptions.Metas["user"]),
url.PathEscape(ctx.Metas["repo"]), url.PathEscape(ctx.RenderOptions.Metas["repo"]),
ctx.Metas["BranchNameSubURL"], ctx.RenderOptions.Metas["BranchNameSubURL"],
url.PathEscape(ctx.RelativePath), url.PathEscape(ctx.RenderOptions.RelativePath),
) )
return ctx.RenderInternal.FormatWithSafeAttrs(output, `<div class="%s" %s="%s"></div>`, playerClassName, playerSrcAttr, rawURL) return ctx.RenderInternal.FormatWithSafeAttrs(output, `<div class="%s" %s="%s"></div>`, playerClassName, playerSrcAttr, rawURL)
} }

View File

@ -4,10 +4,10 @@
package console package console
import ( import (
"context"
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -24,8 +24,7 @@ func TestRenderConsole(t *testing.T) {
canRender := render.CanRender("test", strings.NewReader(k)) canRender := render.CanRender("test", strings.NewReader(k))
assert.True(t, canRender) assert.True(t, canRender)
err := render.Render(&markup.RenderContext{Ctx: git.DefaultContext}, err := render.Render(markup.NewRenderContext(context.Background()), strings.NewReader(k), &buf)
strings.NewReader(k), &buf)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, v, buf.String()) assert.EqualValues(t, v, buf.String())
} }

View File

@ -133,10 +133,10 @@ func (r Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.W
// Check if maxRows or maxSize is reached, and if true, warn. // Check if maxRows or maxSize is reached, and if true, warn.
if (row >= maxRows && maxRows != 0) || (rd.InputOffset() >= maxSize && maxSize != 0) { if (row >= maxRows && maxRows != 0) || (rd.InputOffset() >= maxSize && maxSize != 0) {
warn := `<table class="data-table"><tr><td>` warn := `<table class="data-table"><tr><td>`
rawLink := ` <a href="` + ctx.Links.RawLink() + `/` + util.PathEscapeSegments(ctx.RelativePath) + `">` rawLink := ` <a href="` + ctx.RenderOptions.Links.RawLink() + `/` + util.PathEscapeSegments(ctx.RenderOptions.RelativePath) + `">`
// Try to get the user translation // Try to get the user translation
if locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale); ok { if locale, ok := ctx.Value(translation.ContextKey).(translation.Locale); ok {
warn += locale.TrString("repo.file_too_large") warn += locale.TrString("repo.file_too_large")
rawLink += locale.TrString("repo.file_view_raw") rawLink += locale.TrString("repo.file_view_raw")
} else { } else {

View File

@ -4,10 +4,10 @@
package markup package markup
import ( import (
"context"
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -24,8 +24,7 @@ func TestRenderCSV(t *testing.T) {
for k, v := range kases { for k, v := range kases {
var buf strings.Builder var buf strings.Builder
err := render.Render(&markup.RenderContext{Ctx: git.DefaultContext}, err := render.Render(markup.NewRenderContext(context.Background()), strings.NewReader(k), &buf)
strings.NewReader(k), &buf)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, v, buf.String()) assert.EqualValues(t, v, buf.String())
} }

View File

@ -12,7 +12,6 @@ import (
"runtime" "runtime"
"strings" "strings"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/process"
@ -80,8 +79,8 @@ func envMark(envName string) string {
func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
var ( var (
command = strings.NewReplacer( command = strings.NewReplacer(
envMark("GITEA_PREFIX_SRC"), ctx.Links.SrcLink(), envMark("GITEA_PREFIX_SRC"), ctx.RenderOptions.Links.SrcLink(),
envMark("GITEA_PREFIX_RAW"), ctx.Links.RawLink(), envMark("GITEA_PREFIX_RAW"), ctx.RenderOptions.Links.RawLink(),
).Replace(p.Command) ).Replace(p.Command)
commands = strings.Fields(command) commands = strings.Fields(command)
args = commands[1:] args = commands[1:]
@ -113,22 +112,14 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
args = append(args, f.Name()) args = append(args, f.Name())
} }
if ctx.Ctx == nil { processCtx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.RenderOptions.Links.SrcLink()))
if !setting.IsProd || setting.IsInTesting {
panic("RenderContext did not provide context")
}
log.Warn("RenderContext did not provide context, defaulting to Shutdown context")
ctx.Ctx = graceful.GetManager().ShutdownContext()
}
processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.Links.SrcLink()))
defer finished() defer finished()
cmd := exec.CommandContext(processCtx, commands[0], args...) cmd := exec.CommandContext(processCtx, commands[0], args...)
cmd.Env = append( cmd.Env = append(
os.Environ(), os.Environ(),
"GITEA_PREFIX_SRC="+ctx.Links.SrcLink(), "GITEA_PREFIX_SRC="+ctx.RenderOptions.Links.SrcLink(),
"GITEA_PREFIX_RAW="+ctx.Links.RawLink(), "GITEA_PREFIX_RAW="+ctx.RenderOptions.Links.RawLink(),
) )
if !p.IsInputFile { if !p.IsInputFile {
cmd.Stdin = input cmd.Stdin = input

View File

@ -38,7 +38,7 @@ func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosSt
CommitID: node.Data[m[6]:m[7]], CommitID: node.Data[m[6]:m[7]],
FilePath: node.Data[m[8]:m[9]], FilePath: node.Data[m[8]:m[9]],
} }
if !httplib.IsCurrentGiteaSiteURL(ctx.Ctx, opts.FullURL) { if !httplib.IsCurrentGiteaSiteURL(ctx, opts.FullURL) {
return 0, 0, "", nil return 0, 0, "", nil
} }
u, err := url.Parse(opts.FilePath) u, err := url.Parse(opts.FilePath)
@ -51,7 +51,7 @@ func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosSt
lineStart, _ := strconv.Atoi(strings.TrimPrefix(lineStartStr, "L")) lineStart, _ := strconv.Atoi(strings.TrimPrefix(lineStartStr, "L"))
lineStop, _ := strconv.Atoi(strings.TrimPrefix(lineStopStr, "L")) lineStop, _ := strconv.Atoi(strings.TrimPrefix(lineStopStr, "L"))
opts.LineStart, opts.LineStop = lineStart, lineStop opts.LineStart, opts.LineStop = lineStart, lineStop
h, err := DefaultProcessorHelper.RenderRepoFileCodePreview(ctx.Ctx, opts) h, err := DefaultProcessorHelper.RenderRepoFileCodePreview(ctx, opts)
return m[0], m[1], h, err return m[0], m[1], h, err
} }

View File

@ -9,7 +9,6 @@ import (
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/markup/markdown"
@ -23,10 +22,7 @@ func TestRenderCodePreview(t *testing.T) {
}, },
}) })
test := func(input, expected string) { test := func(input, expected string) {
buffer, err := markup.RenderString(&markup.RenderContext{ buffer, err := markup.RenderString(markup.NewRenderContext(context.Background()).WithMarkupType(markdown.MarkupName), input)
Ctx: git.DefaultContext,
MarkupType: markdown.MarkupName,
}, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
} }

View File

@ -84,7 +84,7 @@ func anyHashPatternExtract(s string) (ret anyHashPatternResult, ok bool) {
// fullHashPatternProcessor renders SHA containing URLs // fullHashPatternProcessor renders SHA containing URLs
func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) { func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.Metas == nil { if ctx.RenderOptions.Metas == nil {
return return
} }
nodeStop := node.NextSibling nodeStop := node.NextSibling
@ -111,7 +111,7 @@ func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) {
} }
func comparePatternProcessor(ctx *RenderContext, node *html.Node) { func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.Metas == nil { if ctx.RenderOptions.Metas == nil {
return return
} }
nodeStop := node.NextSibling nodeStop := node.NextSibling
@ -163,14 +163,14 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
// hashCurrentPatternProcessor renders SHA1 strings to corresponding links that // hashCurrentPatternProcessor renders SHA1 strings to corresponding links that
// are assumed to be in the same repository. // are assumed to be in the same repository.
func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) { func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.Metas == nil || ctx.Metas["user"] == "" || ctx.Metas["repo"] == "" || (ctx.Repo == nil && ctx.GitRepo == nil) { if ctx.RenderOptions.Metas == nil || ctx.RenderOptions.Metas["user"] == "" || ctx.RenderOptions.Metas["repo"] == "" || (ctx.RenderHelper.repoFacade == nil && ctx.RenderHelper.gitRepo == nil) {
return return
} }
start := 0 start := 0
next := node.NextSibling next := node.NextSibling
if ctx.ShaExistCache == nil { if ctx.RenderHelper.shaExistCache == nil {
ctx.ShaExistCache = make(map[string]bool) ctx.RenderHelper.shaExistCache = make(map[string]bool)
} }
for node != nil && node != next && start < len(node.Data) { for node != nil && node != next && start < len(node.Data) {
m := globalVars().hashCurrentPattern.FindStringSubmatchIndex(node.Data[start:]) m := globalVars().hashCurrentPattern.FindStringSubmatchIndex(node.Data[start:])
@ -191,25 +191,25 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
// a commit in the repository before making it a link. // a commit in the repository before making it a link.
// check cache first // check cache first
exist, inCache := ctx.ShaExistCache[hash] exist, inCache := ctx.RenderHelper.shaExistCache[hash]
if !inCache { if !inCache {
if ctx.GitRepo == nil { if ctx.RenderHelper.gitRepo == nil {
var err error var err error
var closer io.Closer var closer io.Closer
ctx.GitRepo, closer, err = gitrepo.RepositoryFromContextOrOpen(ctx.Ctx, ctx.Repo) ctx.RenderHelper.gitRepo, closer, err = gitrepo.RepositoryFromContextOrOpen(ctx, ctx.RenderHelper.repoFacade)
if err != nil { if err != nil {
log.Error("unable to open repository: %s Error: %v", gitrepo.RepoGitURL(ctx.Repo), err) log.Error("unable to open repository: %s Error: %v", gitrepo.RepoGitURL(ctx.RenderHelper.repoFacade), err)
return return
} }
ctx.AddCancel(func() { ctx.AddCancel(func() {
_ = closer.Close() _ = closer.Close()
ctx.GitRepo = nil ctx.RenderHelper.gitRepo = nil
}) })
} }
// Don't use IsObjectExist since it doesn't support short hashs with gogit edition. // Don't use IsObjectExist since it doesn't support short hashs with gogit edition.
exist = ctx.GitRepo.IsReferenceExist(hash) exist = ctx.RenderHelper.gitRepo.IsReferenceExist(hash)
ctx.ShaExistCache[hash] = exist ctx.RenderHelper.shaExistCache[hash] = exist
} }
if !exist { if !exist {
@ -217,7 +217,7 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
continue continue
} }
link := util.URLJoin(ctx.Links.Prefix(), ctx.Metas["user"], ctx.Metas["repo"], "commit", hash) link := util.URLJoin(ctx.RenderOptions.Links.Prefix(), ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], "commit", hash)
replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), "commit")) replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), "commit"))
start = 0 start = 0
node = node.NextSibling.NextSibling node = node.NextSibling.NextSibling

View File

@ -4,12 +4,12 @@
package markup package markup
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
testModule "code.gitea.io/gitea/modules/test" testModule "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -79,11 +79,11 @@ func TestRender_IssueIndexPattern(t *testing.T) {
// numeric: render inputs without valid mentions // numeric: render inputs without valid mentions
test := func(s string) { test := func(s string) {
testRenderIssueIndexPattern(t, s, s, &RenderContext{ testRenderIssueIndexPattern(t, s, s, &RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
}) })
testRenderIssueIndexPattern(t, s, s, &RenderContext{ testRenderIssueIndexPattern(t, s, s, &RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
Metas: numericMetas, RenderOptions: RenderOptions{Metas: numericMetas},
}) })
} }
@ -133,8 +133,8 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
} }
expectedNil := fmt.Sprintf(expectedFmt, links...) expectedNil := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{ testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
Metas: localMetas, RenderOptions: RenderOptions{Metas: localMetas},
}) })
class := "ref-issue" class := "ref-issue"
@ -147,8 +147,8 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
} }
expectedNum := fmt.Sprintf(expectedFmt, links...) expectedNum := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{ testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
Metas: numericMetas, RenderOptions: RenderOptions{Metas: numericMetas},
}) })
} }
@ -184,8 +184,8 @@ func TestRender_IssueIndexPattern3(t *testing.T) {
// alphanumeric: render inputs without valid mentions // alphanumeric: render inputs without valid mentions
test := func(s string) { test := func(s string) {
testRenderIssueIndexPattern(t, s, s, &RenderContext{ testRenderIssueIndexPattern(t, s, s, &RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
Metas: alphanumericMetas, RenderOptions: RenderOptions{Metas: alphanumericMetas},
}) })
} }
test("") test("")
@ -217,8 +217,8 @@ func TestRender_IssueIndexPattern4(t *testing.T) {
} }
expected := fmt.Sprintf(expectedFmt, links...) expected := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expected, &RenderContext{ testRenderIssueIndexPattern(t, s, expected, &RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
Metas: alphanumericMetas, RenderOptions: RenderOptions{Metas: alphanumericMetas},
}) })
} }
test("OTT-1234 test", "%s test", "OTT-1234") test("OTT-1234 test", "%s test", "OTT-1234")
@ -240,8 +240,8 @@ func TestRender_IssueIndexPattern5(t *testing.T) {
expected := fmt.Sprintf(expectedFmt, links...) expected := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expected, &RenderContext{ testRenderIssueIndexPattern(t, s, expected, &RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
Metas: metas, RenderOptions: RenderOptions{Metas: metas},
}) })
} }
@ -264,8 +264,8 @@ func TestRender_IssueIndexPattern5(t *testing.T) {
) )
testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{ testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
Metas: regexpMetas, RenderOptions: RenderOptions{Metas: regexpMetas},
}) })
} }
@ -279,16 +279,16 @@ func TestRender_IssueIndexPattern_NoShortPattern(t *testing.T) {
} }
testRenderIssueIndexPattern(t, "#1", "#1", &RenderContext{ testRenderIssueIndexPattern(t, "#1", "#1", &RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
Metas: metas, RenderOptions: RenderOptions{Metas: metas},
}) })
testRenderIssueIndexPattern(t, "#1312", "#1312", &RenderContext{ testRenderIssueIndexPattern(t, "#1312", "#1312", &RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
Metas: metas, RenderOptions: RenderOptions{Metas: metas},
}) })
testRenderIssueIndexPattern(t, "!1", "!1", &RenderContext{ testRenderIssueIndexPattern(t, "!1", "!1", &RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
Metas: metas, RenderOptions: RenderOptions{Metas: metas},
}) })
} }
@ -301,17 +301,17 @@ func TestRender_RenderIssueTitle(t *testing.T) {
"style": IssueNameStyleNumeric, "style": IssueNameStyleNumeric,
} }
actual, err := RenderIssueTitle(&RenderContext{ actual, err := RenderIssueTitle(&RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
Metas: metas, RenderOptions: RenderOptions{Metas: metas},
}, "#1") }, "#1")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "#1", actual) assert.Equal(t, "#1", actual)
} }
func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) { func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
ctx.Links.AbsolutePrefix = true ctx.RenderOptions.Links.AbsolutePrefix = true
if ctx.Links.Base == "" { if ctx.RenderOptions.Links.Base == "" {
ctx.Links.Base = TestRepoURL ctx.RenderOptions.Links.Base = TestRepoURL
} }
var buf strings.Builder var buf strings.Builder
@ -326,22 +326,18 @@ func TestRender_AutoLink(t *testing.T) {
test := func(input, expected string) { test := func(input, expected string) {
var buffer strings.Builder var buffer strings.Builder
err := PostProcess(&RenderContext{ err := PostProcess(&RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
Links: Links{
Base: TestRepoURL, RenderOptions: RenderOptions{Metas: localMetas, Links: Links{Base: TestRepoURL}},
},
Metas: localMetas,
}, strings.NewReader(input), &buffer) }, strings.NewReader(input), &buffer)
assert.Equal(t, err, nil) assert.Equal(t, err, nil)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
buffer.Reset() buffer.Reset()
err = PostProcess(&RenderContext{ err = PostProcess(&RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
Links: Links{
Base: TestRepoURL, RenderOptions: RenderOptions{Metas: localWikiMetas, Links: Links{Base: TestRepoURL}},
},
Metas: localWikiMetas,
}, strings.NewReader(input), &buffer) }, strings.NewReader(input), &buffer)
assert.Equal(t, err, nil) assert.Equal(t, err, nil)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
@ -368,11 +364,9 @@ func TestRender_FullIssueURLs(t *testing.T) {
test := func(input, expected string) { test := func(input, expected string) {
var result strings.Builder var result strings.Builder
err := postProcess(&RenderContext{ err := postProcess(&RenderContext{
Ctx: git.DefaultContext, ctx: context.Background(),
Links: Links{
Base: TestRepoURL, RenderOptions: RenderOptions{Metas: localMetas, Links: Links{Base: TestRepoURL}},
},
Metas: localMetas,
}, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result) }, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, result.String()) assert.Equal(t, expected, result.String())

View File

@ -19,7 +19,7 @@ import (
) )
func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) { func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.Metas == nil { if ctx.RenderOptions.Metas == nil {
return return
} }
next := node.NextSibling next := node.NextSibling
@ -36,14 +36,14 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
} }
link := node.Data[m[0]:m[1]] link := node.Data[m[0]:m[1]]
if !httplib.IsCurrentGiteaSiteURL(ctx.Ctx, link) { if !httplib.IsCurrentGiteaSiteURL(ctx, link) {
return return
} }
text := "#" + node.Data[m[2]:m[3]] text := "#" + node.Data[m[2]:m[3]]
// if m[4] and m[5] is not -1, then link is to a comment // if m[4] and m[5] is not -1, then link is to a comment
// indicate that in the text by appending (comment) // indicate that in the text by appending (comment)
if m[4] != -1 && m[5] != -1 { if m[4] != -1 && m[5] != -1 {
if locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale); ok { if locale, ok := ctx.Value(translation.ContextKey).(translation.Locale); ok {
text += " " + locale.TrString("repo.from_comment") text += " " + locale.TrString("repo.from_comment")
} else { } else {
text += " (comment)" text += " (comment)"
@ -56,7 +56,7 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
matchOrg := linkParts[len(linkParts)-4] matchOrg := linkParts[len(linkParts)-4]
matchRepo := linkParts[len(linkParts)-3] matchRepo := linkParts[len(linkParts)-3]
if matchOrg == ctx.Metas["user"] && matchRepo == ctx.Metas["repo"] { if matchOrg == ctx.RenderOptions.Metas["user"] && matchRepo == ctx.RenderOptions.Metas["repo"] {
replaceContent(node, m[0], m[1], createLink(ctx, link, text, "ref-issue")) replaceContent(node, m[0], m[1], createLink(ctx, link, text, "ref-issue"))
} else { } else {
text = matchOrg + "/" + matchRepo + text text = matchOrg + "/" + matchRepo + text
@ -67,14 +67,14 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
} }
func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.Metas == nil { if ctx.RenderOptions.Metas == nil {
return return
} }
// crossLinkOnly: do not parse "#123", only parse "owner/repo#123" // crossLinkOnly: do not parse "#123", only parse "owner/repo#123"
// if there is no repo in the context, then the "#123" format can't be parsed // if there is no repo in the context, then the "#123" format can't be parsed
// old logic: crossLinkOnly := ctx.Metas["mode"] == "document" && !ctx.IsWiki // old logic: crossLinkOnly := ctx.RenderOptions.Metas["mode"] == "document" && !ctx.IsWiki
crossLinkOnly := ctx.Metas["markupAllowShortIssuePattern"] != "true" crossLinkOnly := ctx.RenderOptions.Metas["markupAllowShortIssuePattern"] != "true"
var ( var (
found bool found bool
@ -84,20 +84,20 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
next := node.NextSibling next := node.NextSibling
for node != nil && node != next { for node != nil && node != next {
_, hasExtTrackFormat := ctx.Metas["format"] _, hasExtTrackFormat := ctx.RenderOptions.Metas["format"]
// Repos with external issue trackers might still need to reference local PRs // Repos with external issue trackers might still need to reference local PRs
// We need to concern with the first one that shows up in the text, whichever it is // We need to concern with the first one that shows up in the text, whichever it is
isNumericStyle := ctx.Metas["style"] == "" || ctx.Metas["style"] == IssueNameStyleNumeric isNumericStyle := ctx.RenderOptions.Metas["style"] == "" || ctx.RenderOptions.Metas["style"] == IssueNameStyleNumeric
foundNumeric, refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle, crossLinkOnly) foundNumeric, refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle, crossLinkOnly)
switch ctx.Metas["style"] { switch ctx.RenderOptions.Metas["style"] {
case "", IssueNameStyleNumeric: case "", IssueNameStyleNumeric:
found, ref = foundNumeric, refNumeric found, ref = foundNumeric, refNumeric
case IssueNameStyleAlphanumeric: case IssueNameStyleAlphanumeric:
found, ref = references.FindRenderizableReferenceAlphanumeric(node.Data) found, ref = references.FindRenderizableReferenceAlphanumeric(node.Data)
case IssueNameStyleRegexp: case IssueNameStyleRegexp:
pattern, err := regexplru.GetCompiled(ctx.Metas["regexp"]) pattern, err := regexplru.GetCompiled(ctx.RenderOptions.Metas["regexp"])
if err != nil { if err != nil {
return return
} }
@ -121,9 +121,9 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
var link *html.Node var link *html.Node
reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End] reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End]
if hasExtTrackFormat && !ref.IsPull { if hasExtTrackFormat && !ref.IsPull {
ctx.Metas["index"] = ref.Issue ctx.RenderOptions.Metas["index"] = ref.Issue
res, err := vars.Expand(ctx.Metas["format"], ctx.Metas) res, err := vars.Expand(ctx.RenderOptions.Metas["format"], ctx.RenderOptions.Metas)
if err != nil { if err != nil {
// here we could just log the error and continue the rendering // here we could just log the error and continue the rendering
log.Error("unable to expand template vars for ref %s, err: %v", ref.Issue, err) log.Error("unable to expand template vars for ref %s, err: %v", ref.Issue, err)
@ -136,9 +136,9 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
// Gitea will redirect on click as appropriate. // Gitea will redirect on click as appropriate.
issuePath := util.Iif(ref.IsPull, "pulls", "issues") issuePath := util.Iif(ref.IsPull, "pulls", "issues")
if ref.Owner == "" { if ref.Owner == "" {
link = createLink(ctx, util.URLJoin(ctx.Links.Prefix(), ctx.Metas["user"], ctx.Metas["repo"], issuePath, ref.Issue), reftext, "ref-issue") link = createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], issuePath, ref.Issue), reftext, "ref-issue")
} else { } else {
link = createLink(ctx, util.URLJoin(ctx.Links.Prefix(), ref.Owner, ref.Name, issuePath, ref.Issue), reftext, "ref-issue") link = createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), ref.Owner, ref.Name, issuePath, ref.Issue), reftext, "ref-issue")
} }
} }
@ -177,7 +177,7 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
} }
reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha) reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha)
link := createLink(ctx, util.URLJoin(ctx.Links.Prefix(), ref.Owner, ref.Name, "commit", ref.CommitSha), reftext, "commit") link := createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), ref.Owner, ref.Name, "commit", ref.CommitSha), reftext, "commit")
replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link) replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link)
node = node.NextSibling.NextSibling node = node.NextSibling.NextSibling

View File

@ -19,15 +19,15 @@ import (
func ResolveLink(ctx *RenderContext, link, userContentAnchorPrefix string) (result string, resolved bool) { func ResolveLink(ctx *RenderContext, link, userContentAnchorPrefix string) (result string, resolved bool) {
isAnchorFragment := link != "" && link[0] == '#' isAnchorFragment := link != "" && link[0] == '#'
if !isAnchorFragment && !IsFullURLString(link) { if !isAnchorFragment && !IsFullURLString(link) {
linkBase := ctx.Links.Base linkBase := ctx.RenderOptions.Links.Base
if ctx.IsMarkupContentWiki() { if ctx.IsMarkupContentWiki() {
// no need to check if the link should be resolved as a wiki link or a wiki raw link // 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 // just use wiki link here, and it will be redirected to a wiki raw link if necessary
linkBase = ctx.Links.WikiLink() linkBase = ctx.RenderOptions.Links.WikiLink()
} else if ctx.Links.BranchPath != "" || ctx.Links.TreePath != "" { } else if ctx.RenderOptions.Links.BranchPath != "" || ctx.RenderOptions.Links.TreePath != "" {
// if there is no BranchPath, then the link will be something like "/owner/repo/src/{the-file-path}" // if there is no BranchPath, then the link will be something like "/owner/repo/src/{the-file-path}"
// and then this link will be handled by the "legacy-ref" code and be redirected to the default branch like "/owner/repo/src/branch/main/{the-file-path}" // and then this link will be handled by the "legacy-ref" code and be redirected to the default branch like "/owner/repo/src/branch/main/{the-file-path}"
linkBase = ctx.Links.SrcLink() linkBase = ctx.RenderOptions.Links.SrcLink()
} }
link, resolved = util.URLJoin(linkBase, link), true link, resolved = util.URLJoin(linkBase, link), true
} }
@ -147,7 +147,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
} }
if image { if image {
if !absoluteLink { if !absoluteLink {
link = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), link) link = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), link)
} }
title := props["title"] title := props["title"]
if title == "" { if title == "" {

View File

@ -25,15 +25,15 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
loc.Start += start loc.Start += start
loc.End += start loc.End += start
mention := node.Data[loc.Start:loc.End] mention := node.Data[loc.Start:loc.End]
teams, ok := ctx.Metas["teams"] teams, ok := ctx.RenderOptions.Metas["teams"]
// FIXME: util.URLJoin may not be necessary here: // FIXME: util.URLJoin may not be necessary here:
// - setting.AppURL is defined to have a terminal '/' so unless mention[1:] // - setting.AppURL is defined to have a terminal '/' so unless mention[1:]
// is an AppSubURL link we can probably fallback to concatenation. // is an AppSubURL link we can probably fallback to concatenation.
// team mention should follow @orgName/teamName style // team mention should follow @orgName/teamName style
if ok && strings.Contains(mention, "/") { if ok && strings.Contains(mention, "/") {
mentionOrgAndTeam := strings.Split(mention, "/") mentionOrgAndTeam := strings.Split(mention, "/")
if mentionOrgAndTeam[0][1:] == ctx.Metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") { if mentionOrgAndTeam[0][1:] == ctx.RenderOptions.Metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") {
replaceContent(node, loc.Start, loc.End, createLink(ctx, util.URLJoin(ctx.Links.Prefix(), "org", ctx.Metas["org"], "teams", mentionOrgAndTeam[1]), mention, "" /*mention*/)) replaceContent(node, loc.Start, loc.End, createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), "org", ctx.RenderOptions.Metas["org"], "teams", mentionOrgAndTeam[1]), mention, "" /*mention*/))
node = node.NextSibling.NextSibling node = node.NextSibling.NextSibling
start = 0 start = 0
continue continue
@ -43,8 +43,8 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
} }
mentionedUsername := mention[1:] mentionedUsername := mention[1:]
if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) { if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx, mentionedUsername) {
replaceContent(node, loc.Start, loc.End, createLink(ctx, util.URLJoin(ctx.Links.Prefix(), mentionedUsername), mention, "" /*mention*/)) replaceContent(node, loc.Start, loc.End, createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), mentionedUsername), mention, "" /*mention*/))
node = node.NextSibling.NextSibling node = node.NextSibling.NextSibling
start = 0 start = 0
} else { } else {

View File

@ -17,7 +17,7 @@ func visitNodeImg(ctx *RenderContext, img *html.Node) (next *html.Node) {
} }
if IsNonEmptyRelativePath(attr.Val) { if IsNonEmptyRelativePath(attr.Val) {
attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val) attr.Val = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val)
// By default, the "<img>" tag should also be clickable, // By default, the "<img>" tag should also be clickable,
// because frontend use `<img>` to paste the re-scaled image into the markdown, // 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 continue
} }
if IsNonEmptyRelativePath(attr.Val) { if IsNonEmptyRelativePath(attr.Val) {
attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val) attr.Val = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val)
} }
attr.Val = camoHandleLink(attr.Val) attr.Val = camoHandleLink(attr.Val)
node.Attr[i] = attr node.Attr[i] = attr

View File

@ -9,7 +9,6 @@ import (
"testing" "testing"
"code.gitea.io/gitea/modules/emoji" "code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/markup/markdown"
@ -57,16 +56,10 @@ func newMockRepo(ownerName, repoName string) gitrepo.Repository {
func TestRender_Commits(t *testing.T) { func TestRender_Commits(t *testing.T) {
setting.AppURL = markup.TestAppURL setting.AppURL = markup.TestAppURL
test := func(input, expected string) { test := func(input, expected string) {
buffer, err := markup.RenderString(&markup.RenderContext{ buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", localMetas, newMockRepo(testRepoOwnerName, testRepoName), markup.Links{
Ctx: git.DefaultContext,
RelativePath: ".md",
Links: markup.Links{
AbsolutePrefix: true, AbsolutePrefix: true,
Base: markup.TestRepoURL, Base: markup.TestRepoURL,
}, }), input)
Repo: newMockRepo(testRepoOwnerName, testRepoName),
Metas: localMetas,
}, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
} }
@ -112,15 +105,11 @@ func TestRender_CrossReferences(t *testing.T) {
setting.AppURL = markup.TestAppURL setting.AppURL = markup.TestAppURL
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
test := func(input, expected string) { test := func(input, expected string) {
buffer, err := markup.RenderString(&markup.RenderContext{ buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", localMetas,
Ctx: git.DefaultContext, markup.Links{
RelativePath: "a.md",
Links: markup.Links{
AbsolutePrefix: true, AbsolutePrefix: true,
Base: setting.AppSubURL, Base: setting.AppSubURL,
}, }), input)
Metas: localMetas,
}, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
} }
@ -154,13 +143,7 @@ func TestRender_links(t *testing.T) {
setting.AppURL = markup.TestAppURL setting.AppURL = markup.TestAppURL
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
test := func(input, expected string) { test := func(input, expected string) {
buffer, err := markup.RenderString(&markup.RenderContext{ buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", markup.Links{Base: markup.TestRepoURL}), input)
Ctx: git.DefaultContext,
RelativePath: "a.md",
Links: markup.Links{
Base: markup.TestRepoURL,
},
}, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
} }
@ -265,13 +248,7 @@ func TestRender_email(t *testing.T) {
setting.AppURL = markup.TestAppURL setting.AppURL = markup.TestAppURL
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
test := func(input, expected string) { test := func(input, expected string) {
res, err := markup.RenderString(&markup.RenderContext{ res, err := markup.RenderString(markup.NewTestRenderContext("a.md", markup.Links{Base: markup.TestRepoURL}), input)
Ctx: git.DefaultContext,
RelativePath: "a.md",
Links: markup.Links{
Base: markup.TestRepoURL,
},
}, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res))
} }
@ -338,13 +315,7 @@ func TestRender_emoji(t *testing.T) {
test := func(input, expected string) { test := func(input, expected string) {
expected = strings.ReplaceAll(expected, "&", "&amp;") expected = strings.ReplaceAll(expected, "&", "&amp;")
buffer, err := markup.RenderString(&markup.RenderContext{ buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", markup.Links{Base: markup.TestRepoURL}), input)
Ctx: git.DefaultContext,
RelativePath: "a.md",
Links: markup.Links{
Base: markup.TestRepoURL,
},
}, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
} }
@ -404,22 +375,10 @@ func TestRender_ShortLinks(t *testing.T) {
tree := util.URLJoin(markup.TestRepoURL, "src", "master") tree := util.URLJoin(markup.TestRepoURL, "src", "master")
test := func(input, expected, expectedWiki string) { test := func(input, expected, expectedWiki string) {
buffer, err := markdown.RenderString(&markup.RenderContext{ buffer, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: markup.TestRepoURL, BranchPath: "master"}), input)
Ctx: git.DefaultContext,
Links: markup.Links{
Base: markup.TestRepoURL,
BranchPath: "master",
},
}, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
buffer, err = markdown.RenderString(&markup.RenderContext{ buffer, err = markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: markup.TestRepoURL}, localWikiMetas), input)
Ctx: git.DefaultContext,
Links: markup.Links{
Base: markup.TestRepoURL,
},
Metas: localWikiMetas,
}, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer))) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
} }
@ -529,11 +488,7 @@ func TestRender_ShortLinks(t *testing.T) {
func TestRender_RelativeMedias(t *testing.T) { func TestRender_RelativeMedias(t *testing.T) {
render := func(input string, isWiki bool, links markup.Links) string { render := func(input string, isWiki bool, links markup.Links) string {
buffer, err := markdown.RenderString(&markup.RenderContext{ buffer, err := markdown.RenderString(markup.NewTestRenderContext(links, util.Iif(isWiki, localWikiMetas, localMetas)), input)
Ctx: git.DefaultContext,
Links: links,
Metas: util.Iif(isWiki, localWikiMetas, localMetas),
}, input)
assert.NoError(t, err) assert.NoError(t, err)
return strings.TrimSpace(string(buffer)) return strings.TrimSpace(string(buffer))
} }
@ -574,26 +529,14 @@ func Test_ParseClusterFuzz(t *testing.T) {
data := "<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY " data := "<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
var res strings.Builder var res strings.Builder
err := markup.PostProcess(&markup.RenderContext{ err := markup.PostProcess(markup.NewTestRenderContext(markup.Links{Base: "https://example.com"}, localMetas), strings.NewReader(data), &res)
Ctx: git.DefaultContext,
Links: markup.Links{
Base: "https://example.com",
},
Metas: localMetas,
}, strings.NewReader(data), &res)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, res.String(), "<html") assert.NotContains(t, res.String(), "<html")
data = "<!DOCTYPE html>\n<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY " data = "<!DOCTYPE html>\n<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
res.Reset() res.Reset()
err = markup.PostProcess(&markup.RenderContext{ err = markup.PostProcess(markup.NewTestRenderContext(markup.Links{Base: "https://example.com"}, localMetas), strings.NewReader(data), &res)
Ctx: git.DefaultContext,
Links: markup.Links{
Base: "https://example.com",
},
Metas: localMetas,
}, strings.NewReader(data), &res)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, res.String(), "<html") assert.NotContains(t, res.String(), "<html")
@ -606,14 +549,13 @@ func TestPostProcess_RenderDocument(t *testing.T) {
test := func(input, expected string) { test := func(input, expected string) {
var res strings.Builder var res strings.Builder
err := markup.PostProcess(&markup.RenderContext{ err := markup.PostProcess(markup.NewTestRenderContext(
Ctx: git.DefaultContext, markup.Links{
Links: markup.Links{
AbsolutePrefix: true, AbsolutePrefix: true,
Base: "https://example.com", Base: "https://example.com",
}, },
Metas: map[string]string{"user": "go-gitea", "repo": "gitea"}, map[string]string{"user": "go-gitea", "repo": "gitea"},
}, strings.NewReader(input), &res) ), strings.NewReader(input), &res)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String())) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String()))
} }
@ -650,10 +592,7 @@ func TestIssue16020(t *testing.T) {
data := `<img src="data:image/png;base64,i//V"/>` data := `<img src="data:image/png;base64,i//V"/>`
var res strings.Builder var res strings.Builder
err := markup.PostProcess(&markup.RenderContext{ err := markup.PostProcess(markup.NewTestRenderContext(localMetas), strings.NewReader(data), &res)
Ctx: git.DefaultContext,
Metas: localMetas,
}, strings.NewReader(data), &res)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, data, res.String()) assert.Equal(t, data, res.String())
} }
@ -666,29 +605,23 @@ func BenchmarkEmojiPostprocess(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
var res strings.Builder var res strings.Builder
err := markup.PostProcess(&markup.RenderContext{ err := markup.PostProcess(markup.NewTestRenderContext(localMetas), strings.NewReader(data), &res)
Ctx: git.DefaultContext,
Metas: localMetas,
}, strings.NewReader(data), &res)
assert.NoError(b, err) assert.NoError(b, err)
} }
} }
func TestFuzz(t *testing.T) { func TestFuzz(t *testing.T) {
s := "t/l/issues/8#/../../a" s := "t/l/issues/8#/../../a"
renderContext := markup.RenderContext{ renderContext := markup.NewTestRenderContext(
Ctx: git.DefaultContext, markup.Links{
Links: markup.Links{
Base: "https://example.com/go-gitea/gitea", Base: "https://example.com/go-gitea/gitea",
}, },
Metas: map[string]string{ map[string]string{
"user": "go-gitea", "user": "go-gitea",
"repo": "gitea", "repo": "gitea",
}, },
} )
err := markup.PostProcess(renderContext, strings.NewReader(s), io.Discard)
err := markup.PostProcess(&renderContext, strings.NewReader(s), io.Discard)
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -696,10 +629,7 @@ func TestIssue18471(t *testing.T) {
data := `http://domain/org/repo/compare/783b039...da951ce` data := `http://domain/org/repo/compare/783b039...da951ce`
var res strings.Builder var res strings.Builder
err := markup.PostProcess(&markup.RenderContext{ err := markup.PostProcess(markup.NewTestRenderContext(localMetas), strings.NewReader(data), &res)
Ctx: git.DefaultContext,
Metas: localMetas,
}, strings.NewReader(data), &res)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, `<a href="http://domain/org/repo/compare/783b039...da951ce" class="compare"><code class="nohighlight">783b039...da951ce</code></a>`, res.String()) assert.Equal(t, `<a href="http://domain/org/repo/compare/783b039...da951ce" class="compare"><code class="nohighlight">783b039...da951ce</code></a>`, res.String())

View File

@ -79,7 +79,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
// TODO: this was a quite unclear part, old code: `if metas["mode"] != "document" { use comment link break setting }` // 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 // 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. // especially in many tests.
markdownLineBreakStyle := ctx.Metas["markdownLineBreakStyle"] markdownLineBreakStyle := ctx.RenderOptions.Metas["markdownLineBreakStyle"]
if markup.RenderBehaviorForTesting.ForceHardLineBreak { if markup.RenderBehaviorForTesting.ForceHardLineBreak {
v.SetHardLineBreak(true) v.SetHardLineBreak(true)
} else if markdownLineBreakStyle == "comment" { } else if markdownLineBreakStyle == "comment" {

View File

@ -182,7 +182,7 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
bufWithMetadataLength := len(buf) bufWithMetadataLength := len(buf)
rc := &RenderConfig{ rc := &RenderConfig{
Meta: renderMetaModeFromString(string(ctx.RenderMetaAs)), Meta: markup.RenderMetaAsDetails,
Icon: "table", Icon: "table",
Lang: "", Lang: "",
} }
@ -241,7 +241,7 @@ func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Wri
// Render renders Markdown to HTML with all specific handling stuff. // Render renders Markdown to HTML with all specific handling stuff.
func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
ctx.MarkupType = MarkupName ctx.RenderOptions.MarkupType = MarkupName
return markup.Render(ctx, input, output) return markup.Render(ctx, input, output)
} }

View File

@ -4,12 +4,10 @@
package markdown_test package markdown_test
import ( import (
"context"
"html/template" "html/template"
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
@ -67,22 +65,11 @@ func TestRender_StandardLinks(t *testing.T) {
setting.AppURL = AppURL setting.AppURL = AppURL
test := func(input, expected, expectedWiki string) { test := func(input, expected, expectedWiki string) {
buffer, err := markdown.RenderString(&markup.RenderContext{ buffer, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}), input)
Ctx: git.DefaultContext,
Links: markup.Links{
Base: FullURL,
},
}, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
buffer, err = markdown.RenderString(&markup.RenderContext{ buffer, err = markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}, localWikiMetas), input)
Ctx: git.DefaultContext,
Links: markup.Links{
Base: FullURL,
},
Metas: localWikiMetas,
}, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer))) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
} }
@ -101,12 +88,7 @@ func TestRender_Images(t *testing.T) {
setting.AppURL = AppURL setting.AppURL = AppURL
test := func(input, expected string) { test := func(input, expected string) {
buffer, err := markdown.RenderString(&markup.RenderContext{ buffer, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}), input)
Ctx: git.DefaultContext,
Links: markup.Links{
Base: FullURL,
},
}, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
} }
@ -308,14 +290,11 @@ func TestTotal_RenderWiki(t *testing.T) {
setting.AppURL = AppURL setting.AppURL = AppURL
answers := testAnswers(util.URLJoin(FullURL, "wiki"), util.URLJoin(FullURL, "wiki", "raw")) answers := testAnswers(util.URLJoin(FullURL, "wiki"), util.URLJoin(FullURL, "wiki", "raw"))
for i := 0; i < len(sameCases); i++ { for i := 0; i < len(sameCases); i++ {
line, err := markdown.RenderString(&markup.RenderContext{ line, err := markdown.RenderString(markup.NewTestRenderContext(
Ctx: git.DefaultContext, markup.Links{Base: FullURL},
Links: markup.Links{ newMockRepo(testRepoOwnerName, testRepoName),
Base: FullURL, localWikiMetas,
}, ), sameCases[i])
Repo: newMockRepo(testRepoOwnerName, testRepoName),
Metas: localWikiMetas,
}, sameCases[i])
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, answers[i], string(line)) assert.Equal(t, answers[i], string(line))
} }
@ -334,13 +313,7 @@ func TestTotal_RenderWiki(t *testing.T) {
} }
for i := 0; i < len(testCases); i += 2 { for i := 0; i < len(testCases); i += 2 {
line, err := markdown.RenderString(&markup.RenderContext{ line, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}, localWikiMetas), testCases[i])
Ctx: git.DefaultContext,
Links: markup.Links{
Base: FullURL,
},
Metas: localWikiMetas,
}, testCases[i])
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, testCases[i+1], string(line)) assert.EqualValues(t, testCases[i+1], string(line))
} }
@ -352,15 +325,14 @@ func TestTotal_RenderString(t *testing.T) {
setting.AppURL = AppURL setting.AppURL = AppURL
answers := testAnswers(util.URLJoin(FullURL, "src", "master"), util.URLJoin(FullURL, "media", "master")) answers := testAnswers(util.URLJoin(FullURL, "src", "master"), util.URLJoin(FullURL, "media", "master"))
for i := 0; i < len(sameCases); i++ { for i := 0; i < len(sameCases); i++ {
line, err := markdown.RenderString(&markup.RenderContext{ line, err := markdown.RenderString(markup.NewTestRenderContext(
Ctx: git.DefaultContext, markup.Links{
Links: markup.Links{
Base: FullURL, Base: FullURL,
BranchPath: "master", BranchPath: "master",
}, },
Repo: newMockRepo(testRepoOwnerName, testRepoName), newMockRepo(testRepoOwnerName, testRepoName),
Metas: localMetas, localMetas,
}, sameCases[i]) ), sameCases[i])
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, answers[i], string(line)) assert.Equal(t, answers[i], string(line))
} }
@ -368,12 +340,7 @@ func TestTotal_RenderString(t *testing.T) {
testCases := []string{} testCases := []string{}
for i := 0; i < len(testCases); i += 2 { for i := 0; i < len(testCases); i += 2 {
line, err := markdown.RenderString(&markup.RenderContext{ line, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}), testCases[i])
Ctx: git.DefaultContext,
Links: markup.Links{
Base: FullURL,
},
}, testCases[i])
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, template.HTML(testCases[i+1]), line) assert.Equal(t, template.HTML(testCases[i+1]), line)
} }
@ -381,17 +348,17 @@ func TestTotal_RenderString(t *testing.T) {
func TestRender_RenderParagraphs(t *testing.T) { func TestRender_RenderParagraphs(t *testing.T) {
test := func(t *testing.T, str string, cnt int) { test := func(t *testing.T, str string, cnt int) {
res, err := markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, str) res, err := markdown.RenderRawString(markup.NewTestRenderContext(), str)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for unix should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res) assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for unix should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
mac := strings.ReplaceAll(str, "\n", "\r") mac := strings.ReplaceAll(str, "\n", "\r")
res, err = markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, mac) res, err = markdown.RenderRawString(markup.NewTestRenderContext(), mac)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for mac should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res) assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for mac should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
dos := strings.ReplaceAll(str, "\n", "\r\n") dos := strings.ReplaceAll(str, "\n", "\r\n")
res, err = markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, dos) res, err = markdown.RenderRawString(markup.NewTestRenderContext(), dos)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for windows should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res) assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for windows should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
} }
@ -419,7 +386,7 @@ func TestMarkdownRenderRaw(t *testing.T) {
for _, testcase := range testcases { for _, testcase := range testcases {
log.Info("Test markdown render error with fuzzy data: %x, the following errors can be recovered", testcase) log.Info("Test markdown render error with fuzzy data: %x, the following errors can be recovered", testcase)
_, err := markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, string(testcase)) _, err := markdown.RenderRawString(markup.NewTestRenderContext(), string(testcase))
assert.NoError(t, err) assert.NoError(t, err)
} }
} }
@ -432,7 +399,7 @@ func TestRenderSiblingImages_Issue12925(t *testing.T) {
<a href="/image2" target="_blank" rel="nofollow noopener"><img src="/image2" alt="image2"></a></p> <a href="/image2" target="_blank" rel="nofollow noopener"><img src="/image2" alt="image2"></a></p>
` `
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)() defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)()
res, err := markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase) res, err := markdown.RenderRawString(markup.NewTestRenderContext(), testcase)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, res) assert.Equal(t, expected, res)
} }
@ -441,7 +408,7 @@ func TestRenderEmojiInLinks_Issue12331(t *testing.T) {
testcase := `[Link with emoji :moon: in text](https://gitea.io)` testcase := `[Link with emoji :moon: in text](https://gitea.io)`
expected := `<p><a href="https://gitea.io" rel="nofollow">Link with emoji <span class="emoji" aria-label="waxing gibbous moon">🌔</span> in text</a></p> expected := `<p><a href="https://gitea.io" rel="nofollow">Link with emoji <span class="emoji" aria-label="waxing gibbous moon">🌔</span> in text</a></p>
` `
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase) res, err := markdown.RenderString(markup.NewTestRenderContext(), testcase)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, template.HTML(expected), res) assert.Equal(t, template.HTML(expected), res)
} }
@ -479,7 +446,7 @@ func TestColorPreview(t *testing.T) {
} }
for _, test := range positiveTests { for _, test := range positiveTests {
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase) res, err := markdown.RenderString(markup.NewTestRenderContext(), test.testcase)
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase) assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase) assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
} }
@ -498,7 +465,7 @@ func TestColorPreview(t *testing.T) {
} }
for _, test := range negativeTests { for _, test := range negativeTests {
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test) res, err := markdown.RenderString(markup.NewTestRenderContext(), test)
assert.NoError(t, err, "Unexpected error in testcase: %q", test) assert.NoError(t, err, "Unexpected error in testcase: %q", test)
assert.NotContains(t, res, `<span class="color-preview" style="background-color: `, "Unexpected result in testcase %q", test) assert.NotContains(t, res, `<span class="color-preview" style="background-color: `, "Unexpected result in testcase %q", test)
} }
@ -573,7 +540,7 @@ func TestMathBlock(t *testing.T) {
} }
for _, test := range testcases { for _, test := range testcases {
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase) res, err := markdown.RenderString(markup.NewTestRenderContext(), test.testcase)
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase) assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase) assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
} }
@ -610,7 +577,7 @@ foo: bar
} }
for _, test := range testcases { for _, test := range testcases {
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase) res, err := markdown.RenderString(markup.NewTestRenderContext(), test.testcase)
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase) assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase) assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
} }
@ -1003,11 +970,7 @@ space</p>
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)() defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)()
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
for i, c := range cases { for i, c := range cases {
result, err := markdown.RenderString(&markup.RenderContext{ result, err := markdown.RenderString(markup.NewTestRenderContext(c.Links, util.Iif(c.IsWiki, map[string]string{"markupContentMode": "wiki"}, map[string]string{})), input)
Ctx: context.Background(),
Links: c.Links,
Metas: util.Iif(c.IsWiki, map[string]string{"markupContentMode": "wiki"}, map[string]string{}),
}, input)
assert.NoError(t, err, "Unexpected error in testcase: %v", i) assert.NoError(t, err, "Unexpected error in testcase: %v", i)
assert.Equal(t, c.Expected, string(result), "Unexpected result in testcase %v", i) assert.Equal(t, c.Expected, string(result), "Unexpected result in testcase %v", i)
} }
@ -1029,7 +992,7 @@ func TestAttention(t *testing.T) {
} }
test := func(input, expected string) { test := func(input, expected string) {
result, err := markdown.RenderString(&markup.RenderContext{Ctx: context.Background()}, input) result, err := markdown.RenderString(markup.NewTestRenderContext(), input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result))) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result)))
} }
@ -1062,6 +1025,6 @@ func BenchmarkSpecializedMarkdown(b *testing.B) {
func BenchmarkMarkdownRender(b *testing.B) { func BenchmarkMarkdownRender(b *testing.B) {
// 23202 50840 ns/op // 23202 50840 ns/op
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = markdown.RenderString(&markup.RenderContext{Ctx: context.Background()}, "https://example.com\n- a\n- b\n") _, _ = markdown.RenderString(markup.NewTestRenderContext(), "https://example.com\n- a\n- b\n")
} }
} }

View File

@ -21,7 +21,7 @@ func (g *ASTTransformer) transformImage(ctx *markup.RenderContext, v *ast.Image)
// Check if the destination is a real link // Check if the destination is a real link
if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) { if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) {
v.Destination = []byte(giteautil.URLJoin( v.Destination = []byte(giteautil.URLJoin(
ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()),
strings.TrimLeft(string(v.Destination), "/"), strings.TrimLeft(string(v.Destination), "/"),
)) ))
} }

View File

@ -143,15 +143,15 @@ func (r *Writer) resolveLink(kind, link string) string {
kind = org.RegularLink{URL: link}.Kind() kind = org.RegularLink{URL: link}.Kind()
} }
base := r.Ctx.Links.Base base := r.Ctx.RenderOptions.Links.Base
if r.Ctx.IsMarkupContentWiki() { if r.Ctx.IsMarkupContentWiki() {
base = r.Ctx.Links.WikiLink() base = r.Ctx.RenderOptions.Links.WikiLink()
} else if r.Ctx.Links.HasBranchInfo() { } else if r.Ctx.RenderOptions.Links.HasBranchInfo() {
base = r.Ctx.Links.SrcLink() base = r.Ctx.RenderOptions.Links.SrcLink()
} }
if kind == "image" || kind == "video" { if kind == "image" || kind == "video" {
base = r.Ctx.Links.ResolveMediaLink(r.Ctx.IsMarkupContentWiki()) base = r.Ctx.RenderOptions.Links.ResolveMediaLink(r.Ctx.IsMarkupContentWiki())
} }
link = util.URLJoin(base, link) link = util.URLJoin(base, link)

View File

@ -4,10 +4,10 @@
package markup package markup
import ( import (
"os"
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -15,20 +15,21 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
const AppURL = "http://localhost:3000/" func TestMain(m *testing.M) {
setting.AppURL = "http://localhost:3000/"
setting.IsInTesting = true
os.Exit(m.Run())
}
func TestRender_StandardLinks(t *testing.T) { func TestRender_StandardLinks(t *testing.T) {
setting.AppURL = AppURL
test := func(input, expected string, isWiki bool) { test := func(input, expected string, isWiki bool) {
buffer, err := RenderString(&markup.RenderContext{ buffer, err := RenderString(markup.NewTestRenderContext(
Ctx: git.DefaultContext, markup.Links{
Links: markup.Links{
Base: "/relative-path", Base: "/relative-path",
BranchPath: "branch/main", BranchPath: "branch/main",
}, },
Metas: map[string]string{"markupContentMode": util.Iif(isWiki, "wiki", "")}, map[string]string{"markupContentMode": util.Iif(isWiki, "wiki", "")},
}, input) ), input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
} }
@ -42,16 +43,13 @@ func TestRender_StandardLinks(t *testing.T) {
} }
func TestRender_InternalLinks(t *testing.T) { func TestRender_InternalLinks(t *testing.T) {
setting.AppURL = AppURL
test := func(input, expected string) { test := func(input, expected string) {
buffer, err := RenderString(&markup.RenderContext{ buffer, err := RenderString(markup.NewTestRenderContext(
Ctx: git.DefaultContext, markup.Links{
Links: markup.Links{
Base: "/relative-path", Base: "/relative-path",
BranchPath: "branch/main", BranchPath: "branch/main",
}, },
}, input) ), input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
} }
@ -67,15 +65,8 @@ func TestRender_InternalLinks(t *testing.T) {
} }
func TestRender_Media(t *testing.T) { func TestRender_Media(t *testing.T) {
setting.AppURL = AppURL
test := func(input, expected string) { test := func(input, expected string) {
buffer, err := RenderString(&markup.RenderContext{ buffer, err := RenderString(markup.NewTestRenderContext(markup.Links{Base: "./relative-path"}), input)
Ctx: git.DefaultContext,
Links: markup.Links{
Base: "./relative-path",
},
}, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
} }
@ -113,12 +104,8 @@ func TestRender_Media(t *testing.T) {
} }
func TestRender_Source(t *testing.T) { func TestRender_Source(t *testing.T) {
setting.AppURL = AppURL
test := func(input, expected string) { test := func(input, expected string) {
buffer, err := RenderString(&markup.RenderContext{ buffer, err := RenderString(markup.NewTestRenderContext(), input)
Ctx: git.DefaultContext,
}, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
} }

View File

@ -9,6 +9,7 @@ import (
"io" "io"
"net/url" "net/url"
"strings" "strings"
"time"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
@ -42,16 +43,16 @@ var RenderBehaviorForTesting struct {
DisableInternalAttributes bool DisableInternalAttributes bool
} }
// RenderContext represents a render context type RenderOptions struct {
type RenderContext struct { // relative path from tree root of the branch
Ctx context.Context RelativePath string
RelativePath string // relative path from tree root of the branch
// eg: "orgmode", "asciicast", "console" // eg: "orgmode", "asciicast", "console"
// for file mode, it could be left as empty, and will be detected by file extension in RelativePath // for file mode, it could be left as empty, and will be detected by file extension in RelativePath
MarkupType string MarkupType string
Links Links // special link references for rendering, especially when there is a branch/tree path // special link references for rendering, especially when there is a branch/tree path
Links Links
// user&repo, format&style&regexp (for external issue pattern), teams&org (for mention) // user&repo, format&style&regexp (for external issue pattern), teams&org (for mention)
// BranchNameSubURL (for iframe&asciicast) // BranchNameSubURL (for iframe&asciicast)
@ -59,27 +60,95 @@ type RenderContext struct {
// markdownLineBreakStyle (comment, document) // markdownLineBreakStyle (comment, document)
Metas map[string]string Metas map[string]string
GitRepo *git.Repository // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page
Repo gitrepo.Repository InStandalonePage bool
ShaExistCache map[string]bool }
cancelFn func()
SidebarTocNode ast.Node
RenderMetaAs RenderMetaMode
InStandalonePage bool // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page
type RenderHelper struct {
gitRepo *git.Repository
repoFacade gitrepo.Repository
shaExistCache map[string]bool
cancelFn func()
}
// RenderContext represents a render context
type RenderContext struct {
ctx context.Context
SidebarTocNode ast.Node
RenderHelper RenderHelper
RenderOptions RenderOptions
RenderInternal internal.RenderInternal RenderInternal internal.RenderInternal
} }
func (ctx *RenderContext) Deadline() (deadline time.Time, ok bool) {
return ctx.ctx.Deadline()
}
func (ctx *RenderContext) Done() <-chan struct{} {
return ctx.ctx.Done()
}
func (ctx *RenderContext) Err() error {
return ctx.ctx.Err()
}
func (ctx *RenderContext) Value(key any) any {
return ctx.ctx.Value(key)
}
var _ context.Context = (*RenderContext)(nil)
func NewRenderContext(ctx context.Context) *RenderContext {
return &RenderContext{ctx: ctx}
}
func (ctx *RenderContext) WithMarkupType(typ string) *RenderContext {
ctx.RenderOptions.MarkupType = typ
return ctx
}
func (ctx *RenderContext) WithRelativePath(path string) *RenderContext {
ctx.RenderOptions.RelativePath = path
return ctx
}
func (ctx *RenderContext) WithLinks(links Links) *RenderContext {
ctx.RenderOptions.Links = links
return ctx
}
func (ctx *RenderContext) WithMetas(metas map[string]string) *RenderContext {
ctx.RenderOptions.Metas = metas
return ctx
}
func (ctx *RenderContext) WithInStandalonePage(v bool) *RenderContext {
ctx.RenderOptions.InStandalonePage = v
return ctx
}
func (ctx *RenderContext) WithGitRepo(r *git.Repository) *RenderContext {
ctx.RenderHelper.gitRepo = r
return ctx
}
func (ctx *RenderContext) WithRepoFacade(r gitrepo.Repository) *RenderContext {
ctx.RenderHelper.repoFacade = r
return ctx
}
// Cancel runs any cleanup functions that have been registered for this Ctx // Cancel runs any cleanup functions that have been registered for this Ctx
func (ctx *RenderContext) Cancel() { func (ctx *RenderContext) Cancel() {
if ctx == nil { if ctx == nil {
return return
} }
ctx.ShaExistCache = map[string]bool{} ctx.RenderHelper.shaExistCache = map[string]bool{}
if ctx.cancelFn == nil { if ctx.RenderHelper.cancelFn == nil {
return return
} }
ctx.cancelFn() ctx.RenderHelper.cancelFn()
} }
// AddCancel adds the provided fn as a Cleanup for this Ctx // AddCancel adds the provided fn as a Cleanup for this Ctx
@ -87,38 +156,38 @@ func (ctx *RenderContext) AddCancel(fn func()) {
if ctx == nil { if ctx == nil {
return return
} }
oldCancelFn := ctx.cancelFn oldCancelFn := ctx.RenderHelper.cancelFn
if oldCancelFn == nil { if oldCancelFn == nil {
ctx.cancelFn = fn ctx.RenderHelper.cancelFn = fn
return return
} }
ctx.cancelFn = func() { ctx.RenderHelper.cancelFn = func() {
defer oldCancelFn() defer oldCancelFn()
fn() fn()
} }
} }
func (ctx *RenderContext) IsMarkupContentWiki() bool { func (ctx *RenderContext) IsMarkupContentWiki() bool {
return ctx.Metas != nil && ctx.Metas["markupContentMode"] == "wiki" return ctx.RenderOptions.Metas != nil && ctx.RenderOptions.Metas["markupContentMode"] == "wiki"
} }
// Render renders markup file to HTML with all specific handling stuff. // Render renders markup file to HTML with all specific handling stuff.
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error { func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
if ctx.MarkupType == "" && ctx.RelativePath != "" { if ctx.RenderOptions.MarkupType == "" && ctx.RenderOptions.RelativePath != "" {
ctx.MarkupType = DetectMarkupTypeByFileName(ctx.RelativePath) ctx.RenderOptions.MarkupType = DetectMarkupTypeByFileName(ctx.RenderOptions.RelativePath)
if ctx.MarkupType == "" { if ctx.RenderOptions.MarkupType == "" {
return util.NewInvalidArgumentErrorf("unsupported file to render: %q", ctx.RelativePath) return util.NewInvalidArgumentErrorf("unsupported file to render: %q", ctx.RenderOptions.RelativePath)
} }
} }
renderer := renderers[ctx.MarkupType] renderer := renderers[ctx.RenderOptions.MarkupType]
if renderer == nil { if renderer == nil {
return util.NewInvalidArgumentErrorf("unsupported markup type: %q", ctx.MarkupType) return util.NewInvalidArgumentErrorf("unsupported markup type: %q", ctx.RenderOptions.MarkupType)
} }
if ctx.RelativePath != "" { if ctx.RenderOptions.RelativePath != "" {
if externalRender, ok := renderer.(ExternalRenderer); ok && externalRender.DisplayInIFrame() { if externalRender, ok := renderer.(ExternalRenderer); ok && externalRender.DisplayInIFrame() {
if !ctx.InStandalonePage { if !ctx.RenderOptions.InStandalonePage {
// for an external "DisplayInIFrame" render, it could only output its content in a standalone page // 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 // otherwise, a <iframe> should be outputted to embed the external rendered page
return renderIFrame(ctx, output) return renderIFrame(ctx, output)
@ -151,10 +220,10 @@ width="100%%" height="0" scrolling="no" frameborder="0" style="overflow: hidden"
sandbox="allow-scripts" sandbox="allow-scripts"
></iframe>`, ></iframe>`,
setting.AppSubURL, setting.AppSubURL,
url.PathEscape(ctx.Metas["user"]), url.PathEscape(ctx.RenderOptions.Metas["user"]),
url.PathEscape(ctx.Metas["repo"]), url.PathEscape(ctx.RenderOptions.Metas["repo"]),
ctx.Metas["BranchNameSubURL"], ctx.RenderOptions.Metas["BranchNameSubURL"],
url.PathEscape(ctx.RelativePath), url.PathEscape(ctx.RenderOptions.RelativePath),
)) ))
return err return err
} }
@ -176,7 +245,7 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr
pr1, pw1, close1 := pipes() pr1, pw1, close1 := pipes()
defer close1() defer close1()
eg, _ := errgroup.WithContext(ctx.Ctx) eg, _ := errgroup.WithContext(ctx)
var pw2 io.WriteCloser = util.NopCloser{Writer: finalProcessor} var pw2 io.WriteCloser = util.NopCloser{Writer: finalProcessor}
if r, ok := renderer.(ExternalRenderer); !ok || !r.SanitizerDisabled() { if r, ok := renderer.(ExternalRenderer); !ok || !r.SanitizerDisabled() {
@ -230,3 +299,27 @@ func Init(ph *ProcessorHelper) {
func ComposeSimpleDocumentMetas() map[string]string { func ComposeSimpleDocumentMetas() map[string]string {
return map[string]string{"markdownLineBreakStyle": "document"} return map[string]string{"markdownLineBreakStyle": "document"}
} }
// NewTestRenderContext is a helper function to create a RenderContext for testing purpose
// It accepts string (RelativePath), Links, map[string]string (Metas), gitrepo.Repository
func NewTestRenderContext(a ...any) *RenderContext {
if !setting.IsInTesting {
panic("NewTestRenderContext should only be used in testing")
}
ctx := NewRenderContext(context.Background())
for _, v := range a {
switch v := v.(type) {
case string:
ctx = ctx.WithRelativePath(v)
case Links:
ctx = ctx.WithLinks(v)
case map[string]string:
ctx = ctx.WithMetas(v)
case gitrepo.Repository:
ctx = ctx.WithRepoFacade(v)
default:
panic(fmt.Sprintf("unknown type %T", v))
}
}
return ctx
}

View File

@ -38,10 +38,7 @@ func (ut *RenderUtils) RenderCommitMessage(msg string, metas map[string]string)
cleanMsg := template.HTMLEscapeString(msg) cleanMsg := template.HTMLEscapeString(msg)
// we can safely assume that it will not return any error, since there // we can safely assume that it will not return any error, since there
// shouldn't be any special HTML. // shouldn't be any special HTML.
fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ fullMessage, err := markup.RenderCommitMessage(markup.NewRenderContext(ut.ctx).WithMetas(metas), cleanMsg)
Ctx: ut.ctx,
Metas: metas,
}, cleanMsg)
if err != nil { if err != nil {
log.Error("RenderCommitMessage: %v", err) log.Error("RenderCommitMessage: %v", err)
return "" return ""
@ -68,10 +65,7 @@ func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, me
// we can safely assume that it will not return any error, since there // we can safely assume that it will not return any error, since there
// shouldn't be any special HTML. // shouldn't be any special HTML.
renderedMessage, err := markup.RenderCommitMessageSubject(&markup.RenderContext{ renderedMessage, err := markup.RenderCommitMessageSubject(markup.NewRenderContext(ut.ctx).WithMetas(metas), urlDefault, template.HTMLEscapeString(msgLine))
Ctx: ut.ctx,
Metas: metas,
}, urlDefault, template.HTMLEscapeString(msgLine))
if err != nil { if err != nil {
log.Error("RenderCommitMessageSubject: %v", err) log.Error("RenderCommitMessageSubject: %v", err)
return "" return ""
@ -93,10 +87,7 @@ func (ut *RenderUtils) RenderCommitBody(msg string, metas map[string]string) tem
return "" return ""
} }
renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ renderedMessage, err := markup.RenderCommitMessage(markup.NewRenderContext(ut.ctx).WithMetas(metas), template.HTMLEscapeString(msgLine))
Ctx: ut.ctx,
Metas: metas,
}, template.HTMLEscapeString(msgLine))
if err != nil { if err != nil {
log.Error("RenderCommitMessage: %v", err) log.Error("RenderCommitMessage: %v", err)
return "" return ""
@ -115,10 +106,7 @@ func renderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
// RenderIssueTitle renders issue/pull title with defined post processors // RenderIssueTitle renders issue/pull title with defined post processors
func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML { func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML {
renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{ renderedText, err := markup.RenderIssueTitle(markup.NewRenderContext(ut.ctx).WithMetas(metas), template.HTMLEscapeString(text))
Ctx: ut.ctx,
Metas: metas,
}, template.HTMLEscapeString(text))
if err != nil { if err != nil {
log.Error("RenderIssueTitle: %v", err) log.Error("RenderIssueTitle: %v", err)
return "" return ""
@ -186,7 +174,7 @@ func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
// RenderEmoji renders html text with emoji post processors // RenderEmoji renders html text with emoji post processors
func (ut *RenderUtils) RenderEmoji(text string) template.HTML { func (ut *RenderUtils) RenderEmoji(text string) template.HTML {
renderedText, err := markup.RenderEmoji(&markup.RenderContext{Ctx: ut.ctx}, template.HTMLEscapeString(text)) renderedText, err := markup.RenderEmoji(markup.NewRenderContext(ut.ctx), template.HTMLEscapeString(text))
if err != nil { if err != nil {
log.Error("RenderEmoji: %v", err) log.Error("RenderEmoji: %v", err)
return "" return ""
@ -208,10 +196,7 @@ func reactionToEmoji(reaction string) template.HTML {
} }
func (ut *RenderUtils) MarkdownToHtml(input string) template.HTML { //nolint:revive func (ut *RenderUtils) MarkdownToHtml(input string) template.HTML { //nolint:revive
output, err := markdown.RenderString(&markup.RenderContext{ output, err := markdown.RenderString(markup.NewRenderContext(ut.ctx).WithMetas(markup.ComposeSimpleDocumentMetas()), input)
Ctx: ut.ctx,
Metas: markup.ComposeSimpleDocumentMetas(),
}, input)
if err != nil { if err != nil {
log.Error("RenderString: %v", err) log.Error("RenderString: %v", err)
} }

View File

@ -99,9 +99,7 @@ func MarkdownRaw(ctx *context.APIContext) {
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
defer ctx.Req.Body.Close() defer ctx.Req.Body.Close()
if err := markdown.RenderRaw(&markup.RenderContext{ if err := markdown.RenderRaw(markup.NewRenderContext(ctx), ctx.Req.Body, ctx.Resp); err != nil {
Ctx: ctx,
}, ctx.Req.Body, ctx.Resp); err != nil {
ctx.InternalServerError(err) ctx.InternalServerError(err)
return return
} }

View File

@ -28,13 +28,12 @@ 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" // 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" // and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc"
renderCtx := &markup.RenderContext{ renderCtx := markup.NewRenderContext(ctx).
Ctx: ctx, WithLinks(markup.Links{AbsolutePrefix: true}).
Links: markup.Links{AbsolutePrefix: true}, WithMarkupType(markdown.MarkupName)
MarkupType: markdown.MarkupName,
}
if urlPathContext != "" { if urlPathContext != "" {
renderCtx.Links.Base = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext) renderCtx.RenderOptions.Links.Base = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext)
} }
if mode == "" || mode == "markdown" { if mode == "" || mode == "markdown" {
@ -47,15 +46,14 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPa
switch mode { switch mode {
case "gfm": // legacy mode, do nothing case "gfm": // legacy mode, do nothing
case "comment": case "comment":
renderCtx.Metas = map[string]string{"markdownLineBreakStyle": "comment"} renderCtx = renderCtx.WithMetas(map[string]string{"markdownLineBreakStyle": "comment"})
case "wiki": case "wiki":
renderCtx.Metas = map[string]string{"markdownLineBreakStyle": "document", "markupContentMode": "wiki"} renderCtx = renderCtx.WithMetas(map[string]string{"markdownLineBreakStyle": "document", "markupContentMode": "wiki"})
case "file": case "file":
// render the repo file content by its extension // render the repo file content by its extension
renderCtx.Metas = map[string]string{"markdownLineBreakStyle": "document"} renderCtx = renderCtx.WithMetas(map[string]string{"markdownLineBreakStyle": "document"}).
renderCtx.MarkupType = "" WithMarkupType("").
renderCtx.RelativePath = filePath WithRelativePath(filePath)
renderCtx.InStandalonePage = true
default: default:
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode)) ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
return return
@ -70,17 +68,17 @@ 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.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" refPath = strings.TrimSuffix(refPath, "/"+fileDir) // now we get the correct branch path: "branch/features/feat-12"
renderCtx.Links = markup.Links{AbsolutePrefix: true, Base: absoluteBasePrefix, BranchPath: refPath, TreePath: fileDir} renderCtx = renderCtx.WithLinks(markup.Links{AbsolutePrefix: true, Base: absoluteBasePrefix, BranchPath: refPath, TreePath: fileDir})
} }
if repo != nil && repo.Repository != nil { if repo != nil && repo.Repository != nil {
renderCtx.Repo = repo.Repository renderCtx = renderCtx.WithRepoFacade(repo.Repository)
if mode == "file" { if mode == "file" {
renderCtx.Metas = repo.Repository.ComposeDocumentMetas(ctx) renderCtx = renderCtx.WithMetas(repo.Repository.ComposeDocumentMetas(ctx))
} else if mode == "wiki" { } else if mode == "wiki" {
renderCtx.Metas = repo.Repository.ComposeWikiMetas(ctx) renderCtx = renderCtx.WithMetas(repo.Repository.ComposeWikiMetas(ctx))
} else if mode == "comment" { } else if mode == "comment" {
renderCtx.Metas = repo.Repository.ComposeMetas(ctx) renderCtx = renderCtx.WithMetas(repo.Repository.ComposeMetas(ctx))
} }
} }
if err := markup.Render(renderCtx, strings.NewReader(text), ctx.Resp); err != nil { if err := markup.Render(renderCtx, strings.NewReader(text), ctx.Resp); err != nil {

View File

@ -51,16 +51,14 @@ func toReleaseLink(ctx *context.Context, act *activities_model.Action) string {
// renderMarkdown creates a minimal markdown render context from an action. // renderMarkdown creates a minimal markdown render context from an action.
// If rendering fails, the original markdown text is returned // If rendering fails, the original markdown text is returned
func renderMarkdown(ctx *context.Context, act *activities_model.Action, content string) template.HTML { func renderMarkdown(ctx *context.Context, act *activities_model.Action, content string) template.HTML {
markdownCtx := &markup.RenderContext{ markdownCtx := markup.NewRenderContext(ctx).
Ctx: ctx, WithLinks(markup.Links{
Links: markup.Links{
Base: act.GetRepoLink(ctx), Base: act.GetRepoLink(ctx),
}, }).
Metas: map[string]string{ // FIXME: not right here, it should use issue to compose the metas WithMetas(map[string]string{ // FIXME: not right here, it should use issue to compose the metas
"user": act.GetRepoUserName(ctx), "user": act.GetRepoUserName(ctx),
"repo": act.GetRepoName(ctx), "repo": act.GetRepoName(ctx),
}, })
}
markdown, err := markdown.RenderString(markdownCtx, content) markdown, err := markdown.RenderString(markdownCtx, content)
if err != nil { if err != nil {
return templates.SanitizeHTML(content) // old code did so: use SanitizeHTML to render in tmpl return templates.SanitizeHTML(content) // old code did so: use SanitizeHTML to render in tmpl
@ -296,14 +294,13 @@ func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release) (
} }
link := &feeds.Link{Href: rel.HTMLURL()} link := &feeds.Link{Href: rel.HTMLURL()}
content, err = markdown.RenderString(&markup.RenderContext{ content, err = markdown.RenderString(markup.NewRenderContext(ctx).
Ctx: ctx, WithRepoFacade(rel.Repo).
Repo: rel.Repo, WithLinks(markup.Links{
Links: markup.Links{
Base: rel.Repo.Link(), Base: rel.Repo.Link(),
}, }).
Metas: rel.Repo.ComposeMetas(ctx), WithMetas(rel.Repo.ComposeMetas(ctx)),
}, rel.Note) rel.Note)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -41,13 +41,10 @@ func showUserFeed(ctx *context.Context, formatType string) {
return return
} }
ctxUserDescription, err := markdown.RenderString(&markup.RenderContext{ ctxUserDescription, err := markdown.RenderString(markup.NewRenderContext(ctx).
Ctx: ctx, WithLinks(markup.Links{Base: ctx.ContextUser.HTMLURL()}).
Links: markup.Links{ WithMetas(markup.ComposeSimpleDocumentMetas()),
Base: ctx.ContextUser.HTMLURL(), ctx.ContextUser.Description)
},
Metas: markup.ComposeSimpleDocumentMetas(),
}, ctx.ContextUser.Description)
if err != nil { if err != nil {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)
return return

View File

@ -180,17 +180,16 @@ func prepareOrgProfileReadme(ctx *context.Context, viewRepositories bool) bool {
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil { if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
log.Error("failed to GetBlobContent: %v", err) log.Error("failed to GetBlobContent: %v", err)
} else { } else {
if profileContent, err := markdown.RenderString(&markup.RenderContext{ if profileContent, err := markdown.RenderString(markup.NewRenderContext(ctx).
Ctx: ctx, WithGitRepo(profileGitRepo).
GitRepo: profileGitRepo, WithLinks(markup.Links{
Links: markup.Links{
// Pass repo link to markdown render for the full link of media elements. // Pass repo link to markdown render for the full link of media elements.
// The profile of default branch would be shown. // The profile of default branch would be shown.
Base: profileDbRepo.Link(), Base: profileDbRepo.Link(),
BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)), BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
}, }).
Metas: markup.ComposeSimpleDocumentMetas(), WithMetas(markup.ComposeSimpleDocumentMetas()),
}, bytes); err != nil { bytes); err != nil {
log.Error("failed to RenderString: %v", err) log.Error("failed to RenderString: %v", err)
} else { } else {
ctx.Data["ProfileReadme"] = profileContent ctx.Data["ProfileReadme"] = profileContent

View File

@ -392,16 +392,15 @@ func Diff(ctx *context.Context) {
if err == nil { if err == nil {
ctx.Data["NoteCommit"] = note.Commit ctx.Data["NoteCommit"] = note.Commit
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit) ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(&markup.RenderContext{ ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(markup.NewRenderContext(ctx).
Links: markup.Links{ WithLinks(markup.Links{
Base: ctx.Repo.RepoLink, Base: ctx.Repo.RepoLink,
BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)), BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)),
}, }).
Metas: ctx.Repo.Repository.ComposeMetas(ctx), WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
GitRepo: ctx.Repo.GitRepo, WithGitRepo(ctx.Repo.GitRepo).
Repo: ctx.Repo.Repository, WithRepoFacade(ctx.Repo.Repository),
Ctx: ctx, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
}, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
if err != nil { if err != nil {
ctx.ServerError("RenderCommitMessage", err) ctx.ServerError("RenderCommitMessage", err)
return return

View File

@ -149,7 +149,7 @@ func setCsvCompareContext(ctx *context.Context) {
return csvReader, reader, err return csvReader, reader, err
} }
baseReader, baseBlobCloser, err := csvReaderFromCommit(&markup.RenderContext{Ctx: ctx, RelativePath: diffFile.OldName}, baseBlob) baseReader, baseBlobCloser, err := csvReaderFromCommit(markup.NewRenderContext(ctx).WithRelativePath(diffFile.OldName), baseBlob)
if baseBlobCloser != nil { if baseBlobCloser != nil {
defer baseBlobCloser.Close() defer baseBlobCloser.Close()
} }
@ -161,7 +161,7 @@ func setCsvCompareContext(ctx *context.Context) {
return CsvDiffResult{nil, "unable to load file"} return CsvDiffResult{nil, "unable to load file"}
} }
headReader, headBlobCloser, err := csvReaderFromCommit(&markup.RenderContext{Ctx: ctx, RelativePath: diffFile.Name}, headBlob) headReader, headBlobCloser, err := csvReaderFromCommit(markup.NewRenderContext(ctx).WithRelativePath(diffFile.Name), headBlob)
if headBlobCloser != nil { if headBlobCloser != nil {
defer headBlobCloser.Close() defer headBlobCloser.Close()
} }

View File

@ -366,15 +366,12 @@ func UpdateIssueContent(ctx *context.Context) {
} }
} }
content, err := markdown.RenderString(&markup.RenderContext{ content, err := markdown.RenderString(markup.NewRenderContext(ctx).
Links: markup.Links{ WithLinks(markup.Links{Base: ctx.FormString("context")}).
Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ? WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
}, WithGitRepo(ctx.Repo.GitRepo).
Metas: ctx.Repo.Repository.ComposeMetas(ctx), WithRepoFacade(ctx.Repo.Repository),
GitRepo: ctx.Repo.GitRepo, issue.Content)
Repo: ctx.Repo.Repository,
Ctx: ctx,
}, issue.Content)
if err != nil { if err != nil {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)
return return

View File

@ -267,15 +267,12 @@ func UpdateCommentContent(ctx *context.Context) {
var renderedContent template.HTML var renderedContent template.HTML
if comment.Content != "" { if comment.Content != "" {
renderedContent, err = markdown.RenderString(&markup.RenderContext{ renderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
Links: markup.Links{ WithLinks(markup.Links{Base: ctx.FormString("context")}).
Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ? WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
}, WithGitRepo(ctx.Repo.GitRepo).
Metas: ctx.Repo.Repository.ComposeMetas(ctx), WithRepoFacade(ctx.Repo.Repository),
GitRepo: ctx.Repo.GitRepo, comment.Content)
Repo: ctx.Repo.Repository,
Ctx: ctx,
}, comment.Content)
if err != nil { if err != nil {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)
return return

View File

@ -359,15 +359,12 @@ func ViewIssue(ctx *context.Context) {
} }
} }
ctx.Data["IssueWatch"] = iw ctx.Data["IssueWatch"] = iw
issue.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ issue.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
Links: markup.Links{ WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
Base: ctx.Repo.RepoLink, WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
}, WithGitRepo(ctx.Repo.GitRepo).
Metas: ctx.Repo.Repository.ComposeMetas(ctx), WithRepoFacade(ctx.Repo.Repository),
GitRepo: ctx.Repo.GitRepo, issue.Content)
Repo: ctx.Repo.Repository,
Ctx: ctx,
}, issue.Content)
if err != nil { if err != nil {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)
return return
@ -467,15 +464,14 @@ func ViewIssue(ctx *context.Context) {
comment.Issue = issue comment.Issue = issue
if comment.Type == issues_model.CommentTypeComment || comment.Type == issues_model.CommentTypeReview { if comment.Type == issues_model.CommentTypeComment || comment.Type == issues_model.CommentTypeReview {
comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ comment.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
Links: markup.Links{ WithLinks(markup.Links{
Base: ctx.Repo.RepoLink, Base: ctx.Repo.RepoLink,
}, }).
Metas: ctx.Repo.Repository.ComposeMetas(ctx), WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
GitRepo: ctx.Repo.GitRepo, WithGitRepo(ctx.Repo.GitRepo).
Repo: ctx.Repo.Repository, WithRepoFacade(ctx.Repo.Repository),
Ctx: ctx, comment.Content)
}, comment.Content)
if err != nil { if err != nil {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)
return return
@ -550,15 +546,12 @@ func ViewIssue(ctx *context.Context) {
} }
} }
} else if comment.Type.HasContentSupport() { } else if comment.Type.HasContentSupport() {
comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ comment.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
Links: markup.Links{ WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
Base: ctx.Repo.RepoLink, WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
}, WithGitRepo(ctx.Repo.GitRepo).
Metas: ctx.Repo.Repository.ComposeMetas(ctx), WithRepoFacade(ctx.Repo.Repository),
GitRepo: ctx.Repo.GitRepo, comment.Content)
Repo: ctx.Repo.Repository,
Ctx: ctx,
}, comment.Content)
if err != nil { if err != nil {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)
return return

View File

@ -79,15 +79,12 @@ func Milestones(ctx *context.Context) {
} }
} }
for _, m := range miles { for _, m := range miles {
m.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ m.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
Links: markup.Links{ WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
Base: ctx.Repo.RepoLink, WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
}, WithGitRepo(ctx.Repo.GitRepo).
Metas: ctx.Repo.Repository.ComposeMetas(ctx), WithRepoFacade(ctx.Repo.Repository),
GitRepo: ctx.Repo.GitRepo, m.Content)
Repo: ctx.Repo.Repository,
Ctx: ctx,
}, m.Content)
if err != nil { if err != nil {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)
return return
@ -268,15 +265,12 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
return return
} }
milestone.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ milestone.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
Links: markup.Links{ WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
Base: ctx.Repo.RepoLink, WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
}, WithGitRepo(ctx.Repo.GitRepo).
Metas: ctx.Repo.Repository.ComposeMetas(ctx), WithRepoFacade(ctx.Repo.Repository),
GitRepo: ctx.Repo.GitRepo, milestone.Content)
Repo: ctx.Repo.Repository,
Ctx: ctx,
}, milestone.Content)
if err != nil { if err != nil {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)
return return

View File

@ -92,15 +92,12 @@ func Projects(ctx *context.Context) {
} }
for i := range projects { for i := range projects {
projects[i].RenderedContent, err = markdown.RenderString(&markup.RenderContext{ projects[i].RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
Links: markup.Links{ WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
Base: ctx.Repo.RepoLink, WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
}, WithGitRepo(ctx.Repo.GitRepo).
Metas: ctx.Repo.Repository.ComposeMetas(ctx), WithRepoFacade(ctx.Repo.Repository),
GitRepo: ctx.Repo.GitRepo, projects[i].Description)
Repo: ctx.Repo.Repository,
Ctx: ctx,
}, projects[i].Description)
if err != nil { if err != nil {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)
return return
@ -425,15 +422,12 @@ func ViewProject(ctx *context.Context) {
ctx.Data["SelectLabels"] = selectLabels ctx.Data["SelectLabels"] = selectLabels
ctx.Data["AssigneeID"] = assigneeID ctx.Data["AssigneeID"] = assigneeID
project.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ project.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
Links: markup.Links{ WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
Base: ctx.Repo.RepoLink, WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
}, WithGitRepo(ctx.Repo.GitRepo).
Metas: ctx.Repo.Repository.ComposeMetas(ctx), WithRepoFacade(ctx.Repo.Repository),
GitRepo: ctx.Repo.GitRepo, project.Description)
Repo: ctx.Repo.Repository,
Ctx: ctx,
}, project.Description)
if err != nil { if err != nil {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)
return return

View File

@ -114,15 +114,12 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions)
cacheUsers[r.PublisherID] = r.Publisher cacheUsers[r.PublisherID] = r.Publisher
} }
r.RenderedNote, err = markdown.RenderString(&markup.RenderContext{ r.RenderedNote, err = markdown.RenderString(markup.NewRenderContext(ctx).
Links: markup.Links{ WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
Base: ctx.Repo.RepoLink, WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
}, WithGitRepo(ctx.Repo.GitRepo).
Metas: ctx.Repo.Repository.ComposeMetas(ctx), WithRepoFacade(ctx.Repo.Repository),
GitRepo: ctx.Repo.GitRepo, r.Note)
Repo: ctx.Repo.Repository,
Ctx: ctx,
}, r.Note)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -56,18 +56,17 @@ func RenderFile(ctx *context.Context) {
return return
} }
err = markup.Render(&markup.RenderContext{ err = markup.Render(markup.NewRenderContext(ctx).
Ctx: ctx, WithRelativePath(ctx.Repo.TreePath).
RelativePath: ctx.Repo.TreePath, WithLinks(markup.Links{
Links: markup.Links{
Base: ctx.Repo.RepoLink, Base: ctx.Repo.RepoLink,
BranchPath: ctx.Repo.BranchNameSubURL(), BranchPath: ctx.Repo.BranchNameSubURL(),
TreePath: path.Dir(ctx.Repo.TreePath), TreePath: path.Dir(ctx.Repo.TreePath),
}, }).
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx), WithMetas(ctx.Repo.Repository.ComposeDocumentMetas(ctx)).
GitRepo: ctx.Repo.GitRepo, WithGitRepo(ctx.Repo.GitRepo).
InStandalonePage: true, WithInStandalonePage(true),
}, rd, ctx.Resp) rd, ctx.Resp)
if err != nil { if err != nil {
log.Error("Failed to render file %q: %v", ctx.Repo.TreePath, err) log.Error("Failed to render file %q: %v", ctx.Repo.TreePath, err)
http.Error(ctx.Resp, "Failed to render file", http.StatusInternalServerError) http.Error(ctx.Resp, "Failed to render file", http.StatusInternalServerError)

View File

@ -310,18 +310,17 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
ctx.Data["IsMarkup"] = true ctx.Data["IsMarkup"] = true
ctx.Data["MarkupType"] = markupType ctx.Data["MarkupType"] = markupType
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{ ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, markup.NewRenderContext(ctx).
Ctx: ctx, WithMarkupType(markupType).
MarkupType: markupType, WithRelativePath(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).
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). WithLinks(markup.Links{
Links: markup.Links{
Base: ctx.Repo.RepoLink, Base: ctx.Repo.RepoLink,
BranchPath: ctx.Repo.BranchNameSubURL(), BranchPath: ctx.Repo.BranchNameSubURL(),
TreePath: path.Join(ctx.Repo.TreePath, subfolder), TreePath: path.Join(ctx.Repo.TreePath, subfolder),
}, }).
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx), WithMetas(ctx.Repo.Repository.ComposeDocumentMetas(ctx)).
GitRepo: ctx.Repo.GitRepo, WithGitRepo(ctx.Repo.GitRepo),
}, rd) rd)
if err != nil { if err != nil {
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err) log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
delete(ctx.Data, "IsMarkup") delete(ctx.Data, "IsMarkup")
@ -514,18 +513,17 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
ctx.Data["MarkupType"] = markupType ctx.Data["MarkupType"] = markupType
metas := ctx.Repo.Repository.ComposeDocumentMetas(ctx) metas := ctx.Repo.Repository.ComposeDocumentMetas(ctx)
metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL() metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{ ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, markup.NewRenderContext(ctx).
Ctx: ctx, WithMarkupType(markupType).
MarkupType: markupType, WithRelativePath(ctx.Repo.TreePath).
RelativePath: ctx.Repo.TreePath, WithLinks(markup.Links{
Links: markup.Links{
Base: ctx.Repo.RepoLink, Base: ctx.Repo.RepoLink,
BranchPath: ctx.Repo.BranchNameSubURL(), BranchPath: ctx.Repo.BranchNameSubURL(),
TreePath: path.Dir(ctx.Repo.TreePath), TreePath: path.Dir(ctx.Repo.TreePath),
}, }).
Metas: metas, WithMetas(metas).
GitRepo: ctx.Repo.GitRepo, WithGitRepo(ctx.Repo.GitRepo),
}, rd) rd)
if err != nil { if err != nil {
ctx.ServerError("Render", err) ctx.ServerError("Render", err)
return return
@ -606,18 +604,17 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
rd := io.MultiReader(bytes.NewReader(buf), dataRc) rd := io.MultiReader(bytes.NewReader(buf), dataRc)
ctx.Data["IsMarkup"] = true ctx.Data["IsMarkup"] = true
ctx.Data["MarkupType"] = markupType ctx.Data["MarkupType"] = markupType
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{ ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, markup.NewRenderContext(ctx).
Ctx: ctx, WithMarkupType(markupType).
MarkupType: markupType, WithRelativePath(ctx.Repo.TreePath).
RelativePath: ctx.Repo.TreePath, WithLinks(markup.Links{
Links: markup.Links{
Base: ctx.Repo.RepoLink, Base: ctx.Repo.RepoLink,
BranchPath: ctx.Repo.BranchNameSubURL(), BranchPath: ctx.Repo.BranchNameSubURL(),
TreePath: path.Dir(ctx.Repo.TreePath), TreePath: path.Dir(ctx.Repo.TreePath),
}, }).
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx), WithMetas(ctx.Repo.Repository.ComposeDocumentMetas(ctx)).
GitRepo: ctx.Repo.GitRepo, WithGitRepo(ctx.Repo.GitRepo),
}, rd) rd)
if err != nil { if err != nil {
ctx.ServerError("Render", err) ctx.ServerError("Render", err)
return return

View File

@ -288,13 +288,9 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
footerContent = data footerContent = data
} }
rctx := &markup.RenderContext{ rctx := markup.NewRenderContext(ctx).
Ctx: ctx, WithMetas(ctx.Repo.Repository.ComposeWikiMetas(ctx)).
Metas: ctx.Repo.Repository.ComposeWikiMetas(ctx), WithLinks(markup.Links{Base: ctx.Repo.RepoLink})
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
}
buf := &strings.Builder{} buf := &strings.Builder{}
renderFn := func(data []byte) (escaped *charset.EscapeStatus, output string, err error) { renderFn := func(data []byte) (escaped *charset.EscapeStatus, output string, err error) {

View File

@ -49,10 +49,7 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
} }
ctx.Data["OpenIDs"] = openIDs ctx.Data["OpenIDs"] = openIDs
if len(ctx.ContextUser.Description) != 0 { if len(ctx.ContextUser.Description) != 0 {
content, err := markdown.RenderString(&markup.RenderContext{ content, err := markdown.RenderString(markup.NewRenderContext(ctx).WithMetas(markup.ComposeSimpleDocumentMetas()), ctx.ContextUser.Description)
Metas: markup.ComposeSimpleDocumentMetas(),
Ctx: ctx,
}, ctx.ContextUser.Description)
if err != nil { if err != nil {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)
return return

View File

@ -257,14 +257,11 @@ func Milestones(ctx *context.Context) {
continue continue
} }
milestones[i].RenderedContent, err = markdown.RenderString(&markup.RenderContext{ milestones[i].RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
Links: markup.Links{ WithLinks(markup.Links{Base: milestones[i].Repo.Link()}).
Base: milestones[i].Repo.Link(), WithMetas(milestones[i].Repo.ComposeMetas(ctx)).
}, WithRepoFacade(milestones[i].Repo),
Metas: milestones[i].Repo.ComposeMetas(ctx), milestones[i].Content)
Ctx: ctx,
Repo: milestones[i].Repo,
}, milestones[i].Content)
if err != nil { if err != nil {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)
return return

View File

@ -246,10 +246,9 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil { if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
log.Error("failed to GetBlobContent: %v", err) log.Error("failed to GetBlobContent: %v", err)
} else { } else {
if profileContent, err := markdown.RenderString(&markup.RenderContext{ if profileContent, err := markdown.RenderString(markup.NewRenderContext(ctx).
Ctx: ctx, WithGitRepo(profileGitRepo).
GitRepo: profileGitRepo, WithLinks(markup.Links{
Links: markup.Links{
// Give the repo link to the markdown render for the full link of media element. // Give the repo link to the markdown render for the full link of media element.
// the media link usually be like /[user]/[repoName]/media/branch/[branchName], // the media link usually be like /[user]/[repoName]/media/branch/[branchName],
// Eg. /Tom/.profile/media/branch/main // Eg. /Tom/.profile/media/branch/main
@ -257,8 +256,8 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
// https://docs.gitea.com/usage/profile-readme // https://docs.gitea.com/usage/profile-readme
Base: profileDbRepo.Link(), Base: profileDbRepo.Link(),
BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)), BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
}, }),
}, bytes); err != nil { bytes); err != nil {
log.Error("failed to RenderString: %v", err) log.Error("failed to RenderString: %v", err)
} else { } else {
ctx.Data["ProfileReadme"] = profileContent ctx.Data["ProfileReadme"] = profileContent

View File

@ -259,9 +259,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID) ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
if len(ctx.ContextUser.Description) != 0 { if len(ctx.ContextUser.Description) != 0 {
content, err := markdown.RenderString(&markup.RenderContext{ content, err := markdown.RenderString(markup.NewRenderContext(ctx), ctx.ContextUser.Description)
Ctx: ctx,
}, ctx.ContextUser.Description)
if err != nil { if err != nil {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)
return return

View File

@ -219,15 +219,11 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
} }
// This is the body of the new issue or comment, not the mail body // This is the body of the new issue or comment, not the mail body
body, err := markdown.RenderString(&markup.RenderContext{ body, err := markdown.RenderString(markup.NewRenderContext(ctx).
Ctx: ctx, WithRepoFacade(ctx.Issue.Repo).
Repo: ctx.Issue.Repo, WithLinks(markup.Links{AbsolutePrefix: true, Base: ctx.Issue.Repo.HTMLURL()}).
Links: markup.Links{ WithMetas(ctx.Issue.Repo.ComposeMetas(ctx)),
AbsolutePrefix: true, ctx.Content)
Base: ctx.Issue.Repo.HTMLURL(),
},
Metas: ctx.Issue.Repo.ComposeMetas(ctx),
}, ctx.Content)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -56,14 +56,11 @@ func mailNewRelease(ctx context.Context, lang string, tos []*user_model.User, re
locale := translation.NewLocale(lang) locale := translation.NewLocale(lang)
var err error var err error
rel.RenderedNote, err = markdown.RenderString(&markup.RenderContext{ rel.RenderedNote, err = markdown.RenderString(markup.NewRenderContext(ctx).
Ctx: ctx, WithRepoFacade(rel.Repo).
Repo: rel.Repo, WithLinks(markup.Links{Base: rel.Repo.HTMLURL()}).
Links: markup.Links{ WithMetas(rel.Repo.ComposeMetas(ctx)),
Base: rel.Repo.HTMLURL(), rel.Note)
},
Metas: rel.Repo.ComposeMetas(ctx),
}, rel.Note)
if err != nil { if err != nil {
log.Error("markdown.RenderString(%d): %v", rel.RepoID, err) log.Error("markdown.RenderString(%d): %v", rel.RepoID, err)
return return

View File

@ -14,27 +14,22 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
var renderContext = markup.RenderContext{ func newFuzzRenderContext() *markup.RenderContext {
Ctx: context.Background(), return markup.NewRenderContext(context.Background()).
Links: markup.Links{ WithLinks(markup.Links{Base: "https://example.com/go-gitea/gitea"}).
Base: "https://example.com/go-gitea/gitea", WithMetas(map[string]string{"user": "go-gitea", "repo": "gitea"})
},
Metas: map[string]string{
"user": "go-gitea",
"repo": "gitea",
},
} }
func FuzzMarkdownRenderRaw(f *testing.F) { func FuzzMarkdownRenderRaw(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) { f.Fuzz(func(t *testing.T, data []byte) {
setting.AppURL = "http://localhost:3000/" setting.AppURL = "http://localhost:3000/"
markdown.RenderRaw(&renderContext, bytes.NewReader(data), io.Discard) markdown.RenderRaw(newFuzzRenderContext(), bytes.NewReader(data), io.Discard)
}) })
} }
func FuzzMarkupPostProcess(f *testing.F) { func FuzzMarkupPostProcess(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) { f.Fuzz(func(t *testing.T, data []byte) {
setting.AppURL = "http://localhost:3000/" setting.AppURL = "http://localhost:3000/"
markup.PostProcess(&renderContext, bytes.NewReader(data), io.Discard) markup.PostProcess(newFuzzRenderContext(), bytes.NewReader(data), io.Discard)
}) })
} }