diff --git a/assets/go-licenses.json b/assets/go-licenses.json index 796c2d67656d9..fcfde0880041e 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -1090,8 +1090,8 @@ "licenseText": "MIT License\n\nCopyright (c) 2017 Asher\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, { - "name": "github.com/stretchr/testify/assert", - "path": "github.com/stretchr/testify/assert/LICENSE", + "name": "github.com/stretchr/testify", + "path": "github.com/stretchr/testify/LICENSE", "licenseText": "MIT License\n\nCopyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, { 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/main_test.go b/models/renderhelper/main_test.go new file mode 100644 index 0000000000000..abd6786ec6f10 --- /dev/null +++ b/models/renderhelper/main_test.go @@ -0,0 +1,16 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package renderhelper + +import ( + "testing" + + "code.gitea.io/gitea/models/unittest" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m, &unittest.TestOptions{ + FixtureFiles: []string{"repository.yml", "user.yml"}, + }) +} 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..23b7aa10425e8 --- /dev/null +++ b/models/renderhelper/repo_comment_test.go @@ -0,0 +1,77 @@ +// 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 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}) + + t.Run("AutoLink", func(t *testing.T) { + 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) + }) + + t.Run("AbsoluteAndRelative", func(t *testing.T) { + 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) + }) + + t.Run("WithCurrentRefPath", func(t *testing.T) { + rctx := NewRenderContextRepoComment(context.Background(), repo1, RepoCommentOptions{CurrentRefPath: "/commit/1234"}). + 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) + }) +} 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_file_test.go b/models/renderhelper/repo_file_test.go new file mode 100644 index 0000000000000..4b15b3c7b7a8c --- /dev/null +++ b/models/renderhelper/repo_file_test.go @@ -0,0 +1,75 @@ +// 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 TestRepoFile(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}) + + t.Run("AutoLink", func(t *testing.T) { + rctx := NewRenderContextRepoFile(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) + }) + + t.Run("AbsoluteAndRelative", func(t *testing.T) { + rctx := NewRenderContextRepoFile(context.Background(), repo1, RepoFileOptions{ + CurrentRefPath: "branch/main", + }).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) + }) + + t.Run("WithCurrentRefPath", func(t *testing.T) { + rctx := NewRenderContextRepoFile(context.Background(), repo1, RepoFileOptions{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_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/repo_wiki_test.go b/models/renderhelper/repo_wiki_test.go new file mode 100644 index 0000000000000..310e29b995f85 --- /dev/null +++ b/models/renderhelper/repo_wiki_test.go @@ -0,0 +1,60 @@ +// 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 TestRepoWiki(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}) + + t.Run("AutoLink", func(t *testing.T) { + rctx := NewRenderContextRepoWiki(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) + }) + + t.Run("AbsoluteAndRelative", func(t *testing.T) { + rctx := NewRenderContextRepoWiki(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) + }) +} 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..36512e59a8d43 100644 --- a/modules/markup/markdown/transform_image.go +++ b/modules/markup/markdown/transform_image.go @@ -4,10 +4,7 @@ package markdown import ( - "strings" - "code.gitea.io/gitea/modules/markup" - giteautil "code.gitea.io/gitea/modules/util" "github.com/yuin/goldmark/ast" ) @@ -20,10 +17,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()), - strings.TrimLeft(string(v.Destination), "/"), - )) + v.Destination = []byte(ctx.RenderHelper.ResolveLink(string(v.Destination), markup.LinkTypeMedia)) } parent := v.Parent() 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..b913549f9e96c 100644 --- a/modules/markup/render_helper.go +++ b/modules/markup/render_helper.go @@ -6,16 +6,52 @@ package markup import ( "context" "html/template" + + "code.gitea.io/gitea/modules/setting" +) + +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?" or "does it need to check commit ID?" + 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 resolveLinkRelative(context.Background(), "/"+setting.AppSubURL, "", link, false) +} + +var _ RenderHelper = (*SimpleRenderHelper)(nil) diff --git a/modules/markup/render_link.go b/modules/markup/render_link.go new file mode 100644 index 0000000000000..732008ba30629 --- /dev/null +++ b/modules/markup/render_link.go @@ -0,0 +1,42 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package markup + +import ( + "context" + "strings" + + "code.gitea.io/gitea/modules/httplib" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" +) + +func resolveLinkRelative(ctx context.Context, base, cur, link string, absolute bool) (finalLink string) { + if IsFullURLString(link) { + return link + } + if strings.HasPrefix(link, "/") { + if strings.HasPrefix(link, base) && strings.Count(base, "/") >= 4 { + // a trick to tolerate that some users were using absolut paths (the old gitea's behavior) + finalLink = link + } else { + finalLink = util.URLJoin(base, "./", link) + } + } else { + finalLink = util.URLJoin(base, "./", cur, link) + } + finalLink = strings.TrimSuffix(finalLink, "/") + if absolute { + finalLink = httplib.MakeAbsoluteURL(ctx, finalLink) + } + return finalLink +} + +func (ctx *RenderContext) ResolveLinkRelative(base, cur, link string) (finalLink string) { + return resolveLinkRelative(ctx, base, cur, link, ctx.RenderOptions.UseAbsoluteLink) +} + +func (ctx *RenderContext) ResolveLinkApp(link string) string { + return ctx.ResolveLinkRelative("/"+setting.AppSubURL, "", link) +} diff --git a/modules/markup/render_link_test.go b/modules/markup/render_link_test.go new file mode 100644 index 0000000000000..ed11c561bf6e9 --- /dev/null +++ b/modules/markup/render_link_test.go @@ -0,0 +1,25 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package markup + +import ( + "code.gitea.io/gitea/modules/setting" + "context" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestResolveLinkRelative(t *testing.T) { + ctx := context.Background() + setting.AppURL = "http://localhost:3000" + assert.Equal(t, "/a", resolveLinkRelative(ctx, "/a", "", "", false)) + assert.Equal(t, "/a/b", resolveLinkRelative(ctx, "/a", "b", "", false)) + assert.Equal(t, "/a/b/c", resolveLinkRelative(ctx, "/a", "b", "c", false)) + assert.Equal(t, "/a/c", resolveLinkRelative(ctx, "/a", "b", "/c", false)) + assert.Equal(t, "http://localhost:3000/a", resolveLinkRelative(ctx, "/a", "", "", true)) + + // some users might have used absolute paths a lot, so if the prefix overlaps and has enough slashes, we should tolerate it + assert.Equal(t, "/owner/repo/foo/owner/repo/foo/bar/xxx", resolveLinkRelative(ctx, "/owner/repo/foo", "", "/owner/repo/foo/bar/xxx", false)) + assert.Equal(t, "/owner/repo/foo/bar/xxx", resolveLinkRelative(ctx, "/owner/repo/foo/bar", "", "/owner/repo/foo/bar/xxx", false)) +} 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..c0241c0a8c5f3 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 @@ -211,6 +211,7 @@ func TestRenderLabels(t *testing.T) { } func TestUserMention(t *testing.T) { + markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true rendered := newTestRenderUtils().MarkdownToHtml("@no-such-user @mention-user @mention-user") - assert.EqualValues(t, `

    @no-such-user @mention-user @mention-user

    `, strings.TrimSpace(string(rendered))) + assert.EqualValues(t, `

    @no-such-user @mention-user @mention-user

    `, strings.TrimSpace(string(rendered))) } 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..e3e6d9cfcf86b 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,67 @@ 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 } + + // Ideally, this handler should be called with RepoAssigment and get the related repo from context "/owner/repo/markup" + // then render could use the repo to do various things (the permission check has passed) + // + // However, this handler is also exposed as "/markup" without any repo context, + // then since there is no permission check, so we can't use the repo from "context" parameter, + // in this case, only the "path" information could be used which doesn't cause security problems. + var repoModel *repo.Repository + if ctxRepo != nil { + repoModel = 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, repoModel, 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, repoModel, renderhelper.RepoCommentOptions{DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName}) case "wiki": - renderCtx = renderCtx.WithMetas(map[string]string{"markdownLineBreakStyle": "document", "markupContentMode": "wiki"}) + rctx = renderhelper.NewRenderContextRepoWiki(ctx, repoModel, 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, repoModel, 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..7c0b6c23be681 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,10 @@ 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, renderhelper.RepoFileOptions{ + CurrentRefPath: "branch/" + profileDbRepo.DefaultBranch, + }) + 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..b74cd43d51746 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,15 @@ 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) + WithMetas(metas) + + ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd) if err != nil { ctx.ServerError("Render", err) return @@ -604,17 +600,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..22d00c9643793 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,10 @@ 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, renderhelper.RepoFileOptions{ + CurrentRefPath: "branch/" + profileDbRepo.DefaultBranch, + }) + 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) {