From d635e53164b247e274bb092ce6748be2fa9d1ca5 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 22 Nov 2024 13:54:56 +0800 Subject: [PATCH] temp --- models/activities/action.go | 8 +- models/issues/comment_code.go | 10 +- models/renderhelper/commit_checker.go | 53 +++ models/renderhelper/repo_comment.go | 73 ++++ models/renderhelper/repo_comment_test.go | 73 ++++ models/renderhelper/repo_file.go | 77 ++++ models/renderhelper/repo_wiki.go | 79 ++++ models/renderhelper/simple_document.go | 29 ++ models/unittest/unit_tests.go | 7 +- modules/markup/csv/csv.go | 2 +- modules/markup/external/external.go | 10 +- modules/markup/html.go | 3 +- modules/markup/html_codepreview.go | 2 +- modules/markup/html_codepreview_test.go | 8 +- modules/markup/html_commit.go | 35 +- modules/markup/html_internal_test.go | 94 +---- modules/markup/html_issue.go | 9 +- modules/markup/html_link.go | 32 +- modules/markup/html_mention.go | 8 +- modules/markup/html_node.go | 4 +- modules/markup/html_test.go | 202 +++------ modules/markup/main_test.go | 8 +- modules/markup/markdown/goldmark.go | 4 +- modules/markup/markdown/main_test.go | 13 +- modules/markup/markdown/markdown_test.go | 468 ++------------------- modules/markup/markdown/transform_image.go | 2 +- modules/markup/markdown/transform_link.go | 13 +- modules/markup/orgmode/orgmode.go | 15 +- modules/markup/orgmode/orgmode_test.go | 26 +- modules/markup/render.go | 111 ++--- modules/markup/render_helper.go | 49 ++- modules/markup/render_link.go | 37 ++ modules/markup/render_links.go | 56 --- modules/markup/renderer.go | 4 +- modules/markup/sanitizer_default.go | 3 + modules/markup/sanitizer_default_test.go | 1 + modules/templates/util_render_test.go | 8 +- routers/api/v1/misc/markup_test.go | 8 +- routers/common/markup.go | 85 ++-- routers/web/feed/convert.go | 29 +- routers/web/feed/profile.go | 7 +- routers/web/org/home.go | 16 +- routers/web/repo/commit.go | 12 +- routers/web/repo/issue.go | 10 +- routers/web/repo/issue_comment.go | 10 +- routers/web/repo/issue_view.go | 27 +- routers/web/repo/milestone.go | 18 +- routers/web/repo/projects.go | 18 +- routers/web/repo/release.go | 10 +- routers/web/repo/render.go | 18 +- routers/web/repo/view.go | 53 +-- routers/web/repo/wiki.go | 7 +- routers/web/user/home.go | 9 +- routers/web/user/profile.go | 24 +- services/mailer/mail.go | 8 +- services/mailer/mail_release.go | 8 +- services/mailer/mail_test.go | 2 +- services/markup/processorhelper.go | 6 +- tests/fuzz/fuzz_test.go | 5 +- 59 files changed, 852 insertions(+), 1174 deletions(-) create mode 100644 models/renderhelper/commit_checker.go create mode 100644 models/renderhelper/repo_comment.go create mode 100644 models/renderhelper/repo_comment_test.go create mode 100644 models/renderhelper/repo_file.go create mode 100644 models/renderhelper/repo_wiki.go create mode 100644 models/renderhelper/simple_document.go create mode 100644 modules/markup/render_link.go delete mode 100644 modules/markup/render_links.go diff --git a/models/activities/action.go b/models/activities/action.go index 43da557fff398..e74deef1df4cf 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -200,7 +200,7 @@ func (a *Action) LoadActUser(ctx context.Context) { } } -func (a *Action) loadRepo(ctx context.Context) { +func (a *Action) LoadRepo(ctx context.Context) { if a.Repo != nil { return } @@ -250,7 +250,7 @@ func (a *Action) GetActDisplayNameTitle(ctx context.Context) string { // GetRepoUserName returns the name of the action repository owner. func (a *Action) GetRepoUserName(ctx context.Context) string { - a.loadRepo(ctx) + a.LoadRepo(ctx) if a.Repo == nil { return "(non-existing-repo)" } @@ -265,7 +265,7 @@ func (a *Action) ShortRepoUserName(ctx context.Context) string { // GetRepoName returns the name of the action repository. func (a *Action) GetRepoName(ctx context.Context) string { - a.loadRepo(ctx) + a.LoadRepo(ctx) if a.Repo == nil { return "(non-existing-repo)" } @@ -644,7 +644,7 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error { } if repoChanged { - act.loadRepo(ctx) + act.LoadRepo(ctx) repo = act.Repo // check repo owner exist. diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go index 751550f37a6b6..67a77ceb13f36 100644 --- a/models/issues/comment_code.go +++ b/models/issues/comment_code.go @@ -7,8 +7,8 @@ import ( "context" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/renderhelper" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "xorm.io/builder" @@ -112,12 +112,8 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu } var err error - rctx := markup.NewRenderContext(ctx). - WithRepoFacade(issue.Repo). - WithLinks(markup.Links{Base: issue.Repo.Link()}). - WithMetas(issue.Repo.ComposeMetas(ctx)) - if comment.RenderedContent, err = markdown.RenderString(rctx, - comment.Content); err != nil { + rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo) + if comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content); err != nil { return nil, err } } diff --git a/models/renderhelper/commit_checker.go b/models/renderhelper/commit_checker.go new file mode 100644 index 0000000000000..4815643e67348 --- /dev/null +++ b/models/renderhelper/commit_checker.go @@ -0,0 +1,53 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package renderhelper + +import ( + "context" + "io" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/log" +) + +type commitChecker struct { + ctx context.Context + commitCache map[string]bool + gitRepoFacade gitrepo.Repository + + gitRepo *git.Repository + gitRepoCloser io.Closer +} + +func newCommitChecker(ctx context.Context, gitRepo gitrepo.Repository) *commitChecker { + return &commitChecker{ctx: ctx, commitCache: make(map[string]bool), gitRepoFacade: gitRepo} +} + +func (c *commitChecker) Close() error { + if c != nil && c.gitRepoCloser != nil { + return c.gitRepoCloser.Close() + } + return nil +} + +func (c *commitChecker) IsCommitIDExisting(commitID string) bool { + exist, inCache := c.commitCache[commitID] + if inCache { + return exist + } + + if c.gitRepo == nil { + r, closer, err := gitrepo.RepositoryFromContextOrOpen(c.ctx, c.gitRepoFacade) + if err != nil { + log.Error("unable to open repository: %s Error: %v", gitrepo.RepoGitURL(c.gitRepoFacade), err) + return false + } + c.gitRepo, c.gitRepoCloser = r, closer + } + + exist = c.gitRepo.IsReferenceExist(commitID) // Don't use IsObjectExist since it doesn't support short hashs with gogit edition. + c.commitCache[commitID] = exist + return exist +} diff --git a/models/renderhelper/repo_comment.go b/models/renderhelper/repo_comment.go new file mode 100644 index 0000000000000..6bd5e91ad1768 --- /dev/null +++ b/models/renderhelper/repo_comment.go @@ -0,0 +1,73 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package renderhelper + +import ( + "context" + "fmt" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/util" +) + +type RepoComment struct { + ctx *markup.RenderContext + opts RepoCommentOptions + + commitChecker *commitChecker + repoLink string +} + +func (r *RepoComment) CleanUp() { + _ = r.commitChecker.Close() +} + +func (r *RepoComment) IsCommitIDExisting(commitID string) bool { + return r.commitChecker.IsCommitIDExisting(commitID) +} + +func (r *RepoComment) ResolveLink(link string, likeType markup.LinkType) (finalLink string) { + switch likeType { + case markup.LinkTypeApp: + finalLink = r.ctx.ResolveLinkApp(link) + default: + finalLink = r.ctx.ResolveLinkRelative(r.repoLink, r.opts.CurrentRefPath, link) + } + return finalLink +} + +var _ markup.RenderHelper = (*RepoComment)(nil) + +type RepoCommentOptions struct { + DeprecatedRepoName string // it is only a patch for the non-standard "markup" api + DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api + CurrentRefPath string // eg: "branch/main" or "commit/11223344" +} + +func NewRenderContextRepoComment(ctx context.Context, repo *repo_model.Repository, opts ...RepoCommentOptions) *markup.RenderContext { + helper := &RepoComment{ + repoLink: repo.Link(), + opts: util.OptionalArg(opts), + } + rctx := markup.NewRenderContext(ctx) + helper.ctx = rctx + if repo != nil { + helper.repoLink = repo.Link() + helper.commitChecker = newCommitChecker(ctx, repo) + rctx = rctx.WithMetas(repo.ComposeMetas(ctx)) + } else { + // this is almost dead code, only to pass the incorrect tests + helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName) + rctx = rctx.WithMetas(map[string]string{ + "user": helper.opts.DeprecatedOwnerName, + "repo": helper.opts.DeprecatedRepoName, + + "markdownLineBreakStyle": "comment", + "markupAllowShortIssuePattern": "true", + }) + } + rctx = rctx.WithHelper(helper) + return rctx +} diff --git a/models/renderhelper/repo_comment_test.go b/models/renderhelper/repo_comment_test.go new file mode 100644 index 0000000000000..d2f39d65a6df3 --- /dev/null +++ b/models/renderhelper/repo_comment_test.go @@ -0,0 +1,73 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package renderhelper + +import ( + "context" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/markdown" + + "github.com/stretchr/testify/assert" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m, &unittest.TestOptions{ + FixtureFiles: []string{"repository.yml", "user.yml"}, + }) +} + +func TestRepoComment(t *testing.T) { + unittest.PrepareTestEnv(t) + markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true + markup.Init(&markup.RenderHelperFuncs{ + IsUsernameMentionable: func(ctx context.Context, username string) bool { + return username == "user2" + }, + }) + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + rctx := NewRenderContextRepoComment(context.Background(), repo1).WithMarkupType(markdown.MarkupName) + rendered, err := markup.RenderString(rctx, ` +65f1bf27bc3bf70f64657658635e66094edbcb4d +#1 +@user2 +`) + assert.NoError(t, err) + assert.Equal(t, + `

65f1bf27bc
+#1
+@user2

+`, rendered) + + rctx = NewRenderContextRepoComment(context.Background(), repo1).WithMarkupType(markdown.MarkupName) + rendered, err = markup.RenderString(rctx, ` +[/test](/test) +[./test](./test) +![/image](/image) +![./image](./image) +`) + assert.NoError(t, err) + assert.Equal(t, + `

/test
+./test
+/image
+./image

+`, rendered) + + rctx = NewRenderContextRepoComment(context.Background(), repo1, RepoCommentOptions{CurrentRefPath: "/commit/1234"}). + WithMarkupType(markdown.MarkupName) + rendered, err = markup.RenderString(rctx, ` +[/test](/test) +![/image](/image) +`) + assert.NoError(t, err) + assert.Equal(t, `

/test
+/image

+`, rendered) +} diff --git a/models/renderhelper/repo_file.go b/models/renderhelper/repo_file.go new file mode 100644 index 0000000000000..794828c617461 --- /dev/null +++ b/models/renderhelper/repo_file.go @@ -0,0 +1,77 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package renderhelper + +import ( + "context" + "fmt" + "path" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/util" +) + +type RepoFile struct { + ctx *markup.RenderContext + opts RepoFileOptions + + commitChecker *commitChecker + repoLink string +} + +func (r *RepoFile) CleanUp() { + _ = r.commitChecker.Close() +} + +func (r *RepoFile) IsCommitIDExisting(commitID string) bool { + return r.commitChecker.IsCommitIDExisting(commitID) +} + +func (r *RepoFile) ResolveLink(link string, likeType markup.LinkType) string { + finalLink := link + switch likeType { + case markup.LinkTypeApp: + finalLink = r.ctx.ResolveLinkApp(link) + case markup.LinkTypeDefault: + finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "src", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link) + case markup.LinkTypeRaw: + finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "raw", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link) + case markup.LinkTypeMedia: + finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "media", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link) + } + return finalLink +} + +var _ markup.RenderHelper = (*RepoFile)(nil) + +type RepoFileOptions struct { + DeprecatedRepoName string // it is only a patch for the non-standard "markup" api + DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api + + CurrentRefPath string // eg: "branch/main" + CurrentTreePath string // eg: "path/to/file" in the repo +} + +func NewRenderContextRepoFile(ctx context.Context, repo *repo_model.Repository, opts ...RepoFileOptions) *markup.RenderContext { + helper := &RepoFile{opts: util.OptionalArg(opts)} + rctx := markup.NewRenderContext(ctx) + helper.ctx = rctx + if repo != nil { + helper.repoLink = repo.Link() + helper.commitChecker = newCommitChecker(ctx, repo) + rctx = rctx.WithMetas(repo.ComposeDocumentMetas(ctx)) + } else { + // this is almost dead code, only to pass the incorrect tests + helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName) + rctx = rctx.WithMetas(map[string]string{ + "user": helper.opts.DeprecatedOwnerName, + "repo": helper.opts.DeprecatedRepoName, + + "markdownLineBreakStyle": "document", + }) + } + rctx = rctx.WithHelper(helper) + return rctx +} diff --git a/models/renderhelper/repo_wiki.go b/models/renderhelper/repo_wiki.go new file mode 100644 index 0000000000000..8d82aa586149b --- /dev/null +++ b/models/renderhelper/repo_wiki.go @@ -0,0 +1,79 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package renderhelper + +import ( + "context" + "fmt" + "path" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/markdown" + "code.gitea.io/gitea/modules/util" +) + +type RepoWiki struct { + ctx *markup.RenderContext + opts RepoWikiOptions + + commitChecker *commitChecker + repoLink string +} + +func (r *RepoWiki) CleanUp() { + _ = r.commitChecker.Close() +} + +func (r *RepoWiki) IsCommitIDExisting(commitID string) bool { + return r.commitChecker.IsCommitIDExisting(commitID) +} + +func (r *RepoWiki) ResolveLink(link string, likeType markup.LinkType) string { + finalLink := link + switch likeType { + case markup.LinkTypeApp: + finalLink = r.ctx.ResolveLinkApp(link) + case markup.LinkTypeDefault: + finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "wiki", r.opts.currentRefPath), r.opts.currentTreePath, link) + case markup.LinkTypeMedia: + finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "wiki/raw", r.opts.currentRefPath), r.opts.currentTreePath, link) + // wiki doesn't use src or raw + } + return finalLink +} + +var _ markup.RenderHelper = (*RepoWiki)(nil) + +type RepoWikiOptions struct { + DeprecatedRepoName string // it is only a patch for the non-standard "markup" api + DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api + + // these options are not used at the moment because Wiki doesn't support sub-path, nor branch + currentRefPath string // eg: "branch/main" + currentTreePath string // eg: "path/to/file" in the repo +} + +func NewRenderContextRepoWiki(ctx context.Context, repo *repo_model.Repository, opts ...RepoWikiOptions) *markup.RenderContext { + helper := &RepoWiki{opts: util.OptionalArg(opts)} + rctx := markup.NewRenderContext(ctx).WithMarkupType(markdown.MarkupName) + if repo != nil { + helper.repoLink = repo.Link() + helper.commitChecker = newCommitChecker(ctx, repo) + rctx = rctx.WithMetas(repo.ComposeWikiMetas(ctx)) + } else { + // this is almost dead code, only to pass the incorrect tests + helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName) + rctx = rctx.WithMetas(map[string]string{ + "user": helper.opts.DeprecatedOwnerName, + "repo": helper.opts.DeprecatedRepoName, + + "markdownLineBreakStyle": "document", + "markupAllowShortIssuePattern": "true", + }) + } + rctx = rctx.WithHelper(helper) + helper.ctx = rctx + return rctx +} diff --git a/models/renderhelper/simple_document.go b/models/renderhelper/simple_document.go new file mode 100644 index 0000000000000..91d888aa878d4 --- /dev/null +++ b/models/renderhelper/simple_document.go @@ -0,0 +1,29 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package renderhelper + +import ( + "context" + + "code.gitea.io/gitea/modules/markup" +) + +type SimpleDocument struct { + *markup.SimpleRenderHelper + ctx *markup.RenderContext + baseLink string +} + +func (r *SimpleDocument) ResolveLink(link string, likeType markup.LinkType) string { + return r.ctx.ResolveLinkRelative(r.baseLink, "", link) +} + +var _ markup.RenderHelper = (*SimpleDocument)(nil) + +func NewRenderContextSimpleDocument(ctx context.Context, baseLink string) *markup.RenderContext { + helper := &SimpleDocument{baseLink: baseLink} + rctx := markup.NewRenderContext(ctx).WithHelper(helper).WithMetas(markup.ComposeSimpleDocumentMetas()) + helper.ctx = rctx + return rctx +} diff --git a/models/unittest/unit_tests.go b/models/unittest/unit_tests.go index 75898436fc52f..3b0f28d3a60d3 100644 --- a/models/unittest/unit_tests.go +++ b/models/unittest/unit_tests.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "xorm.io/builder" ) @@ -64,10 +65,10 @@ func BeanExists(t assert.TestingT, bean any, conditions ...any) bool { } // AssertExistsAndLoadBean assert that a bean exists and load it from the test database -func AssertExistsAndLoadBean[T any](t assert.TestingT, bean T, conditions ...any) T { +func AssertExistsAndLoadBean[T any](t require.TestingT, bean T, conditions ...any) T { exists, err := LoadBeanIfExists(bean, conditions...) - assert.NoError(t, err) - assert.True(t, exists, + require.NoError(t, err) + require.True(t, exists, "Expected to find %+v (of type %T, with conditions %+v), but did not", bean, bean, conditions) return bean diff --git a/modules/markup/csv/csv.go b/modules/markup/csv/csv.go index b7d7a1b35be45..61977d458ae11 100644 --- a/modules/markup/csv/csv.go +++ b/modules/markup/csv/csv.go @@ -133,7 +133,7 @@ func (r Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.W // Check if maxRows or maxSize is reached, and if true, warn. if (row >= maxRows && maxRows != 0) || (rd.InputOffset() >= maxSize && maxSize != 0) { warn := `
` - rawLink := ` ` + rawLink := ` ` // Try to get the user translation if locale, ok := ctx.Value(translation.ContextKey).(translation.Locale); ok { diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go index 98708e99b8622..03242e569ede5 100644 --- a/modules/markup/external/external.go +++ b/modules/markup/external/external.go @@ -79,8 +79,8 @@ func envMark(envName string) string { func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { var ( command = strings.NewReplacer( - envMark("GITEA_PREFIX_SRC"), ctx.RenderOptions.Links.SrcLink(), - envMark("GITEA_PREFIX_RAW"), ctx.RenderOptions.Links.RawLink(), + envMark("GITEA_PREFIX_SRC"), ctx.RenderHelper.ResolveLink("", markup.LinkTypeDefault), + envMark("GITEA_PREFIX_RAW"), ctx.RenderHelper.ResolveLink("", markup.LinkTypeRaw), ).Replace(p.Command) commands = strings.Fields(command) args = commands[1:] @@ -112,14 +112,14 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io. args = append(args, f.Name()) } - processCtx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.RenderOptions.Links.SrcLink())) + processCtx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.RenderHelper.ResolveLink("", markup.LinkTypeDefault))) defer finished() cmd := exec.CommandContext(processCtx, commands[0], args...) cmd.Env = append( os.Environ(), - "GITEA_PREFIX_SRC="+ctx.RenderOptions.Links.SrcLink(), - "GITEA_PREFIX_RAW="+ctx.RenderOptions.Links.RawLink(), + "GITEA_PREFIX_SRC="+ctx.RenderHelper.ResolveLink("", markup.LinkTypeDefault), + "GITEA_PREFIX_RAW="+ctx.RenderHelper.ResolveLink("", markup.LinkTypeRaw), ) if !p.IsInputFile { cmd.Stdin = input diff --git a/modules/markup/html.go b/modules/markup/html.go index e8799c401c537..0b1e9b32242c2 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -260,7 +260,6 @@ func RenderEmoji(ctx *RenderContext, content string) (string, error) { } func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output io.Writer) error { - defer ctx.Cancel() // FIXME: don't read all content to memory rawHTML, err := io.ReadAll(input) if err != nil { @@ -396,7 +395,7 @@ func createLink(ctx *RenderContext, href, content, class string) *html.Node { Data: atom.A.String(), Attr: []html.Attribute{{Key: "href", Val: href}}, } - if !RenderBehaviorForTesting.DisableInternalAttributes { + if !RenderBehaviorForTesting.DisableAdditionalAttributes { a.Attr = append(a.Attr, html.Attribute{Key: "data-markdown-generated-content"}) } if class != "" { diff --git a/modules/markup/html_codepreview.go b/modules/markup/html_codepreview.go index 68886a3434d0a..1d6d9a61091aa 100644 --- a/modules/markup/html_codepreview.go +++ b/modules/markup/html_codepreview.go @@ -51,7 +51,7 @@ func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosSt lineStart, _ := strconv.Atoi(strings.TrimPrefix(lineStartStr, "L")) lineStop, _ := strconv.Atoi(strings.TrimPrefix(lineStopStr, "L")) opts.LineStart, opts.LineStop = lineStart, lineStop - h, err := DefaultProcessorHelper.RenderRepoFileCodePreview(ctx, opts) + h, err := DefaultRenderHelperFuncs.RenderRepoFileCodePreview(ctx, opts) return m[0], m[1], h, err } diff --git a/modules/markup/html_codepreview_test.go b/modules/markup/html_codepreview_test.go index 7c0db59d06acc..3d99348ef18d5 100644 --- a/modules/markup/html_codepreview_test.go +++ b/modules/markup/html_codepreview_test.go @@ -16,16 +16,16 @@ import ( ) func TestRenderCodePreview(t *testing.T) { - markup.Init(&markup.ProcessorHelper{ - RenderRepoFileCodePreview: func(ctx context.Context, opts markup.RenderCodePreviewOptions) (template.HTML, error) { + markup.Init(&markup.RenderHelperFuncs{ + RenderRepoFileCodePreview: func(ctx context.Context, options markup.RenderCodePreviewOptions) (template.HTML, error) { return "
code preview
", nil }, }) test := func(input, expected string) { - buffer, err := markup.RenderString(markup.NewRenderContext(context.Background()).WithMarkupType(markdown.MarkupName), input) + buffer, err := markup.RenderString(markup.NewTestRenderContext().WithMarkupType(markdown.MarkupName), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } test("http://localhost:3000/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", "

code preview

") - test("http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", `

http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20

`) + test("http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", `

http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20

`) } diff --git a/modules/markup/html_commit.go b/modules/markup/html_commit.go index 0649f84664073..358e7b06ba538 100644 --- a/modules/markup/html_commit.go +++ b/modules/markup/html_commit.go @@ -4,13 +4,10 @@ package markup import ( - "io" "slices" "strings" "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/gitrepo" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" "golang.org/x/net/html" @@ -163,15 +160,12 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) { // hashCurrentPatternProcessor renders SHA1 strings to corresponding links that // are assumed to be in the same repository. func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) { - if ctx.RenderOptions.Metas == nil || ctx.RenderOptions.Metas["user"] == "" || ctx.RenderOptions.Metas["repo"] == "" || (ctx.RenderHelper.repoFacade == nil && ctx.RenderHelper.gitRepo == nil) { + if ctx.RenderOptions.Metas == nil || ctx.RenderOptions.Metas["user"] == "" || ctx.RenderOptions.Metas["repo"] == "" || ctx.RenderHelper == nil { return } start := 0 next := node.NextSibling - if ctx.RenderHelper.shaExistCache == nil { - ctx.RenderHelper.shaExistCache = make(map[string]bool) - } for node != nil && node != next && start < len(node.Data) { m := globalVars().hashCurrentPattern.FindStringSubmatchIndex(node.Data[start:]) if m == nil { @@ -189,35 +183,12 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) { // as used by git and github for linking and thus we have to do similar. // Because of this, we check to make sure that a matched hash is actually // a commit in the repository before making it a link. - - // check cache first - exist, inCache := ctx.RenderHelper.shaExistCache[hash] - if !inCache { - if ctx.RenderHelper.gitRepo == nil { - var err error - var closer io.Closer - ctx.RenderHelper.gitRepo, closer, err = gitrepo.RepositoryFromContextOrOpen(ctx, ctx.RenderHelper.repoFacade) - if err != nil { - log.Error("unable to open repository: %s Error: %v", gitrepo.RepoGitURL(ctx.RenderHelper.repoFacade), err) - return - } - ctx.AddCancel(func() { - _ = closer.Close() - ctx.RenderHelper.gitRepo = nil - }) - } - - // Don't use IsObjectExist since it doesn't support short hashs with gogit edition. - exist = ctx.RenderHelper.gitRepo.IsReferenceExist(hash) - ctx.RenderHelper.shaExistCache[hash] = exist - } - - if !exist { + if !ctx.RenderHelper.IsCommitIDExisting(hash) { start = m[3] continue } - link := util.URLJoin(ctx.RenderOptions.Links.Prefix(), ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], "commit", hash) + link := ctx.RenderHelper.ResolveLink(util.URLJoin(ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], "commit", hash), LinkTypeApp) replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), "commit")) start = 0 node = node.NextSibling.NextSibling diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 7b143664fe81d..7f2057a3438eb 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -4,7 +4,6 @@ package markup import ( - "context" "fmt" "strconv" "strings" @@ -34,8 +33,7 @@ func numericIssueLink(baseURL, class string, index int, marker string) string { // link an HTML link func link(href, class, contents string) string { - extra := ` data-markdown-generated-content=""` - extra += util.Iif(class != "", ` class="`+class+`"`, "") + extra := util.Iif(class != "", ` class="`+class+`"`, "") return fmt.Sprintf(`%s`, href, extra, contents) } @@ -69,22 +67,11 @@ var localMetas = map[string]string{ "markupAllowShortIssuePattern": "true", } -var localWikiMetas = map[string]string{ - "user": "test-owner", - "repo": "test-repo", - "markupContentMode": "wiki", -} - func TestRender_IssueIndexPattern(t *testing.T) { // numeric: render inputs without valid mentions test := func(s string) { - testRenderIssueIndexPattern(t, s, s, &RenderContext{ - ctx: context.Background(), - }) - testRenderIssueIndexPattern(t, s, s, &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: numericMetas}, - }) + testRenderIssueIndexPattern(t, s, s, NewTestRenderContext()) + testRenderIssueIndexPattern(t, s, s, NewTestRenderContext(numericMetas)) } // should not render anything when there are no mentions @@ -132,10 +119,7 @@ func TestRender_IssueIndexPattern2(t *testing.T) { links[i] = numericIssueLink(util.URLJoin(TestRepoURL, path), "ref-issue", index, marker) } expectedNil := fmt.Sprintf(expectedFmt, links...) - testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: localMetas}, - }) + testRenderIssueIndexPattern(t, s, expectedNil, NewTestRenderContext(TestAppURL, localMetas)) class := "ref-issue" if isExternal { @@ -146,10 +130,7 @@ func TestRender_IssueIndexPattern2(t *testing.T) { links[i] = numericIssueLink(prefix, class, index, marker) } expectedNum := fmt.Sprintf(expectedFmt, links...) - testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: numericMetas}, - }) + testRenderIssueIndexPattern(t, s, expectedNum, NewTestRenderContext(TestAppURL, numericMetas)) } // should render freestanding mentions @@ -183,10 +164,7 @@ func TestRender_IssueIndexPattern3(t *testing.T) { // alphanumeric: render inputs without valid mentions test := func(s string) { - testRenderIssueIndexPattern(t, s, s, &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: alphanumericMetas}, - }) + testRenderIssueIndexPattern(t, s, s, NewTestRenderContext(alphanumericMetas)) } test("") test("this is a test") @@ -216,10 +194,7 @@ func TestRender_IssueIndexPattern4(t *testing.T) { links[i] = externalIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue ref-external-issue", name) } expected := fmt.Sprintf(expectedFmt, links...) - testRenderIssueIndexPattern(t, s, expected, &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: alphanumericMetas}, - }) + testRenderIssueIndexPattern(t, s, expected, NewTestRenderContext(alphanumericMetas)) } test("OTT-1234 test", "%s test", "OTT-1234") test("test T-12 issue", "test %s issue", "T-12") @@ -239,10 +214,7 @@ func TestRender_IssueIndexPattern5(t *testing.T) { } expected := fmt.Sprintf(expectedFmt, links...) - testRenderIssueIndexPattern(t, s, expected, &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: metas}, - }) + testRenderIssueIndexPattern(t, s, expected, NewTestRenderContext(metas)) } test("abc ISSUE-123 def", "abc %s def", @@ -263,10 +235,7 @@ func TestRender_IssueIndexPattern5(t *testing.T) { []string{"ISSUE-123"}, ) - testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: regexpMetas}, - }) + testRenderIssueIndexPattern(t, "will not match", "will not match", NewTestRenderContext(regexpMetas)) } func TestRender_IssueIndexPattern_NoShortPattern(t *testing.T) { @@ -278,18 +247,9 @@ func TestRender_IssueIndexPattern_NoShortPattern(t *testing.T) { "style": IssueNameStyleNumeric, } - testRenderIssueIndexPattern(t, "#1", "#1", &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: metas}, - }) - testRenderIssueIndexPattern(t, "#1312", "#1312", &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: metas}, - }) - testRenderIssueIndexPattern(t, "!1", "!1", &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: metas}, - }) + testRenderIssueIndexPattern(t, "#1", "#1", NewTestRenderContext(metas)) + testRenderIssueIndexPattern(t, "#1312", "#1312", NewTestRenderContext(metas)) + testRenderIssueIndexPattern(t, "!1", "!1", NewTestRenderContext(metas)) } func TestRender_RenderIssueTitle(t *testing.T) { @@ -300,20 +260,12 @@ func TestRender_RenderIssueTitle(t *testing.T) { "repo": "someRepo", "style": IssueNameStyleNumeric, } - actual, err := RenderIssueTitle(&RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: metas}, - }, "#1") + actual, err := RenderIssueTitle(NewTestRenderContext(metas), "#1") assert.NoError(t, err) assert.Equal(t, "#1", actual) } func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) { - ctx.RenderOptions.Links.AbsolutePrefix = true - if ctx.RenderOptions.Links.Base == "" { - ctx.RenderOptions.Links.Base = TestRepoURL - } - var buf strings.Builder err := postProcess(ctx, []processor{issueIndexPatternProcessor}, strings.NewReader(input), &buf) assert.NoError(t, err) @@ -325,20 +277,12 @@ func TestRender_AutoLink(t *testing.T) { test := func(input, expected string) { var buffer strings.Builder - err := PostProcess(&RenderContext{ - ctx: context.Background(), - - RenderOptions: RenderOptions{Metas: localMetas, Links: Links{Base: TestRepoURL}}, - }, strings.NewReader(input), &buffer) + err := PostProcess(NewTestRenderContext(localMetas), strings.NewReader(input), &buffer) assert.Equal(t, err, nil) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) buffer.Reset() - err = PostProcess(&RenderContext{ - ctx: context.Background(), - - RenderOptions: RenderOptions{Metas: localWikiMetas, Links: Links{Base: TestRepoURL}}, - }, strings.NewReader(input), &buffer) + err = PostProcess(NewTestRenderContext(localMetas), strings.NewReader(input), &buffer) assert.Equal(t, err, nil) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) } @@ -360,14 +304,10 @@ func TestRender_AutoLink(t *testing.T) { func TestRender_FullIssueURLs(t *testing.T) { setting.AppURL = TestAppURL - defer testModule.MockVariableValue(&RenderBehaviorForTesting.DisableInternalAttributes, true)() + defer testModule.MockVariableValue(&RenderBehaviorForTesting.DisableAdditionalAttributes, true)() test := func(input, expected string) { var result strings.Builder - err := postProcess(&RenderContext{ - ctx: context.Background(), - - RenderOptions: RenderOptions{Metas: localMetas, Links: Links{Base: TestRepoURL}}, - }, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result) + err := postProcess(NewTestRenderContext(localMetas), []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result) assert.NoError(t, err) assert.Equal(t, expected, result.String()) } diff --git a/modules/markup/html_issue.go b/modules/markup/html_issue.go index a75a8b3290066..e64ec76c3d2fc 100644 --- a/modules/markup/html_issue.go +++ b/modules/markup/html_issue.go @@ -136,9 +136,11 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { // Gitea will redirect on click as appropriate. issuePath := util.Iif(ref.IsPull, "pulls", "issues") if ref.Owner == "" { - link = createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], issuePath, ref.Issue), reftext, "ref-issue") + linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], issuePath, ref.Issue), LinkTypeApp) + link = createLink(ctx, linkHref, reftext, "ref-issue") } else { - link = createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), ref.Owner, ref.Name, issuePath, ref.Issue), reftext, "ref-issue") + linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, issuePath, ref.Issue), LinkTypeApp) + link = createLink(ctx, linkHref, reftext, "ref-issue") } } @@ -177,7 +179,8 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) { } reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha) - link := createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), ref.Owner, ref.Name, "commit", ref.CommitSha), reftext, "commit") + linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, "commit", ref.CommitSha), LinkTypeApp) + link := createLink(ctx, linkHref, reftext, "commit") replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link) node = node.NextSibling.NextSibling diff --git a/modules/markup/html_link.go b/modules/markup/html_link.go index f6700161b53f8..5fd38b63cd370 100644 --- a/modules/markup/html_link.go +++ b/modules/markup/html_link.go @@ -6,37 +6,14 @@ package markup import ( "net/url" "path" - "path/filepath" "strings" "code.gitea.io/gitea/modules/markup/common" - "code.gitea.io/gitea/modules/util" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) -func ResolveLink(ctx *RenderContext, link, userContentAnchorPrefix string) (result string, resolved bool) { - isAnchorFragment := link != "" && link[0] == '#' - if !isAnchorFragment && !IsFullURLString(link) { - linkBase := ctx.RenderOptions.Links.Base - if ctx.IsMarkupContentWiki() { - // no need to check if the link should be resolved as a wiki link or a wiki raw link - // just use wiki link here, and it will be redirected to a wiki raw link if necessary - linkBase = ctx.RenderOptions.Links.WikiLink() - } 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}" - // 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.RenderOptions.Links.SrcLink() - } - link, resolved = util.URLJoin(linkBase, link), true - } - if isAnchorFragment && userContentAnchorPrefix != "" { - link, resolved = userContentAnchorPrefix+link[1:], true - } - return link, resolved -} - func shortLinkProcessor(ctx *RenderContext, node *html.Node) { next := node.NextSibling for node != nil && node != next { @@ -116,7 +93,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { name += tail image := false - ext := filepath.Ext(link) + ext := path.Ext(link) switch ext { // fast path: empty string, ignore case "": @@ -139,6 +116,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { if image { link = strings.ReplaceAll(link, " ", "+") } else { + // the hacky wiki name encoding: space to "-" link = strings.ReplaceAll(link, " ", "-") // FIXME: it should support dashes in the link, eg: "the-dash-support.-" } if !strings.Contains(link, "/") { @@ -146,9 +124,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { } } if image { - if !absoluteLink { - link = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), link) - } + link = ctx.RenderHelper.ResolveLink(link, LinkTypeMedia) title := props["title"] if title == "" { title = props["alt"] @@ -174,7 +150,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { childNode.Attr = childNode.Attr[:2] } } else { - link, _ = ResolveLink(ctx, link, "") + link = ctx.RenderHelper.ResolveLink(link, LinkTypeDefault) childNode.Type = html.TextNode childNode.Data = name } diff --git a/modules/markup/html_mention.go b/modules/markup/html_mention.go index 4243eeb20f93b..fffa12e7b740e 100644 --- a/modules/markup/html_mention.go +++ b/modules/markup/html_mention.go @@ -33,7 +33,8 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) { if ok && strings.Contains(mention, "/") { mentionOrgAndTeam := strings.Split(mention, "/") 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.RenderOptions.Links.Prefix(), "org", ctx.RenderOptions.Metas["org"], "teams", mentionOrgAndTeam[1]), mention, "" /*mention*/)) + link := ctx.RenderHelper.ResolveLink(util.URLJoin("org", ctx.RenderOptions.Metas["org"], "teams", mentionOrgAndTeam[1]), LinkTypeApp) + replaceContent(node, loc.Start, loc.End, createLink(ctx, link, mention, "" /*mention*/)) node = node.NextSibling.NextSibling start = 0 continue @@ -43,8 +44,9 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) { } mentionedUsername := mention[1:] - if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx, mentionedUsername) { - replaceContent(node, loc.Start, loc.End, createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), mentionedUsername), mention, "" /*mention*/)) + if DefaultRenderHelperFuncs != nil && DefaultRenderHelperFuncs.IsUsernameMentionable(ctx, mentionedUsername) { + link := ctx.RenderHelper.ResolveLink(mentionedUsername, LinkTypeApp) + replaceContent(node, loc.Start, loc.End, createLink(ctx, link, mention, "" /*mention*/)) node = node.NextSibling.NextSibling start = 0 } else { diff --git a/modules/markup/html_node.go b/modules/markup/html_node.go index a7c323fcba8a9..76cdce9245b9d 100644 --- a/modules/markup/html_node.go +++ b/modules/markup/html_node.go @@ -17,7 +17,7 @@ func visitNodeImg(ctx *RenderContext, img *html.Node) (next *html.Node) { } if IsNonEmptyRelativePath(attr.Val) { - attr.Val = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val) + attr.Val = util.URLJoin(ctx.RenderHelper.ResolveLink(attr.Val, LinkTypeMedia)) // By default, the "" tag should also be clickable, // because frontend use `` to paste the re-scaled image into the markdown, @@ -53,7 +53,7 @@ func visitNodeVideo(ctx *RenderContext, node *html.Node) (next *html.Node) { continue } if IsNonEmptyRelativePath(attr.Val) { - attr.Val = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val) + attr.Val = util.URLJoin(ctx.RenderHelper.ResolveLink(attr.Val, LinkTypeMedia)) } attr.Val = camoHandleLink(attr.Val) node.Attr[i] = attr diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 7366965a9d5ce..f82c6b997fdad 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -9,7 +9,6 @@ import ( "testing" "code.gitea.io/gitea/modules/emoji" - "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" @@ -22,44 +21,13 @@ import ( var ( testRepoOwnerName = "user13" testRepoName = "repo11" - localMetas = map[string]string{ - "user": testRepoOwnerName, - "repo": testRepoName, - } - localWikiMetas = map[string]string{ - "user": testRepoOwnerName, - "repo": testRepoName, - "markupContentMode": "wiki", - } + localMetas = map[string]string{"user": testRepoOwnerName, "repo": testRepoName} ) -type mockRepo struct { - OwnerName string - RepoName string -} - -func (m *mockRepo) GetOwnerName() string { - return m.OwnerName -} - -func (m *mockRepo) GetName() string { - return m.RepoName -} - -func newMockRepo(ownerName, repoName string) gitrepo.Repository { - return &mockRepo{ - OwnerName: ownerName, - RepoName: repoName, - } -} - func TestRender_Commits(t *testing.T) { - setting.AppURL = markup.TestAppURL test := func(input, expected string) { - buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", localMetas, newMockRepo(testRepoOwnerName, testRepoName), markup.Links{ - AbsolutePrefix: true, - Base: markup.TestRepoURL, - }), input) + rctx := markup.NewTestRenderContext(markup.TestAppURL, localMetas).WithRelativePath("a.md") + buffer, err := markup.RenderString(rctx, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } @@ -102,14 +70,10 @@ func TestRender_Commits(t *testing.T) { } func TestRender_CrossReferences(t *testing.T) { - setting.AppURL = markup.TestAppURL - defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() + defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() test := func(input, expected string) { - buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", localMetas, - markup.Links{ - AbsolutePrefix: true, - Base: setting.AppSubURL, - }), input) + rctx := markup.NewTestRenderContext(markup.TestAppURL, localMetas).WithRelativePath("a.md") + buffer, err := markup.RenderString(rctx, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } @@ -141,9 +105,9 @@ func TestRender_CrossReferences(t *testing.T) { func TestRender_links(t *testing.T) { setting.AppURL = markup.TestAppURL - defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() + defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() test := func(input, expected string) { - buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", markup.Links{Base: markup.TestRepoURL}), input) + buffer, err := markup.RenderString(markup.NewTestRenderContext().WithRelativePath("a.md"), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } @@ -246,9 +210,9 @@ func TestRender_links(t *testing.T) { func TestRender_email(t *testing.T) { setting.AppURL = markup.TestAppURL - defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() + defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() test := func(input, expected string) { - res, err := markup.RenderString(markup.NewTestRenderContext("a.md", markup.Links{Base: markup.TestRepoURL}), input) + res, err := markup.RenderString(markup.NewTestRenderContext().WithRelativePath("a.md"), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res)) } @@ -315,7 +279,7 @@ func TestRender_emoji(t *testing.T) { test := func(input, expected string) { expected = strings.ReplaceAll(expected, "&", "&") - buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", markup.Links{Base: markup.TestRepoURL}), input) + buffer, err := markup.RenderString(markup.NewTestRenderContext().WithRelativePath("a.md"), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } @@ -374,188 +338,166 @@ func TestRender_ShortLinks(t *testing.T) { setting.AppURL = markup.TestAppURL tree := util.URLJoin(markup.TestRepoURL, "src", "master") - test := func(input, expected, expectedWiki string) { - buffer, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: markup.TestRepoURL, BranchPath: "master"}), input) + test := func(input, expected string) { + buffer, err := markdown.RenderString(markup.NewTestRenderContext(tree), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) - buffer, err = markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: markup.TestRepoURL}, localWikiMetas), input) - assert.NoError(t, err) - assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer))) } - mediatree := util.URLJoin(markup.TestRepoURL, "media", "master") url := util.URLJoin(tree, "Link") otherURL := util.URLJoin(tree, "Other-Link") encodedURL := util.URLJoin(tree, "Link%3F") - imgurl := util.URLJoin(mediatree, "Link.jpg") - otherImgurl := util.URLJoin(mediatree, "Link+Other.jpg") - encodedImgurl := util.URLJoin(mediatree, "Link+%23.jpg") - notencodedImgurl := util.URLJoin(mediatree, "some", "path", "Link+#.jpg") - urlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Link") - otherURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Other-Link") - encodedURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Link%3F") - imgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "Link.jpg") - otherImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "Link+Other.jpg") - encodedImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "Link+%23.jpg") - notencodedImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "some", "path", "Link+#.jpg") + imgurl := util.URLJoin(tree, "Link.jpg") + otherImgurl := util.URLJoin(tree, "Link+Other.jpg") + encodedImgurl := util.URLJoin(tree, "Link+%23.jpg") + notencodedImgurl := util.URLJoin(tree, "some", "path", "Link+#.jpg") renderableFileURL := util.URLJoin(tree, "markdown_file.md") - renderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "markdown_file.md") unrenderableFileURL := util.URLJoin(tree, "file.zip") - unrenderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "file.zip") favicon := "http://google.com/favicon.ico" test( "[[Link]]", `

Link

`, - `

Link

`) + ) test( "[[Link.-]]", `

Link.-

`, - `

Link.-

`) + ) test( "[[Link.jpg]]", `

Link.jpg

`, - `

Link.jpg

`) + ) test( "[["+favicon+"]]", `

`+favicon+`

`, - `

`+favicon+`

`) + ) test( "[[Name|Link]]", `

Name

`, - `

Name

`) + ) test( "[[Name|Link.jpg]]", `

Name

`, - `

Name

`) + ) test( "[[Name|Link.jpg|alt=AltName]]", `

AltName

`, - `

AltName

`) + ) test( "[[Name|Link.jpg|title=Title]]", `

Title

`, - `

Title

`) + ) test( "[[Name|Link.jpg|alt=AltName|title=Title]]", `

AltName

`, - `

AltName

`) + ) test( "[[Name|Link.jpg|alt=\"AltName\"|title='Title']]", `

AltName

`, - `

AltName

`) + ) test( "[[Name|Link Other.jpg|alt=\"AltName\"|title='Title']]", `

AltName

`, - `

AltName

`) + ) test( "[[Link]] [[Other Link]]", `

Link Other Link

`, - `

Link Other Link

`) + ) test( "[[Link?]]", `

Link?

`, - `

Link?

`) + ) test( "[[Link]] [[Other Link]] [[Link?]]", `

Link Other Link Link?

`, - `

Link Other Link Link?

`) + ) test( "[[markdown_file.md]]", `

markdown_file.md

`, - `

markdown_file.md

`) + ) test( "[[file.zip]]", `

file.zip

`, - `

file.zip

`) + ) test( "[[Link #.jpg]]", `

Link #.jpg

`, - `

Link #.jpg

`) + ) test( "[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]", `

AltName

`, - `

AltName

`) + ) test( "[[some/path/Link #.jpg]]", `

some/path/Link #.jpg

`, - `

some/path/Link #.jpg

`) + ) test( "

[[foobar]]

", `

[[foobar]]

`, - `

[[foobar]]

`) + ) } -func TestRender_RelativeMedias(t *testing.T) { - render := func(input string, isWiki bool, links markup.Links) string { - buffer, err := markdown.RenderString(markup.NewTestRenderContext(links, util.Iif(isWiki, localWikiMetas, localMetas)), input) - assert.NoError(t, err) - return strings.TrimSpace(string(buffer)) - } - - out := render(``, false, markup.Links{Base: "/test-owner/test-repo"}) - assert.Equal(t, ``, out) +/* + func TestRender_RelativeMedias(t *testing.T) { + render := func(input string, isWiki bool, links markup.Links) string { + buffer, err := markdown.RenderString(markup.NewTestRenderContext(links, util.Iif(isWiki, localWikiMetas, localMetas)), input) + assert.NoError(t, err) + return strings.TrimSpace(string(buffer)) + } - out = render(``, true, markup.Links{Base: "/test-owner/test-repo"}) - assert.Equal(t, ``, out) + out := render(``, false, markup.Links{Base: "/test-owner/test-repo"}) + assert.Equal(t, ``, out) - out = render(``, false, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"}) - assert.Equal(t, ``, out) + out = render(``, true, markup.Links{Base: "/test-owner/test-repo"}) + assert.Equal(t, ``, out) - out = render(``, true, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"}) - assert.Equal(t, ``, out) + out = render(``, false, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"}) + assert.Equal(t, ``, out) - out = render(``, true, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"}) - assert.Equal(t, ``, out) + out = render(``, true, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"}) + assert.Equal(t, ``, out) - out = render(`
https://google.com/

` - test("", googleRendered, googleRendered) - - lnk := util.URLJoin(FullURL, "WikiPage") - lnkWiki := util.URLJoin(FullURL, "wiki", "WikiPage") - test("[WikiPage](WikiPage)", - `

WikiPage

`, - `

WikiPage

`) + test("", googleRendered) + test("[Link](Link)", `

Link

`) } func TestRender_Images(t *testing.T) { setting.AppURL = AppURL test := func(input, expected string) { - buffer, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}), input) + buffer, err := markdown.RenderString(markup.NewTestRenderContext(FullURL), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) } @@ -122,12 +85,12 @@ func TestRender_Images(t *testing.T) { `

`+title+`

`) } -func testAnswers(baseURLContent, baseURLImages string) []string { +func testAnswers(baseURL string) []string { return []string{ `

Wiki! Enjoy :)

See commit 65f1bf27bc

Ideas and codes

@@ -135,8 +98,8 @@ func testAnswers(baseURLContent, baseURLImages string) []string {
  • Bezier widget (by @r-lyeh) ocornut/imgui#786
  • Bezier widget (by @r-lyeh) #786
  • Node graph editors https://github.com/ocornut/imgui/issues/306
  • -
  • Memory Editor
  • -
  • Plot var helper
  • +
  • Memory Editor
  • +
  • Plot var helper
  • `, `

    What is Wine Staging?

    @@ -146,14 +109,14 @@ func testAnswers(baseURLContent, baseURLImages string) []string { - - + + - - + +
    images/icon-install.pngInstallationimages/icon-install.pngInstallation
    images/icon-usage.pngUsageimages/icon-usage.pngUsage
    @@ -161,9 +124,9 @@ func testAnswers(baseURLContent, baseURLImages string) []string { `

    Excelsior JET allows you to create native executables for Windows, Linux and Mac OS X.

    1. Package your libGDX application
      -images/1.png
    2. +images/1.png
    3. Perform a test run by hitting the Run! button.
      -images/2.png
    4. +images/2.png

    More tests

    (from https://www.markdownguide.org/extended-syntax/)

    @@ -284,66 +247,20 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno `, } -func TestTotal_RenderWiki(t *testing.T) { - defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)() - defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() - setting.AppURL = AppURL - answers := testAnswers(util.URLJoin(FullURL, "wiki"), util.URLJoin(FullURL, "wiki", "raw")) - for i := 0; i < len(sameCases); i++ { - line, err := markdown.RenderString(markup.NewTestRenderContext( - markup.Links{Base: FullURL}, - newMockRepo(testRepoOwnerName, testRepoName), - localWikiMetas, - ), sameCases[i]) - assert.NoError(t, err) - assert.Equal(t, answers[i], string(line)) - } - - testCases := []string{ - // Guard wiki sidebar: special syntax - `[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`, - // rendered - `

    Guardfile-DSL / Configuring-Guard

    -`, - // special syntax - `[[Name|Link]]`, - // rendered - `

    Name

    -`, - } - - for i := 0; i < len(testCases); i += 2 { - line, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}, localWikiMetas), testCases[i]) - assert.NoError(t, err) - assert.EqualValues(t, testCases[i+1], string(line)) - } -} - func TestTotal_RenderString(t *testing.T) { defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)() - defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() - setting.AppURL = AppURL - answers := testAnswers(util.URLJoin(FullURL, "src", "master"), util.URLJoin(FullURL, "media", "master")) + defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() + markup.Init(&markup.RenderHelperFuncs{ + IsUsernameMentionable: func(ctx context.Context, username string) bool { + return username == "r-lyeh" + }, + }) + answers := testAnswers("") for i := 0; i < len(sameCases); i++ { - line, err := markdown.RenderString(markup.NewTestRenderContext( - markup.Links{ - Base: FullURL, - BranchPath: "master", - }, - newMockRepo(testRepoOwnerName, testRepoName), - localMetas, - ), sameCases[i]) + line, err := markdown.RenderString(markup.NewTestRenderContext(localMetas), sameCases[i]) assert.NoError(t, err) assert.Equal(t, answers[i], string(line)) } - - testCases := []string{} - - for i := 0; i < len(testCases); i += 2 { - line, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}), testCases[i]) - assert.NoError(t, err) - assert.Equal(t, template.HTML(testCases[i+1]), line) - } } func TestRender_RenderParagraphs(t *testing.T) { @@ -609,13 +526,9 @@ mail@domain.com ` input = strings.ReplaceAll(input, "${SPACE}", " ") // replace ${SPACE} with " ", to avoid some editor's auto-trimming cases := []struct { - Links markup.Links - IsWiki bool Expected string }{ - { // 0 - Links: markup.Links{}, - IsWiki: false, + { Expected: `

    space @mention-user
    /just/a/path.bin
    https://example.com/file.bin
    @@ -638,339 +551,14 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    @mention-user test
    #123
    space

    -`, - }, - { // 1 - Links: markup.Links{}, - IsWiki: true, - Expected: `

    space @mention-user
    -/just/a/path.bin
    -https://example.com/file.bin
    -local link
    -remote link
    -local link
    -remote link
    -local image
    -local image
    -local image
    -remote image
    -local image
    -remote link
    -88fc37a3c0...12fc37a3c0 (hash)
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    -88fc37a3c0
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -👍
    -mail@domain.com
    -@mention-user test
    -#123
    -space

    -`, - }, - { // 2 - Links: markup.Links{ - Base: "https://gitea.io/", - }, - IsWiki: false, - Expected: `

    space @mention-user
    -/just/a/path.bin
    -https://example.com/file.bin
    -local link
    -remote link
    -local link
    -remote link
    -local image
    -local image
    -local image
    -remote image
    -local image
    -remote link
    -88fc37a3c0...12fc37a3c0 (hash)
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    -88fc37a3c0
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -👍
    -mail@domain.com
    -@mention-user test
    -#123
    -space

    -`, - }, - { // 3 - Links: markup.Links{ - Base: "https://gitea.io/", - }, - IsWiki: true, - Expected: `

    space @mention-user
    -/just/a/path.bin
    -https://example.com/file.bin
    -local link
    -remote link
    -local link
    -remote link
    -local image
    -local image
    -local image
    -remote image
    -local image
    -remote link
    -88fc37a3c0...12fc37a3c0 (hash)
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    -88fc37a3c0
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -👍
    -mail@domain.com
    -@mention-user test
    -#123
    -space

    -`, - }, - { // 4 - Links: markup.Links{ - Base: "/relative/path", - }, - IsWiki: false, - Expected: `

    space @mention-user
    -/just/a/path.bin
    -https://example.com/file.bin
    -local link
    -remote link
    -local link
    -remote link
    -local image
    -local image
    -local image
    -remote image
    -local image
    -remote link
    -88fc37a3c0...12fc37a3c0 (hash)
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    -88fc37a3c0
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -👍
    -mail@domain.com
    -@mention-user test
    -#123
    -space

    -`, - }, - { // 5 - Links: markup.Links{ - Base: "/relative/path", - }, - IsWiki: true, - Expected: `

    space @mention-user
    -/just/a/path.bin
    -https://example.com/file.bin
    -local link
    -remote link
    -local link
    -remote link
    -local image
    -local image
    -local image
    -remote image
    -local image
    -remote link
    -88fc37a3c0...12fc37a3c0 (hash)
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    -88fc37a3c0
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -👍
    -mail@domain.com
    -@mention-user test
    -#123
    -space

    -`, - }, - { // 6 - Links: markup.Links{ - Base: "/user/repo", - BranchPath: "branch/main", - }, - IsWiki: false, - Expected: `

    space @mention-user
    -/just/a/path.bin
    -https://example.com/file.bin
    -local link
    -remote link
    -local link
    -remote link
    -local image
    -local image
    -local image
    -remote image
    -local image
    -remote link
    -88fc37a3c0...12fc37a3c0 (hash)
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    -88fc37a3c0
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -👍
    -mail@domain.com
    -@mention-user test
    -#123
    -space

    -`, - }, - { // 7 - Links: markup.Links{ - Base: "/relative/path", - BranchPath: "branch/main", - }, - IsWiki: true, - Expected: `

    space @mention-user
    -/just/a/path.bin
    -https://example.com/file.bin
    -local link
    -remote link
    -local link
    -remote link
    -local image
    -local image
    -local image
    -remote image
    -local image
    -remote link
    -88fc37a3c0...12fc37a3c0 (hash)
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    -88fc37a3c0
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -👍
    -mail@domain.com
    -@mention-user test
    -#123
    -space

    -`, - }, - { // 8 - Links: markup.Links{ - Base: "/user/repo", - TreePath: "sub/folder", - }, - IsWiki: false, - Expected: `

    space @mention-user
    -/just/a/path.bin
    -https://example.com/file.bin
    -local link
    -remote link
    -local link
    -remote link
    -local image
    -local image
    -local image
    -remote image
    -local image
    -remote link
    -88fc37a3c0...12fc37a3c0 (hash)
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    -88fc37a3c0
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -👍
    -mail@domain.com
    -@mention-user test
    -#123
    -space

    -`, - }, - { // 9 - Links: markup.Links{ - Base: "/relative/path", - TreePath: "sub/folder", - }, - IsWiki: true, - Expected: `

    space @mention-user
    -/just/a/path.bin
    -https://example.com/file.bin
    -local link
    -remote link
    -local link
    -remote link
    -local image
    -local image
    -local image
    -remote image
    -local image
    -remote link
    -88fc37a3c0...12fc37a3c0 (hash)
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    -88fc37a3c0
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -👍
    -mail@domain.com
    -@mention-user test
    -#123
    -space

    -`, - }, - { // 10 - Links: markup.Links{ - Base: "/user/repo", - BranchPath: "branch/main", - TreePath: "sub/folder", - }, - IsWiki: false, - Expected: `

    space @mention-user
    -/just/a/path.bin
    -https://example.com/file.bin
    -local link
    -remote link
    -local link
    -remote link
    -local image
    -local image
    -local image
    -remote image
    -local image
    -remote link
    -88fc37a3c0...12fc37a3c0 (hash)
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    -88fc37a3c0
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -👍
    -mail@domain.com
    -@mention-user test
    -#123
    -space

    -`, - }, - { // 11 - Links: markup.Links{ - Base: "/relative/path", - BranchPath: "branch/main", - TreePath: "sub/folder", - }, - IsWiki: true, - Expected: `

    space @mention-user
    -/just/a/path.bin
    -https://example.com/file.bin
    -local link
    -remote link
    -local link
    -remote link
    -local image
    -local image
    -local image
    -remote image
    -local image
    -remote link
    -88fc37a3c0...12fc37a3c0 (hash)
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    -88fc37a3c0
    -com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -👍
    -mail@domain.com
    -@mention-user test
    -#123
    -space

    `, }, } defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)() - defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() + defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() for i, c := range cases { - result, err := markdown.RenderString(markup.NewTestRenderContext(c.Links, util.Iif(c.IsWiki, map[string]string{"markupContentMode": "wiki"}, map[string]string{})), input) + result, err := markdown.RenderString(markup.NewTestRenderContext(localMetas), input) assert.NoError(t, err, "Unexpected error in testcase: %v", i) assert.Equal(t, c.Expected, string(result), "Unexpected result in testcase %v", i) } diff --git a/modules/markup/markdown/transform_image.go b/modules/markup/markdown/transform_image.go index c2cbffc1c187b..59942c7c67b0a 100644 --- a/modules/markup/markdown/transform_image.go +++ b/modules/markup/markdown/transform_image.go @@ -21,7 +21,7 @@ func (g *ASTTransformer) transformImage(ctx *markup.RenderContext, v *ast.Image) // Check if the destination is a real link if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) { v.Destination = []byte(giteautil.URLJoin( - ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), + ctx.RenderHelper.ResolveLink("", markup.LinkTypeMedia), strings.TrimLeft(string(v.Destination), "/"), )) } diff --git a/modules/markup/markdown/transform_link.go b/modules/markup/markdown/transform_link.go index 38fbf693ab837..51c2c915d8336 100644 --- a/modules/markup/markdown/transform_link.go +++ b/modules/markup/markdown/transform_link.go @@ -9,8 +9,19 @@ import ( "github.com/yuin/goldmark/ast" ) +func resolveLink(ctx *markup.RenderContext, link, userContentAnchorPrefix string) (result string, resolved bool) { + isAnchorFragment := link != "" && link[0] == '#' + if !isAnchorFragment && !markup.IsFullURLString(link) { + link, resolved = ctx.RenderHelper.ResolveLink(link, markup.LinkTypeDefault), true + } + if isAnchorFragment && userContentAnchorPrefix != "" { + link, resolved = userContentAnchorPrefix+link[1:], true + } + return link, resolved +} + func (g *ASTTransformer) transformLink(ctx *markup.RenderContext, v *ast.Link) { - if link, resolved := markup.ResolveLink(ctx, string(v.Destination), "#user-content-"); resolved { + if link, resolved := resolveLink(ctx, string(v.Destination), "#user-content-"); resolved { v.Destination = []byte(link) } } diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go index cf719cf4e906e..31257351ae823 100644 --- a/modules/markup/orgmode/orgmode.go +++ b/modules/markup/orgmode/orgmode.go @@ -13,7 +13,6 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma/v2/lexers" @@ -142,19 +141,11 @@ func (r *Writer) resolveLink(kind, link string) string { // so we need to try to guess the link kind again here kind = org.RegularLink{URL: link}.Kind() } - - base := r.Ctx.RenderOptions.Links.Base - if r.Ctx.IsMarkupContentWiki() { - base = r.Ctx.RenderOptions.Links.WikiLink() - } else if r.Ctx.RenderOptions.Links.HasBranchInfo() { - base = r.Ctx.RenderOptions.Links.SrcLink() - } - if kind == "image" || kind == "video" { - base = r.Ctx.RenderOptions.Links.ResolveMediaLink(r.Ctx.IsMarkupContentWiki()) + link = r.Ctx.RenderHelper.ResolveLink(link, markup.LinkTypeMedia) + } else { + link = r.Ctx.RenderHelper.ResolveLink(link, markup.LinkTypeDefault) } - - link = util.URLJoin(base, link) } return link } diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go index 4048ae2475ff7..d30df3b1886f5 100644 --- a/modules/markup/orgmode/orgmode_test.go +++ b/modules/markup/orgmode/orgmode_test.go @@ -10,7 +10,6 @@ import ( "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) @@ -22,34 +21,21 @@ func TestMain(m *testing.M) { } func TestRender_StandardLinks(t *testing.T) { - test := func(input, expected string, isWiki bool) { - buffer, err := RenderString(markup.NewTestRenderContext( - markup.Links{ - Base: "/relative-path", - BranchPath: "branch/main", - }, - map[string]string{"markupContentMode": util.Iif(isWiki, "wiki", "")}, - ), input) + test := func(input, expected string) { + buffer, err := RenderString(markup.NewTestRenderContext("/relative-path/media/branch/main/"), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } test("[[https://google.com/]]", - `

    https://google.com/

    `, false) - test("[[WikiPage][The WikiPage Desc]]", - `

    The WikiPage Desc

    `, true) + `

    https://google.com/

    `) test("[[ImageLink.svg][The Image Desc]]", - `

    The Image Desc

    `, false) + `

    The Image Desc

    `) } func TestRender_InternalLinks(t *testing.T) { test := func(input, expected string) { - buffer, err := RenderString(markup.NewTestRenderContext( - markup.Links{ - Base: "/relative-path", - BranchPath: "branch/main", - }, - ), input) + buffer, err := RenderString(markup.NewTestRenderContext("/relative-path/src/branch/main"), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } @@ -66,7 +52,7 @@ func TestRender_InternalLinks(t *testing.T) { func TestRender_Media(t *testing.T) { test := func(input, expected string) { - buffer, err := RenderString(markup.NewTestRenderContext(markup.Links{Base: "./relative-path"}), input) + buffer, err := RenderString(markup.NewTestRenderContext("./relative-path"), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } diff --git a/modules/markup/render.go b/modules/markup/render.go index e251f47fc9107..be75d08c8c323 100644 --- a/modules/markup/render.go +++ b/modules/markup/render.go @@ -11,8 +11,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/markup/internal" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -38,12 +36,14 @@ var RenderBehaviorForTesting struct { // * However, many places render the content without setting "mode" in Metas, all these places used comment line break setting incorrectly ForceHardLineBreak bool - // Gitea will emit some internal attributes for various purposes, these attributes don't affect rendering. + // Gitea will emit some additional attributes for various purposes, these attributes don't affect rendering. // But there are too many hard-coded test cases, to avoid changing all of them again and again, we can disable emitting these internal attributes. - DisableInternalAttributes bool + DisableAdditionalAttributes bool } type RenderOptions struct { + UseAbsoluteLink bool + // relative path from tree root of the branch RelativePath string @@ -51,12 +51,9 @@ type RenderOptions struct { // for file mode, it could be left as empty, and will be detected by file extension in RelativePath MarkupType string - // special link references for rendering, especially when there is a branch/tree path - Links Links - // user&repo, format&style®exp (for external issue pattern), teams&org (for mention) // BranchNameSubURL (for iframe&asciicast) - // markupAllowShortIssuePattern, markupContentMode (wiki) + // markupAllowShortIssuePattern // markdownLineBreakStyle (comment, document) Metas map[string]string @@ -64,13 +61,6 @@ type RenderOptions struct { InStandalonePage bool } -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 @@ -101,7 +91,7 @@ func (ctx *RenderContext) Value(key any) any { var _ context.Context = (*RenderContext)(nil) func NewRenderContext(ctx context.Context) *RenderContext { - return &RenderContext{ctx: ctx} + return &RenderContext{ctx: ctx, RenderHelper: &SimpleRenderHelper{}} } func (ctx *RenderContext) WithMarkupType(typ string) *RenderContext { @@ -114,11 +104,6 @@ func (ctx *RenderContext) WithRelativePath(path string) *RenderContext { 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 @@ -129,48 +114,16 @@ func (ctx *RenderContext) WithInStandalonePage(v bool) *RenderContext { return ctx } -func (ctx *RenderContext) WithGitRepo(r *git.Repository) *RenderContext { - ctx.RenderHelper.gitRepo = r +func (ctx *RenderContext) WithUseAbsoluteLink(v bool) *RenderContext { + ctx.RenderOptions.UseAbsoluteLink = v return ctx } -func (ctx *RenderContext) WithRepoFacade(r gitrepo.Repository) *RenderContext { - ctx.RenderHelper.repoFacade = r +func (ctx *RenderContext) WithHelper(helper RenderHelper) *RenderContext { + ctx.RenderHelper = helper return ctx } -// Cancel runs any cleanup functions that have been registered for this Ctx -func (ctx *RenderContext) Cancel() { - if ctx == nil { - return - } - ctx.RenderHelper.shaExistCache = map[string]bool{} - if ctx.RenderHelper.cancelFn == nil { - return - } - ctx.RenderHelper.cancelFn() -} - -// AddCancel adds the provided fn as a Cleanup for this Ctx -func (ctx *RenderContext) AddCancel(fn func()) { - if ctx == nil { - return - } - oldCancelFn := ctx.RenderHelper.cancelFn - if oldCancelFn == nil { - ctx.RenderHelper.cancelFn = fn - return - } - ctx.RenderHelper.cancelFn = func() { - defer oldCancelFn() - fn() - } -} - -func (ctx *RenderContext) IsMarkupContentWiki() bool { - return ctx.RenderOptions.Metas != nil && ctx.RenderOptions.Metas["markupContentMode"] == "wiki" -} - // Render renders markup file to HTML with all specific handling stuff. func Render(ctx *RenderContext, input io.Reader, output io.Writer) error { if ctx.RenderOptions.MarkupType == "" && ctx.RenderOptions.RelativePath != "" { @@ -237,6 +190,10 @@ func pipes() (io.ReadCloser, io.WriteCloser, func()) { } func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error { + if ctx.RenderHelper != nil { + defer ctx.RenderHelper.CleanUp() + } + finalProcessor := ctx.RenderInternal.Init(output) defer finalProcessor.Close() @@ -278,11 +235,8 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr } // Init initializes the render global variables -func Init(ph *ProcessorHelper) { - if ph != nil { - DefaultProcessorHelper = *ph - } - +func Init(renderHelpFuncs *RenderHelperFuncs) { + DefaultRenderHelperFuncs = renderHelpFuncs if len(setting.Markdown.CustomURLSchemes) > 0 { CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes) } @@ -300,23 +254,38 @@ func ComposeSimpleDocumentMetas() map[string]string { return map[string]string{"markdownLineBreakStyle": "document"} } +type TestRenderHelper struct { + ctx *RenderContext + BaseLink string +} + +func (r *TestRenderHelper) CleanUp() {} + +func (r *TestRenderHelper) IsCommitIDExisting(commitID string) bool { + return strings.HasPrefix(commitID, "65f1bf2") //|| strings.HasPrefix(commitID, "88fc37a") +} + +func (r *TestRenderHelper) ResolveLink(link string, likeType LinkType) string { + return r.ctx.ResolveLinkRelative(r.BaseLink, "", link) +} + +var _ RenderHelper = (*TestRenderHelper)(nil) + // 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 { +// It accepts string (BaseLink), map[string]string (Metas) +func NewTestRenderContext(baseLinkOrMetas ...any) *RenderContext { if !setting.IsInTesting { panic("NewTestRenderContext should only be used in testing") } - ctx := NewRenderContext(context.Background()) - for _, v := range a { + helper := &TestRenderHelper{} + ctx := NewRenderContext(context.Background()).WithHelper(helper) + helper.ctx = ctx + for _, v := range baseLinkOrMetas { switch v := v.(type) { case string: - ctx = ctx.WithRelativePath(v) - case Links: - ctx = ctx.WithLinks(v) + helper.BaseLink = v case map[string]string: ctx = ctx.WithMetas(v) - case gitrepo.Repository: - ctx = ctx.WithRepoFacade(v) default: panic(fmt.Sprintf("unknown type %T", v)) } diff --git a/modules/markup/render_helper.go b/modules/markup/render_helper.go index c1613261bdd4d..d4b2858931b62 100644 --- a/modules/markup/render_helper.go +++ b/modules/markup/render_helper.go @@ -6,16 +6,53 @@ package markup import ( "context" "html/template" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" +) + +type LinkType string + +const ( + LinkTypeApp LinkType = "app" // the link is relative to the AppSubURL + LinkTypeDefault LinkType = "default" // the link is relative to the default base (eg: repo link, or current ref tree path) + LinkTypeMedia LinkType = "media" // the link should be used to access media files (images, videos) + LinkTypeRaw LinkType = "raw" // not really useful, mainly for environment GITEA_PREFIX_RAW for external renders ) -// ProcessorHelper is a helper for the rendering processors (it could be renamed to RenderHelper in the future). -// The main purpose of this helper is to decouple some functions which are not directly available in this package. -type ProcessorHelper struct { - IsUsernameMentionable func(ctx context.Context, username string) bool +type RenderHelper interface { + CleanUp() - ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute + // TODO: such dependency is not ideal. We should decouple the processors step by step. + // It should make the render choose different processors for different purposes, + // but not make processors to guess "is it rendering a comment or a wiki". + IsCommitIDExisting(commitID string) bool + ResolveLink(link string, likeType LinkType) string +} + +// RenderHelperFuncs is used to decouple cycle-import +// At the moment there are different packages: +// modules/markup: basic markup rendering +// models/renderhelper: need to access models and git repo, and models/issues needs it +// services/markup: some real helper functions could only be provided here because it needs to access various services & templates +type RenderHelperFuncs struct { + IsUsernameMentionable func(ctx context.Context, username string) bool RenderRepoFileCodePreview func(ctx context.Context, options RenderCodePreviewOptions) (template.HTML, error) } -var DefaultProcessorHelper ProcessorHelper +var DefaultRenderHelperFuncs *RenderHelperFuncs + +type SimpleRenderHelper struct{} + +func (r *SimpleRenderHelper) CleanUp() {} + +func (r *SimpleRenderHelper) IsCommitIDExisting(commitID string) bool { + return false +} + +func (r *SimpleRenderHelper) ResolveLink(link string, likeType LinkType) string { + return util.URLJoin("/"+setting.AppSubURL, ".", link) +} + +var _ RenderHelper = (*SimpleRenderHelper)(nil) diff --git a/modules/markup/render_link.go b/modules/markup/render_link.go new file mode 100644 index 0000000000000..d046d25aa655d --- /dev/null +++ b/modules/markup/render_link.go @@ -0,0 +1,37 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package markup + +import ( + "strings" + + "code.gitea.io/gitea/modules/httplib" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" +) + +func isAbsolute(link string) bool { + p1 := strings.IndexByte(link, ':') + p2 := strings.IndexByte(link, '/') + return (p1 != -1 && p1 < p2) /* https://xxx */ || (p1 != -1 && p2 == -1) /* mailto:... */ +} + +func (ctx *RenderContext) ResolveLinkRelative(base, cur, link string) (finalLink string) { + if isAbsolute(link) { + return link + } + if strings.HasPrefix(link, "/") { + finalLink = util.URLJoin(base, "./", link) + } else { + finalLink = util.URLJoin(base, "./", cur, link) + } + if ctx.RenderOptions.UseAbsoluteLink { + finalLink = httplib.MakeAbsoluteURL(ctx, finalLink) + } + return finalLink +} + +func (ctx *RenderContext) ResolveLinkApp(link string) string { + return ctx.ResolveLinkRelative("/"+setting.AppSubURL, "", link) +} diff --git a/modules/markup/render_links.go b/modules/markup/render_links.go deleted file mode 100644 index c8339d8f8b3f2..0000000000000 --- a/modules/markup/render_links.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package markup - -import ( - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" -) - -type Links struct { - AbsolutePrefix bool // add absolute URL prefix to auto-resolved links like "#issue", but not for pre-provided links and medias - Base string // base prefix for pre-provided links and medias (images, videos), usually it is the path to the repo - BranchPath string // actually it is the ref path, eg: "branch/features/feat-12", "tag/v1.0" - TreePath string // the dir of the file, eg: "doc" if the file "doc/CHANGE.md" is being rendered -} - -func (l *Links) Prefix() string { - if l.AbsolutePrefix { - return setting.AppURL - } - return setting.AppSubURL -} - -func (l *Links) HasBranchInfo() bool { - return l.BranchPath != "" -} - -func (l *Links) SrcLink() string { - return util.URLJoin(l.Base, "src", l.BranchPath, l.TreePath) -} - -func (l *Links) MediaLink() string { - return util.URLJoin(l.Base, "media", l.BranchPath, l.TreePath) -} - -func (l *Links) RawLink() string { - return util.URLJoin(l.Base, "raw", l.BranchPath, l.TreePath) -} - -func (l *Links) WikiLink() string { - return util.URLJoin(l.Base, "wiki") -} - -func (l *Links) WikiRawLink() string { - return util.URLJoin(l.Base, "wiki/raw") -} - -func (l *Links) ResolveMediaLink(isWiki bool) string { - if isWiki { - return l.WikiRawLink() - } else if l.HasBranchInfo() { - return l.MediaLink() - } - return l.Base -} diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index 9b993de7b3eb9..35f90eb46cbd9 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -6,7 +6,7 @@ package markup import ( "bytes" "io" - "path/filepath" + "path" "strings" "code.gitea.io/gitea/modules/setting" @@ -55,7 +55,7 @@ func RegisterRenderer(renderer Renderer) { // GetRendererByFileName get renderer by filename func GetRendererByFileName(filename string) Renderer { - extension := strings.ToLower(filepath.Ext(filename)) + extension := strings.ToLower(path.Ext(filename)) return extRenderers[extension] } diff --git a/modules/markup/sanitizer_default.go b/modules/markup/sanitizer_default.go index 0fa54efd45ef9..6df9ac9baebaa 100644 --- a/modules/markup/sanitizer_default.go +++ b/modules/markup/sanitizer_default.go @@ -26,6 +26,9 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy { policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input") policy.AllowAttrs("checked", "disabled", "data-source-position").OnElements("input") + // Chroma always uses 1-2 letters for style names, we could tolerate it at the moment + policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(\w{1,2} )*\w{1,2}$`)).OnElements("span") + // Custom URL-Schemes if len(setting.Markdown.CustomURLSchemes) > 0 { policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...) diff --git a/modules/markup/sanitizer_default_test.go b/modules/markup/sanitizer_default_test.go index c5c43695ea08a..e6fbae50567f4 100644 --- a/modules/markup/sanitizer_default_test.go +++ b/modules/markup/sanitizer_default_test.go @@ -19,6 +19,7 @@ func TestSanitizer(t *testing.T) { // Code highlighting class ``, ``, ``, ``, + ``, ``, // Input checkbox ``, ``, diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go index cf6d839cbf686..d0ca8f8e05016 100644 --- a/modules/templates/util_render_test.go +++ b/modules/templates/util_render_test.go @@ -59,7 +59,7 @@ func TestMain(m *testing.M) { if err := git.InitSimple(context.Background()); err != nil { log.Fatal("git init failed, err: %v", err) } - markup.Init(&markup.ProcessorHelper{ + markup.Init(&markup.RenderHelperFuncs{ IsUsernameMentionable: func(ctx context.Context, username string) bool { return username == "mention-user" }, @@ -74,7 +74,7 @@ func newTestRenderUtils() *RenderUtils { } func TestRenderCommitBody(t *testing.T) { - defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() + defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() type args struct { msg string } @@ -145,7 +145,7 @@ func TestRenderCommitMessageLinkSubject(t *testing.T) { } func TestRenderIssueTitle(t *testing.T) { - defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() + defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() expected := ` space @mention-user /just/a/path.bin https://example.com/file.bin @@ -172,7 +172,7 @@ mail@domain.com } func TestRenderMarkdownToHtml(t *testing.T) { - defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() + defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() expected := `

    space @mention-user
    /just/a/path.bin https://example.com/file.bin diff --git a/routers/api/v1/misc/markup_test.go b/routers/api/v1/misc/markup_test.go index 6b8c09034ac72..921e7b2750e77 100644 --- a/routers/api/v1/misc/markup_test.go +++ b/routers/api/v1/misc/markup_test.go @@ -25,7 +25,7 @@ const AppURL = "http://localhost:3000/" func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expectedBody string, expectedCode int) { setting.AppURL = AppURL - defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() + defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() context := "/gogits/gogs" if !wiki { context += path.Join("/src/branch/main", path.Dir(filePath)) @@ -46,7 +46,7 @@ func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expe } func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody string, responseCode int) { - defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() + defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() setting.AppURL = AppURL context := "/gogits/gogs" if !wiki { @@ -67,7 +67,7 @@ func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody } func TestAPI_RenderGFM(t *testing.T) { - markup.Init(&markup.ProcessorHelper{ + markup.Init(&markup.RenderHelperFuncs{ IsUsernameMentionable: func(ctx go_context.Context, username string) bool { return username == "r-lyeh" }, @@ -182,6 +182,7 @@ var simpleCases = []string{ func TestAPI_RenderSimple(t *testing.T) { setting.AppURL = AppURL + markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true options := api.MarkdownOption{ Mode: "markdown", Text: "", @@ -199,6 +200,7 @@ func TestAPI_RenderSimple(t *testing.T) { func TestAPI_RenderRaw(t *testing.T) { setting.AppURL = AppURL + markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown") for i := 0; i < len(simpleCases); i += 2 { ctx.Req.Body = io.NopCloser(strings.NewReader(simpleCases[i])) diff --git a/routers/common/markup.go b/routers/common/markup.go index 59f338c2bccc6..71facc280f84f 100644 --- a/routers/common/markup.go +++ b/routers/common/markup.go @@ -11,6 +11,8 @@ import ( "path" "strings" + "code.gitea.io/gitea/models/renderhelper" + "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" @@ -20,7 +22,7 @@ import ( ) // RenderMarkup renders markup text for the /markup and /markdown endpoints -func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPathContext, filePath string) { +func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, urlPathContext, filePath string) { // urlPathContext format is "/subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}" // filePath is the path of the file to render if the end user is trying to preview a repo file (mode == "file") // filePath will be used as RenderContext.RelativePath @@ -28,60 +30,61 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPa // for example, when previewing file "/gitea/owner/repo/src/branch/features/feat-123/doc/CHANGE.md", then filePath is "doc/CHANGE.md" // and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc" - renderCtx := markup.NewRenderContext(ctx). - WithLinks(markup.Links{AbsolutePrefix: true}). - WithMarkupType(markdown.MarkupName) - - if urlPathContext != "" { - renderCtx.RenderOptions.Links.Base = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext) - } - if mode == "" || mode == "markdown" { // raw markdown doesn't need any special handling - if err := markdown.RenderRaw(renderCtx, strings.NewReader(text), ctx.Resp); err != nil { + baseLink := urlPathContext + if baseLink == "" { + baseLink = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext) + } + rctx := renderhelper.NewRenderContextSimpleDocument(ctx, baseLink).WithUseAbsoluteLink(true). + WithMarkupType(markdown.MarkupName) + if err := markdown.RenderRaw(rctx, strings.NewReader(text), ctx.Resp); err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) } return } + + var repo *repo.Repository + if ctxRepo != nil { + repo = ctxRepo.Repository + } + var repoOwnerName, repoName, refPath, treePath string + repoLinkPath := strings.TrimPrefix(urlPathContext, setting.AppSubURL+"/") + fields := strings.SplitN(repoLinkPath, "/", 5) + if len(fields) == 5 && fields[2] == "src" && (fields[3] == "branch" || fields[3] == "commit" || fields[3] == "tag") { + // absolute base prefix is something like "https://host/subpath/{user}/{repo}" + repoOwnerName, repoName = fields[0], fields[1] + treePath = path.Dir(filePath) // it is "doc" if filePath is "doc/CHANGE.md" + refPath = strings.Join(fields[3:], "/") // it is "branch/features/feat-12/doc" + refPath = strings.TrimSuffix(refPath, "/"+treePath) // now we get the correct branch path: "branch/features/feat-12" + } else if fields = strings.SplitN(repoLinkPath, "/", 3); len(fields) == 2 { + repoOwnerName, repoName = fields[0], fields[1] + } + + var rctx *markup.RenderContext switch mode { - case "gfm": // legacy mode, do nothing + case "gfm": // legacy mode + rctx = renderhelper.NewRenderContextRepoFile(ctx, repo, renderhelper.RepoFileOptions{ + DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName, + CurrentRefPath: refPath, CurrentTreePath: treePath, + }) + rctx = rctx.WithMarkupType(markdown.MarkupName) case "comment": - renderCtx = renderCtx.WithMetas(map[string]string{"markdownLineBreakStyle": "comment"}) + rctx = renderhelper.NewRenderContextRepoComment(ctx, repo, renderhelper.RepoCommentOptions{DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName}) case "wiki": - renderCtx = renderCtx.WithMetas(map[string]string{"markdownLineBreakStyle": "document", "markupContentMode": "wiki"}) + rctx = renderhelper.NewRenderContextRepoWiki(ctx, repo, renderhelper.RepoWikiOptions{DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName}) case "file": - // render the repo file content by its extension - renderCtx = renderCtx.WithMetas(map[string]string{"markdownLineBreakStyle": "document"}). - WithMarkupType(""). - WithRelativePath(filePath) + rctx = renderhelper.NewRenderContextRepoFile(ctx, repo, renderhelper.RepoFileOptions{ + DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName, + CurrentRefPath: refPath, CurrentTreePath: treePath, + }) + rctx = rctx.WithMarkupType("").WithRelativePath(filePath) // render the repo file content by its extension default: ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode)) return } - - fields := strings.SplitN(strings.TrimPrefix(urlPathContext, setting.AppSubURL+"/"), "/", 5) - if len(fields) == 5 && fields[2] == "src" && (fields[3] == "branch" || fields[3] == "commit" || fields[3] == "tag") { - // absolute base prefix is something like "https://host/subpath/{user}/{repo}" - absoluteBasePrefix := fmt.Sprintf("%s%s/%s", httplib.GuessCurrentAppURL(ctx), fields[0], fields[1]) - - fileDir := path.Dir(filePath) // it is "doc" if filePath is "doc/CHANGE.md" - refPath := strings.Join(fields[3:], "/") // it is "branch/features/feat-12/doc" - refPath = strings.TrimSuffix(refPath, "/"+fileDir) // now we get the correct branch path: "branch/features/feat-12" - - renderCtx = renderCtx.WithLinks(markup.Links{AbsolutePrefix: true, Base: absoluteBasePrefix, BranchPath: refPath, TreePath: fileDir}) - } - - if repo != nil && repo.Repository != nil { - renderCtx = renderCtx.WithRepoFacade(repo.Repository) - if mode == "file" { - renderCtx = renderCtx.WithMetas(repo.Repository.ComposeDocumentMetas(ctx)) - } else if mode == "wiki" { - renderCtx = renderCtx.WithMetas(repo.Repository.ComposeWikiMetas(ctx)) - } else if mode == "comment" { - renderCtx = renderCtx.WithMetas(repo.Repository.ComposeMetas(ctx)) - } - } - if err := markup.Render(renderCtx, strings.NewReader(text), ctx.Resp); err != nil { + rctx = rctx.WithUseAbsoluteLink(true) + if err := markup.Render(rctx, strings.NewReader(text), ctx.Resp); err != nil { if errors.Is(err, util.ErrInvalidArgument) { ctx.Error(http.StatusUnprocessableEntity, err.Error()) } else { diff --git a/routers/web/feed/convert.go b/routers/web/feed/convert.go index fad7dfdf5e0ee..050727f12ac15 100644 --- a/routers/web/feed/convert.go +++ b/routers/web/feed/convert.go @@ -13,8 +13,8 @@ import ( "strings" activities_model "code.gitea.io/gitea/models/activities" + "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" @@ -51,19 +51,16 @@ func toReleaseLink(ctx *context.Context, act *activities_model.Action) string { // renderMarkdown creates a minimal markdown render context from an action. // If rendering fails, the original markdown text is returned func renderMarkdown(ctx *context.Context, act *activities_model.Action, content string) template.HTML { - markdownCtx := markup.NewRenderContext(ctx). - WithLinks(markup.Links{ - Base: act.GetRepoLink(ctx), - }). - WithMetas(map[string]string{ // FIXME: not right here, it should use issue to compose the metas - "user": act.GetRepoUserName(ctx), - "repo": act.GetRepoName(ctx), - }) - markdown, err := markdown.RenderString(markdownCtx, content) + act.LoadRepo(ctx) + if act.Repo == nil { + return "" + } + rctx := renderhelper.NewRenderContextRepoComment(ctx, act.Repo).WithUseAbsoluteLink(true) + rendered, err := markdown.RenderString(rctx, content) // no need to sanitize, it's already done in render if err != nil { - return templates.SanitizeHTML(content) // old code did so: use SanitizeHTML to render in tmpl + return "" } - return markdown + return rendered } // feedActionsToFeedItems convert gitea's Action feed to feeds Item @@ -294,12 +291,8 @@ func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release) ( } link := &feeds.Link{Href: rel.HTMLURL()} - content, err = markdown.RenderString(markup.NewRenderContext(ctx). - WithRepoFacade(rel.Repo). - WithLinks(markup.Links{ - Base: rel.Repo.Link(), - }). - WithMetas(rel.Repo.ComposeMetas(ctx)), + rctx := renderhelper.NewRenderContextRepoComment(ctx, rel.Repo).WithUseAbsoluteLink(true) + content, err = markdown.RenderString(rctx, rel.Note) if err != nil { return nil, err diff --git a/routers/web/feed/profile.go b/routers/web/feed/profile.go index 7c4864b45ef15..47de7c089def1 100644 --- a/routers/web/feed/profile.go +++ b/routers/web/feed/profile.go @@ -7,7 +7,7 @@ import ( "time" activities_model "code.gitea.io/gitea/models/activities" - "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/models/renderhelper" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/services/context" @@ -41,9 +41,8 @@ func showUserFeed(ctx *context.Context, formatType string) { return } - ctxUserDescription, err := markdown.RenderString(markup.NewRenderContext(ctx). - WithLinks(markup.Links{Base: ctx.ContextUser.HTMLURL()}). - WithMetas(markup.ComposeSimpleDocumentMetas()), + rctx := renderhelper.NewRenderContextSimpleDocument(ctx, ctx.ContextUser.HTMLURL()) + ctxUserDescription, err := markdown.RenderString(rctx, ctx.ContextUser.Description) if err != nil { ctx.ServerError("RenderString", err) diff --git a/routers/web/org/home.go b/routers/web/org/home.go index d0ac82b1b094e..bfcd3572ce99f 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -6,18 +6,16 @@ package org import ( "fmt" "net/http" - "path" "strings" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" + "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" shared_user "code.gitea.io/gitea/routers/web/shared/user" "code.gitea.io/gitea/services/context" ) @@ -180,16 +178,8 @@ func prepareOrgProfileReadme(ctx *context.Context, viewRepositories bool) bool { if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil { log.Error("failed to GetBlobContent: %v", err) } else { - if profileContent, err := markdown.RenderString(markup.NewRenderContext(ctx). - WithGitRepo(profileGitRepo). - WithLinks(markup.Links{ - // Pass repo link to markdown render for the full link of media elements. - // The profile of default branch would be shown. - Base: profileDbRepo.Link(), - BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)), - }). - WithMetas(markup.ComposeSimpleDocumentMetas()), - bytes); err != nil { + rctx := renderhelper.NewRenderContextRepoFile(ctx, profileDbRepo) + if profileContent, err := markdown.RenderString(rctx, bytes); err != nil { log.Error("failed to RenderString: %v", err) } else { ctx.Data["ProfileReadme"] = profileContent diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 87b1f9019a00f..0be9689c3f5e3 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -15,6 +15,7 @@ import ( asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" + "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -392,15 +393,8 @@ func Diff(ctx *context.Context) { if err == nil { ctx.Data["NoteCommit"] = note.Commit ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit) - ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(markup.NewRenderContext(ctx). - WithLinks(markup.Links{ - Base: ctx.Repo.RepoLink, - BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)), - }). - WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)). - WithGitRepo(ctx.Repo.GitRepo). - WithRepoFacade(ctx.Repo.Repository), - template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{})))) + rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{CurrentRefPath: path.Join("commit", util.PathEscapeSegments(commitID))}) + ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(rctx, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{})))) if err != nil { ctx.ServerError("RenderCommitMessage", err) return diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index d52dbf393945e..415f34d1fbe40 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -18,12 +18,12 @@ import ( "code.gitea.io/gitea/models/organization" access_model "code.gitea.io/gitea/models/perm/access" project_model "code.gitea.io/gitea/models/project" + "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/optional" api "code.gitea.io/gitea/modules/structs" @@ -366,12 +366,8 @@ func UpdateIssueContent(ctx *context.Context) { } } - content, err := markdown.RenderString(markup.NewRenderContext(ctx). - WithLinks(markup.Links{Base: ctx.FormString("context")}). - WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)). - WithGitRepo(ctx.Repo.GitRepo). - WithRepoFacade(ctx.Repo.Repository), - issue.Content) + rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository) + content, err := markdown.RenderString(rctx, issue.Content) if err != nil { ctx.ServerError("RenderString", err) return diff --git a/routers/web/repo/issue_comment.go b/routers/web/repo/issue_comment.go index 33105d67ca640..6b7b29d9d71bc 100644 --- a/routers/web/repo/issue_comment.go +++ b/routers/web/repo/issue_comment.go @@ -10,10 +10,10 @@ import ( "net/http" issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/renderhelper" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -267,12 +267,8 @@ func UpdateCommentContent(ctx *context.Context) { var renderedContent template.HTML if comment.Content != "" { - renderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx). - WithLinks(markup.Links{Base: ctx.FormString("context")}). - WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)). - WithGitRepo(ctx.Repo.GitRepo). - WithRepoFacade(ctx.Repo.Repository), - comment.Content) + rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository) + renderedContent, err = markdown.RenderString(rctx, comment.Content) if err != nil { ctx.ServerError("RenderString", err) return diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index 55d36cfefa781..54ff36db4925b 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -19,6 +19,7 @@ import ( access_model "code.gitea.io/gitea/models/perm/access" project_model "code.gitea.io/gitea/models/project" pull_model "code.gitea.io/gitea/models/pull" + "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -359,12 +360,8 @@ func ViewIssue(ctx *context.Context) { } } ctx.Data["IssueWatch"] = iw - issue.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx). - WithLinks(markup.Links{Base: ctx.Repo.RepoLink}). - WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)). - WithGitRepo(ctx.Repo.GitRepo). - WithRepoFacade(ctx.Repo.Repository), - issue.Content) + rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository) + issue.RenderedContent, err = markdown.RenderString(rctx, issue.Content) if err != nil { ctx.ServerError("RenderString", err) return @@ -464,14 +461,8 @@ func ViewIssue(ctx *context.Context) { comment.Issue = issue if comment.Type == issues_model.CommentTypeComment || comment.Type == issues_model.CommentTypeReview { - comment.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx). - WithLinks(markup.Links{ - Base: ctx.Repo.RepoLink, - }). - WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)). - WithGitRepo(ctx.Repo.GitRepo). - WithRepoFacade(ctx.Repo.Repository), - comment.Content) + rctx = renderhelper.NewRenderContextRepoComment(ctx, repo) + comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content) if err != nil { ctx.ServerError("RenderString", err) return @@ -546,12 +537,8 @@ func ViewIssue(ctx *context.Context) { } } } else if comment.Type.HasContentSupport() { - comment.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx). - WithLinks(markup.Links{Base: ctx.Repo.RepoLink}). - WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)). - WithGitRepo(ctx.Repo.GitRepo). - WithRepoFacade(ctx.Repo.Repository), - comment.Content) + rctx = renderhelper.NewRenderContextRepoComment(ctx, repo) + comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content) if err != nil { ctx.ServerError("RenderString", err) return diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go index 7361fe66bcc57..3afdcfad8bfd1 100644 --- a/routers/web/repo/milestone.go +++ b/routers/web/repo/milestone.go @@ -10,8 +10,8 @@ import ( "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/renderhelper" "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" @@ -79,12 +79,8 @@ func Milestones(ctx *context.Context) { } } for _, m := range miles { - m.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx). - WithLinks(markup.Links{Base: ctx.Repo.RepoLink}). - WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)). - WithGitRepo(ctx.Repo.GitRepo). - WithRepoFacade(ctx.Repo.Repository), - m.Content) + rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository) + m.RenderedContent, err = markdown.RenderString(rctx, m.Content) if err != nil { ctx.ServerError("RenderString", err) return @@ -265,12 +261,8 @@ func MilestoneIssuesAndPulls(ctx *context.Context) { return } - milestone.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx). - WithLinks(markup.Links{Base: ctx.Repo.RepoLink}). - WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)). - WithGitRepo(ctx.Repo.GitRepo). - WithRepoFacade(ctx.Repo.Repository), - milestone.Content) + rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository) + milestone.RenderedContent, err = markdown.RenderString(rctx, milestone.Content) if err != nil { ctx.ServerError("RenderString", err) return diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index cce13df3be876..799ce3ad804c3 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -13,11 +13,11 @@ import ( issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/perm" project_model "code.gitea.io/gitea/models/project" + "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" @@ -92,12 +92,8 @@ func Projects(ctx *context.Context) { } for i := range projects { - projects[i].RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx). - WithLinks(markup.Links{Base: ctx.Repo.RepoLink}). - WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)). - WithGitRepo(ctx.Repo.GitRepo). - WithRepoFacade(ctx.Repo.Repository), - projects[i].Description) + rctx := renderhelper.NewRenderContextRepoComment(ctx, repo) + projects[i].RenderedContent, err = markdown.RenderString(rctx, projects[i].Description) if err != nil { ctx.ServerError("RenderString", err) return @@ -422,12 +418,8 @@ func ViewProject(ctx *context.Context) { ctx.Data["SelectLabels"] = selectLabels ctx.Data["AssigneeID"] = assigneeID - project.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx). - WithLinks(markup.Links{Base: ctx.Repo.RepoLink}). - WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)). - WithGitRepo(ctx.Repo.GitRepo). - WithRepoFacade(ctx.Repo.Repository), - project.Description) + rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository) + project.RenderedContent, err = markdown.RenderString(rctx, project.Description) if err != nil { ctx.ServerError("RenderString", err) return diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 1b5305a90db5f..96c512dd3df95 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -13,13 +13,13 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" + "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" @@ -114,12 +114,8 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions) cacheUsers[r.PublisherID] = r.Publisher } - r.RenderedNote, err = markdown.RenderString(markup.NewRenderContext(ctx). - WithLinks(markup.Links{Base: ctx.Repo.RepoLink}). - WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)). - WithGitRepo(ctx.Repo.GitRepo). - WithRepoFacade(ctx.Repo.Repository), - r.Note) + rctx := renderhelper.NewRenderContextRepoComment(ctx, r.Repo) + r.RenderedNote, err = markdown.RenderString(rctx, r.Note) if err != nil { return nil, err } diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go index c551e44f46fc7..856425ae355c5 100644 --- a/routers/web/repo/render.go +++ b/routers/web/repo/render.go @@ -9,6 +9,7 @@ import ( "net/http" "path" + "code.gitea.io/gitea/models/renderhelper" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" @@ -56,17 +57,12 @@ func RenderFile(ctx *context.Context) { return } - err = markup.Render(markup.NewRenderContext(ctx). - WithRelativePath(ctx.Repo.TreePath). - WithLinks(markup.Links{ - Base: ctx.Repo.RepoLink, - BranchPath: ctx.Repo.BranchNameSubURL(), - TreePath: path.Dir(ctx.Repo.TreePath), - }). - WithMetas(ctx.Repo.Repository.ComposeDocumentMetas(ctx)). - WithGitRepo(ctx.Repo.GitRepo). - WithInStandalonePage(true), - rd, ctx.Resp) + rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{ + CurrentRefPath: ctx.Repo.BranchNameSubURL(), + CurrentTreePath: path.Dir(ctx.Repo.TreePath), + }).WithRelativePath(ctx.Repo.TreePath).WithInStandalonePage(true) + + err = markup.Render(rctx, rd, ctx.Resp) if err != nil { log.Error("Failed to render file %q: %v", ctx.Repo.TreePath, err) http.Error(ctx.Resp, "Failed to render file", http.StatusInternalServerError) diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index aacd7de6b1782..e603234522c62 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -31,6 +31,7 @@ import ( git_model "code.gitea.io/gitea/models/git" issue_model "code.gitea.io/gitea/models/issues" access_model "code.gitea.io/gitea/models/perm/access" + "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -310,17 +311,14 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr ctx.Data["IsMarkup"] = true ctx.Data["MarkupType"] = markupType - ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, markup.NewRenderContext(ctx). + rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{ + CurrentRefPath: ctx.Repo.BranchNameSubURL(), + CurrentTreePath: path.Join(ctx.Repo.TreePath, subfolder), + }). WithMarkupType(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). - WithLinks(markup.Links{ - Base: ctx.Repo.RepoLink, - BranchPath: ctx.Repo.BranchNameSubURL(), - TreePath: path.Join(ctx.Repo.TreePath, subfolder), - }). - WithMetas(ctx.Repo.Repository.ComposeDocumentMetas(ctx)). - WithGitRepo(ctx.Repo.GitRepo), - rd) + WithRelativePath(path.Join(ctx.Repo.TreePath, subfolder, readmeFile.Name())) // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path). + + ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd) if err != nil { log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err) delete(ctx.Data, "IsMarkup") @@ -513,17 +511,14 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { ctx.Data["MarkupType"] = markupType metas := ctx.Repo.Repository.ComposeDocumentMetas(ctx) metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL() - ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, markup.NewRenderContext(ctx). + rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{ + CurrentRefPath: ctx.Repo.BranchNameSubURL(), + CurrentTreePath: path.Dir(ctx.Repo.TreePath), + }). WithMarkupType(markupType). - WithRelativePath(ctx.Repo.TreePath). - WithLinks(markup.Links{ - Base: ctx.Repo.RepoLink, - BranchPath: ctx.Repo.BranchNameSubURL(), - TreePath: path.Dir(ctx.Repo.TreePath), - }). - WithMetas(metas). - WithGitRepo(ctx.Repo.GitRepo), - rd) + WithRelativePath(ctx.Repo.TreePath) + + ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd) if err != nil { ctx.ServerError("Render", err) return @@ -604,17 +599,15 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { rd := io.MultiReader(bytes.NewReader(buf), dataRc) ctx.Data["IsMarkup"] = true ctx.Data["MarkupType"] = markupType - ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, markup.NewRenderContext(ctx). + + rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{ + CurrentRefPath: ctx.Repo.BranchNameSubURL(), + CurrentTreePath: path.Dir(ctx.Repo.TreePath), + }). WithMarkupType(markupType). - WithRelativePath(ctx.Repo.TreePath). - WithLinks(markup.Links{ - Base: ctx.Repo.RepoLink, - BranchPath: ctx.Repo.BranchNameSubURL(), - TreePath: path.Dir(ctx.Repo.TreePath), - }). - WithMetas(ctx.Repo.Repository.ComposeDocumentMetas(ctx)). - WithGitRepo(ctx.Repo.GitRepo), - rd) + WithRelativePath(ctx.Repo.TreePath) + + ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd) if err != nil { ctx.ServerError("Render", err) return diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index eda3320ff0d27..b2dd846fafd62 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -14,6 +14,7 @@ import ( "strings" git_model "code.gitea.io/gitea/models/git" + "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/base" @@ -288,11 +289,9 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { footerContent = data } - rctx := markup.NewRenderContext(ctx). - WithMetas(ctx.Repo.Repository.ComposeWikiMetas(ctx)). - WithLinks(markup.Links{Base: ctx.Repo.RepoLink}) - buf := &strings.Builder{} + rctx := renderhelper.NewRenderContextRepoWiki(ctx, ctx.Repo.Repository) + buf := &strings.Builder{} renderFn := func(data []byte) (escaped *charset.EscapeStatus, output string, err error) { markupRd, markupWr := io.Pipe() defer markupWr.Close() diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 0bd0371f14465..6149ccb08d54e 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -20,6 +20,7 @@ import ( git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" + "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -27,7 +28,6 @@ import ( "code.gitea.io/gitea/modules/container" issue_indexer "code.gitea.io/gitea/modules/indexer/issues" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" @@ -257,11 +257,8 @@ func Milestones(ctx *context.Context) { continue } - milestones[i].RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx). - WithLinks(markup.Links{Base: milestones[i].Repo.Link()}). - WithMetas(milestones[i].Repo.ComposeMetas(ctx)). - WithRepoFacade(milestones[i].Repo), - milestones[i].Content) + rctx := renderhelper.NewRenderContextRepoComment(ctx, milestones[i].Repo) + milestones[i].RenderedContent, err = markdown.RenderString(rctx, milestones[i].Content) if err != nil { ctx.ServerError("RenderString", err) return diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 2c9487bbc08d9..d9947180fc89b 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -7,21 +7,19 @@ package user import ( "fmt" "net/http" - "path" "strings" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/web/feed" "code.gitea.io/gitea/routers/web/org" shared_user "code.gitea.io/gitea/routers/web/shared/user" @@ -72,17 +70,17 @@ func userProfile(ctx *context.Context) { ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) } - profileDbRepo, profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer) + profileDbRepo, _ /*profileGitRepo*/, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer) defer profileClose() showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) - prepareUserProfileTabData(ctx, showPrivate, profileDbRepo, profileGitRepo, profileReadmeBlob) + prepareUserProfileTabData(ctx, showPrivate, profileDbRepo, profileReadmeBlob) // call PrepareContextForProfileBigAvatar later to avoid re-querying the NumFollowers & NumFollowing shared_user.PrepareContextForProfileBigAvatar(ctx) ctx.HTML(http.StatusOK, tplProfile) } -func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDbRepo *repo_model.Repository, profileGitRepo *git.Repository, profileReadme *git.Blob) { +func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) { // if there is a profile readme, default to "overview" page, otherwise, default to "repositories" page // if there is not a profile readme, the overview tab should be treated as the repositories tab tab := ctx.FormString("tab") @@ -246,18 +244,8 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil { log.Error("failed to GetBlobContent: %v", err) } else { - if profileContent, err := markdown.RenderString(markup.NewRenderContext(ctx). - WithGitRepo(profileGitRepo). - WithLinks(markup.Links{ - // 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], - // Eg. /Tom/.profile/media/branch/main - // The branch shown on the profile page is the default branch, this need to be in sync with doc, see: - // https://docs.gitea.com/usage/profile-readme - Base: profileDbRepo.Link(), - BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)), - }), - bytes); err != nil { + rctx := renderhelper.NewRenderContextRepoFile(ctx, profileDbRepo) + if profileContent, err := markdown.RenderString(rctx, bytes); err != nil { log.Error("failed to RenderString: %v", err) } else { ctx.Data["ProfileReadme"] = profileContent diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 162e497dc02be..8eee32a8c67ec 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -18,12 +18,12 @@ import ( activities_model "code.gitea.io/gitea/models/activities" issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/emoji" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -219,10 +219,8 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient } // This is the body of the new issue or comment, not the mail body - body, err := markdown.RenderString(markup.NewRenderContext(ctx). - WithRepoFacade(ctx.Issue.Repo). - WithLinks(markup.Links{AbsolutePrefix: true, Base: ctx.Issue.Repo.HTMLURL()}). - WithMetas(ctx.Issue.Repo.ComposeMetas(ctx)), + rctx := renderhelper.NewRenderContextRepoComment(ctx.Context, ctx.Issue.Repo).WithUseAbsoluteLink(true) + body, err := markdown.RenderString(rctx, ctx.Content) if err != nil { return nil, err diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go index 3298c2273a945..af1a7a266205b 100644 --- a/services/mailer/mail_release.go +++ b/services/mailer/mail_release.go @@ -7,11 +7,11 @@ import ( "bytes" "context" + "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/translation" @@ -56,10 +56,8 @@ func mailNewRelease(ctx context.Context, lang string, tos []*user_model.User, re locale := translation.NewLocale(lang) var err error - rel.RenderedNote, err = markdown.RenderString(markup.NewRenderContext(ctx). - WithRepoFacade(rel.Repo). - WithLinks(markup.Links{Base: rel.Repo.HTMLURL()}). - WithMetas(rel.Repo.ComposeMetas(ctx)), + rctx := renderhelper.NewRenderContextRepoComment(ctx, rel.Repo).WithUseAbsoluteLink(true) + rel.RenderedNote, err = markdown.RenderString(rctx, rel.Note) if err != nil { log.Error("markdown.RenderString(%d): %v", rel.RepoID, err) diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index 40fd21dea58a0..663ffa85ef377 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -70,7 +70,7 @@ func prepareMailerTest(t *testing.T) (doer *user_model.User, repo *repo_model.Re func TestComposeIssueCommentMessage(t *testing.T) { doer, _, issue, comment := prepareMailerTest(t) - markup.Init(&markup.ProcessorHelper{ + markup.Init(&markup.RenderHelperFuncs{ IsUsernameMentionable: func(ctx context.Context, username string) bool { return username == doer.Name }, diff --git a/services/markup/processorhelper.go b/services/markup/processorhelper.go index 68487fb8dbb57..1f1abf496a3e0 100644 --- a/services/markup/processorhelper.go +++ b/services/markup/processorhelper.go @@ -11,10 +11,8 @@ import ( gitea_context "code.gitea.io/gitea/services/context" ) -func ProcessorHelper() *markup.ProcessorHelper { - return &markup.ProcessorHelper{ - ElementDir: "auto", // set dir="auto" for necessary (eg:

    , , etc) tags - +func ProcessorHelper() *markup.RenderHelperFuncs { + return &markup.RenderHelperFuncs{ RenderRepoFileCodePreview: renderRepoFileCodePreview, IsUsernameMentionable: func(ctx context.Context, username string) bool { mentionedUser, err := user.GetUserByName(ctx, username) diff --git a/tests/fuzz/fuzz_test.go b/tests/fuzz/fuzz_test.go index 78d3027547d84..946f7c46f1122 100644 --- a/tests/fuzz/fuzz_test.go +++ b/tests/fuzz/fuzz_test.go @@ -5,7 +5,6 @@ package fuzz import ( "bytes" - "context" "io" "testing" @@ -15,9 +14,7 @@ import ( ) func newFuzzRenderContext() *markup.RenderContext { - return markup.NewRenderContext(context.Background()). - WithLinks(markup.Links{Base: "https://example.com/go-gitea/gitea"}). - WithMetas(map[string]string{"user": "go-gitea", "repo": "gitea"}) + return markup.NewTestRenderContext("https://example.com/go-gitea/gitea", map[string]string{"user": "go-gitea", "repo": "gitea"}) } func FuzzMarkdownRenderRaw(f *testing.F) {