mirror of
https://github.com/go-gitea/gitea
synced 2024-12-22 15:17:54 +01:00
Refactor markup render system (#32612)
This PR removes (almost) all path tricks, and introduces "renderhelper" package. Now we can clearly see the rendering behaviors for comment/file/wiki, more details are in "renderhelper" tests. Fix #31411 , fix #18592, fix #25632 and maybe more problems. (ps: fix #32608 by the way)
This commit is contained in:
parent
fa175c1694
commit
633785a5f3
4
assets/go-licenses.json
generated
4
assets/go-licenses.json
generated
@ -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"
|
"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",
|
"name": "github.com/stretchr/testify",
|
||||||
"path": "github.com/stretchr/testify/assert/LICENSE",
|
"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"
|
"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"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -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 {
|
if a.Repo != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -250,7 +250,7 @@ func (a *Action) GetActDisplayNameTitle(ctx context.Context) string {
|
|||||||
|
|
||||||
// GetRepoUserName returns the name of the action repository owner.
|
// GetRepoUserName returns the name of the action repository owner.
|
||||||
func (a *Action) GetRepoUserName(ctx context.Context) string {
|
func (a *Action) GetRepoUserName(ctx context.Context) string {
|
||||||
a.loadRepo(ctx)
|
a.LoadRepo(ctx)
|
||||||
if a.Repo == nil {
|
if a.Repo == nil {
|
||||||
return "(non-existing-repo)"
|
return "(non-existing-repo)"
|
||||||
}
|
}
|
||||||
@ -265,7 +265,7 @@ func (a *Action) ShortRepoUserName(ctx context.Context) string {
|
|||||||
|
|
||||||
// GetRepoName returns the name of the action repository.
|
// GetRepoName returns the name of the action repository.
|
||||||
func (a *Action) GetRepoName(ctx context.Context) string {
|
func (a *Action) GetRepoName(ctx context.Context) string {
|
||||||
a.loadRepo(ctx)
|
a.LoadRepo(ctx)
|
||||||
if a.Repo == nil {
|
if a.Repo == nil {
|
||||||
return "(non-existing-repo)"
|
return "(non-existing-repo)"
|
||||||
}
|
}
|
||||||
@ -644,7 +644,7 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if repoChanged {
|
if repoChanged {
|
||||||
act.loadRepo(ctx)
|
act.LoadRepo(ctx)
|
||||||
repo = act.Repo
|
repo = act.Repo
|
||||||
|
|
||||||
// check repo owner exist.
|
// check repo owner exist.
|
||||||
|
@ -7,8 +7,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
@ -112,12 +112,8 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
rctx := markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo)
|
||||||
WithRepoFacade(issue.Repo).
|
if comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content); err != nil {
|
||||||
WithLinks(markup.Links{Base: issue.Repo.Link()}).
|
|
||||||
WithMetas(issue.Repo.ComposeMetas(ctx))
|
|
||||||
if comment.RenderedContent, err = markdown.RenderString(rctx,
|
|
||||||
comment.Content); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
53
models/renderhelper/commit_checker.go
Normal file
53
models/renderhelper/commit_checker.go
Normal file
@ -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
|
||||||
|
}
|
27
models/renderhelper/main_test.go
Normal file
27
models/renderhelper/main_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package renderhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
unittest.MainTest(m, &unittest.TestOptions{
|
||||||
|
FixtureFiles: []string{"repository.yml", "user.yml"},
|
||||||
|
SetUp: func() error {
|
||||||
|
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
||||||
|
markup.Init(&markup.RenderHelperFuncs{
|
||||||
|
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
||||||
|
return username == "user2"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
73
models/renderhelper/repo_comment.go
Normal file
73
models/renderhelper/repo_comment.go
Normal file
@ -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
|
||||||
|
}
|
76
models/renderhelper/repo_comment_test.go
Normal file
76
models/renderhelper/repo_comment_test.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// 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)
|
||||||
|
|
||||||
|
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,
|
||||||
|
`<p><a href="/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d" rel="nofollow"><code>65f1bf27bc</code></a><br/>
|
||||||
|
<a href="/user2/repo1/issues/1" class="ref-issue" rel="nofollow">#1</a><br/>
|
||||||
|
<a href="/user2" rel="nofollow">@user2</a></p>
|
||||||
|
`, rendered)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("AbsoluteAndRelative", func(t *testing.T) {
|
||||||
|
rctx := NewRenderContextRepoComment(context.Background(), repo1).WithMarkupType(markdown.MarkupName)
|
||||||
|
|
||||||
|
// It is Gitea's old behavior, the relative path is resolved to the repo path
|
||||||
|
// It is different from GitHub, GitHub resolves relative links to current page's path
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
[/test](/test)
|
||||||
|
[./test](./test)
|
||||||
|
![/image](/image)
|
||||||
|
![./image](./image)
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t,
|
||||||
|
`<p><a href="/user2/repo1/test" rel="nofollow">/test</a><br/>
|
||||||
|
<a href="/user2/repo1/test" rel="nofollow">./test</a><br/>
|
||||||
|
<a href="/user2/repo1/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/image" alt="/image"/></a><br/>
|
||||||
|
<a href="/user2/repo1/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/image" alt="./image"/></a></p>
|
||||||
|
`, rendered)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithCurrentRefPath", func(t *testing.T) {
|
||||||
|
rctx := NewRenderContextRepoComment(context.Background(), repo1, RepoCommentOptions{CurrentRefPath: "/commit/1234"}).
|
||||||
|
WithMarkupType(markdown.MarkupName)
|
||||||
|
|
||||||
|
// the ref path is only used to render commit message: a commit message is rendered at the commit page with its commit ID path
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
[/test](/test)
|
||||||
|
[./test](./test)
|
||||||
|
![/image](/image)
|
||||||
|
![./image](./image)
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, `<p><a href="/user2/repo1/test" rel="nofollow">/test</a><br/>
|
||||||
|
<a href="/user2/repo1/commit/1234/test" rel="nofollow">./test</a><br/>
|
||||||
|
<a href="/user2/repo1/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/image" alt="/image"/></a><br/>
|
||||||
|
<a href="/user2/repo1/commit/1234/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/commit/1234/image" alt="./image"/></a></p>
|
||||||
|
`, rendered)
|
||||||
|
})
|
||||||
|
}
|
77
models/renderhelper/repo_file.go
Normal file
77
models/renderhelper/repo_file.go
Normal file
@ -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
|
||||||
|
}
|
83
models/renderhelper/repo_file_test.go
Normal file
83
models/renderhelper/repo_file_test.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// 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)
|
||||||
|
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,
|
||||||
|
`<p><a href="/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d" rel="nofollow"><code>65f1bf27bc</code></a>
|
||||||
|
#1
|
||||||
|
<a href="/user2" rel="nofollow">@user2</a></p>
|
||||||
|
`, 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,
|
||||||
|
`<p><a href="/user2/repo1/src/branch/main/test" rel="nofollow">/test</a>
|
||||||
|
<a href="/user2/repo1/src/branch/main/test" rel="nofollow">./test</a>
|
||||||
|
<a href="/user2/repo1/media/branch/main/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/media/branch/main/image" alt="/image"/></a>
|
||||||
|
<a href="/user2/repo1/media/branch/main/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/media/branch/main/image" alt="./image"/></a></p>
|
||||||
|
`, 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, `<p><a href="/user2/repo1/src/commit/1234/test" rel="nofollow">/test</a>
|
||||||
|
<a href="/user2/repo1/media/commit/1234/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/media/commit/1234/image" alt="/image"/></a></p>
|
||||||
|
`, rendered)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithCurrentRefPathByTag", func(t *testing.T) {
|
||||||
|
rctx := NewRenderContextRepoFile(context.Background(), repo1, RepoFileOptions{
|
||||||
|
CurrentRefPath: "/commit/1234",
|
||||||
|
CurrentTreePath: "my-dir",
|
||||||
|
}).
|
||||||
|
WithMarkupType(markdown.MarkupName)
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
<img src="LINK">
|
||||||
|
<video src="LINK">
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, `<a href="/user2/repo1/media/commit/1234/my-dir/LINK" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/media/commit/1234/my-dir/LINK"/></a>
|
||||||
|
<video src="/user2/repo1/media/commit/1234/my-dir/LINK">
|
||||||
|
</video>`, rendered)
|
||||||
|
})
|
||||||
|
}
|
80
models/renderhelper/repo_wiki.go
Normal file
80
models/renderhelper/repo_wiki.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// 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)
|
||||||
|
case markup.LinkTypeRaw: // wiki doesn't use it
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
65
models/renderhelper/repo_wiki_test.go
Normal file
65
models/renderhelper/repo_wiki_test.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// 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)
|
||||||
|
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,
|
||||||
|
`<p><a href="/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d" rel="nofollow"><code>65f1bf27bc</code></a>
|
||||||
|
<a href="/user2/repo1/issues/1" class="ref-issue" rel="nofollow">#1</a>
|
||||||
|
<a href="/user2" rel="nofollow">@user2</a></p>
|
||||||
|
`, 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,
|
||||||
|
`<p><a href="/user2/repo1/wiki/test" rel="nofollow">/test</a>
|
||||||
|
<a href="/user2/repo1/wiki/test" rel="nofollow">./test</a>
|
||||||
|
<a href="/user2/repo1/wiki/raw/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/wiki/raw/image" alt="/image"/></a>
|
||||||
|
<a href="/user2/repo1/wiki/raw/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/wiki/raw/image" alt="./image"/></a></p>
|
||||||
|
`, rendered)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PathInTag", func(t *testing.T) {
|
||||||
|
rctx := NewRenderContextRepoWiki(context.Background(), repo1).WithMarkupType(markdown.MarkupName)
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
<img src="LINK">
|
||||||
|
<video src="LINK">
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, `<a href="/user2/repo1/wiki/raw/LINK" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/wiki/raw/LINK"/></a>
|
||||||
|
<video src="/user2/repo1/wiki/raw/LINK">
|
||||||
|
</video>`, rendered)
|
||||||
|
})
|
||||||
|
}
|
29
models/renderhelper/simple_document.go
Normal file
29
models/renderhelper/simple_document.go
Normal file
@ -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
|
||||||
|
}
|
40
models/renderhelper/simple_document_test.go
Normal file
40
models/renderhelper/simple_document_test.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package renderhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"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 TestSimpleDocument(t *testing.T) {
|
||||||
|
unittest.PrepareTestEnv(t)
|
||||||
|
rctx := NewRenderContextSimpleDocument(context.Background(), "/base").WithMarkupType(markdown.MarkupName)
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
65f1bf27bc3bf70f64657658635e66094edbcb4d
|
||||||
|
#1
|
||||||
|
@user2
|
||||||
|
|
||||||
|
[/test](/test)
|
||||||
|
[./test](./test)
|
||||||
|
![/image](/image)
|
||||||
|
![./image](./image)
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t,
|
||||||
|
`<p>65f1bf27bc3bf70f64657658635e66094edbcb4d
|
||||||
|
#1
|
||||||
|
<a href="/base/user2" rel="nofollow">@user2</a></p>
|
||||||
|
<p><a href="/base/test" rel="nofollow">/test</a>
|
||||||
|
<a href="/base/test" rel="nofollow">./test</a>
|
||||||
|
<a href="/base/image" target="_blank" rel="nofollow noopener"><img src="/base/image" alt="/image"/></a>
|
||||||
|
<a href="/base/image" target="_blank" rel="nofollow noopener"><img src="/base/image" alt="./image"/></a></p>
|
||||||
|
`, rendered)
|
||||||
|
}
|
@ -9,6 +9,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"xorm.io/builder"
|
"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
|
// 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...)
|
exists, err := LoadBeanIfExists(bean, conditions...)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, exists,
|
require.True(t, exists,
|
||||||
"Expected to find %+v (of type %T, with conditions %+v), but did not",
|
"Expected to find %+v (of type %T, with conditions %+v), but did not",
|
||||||
bean, bean, conditions)
|
bean, bean, conditions)
|
||||||
return bean
|
return bean
|
||||||
|
@ -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.
|
// Check if maxRows or maxSize is reached, and if true, warn.
|
||||||
if (row >= maxRows && maxRows != 0) || (rd.InputOffset() >= maxSize && maxSize != 0) {
|
if (row >= maxRows && maxRows != 0) || (rd.InputOffset() >= maxSize && maxSize != 0) {
|
||||||
warn := `<table class="data-table"><tr><td>`
|
warn := `<table class="data-table"><tr><td>`
|
||||||
rawLink := ` <a href="` + ctx.RenderOptions.Links.RawLink() + `/` + util.PathEscapeSegments(ctx.RenderOptions.RelativePath) + `">`
|
rawLink := ` <a href="` + ctx.RenderHelper.ResolveLink(util.PathEscapeSegments(ctx.RenderOptions.RelativePath), markup.LinkTypeRaw) + `">`
|
||||||
|
|
||||||
// Try to get the user translation
|
// Try to get the user translation
|
||||||
if locale, ok := ctx.Value(translation.ContextKey).(translation.Locale); ok {
|
if locale, ok := ctx.Value(translation.ContextKey).(translation.Locale); ok {
|
||||||
|
10
modules/markup/external/external.go
vendored
10
modules/markup/external/external.go
vendored
@ -79,8 +79,8 @@ func envMark(envName string) string {
|
|||||||
func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||||
var (
|
var (
|
||||||
command = strings.NewReplacer(
|
command = strings.NewReplacer(
|
||||||
envMark("GITEA_PREFIX_SRC"), ctx.RenderOptions.Links.SrcLink(),
|
envMark("GITEA_PREFIX_SRC"), ctx.RenderHelper.ResolveLink("", markup.LinkTypeDefault),
|
||||||
envMark("GITEA_PREFIX_RAW"), ctx.RenderOptions.Links.RawLink(),
|
envMark("GITEA_PREFIX_RAW"), ctx.RenderHelper.ResolveLink("", markup.LinkTypeRaw),
|
||||||
).Replace(p.Command)
|
).Replace(p.Command)
|
||||||
commands = strings.Fields(command)
|
commands = strings.Fields(command)
|
||||||
args = commands[1:]
|
args = commands[1:]
|
||||||
@ -112,14 +112,14 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
|
|||||||
args = append(args, f.Name())
|
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()
|
defer finished()
|
||||||
|
|
||||||
cmd := exec.CommandContext(processCtx, commands[0], args...)
|
cmd := exec.CommandContext(processCtx, commands[0], args...)
|
||||||
cmd.Env = append(
|
cmd.Env = append(
|
||||||
os.Environ(),
|
os.Environ(),
|
||||||
"GITEA_PREFIX_SRC="+ctx.RenderOptions.Links.SrcLink(),
|
"GITEA_PREFIX_SRC="+ctx.RenderHelper.ResolveLink("", markup.LinkTypeDefault),
|
||||||
"GITEA_PREFIX_RAW="+ctx.RenderOptions.Links.RawLink(),
|
"GITEA_PREFIX_RAW="+ctx.RenderHelper.ResolveLink("", markup.LinkTypeRaw),
|
||||||
)
|
)
|
||||||
if !p.IsInputFile {
|
if !p.IsInputFile {
|
||||||
cmd.Stdin = input
|
cmd.Stdin = input
|
||||||
|
@ -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 {
|
func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output io.Writer) error {
|
||||||
defer ctx.Cancel()
|
|
||||||
// FIXME: don't read all content to memory
|
// FIXME: don't read all content to memory
|
||||||
rawHTML, err := io.ReadAll(input)
|
rawHTML, err := io.ReadAll(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -396,7 +395,7 @@ func createLink(ctx *RenderContext, href, content, class string) *html.Node {
|
|||||||
Data: atom.A.String(),
|
Data: atom.A.String(),
|
||||||
Attr: []html.Attribute{{Key: "href", Val: href}},
|
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"})
|
a.Attr = append(a.Attr, html.Attribute{Key: "data-markdown-generated-content"})
|
||||||
}
|
}
|
||||||
if class != "" {
|
if class != "" {
|
||||||
|
@ -51,7 +51,7 @@ func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosSt
|
|||||||
lineStart, _ := strconv.Atoi(strings.TrimPrefix(lineStartStr, "L"))
|
lineStart, _ := strconv.Atoi(strings.TrimPrefix(lineStartStr, "L"))
|
||||||
lineStop, _ := strconv.Atoi(strings.TrimPrefix(lineStopStr, "L"))
|
lineStop, _ := strconv.Atoi(strings.TrimPrefix(lineStopStr, "L"))
|
||||||
opts.LineStart, opts.LineStop = lineStart, lineStop
|
opts.LineStart, opts.LineStop = lineStart, lineStop
|
||||||
h, err := DefaultProcessorHelper.RenderRepoFileCodePreview(ctx, opts)
|
h, err := DefaultRenderHelperFuncs.RenderRepoFileCodePreview(ctx, opts)
|
||||||
return m[0], m[1], h, err
|
return m[0], m[1], h, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,16 +16,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestRenderCodePreview(t *testing.T) {
|
func TestRenderCodePreview(t *testing.T) {
|
||||||
markup.Init(&markup.ProcessorHelper{
|
markup.Init(&markup.RenderHelperFuncs{
|
||||||
RenderRepoFileCodePreview: func(ctx context.Context, opts markup.RenderCodePreviewOptions) (template.HTML, error) {
|
RenderRepoFileCodePreview: func(ctx context.Context, options markup.RenderCodePreviewOptions) (template.HTML, error) {
|
||||||
return "<div>code preview</div>", nil
|
return "<div>code preview</div>", nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
test := func(input, expected string) {
|
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.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
test("http://localhost:3000/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", "<p><div>code preview</div></p>")
|
test("http://localhost:3000/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", "<p><div>code preview</div></p>")
|
||||||
test("http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", `<p><a href="http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20" data-markdown-generated-content="" rel="nofollow">http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20</a></p>`)
|
test("http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", `<p><a href="http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20" rel="nofollow">http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20</a></p>`)
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,10 @@
|
|||||||
package markup
|
package markup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
@ -163,15 +160,12 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
// hashCurrentPatternProcessor renders SHA1 strings to corresponding links that
|
// hashCurrentPatternProcessor renders SHA1 strings to corresponding links that
|
||||||
// are assumed to be in the same repository.
|
// are assumed to be in the same repository.
|
||||||
func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
|
func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
if ctx.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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
start := 0
|
start := 0
|
||||||
next := node.NextSibling
|
next := node.NextSibling
|
||||||
if ctx.RenderHelper.shaExistCache == nil {
|
|
||||||
ctx.RenderHelper.shaExistCache = make(map[string]bool)
|
|
||||||
}
|
|
||||||
for node != nil && node != next && start < len(node.Data) {
|
for node != nil && node != next && start < len(node.Data) {
|
||||||
m := globalVars().hashCurrentPattern.FindStringSubmatchIndex(node.Data[start:])
|
m := globalVars().hashCurrentPattern.FindStringSubmatchIndex(node.Data[start:])
|
||||||
if m == nil {
|
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.
|
// 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
|
// Because of this, we check to make sure that a matched hash is actually
|
||||||
// a commit in the repository before making it a link.
|
// a commit in the repository before making it a link.
|
||||||
|
if !ctx.RenderHelper.IsCommitIDExisting(hash) {
|
||||||
// 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 {
|
|
||||||
start = m[3]
|
start = m[3]
|
||||||
continue
|
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"))
|
replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), "commit"))
|
||||||
start = 0
|
start = 0
|
||||||
node = node.NextSibling.NextSibling
|
node = node.NextSibling.NextSibling
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
package markup
|
package markup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -34,8 +33,7 @@ func numericIssueLink(baseURL, class string, index int, marker string) string {
|
|||||||
|
|
||||||
// link an HTML link
|
// link an HTML link
|
||||||
func link(href, class, contents string) string {
|
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(`<a href="%s"%s>%s</a>`, href, extra, contents)
|
return fmt.Sprintf(`<a href="%s"%s>%s</a>`, href, extra, contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,22 +67,11 @@ var localMetas = map[string]string{
|
|||||||
"markupAllowShortIssuePattern": "true",
|
"markupAllowShortIssuePattern": "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
var localWikiMetas = map[string]string{
|
|
||||||
"user": "test-owner",
|
|
||||||
"repo": "test-repo",
|
|
||||||
"markupContentMode": "wiki",
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRender_IssueIndexPattern(t *testing.T) {
|
func TestRender_IssueIndexPattern(t *testing.T) {
|
||||||
// numeric: render inputs without valid mentions
|
// numeric: render inputs without valid mentions
|
||||||
test := func(s string) {
|
test := func(s string) {
|
||||||
testRenderIssueIndexPattern(t, s, s, &RenderContext{
|
testRenderIssueIndexPattern(t, s, s, NewTestRenderContext())
|
||||||
ctx: context.Background(),
|
testRenderIssueIndexPattern(t, s, s, NewTestRenderContext(numericMetas))
|
||||||
})
|
|
||||||
testRenderIssueIndexPattern(t, s, s, &RenderContext{
|
|
||||||
ctx: context.Background(),
|
|
||||||
RenderOptions: RenderOptions{Metas: numericMetas},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// should not render anything when there are no mentions
|
// 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)
|
links[i] = numericIssueLink(util.URLJoin(TestRepoURL, path), "ref-issue", index, marker)
|
||||||
}
|
}
|
||||||
expectedNil := fmt.Sprintf(expectedFmt, links...)
|
expectedNil := fmt.Sprintf(expectedFmt, links...)
|
||||||
testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{
|
testRenderIssueIndexPattern(t, s, expectedNil, NewTestRenderContext(TestAppURL, localMetas))
|
||||||
ctx: context.Background(),
|
|
||||||
RenderOptions: RenderOptions{Metas: localMetas},
|
|
||||||
})
|
|
||||||
|
|
||||||
class := "ref-issue"
|
class := "ref-issue"
|
||||||
if isExternal {
|
if isExternal {
|
||||||
@ -146,10 +130,7 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
|
|||||||
links[i] = numericIssueLink(prefix, class, index, marker)
|
links[i] = numericIssueLink(prefix, class, index, marker)
|
||||||
}
|
}
|
||||||
expectedNum := fmt.Sprintf(expectedFmt, links...)
|
expectedNum := fmt.Sprintf(expectedFmt, links...)
|
||||||
testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{
|
testRenderIssueIndexPattern(t, s, expectedNum, NewTestRenderContext(TestAppURL, numericMetas))
|
||||||
ctx: context.Background(),
|
|
||||||
RenderOptions: RenderOptions{Metas: numericMetas},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// should render freestanding mentions
|
// should render freestanding mentions
|
||||||
@ -183,10 +164,7 @@ func TestRender_IssueIndexPattern3(t *testing.T) {
|
|||||||
|
|
||||||
// alphanumeric: render inputs without valid mentions
|
// alphanumeric: render inputs without valid mentions
|
||||||
test := func(s string) {
|
test := func(s string) {
|
||||||
testRenderIssueIndexPattern(t, s, s, &RenderContext{
|
testRenderIssueIndexPattern(t, s, s, NewTestRenderContext(alphanumericMetas))
|
||||||
ctx: context.Background(),
|
|
||||||
RenderOptions: RenderOptions{Metas: alphanumericMetas},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
test("")
|
test("")
|
||||||
test("this is a 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)
|
links[i] = externalIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue ref-external-issue", name)
|
||||||
}
|
}
|
||||||
expected := fmt.Sprintf(expectedFmt, links...)
|
expected := fmt.Sprintf(expectedFmt, links...)
|
||||||
testRenderIssueIndexPattern(t, s, expected, &RenderContext{
|
testRenderIssueIndexPattern(t, s, expected, NewTestRenderContext(alphanumericMetas))
|
||||||
ctx: context.Background(),
|
|
||||||
RenderOptions: RenderOptions{Metas: alphanumericMetas},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
test("OTT-1234 test", "%s test", "OTT-1234")
|
test("OTT-1234 test", "%s test", "OTT-1234")
|
||||||
test("test T-12 issue", "test %s issue", "T-12")
|
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...)
|
expected := fmt.Sprintf(expectedFmt, links...)
|
||||||
testRenderIssueIndexPattern(t, s, expected, &RenderContext{
|
testRenderIssueIndexPattern(t, s, expected, NewTestRenderContext(metas))
|
||||||
ctx: context.Background(),
|
|
||||||
RenderOptions: RenderOptions{Metas: metas},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("abc ISSUE-123 def", "abc %s def",
|
test("abc ISSUE-123 def", "abc %s def",
|
||||||
@ -263,10 +235,7 @@ func TestRender_IssueIndexPattern5(t *testing.T) {
|
|||||||
[]string{"ISSUE-123"},
|
[]string{"ISSUE-123"},
|
||||||
)
|
)
|
||||||
|
|
||||||
testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{
|
testRenderIssueIndexPattern(t, "will not match", "will not match", NewTestRenderContext(regexpMetas))
|
||||||
ctx: context.Background(),
|
|
||||||
RenderOptions: RenderOptions{Metas: regexpMetas},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRender_IssueIndexPattern_NoShortPattern(t *testing.T) {
|
func TestRender_IssueIndexPattern_NoShortPattern(t *testing.T) {
|
||||||
@ -278,18 +247,9 @@ func TestRender_IssueIndexPattern_NoShortPattern(t *testing.T) {
|
|||||||
"style": IssueNameStyleNumeric,
|
"style": IssueNameStyleNumeric,
|
||||||
}
|
}
|
||||||
|
|
||||||
testRenderIssueIndexPattern(t, "#1", "#1", &RenderContext{
|
testRenderIssueIndexPattern(t, "#1", "#1", NewTestRenderContext(metas))
|
||||||
ctx: context.Background(),
|
testRenderIssueIndexPattern(t, "#1312", "#1312", NewTestRenderContext(metas))
|
||||||
RenderOptions: RenderOptions{Metas: metas},
|
testRenderIssueIndexPattern(t, "!1", "!1", NewTestRenderContext(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},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRender_RenderIssueTitle(t *testing.T) {
|
func TestRender_RenderIssueTitle(t *testing.T) {
|
||||||
@ -300,20 +260,12 @@ func TestRender_RenderIssueTitle(t *testing.T) {
|
|||||||
"repo": "someRepo",
|
"repo": "someRepo",
|
||||||
"style": IssueNameStyleNumeric,
|
"style": IssueNameStyleNumeric,
|
||||||
}
|
}
|
||||||
actual, err := RenderIssueTitle(&RenderContext{
|
actual, err := RenderIssueTitle(NewTestRenderContext(metas), "#1")
|
||||||
ctx: context.Background(),
|
|
||||||
RenderOptions: RenderOptions{Metas: metas},
|
|
||||||
}, "#1")
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "#1", actual)
|
assert.Equal(t, "#1", actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
|
func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
|
||||||
ctx.RenderOptions.Links.AbsolutePrefix = true
|
|
||||||
if ctx.RenderOptions.Links.Base == "" {
|
|
||||||
ctx.RenderOptions.Links.Base = TestRepoURL
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
err := postProcess(ctx, []processor{issueIndexPatternProcessor}, strings.NewReader(input), &buf)
|
err := postProcess(ctx, []processor{issueIndexPatternProcessor}, strings.NewReader(input), &buf)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -325,20 +277,12 @@ func TestRender_AutoLink(t *testing.T) {
|
|||||||
|
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
var buffer strings.Builder
|
var buffer strings.Builder
|
||||||
err := PostProcess(&RenderContext{
|
err := PostProcess(NewTestRenderContext(localMetas), strings.NewReader(input), &buffer)
|
||||||
ctx: context.Background(),
|
|
||||||
|
|
||||||
RenderOptions: RenderOptions{Metas: localMetas, Links: Links{Base: TestRepoURL}},
|
|
||||||
}, strings.NewReader(input), &buffer)
|
|
||||||
assert.Equal(t, err, nil)
|
assert.Equal(t, err, nil)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
err = PostProcess(&RenderContext{
|
err = PostProcess(NewTestRenderContext(localMetas), strings.NewReader(input), &buffer)
|
||||||
ctx: context.Background(),
|
|
||||||
|
|
||||||
RenderOptions: RenderOptions{Metas: localWikiMetas, Links: Links{Base: TestRepoURL}},
|
|
||||||
}, strings.NewReader(input), &buffer)
|
|
||||||
assert.Equal(t, err, nil)
|
assert.Equal(t, err, nil)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
|
||||||
}
|
}
|
||||||
@ -360,14 +304,10 @@ func TestRender_AutoLink(t *testing.T) {
|
|||||||
|
|
||||||
func TestRender_FullIssueURLs(t *testing.T) {
|
func TestRender_FullIssueURLs(t *testing.T) {
|
||||||
setting.AppURL = TestAppURL
|
setting.AppURL = TestAppURL
|
||||||
defer testModule.MockVariableValue(&RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
defer testModule.MockVariableValue(&RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
var result strings.Builder
|
var result strings.Builder
|
||||||
err := postProcess(&RenderContext{
|
err := postProcess(NewTestRenderContext(localMetas), []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result)
|
||||||
ctx: context.Background(),
|
|
||||||
|
|
||||||
RenderOptions: RenderOptions{Metas: localMetas, Links: Links{Base: TestRepoURL}},
|
|
||||||
}, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expected, result.String())
|
assert.Equal(t, expected, result.String())
|
||||||
}
|
}
|
||||||
|
@ -136,9 +136,11 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
// Gitea will redirect on click as appropriate.
|
// Gitea will redirect on click as appropriate.
|
||||||
issuePath := util.Iif(ref.IsPull, "pulls", "issues")
|
issuePath := util.Iif(ref.IsPull, "pulls", "issues")
|
||||||
if ref.Owner == "" {
|
if ref.Owner == "" {
|
||||||
link = createLink(ctx, util.URLJoin(ctx.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 {
|
} 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)
|
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)
|
replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link)
|
||||||
node = node.NextSibling.NextSibling
|
node = node.NextSibling.NextSibling
|
||||||
|
@ -6,37 +6,14 @@ package markup
|
|||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/markup/common"
|
"code.gitea.io/gitea/modules/markup/common"
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
"golang.org/x/net/html/atom"
|
"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) {
|
func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
next := node.NextSibling
|
next := node.NextSibling
|
||||||
for node != nil && node != next {
|
for node != nil && node != next {
|
||||||
@ -116,7 +93,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
|
|
||||||
name += tail
|
name += tail
|
||||||
image := false
|
image := false
|
||||||
ext := filepath.Ext(link)
|
ext := path.Ext(link)
|
||||||
switch ext {
|
switch ext {
|
||||||
// fast path: empty string, ignore
|
// fast path: empty string, ignore
|
||||||
case "":
|
case "":
|
||||||
@ -139,6 +116,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
if image {
|
if image {
|
||||||
link = strings.ReplaceAll(link, " ", "+")
|
link = strings.ReplaceAll(link, " ", "+")
|
||||||
} else {
|
} else {
|
||||||
|
// the hacky wiki name encoding: space to "-"
|
||||||
link = strings.ReplaceAll(link, " ", "-") // FIXME: it should support dashes in the link, eg: "the-dash-support.-"
|
link = strings.ReplaceAll(link, " ", "-") // FIXME: it should support dashes in the link, eg: "the-dash-support.-"
|
||||||
}
|
}
|
||||||
if !strings.Contains(link, "/") {
|
if !strings.Contains(link, "/") {
|
||||||
@ -146,9 +124,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if image {
|
if image {
|
||||||
if !absoluteLink {
|
link = ctx.RenderHelper.ResolveLink(link, LinkTypeMedia)
|
||||||
link = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), link)
|
|
||||||
}
|
|
||||||
title := props["title"]
|
title := props["title"]
|
||||||
if title == "" {
|
if title == "" {
|
||||||
title = props["alt"]
|
title = props["alt"]
|
||||||
@ -174,7 +150,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
childNode.Attr = childNode.Attr[:2]
|
childNode.Attr = childNode.Attr[:2]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
link, _ = ResolveLink(ctx, link, "")
|
link = ctx.RenderHelper.ResolveLink(link, LinkTypeDefault)
|
||||||
childNode.Type = html.TextNode
|
childNode.Type = html.TextNode
|
||||||
childNode.Data = name
|
childNode.Data = name
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,8 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
if ok && strings.Contains(mention, "/") {
|
if ok && strings.Contains(mention, "/") {
|
||||||
mentionOrgAndTeam := strings.Split(mention, "/")
|
mentionOrgAndTeam := strings.Split(mention, "/")
|
||||||
if mentionOrgAndTeam[0][1:] == ctx.RenderOptions.Metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") {
|
if mentionOrgAndTeam[0][1:] == ctx.RenderOptions.Metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") {
|
||||||
replaceContent(node, loc.Start, loc.End, createLink(ctx, util.URLJoin(ctx.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
|
node = node.NextSibling.NextSibling
|
||||||
start = 0
|
start = 0
|
||||||
continue
|
continue
|
||||||
@ -43,8 +44,9 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
}
|
}
|
||||||
mentionedUsername := mention[1:]
|
mentionedUsername := mention[1:]
|
||||||
|
|
||||||
if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx, mentionedUsername) {
|
if DefaultRenderHelperFuncs != nil && DefaultRenderHelperFuncs.IsUsernameMentionable(ctx, mentionedUsername) {
|
||||||
replaceContent(node, loc.Start, loc.End, createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), mentionedUsername), mention, "" /*mention*/))
|
link := ctx.RenderHelper.ResolveLink(mentionedUsername, LinkTypeApp)
|
||||||
|
replaceContent(node, loc.Start, loc.End, createLink(ctx, link, mention, "" /*mention*/))
|
||||||
node = node.NextSibling.NextSibling
|
node = node.NextSibling.NextSibling
|
||||||
start = 0
|
start = 0
|
||||||
} else {
|
} else {
|
||||||
|
@ -4,8 +4,6 @@
|
|||||||
package markup
|
package markup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,7 +15,7 @@ func visitNodeImg(ctx *RenderContext, img *html.Node) (next *html.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if IsNonEmptyRelativePath(attr.Val) {
|
if IsNonEmptyRelativePath(attr.Val) {
|
||||||
attr.Val = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val)
|
attr.Val = ctx.RenderHelper.ResolveLink(attr.Val, LinkTypeMedia)
|
||||||
|
|
||||||
// By default, the "<img>" tag should also be clickable,
|
// By default, the "<img>" tag should also be clickable,
|
||||||
// because frontend use `<img>` to paste the re-scaled image into the markdown,
|
// because frontend use `<img>` to paste the re-scaled image into the markdown,
|
||||||
@ -53,7 +51,7 @@ func visitNodeVideo(ctx *RenderContext, node *html.Node) (next *html.Node) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if IsNonEmptyRelativePath(attr.Val) {
|
if IsNonEmptyRelativePath(attr.Val) {
|
||||||
attr.Val = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val)
|
attr.Val = ctx.RenderHelper.ResolveLink(attr.Val, LinkTypeMedia)
|
||||||
}
|
}
|
||||||
attr.Val = camoHandleLink(attr.Val)
|
attr.Val = camoHandleLink(attr.Val)
|
||||||
node.Attr[i] = attr
|
node.Attr[i] = attr
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/emoji"
|
"code.gitea.io/gitea/modules/emoji"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -22,44 +21,13 @@ import (
|
|||||||
var (
|
var (
|
||||||
testRepoOwnerName = "user13"
|
testRepoOwnerName = "user13"
|
||||||
testRepoName = "repo11"
|
testRepoName = "repo11"
|
||||||
localMetas = map[string]string{
|
localMetas = map[string]string{"user": testRepoOwnerName, "repo": testRepoName}
|
||||||
"user": testRepoOwnerName,
|
|
||||||
"repo": testRepoName,
|
|
||||||
}
|
|
||||||
localWikiMetas = map[string]string{
|
|
||||||
"user": testRepoOwnerName,
|
|
||||||
"repo": testRepoName,
|
|
||||||
"markupContentMode": "wiki",
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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) {
|
func TestRender_Commits(t *testing.T) {
|
||||||
setting.AppURL = markup.TestAppURL
|
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", localMetas, newMockRepo(testRepoOwnerName, testRepoName), markup.Links{
|
rctx := markup.NewTestRenderContext(markup.TestAppURL, localMetas).WithRelativePath("a.md")
|
||||||
AbsolutePrefix: true,
|
buffer, err := markup.RenderString(rctx, input)
|
||||||
Base: markup.TestRepoURL,
|
|
||||||
}), input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
@ -102,14 +70,10 @@ func TestRender_Commits(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRender_CrossReferences(t *testing.T) {
|
func TestRender_CrossReferences(t *testing.T) {
|
||||||
setting.AppURL = markup.TestAppURL
|
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
||||||
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", localMetas,
|
rctx := markup.NewTestRenderContext(markup.TestAppURL, localMetas).WithRelativePath("a.md")
|
||||||
markup.Links{
|
buffer, err := markup.RenderString(rctx, input)
|
||||||
AbsolutePrefix: true,
|
|
||||||
Base: setting.AppSubURL,
|
|
||||||
}), input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
@ -141,9 +105,9 @@ func TestRender_CrossReferences(t *testing.T) {
|
|||||||
|
|
||||||
func TestRender_links(t *testing.T) {
|
func TestRender_links(t *testing.T) {
|
||||||
setting.AppURL = markup.TestAppURL
|
setting.AppURL = markup.TestAppURL
|
||||||
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
||||||
test := func(input, expected string) {
|
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.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
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) {
|
func TestRender_email(t *testing.T) {
|
||||||
setting.AppURL = markup.TestAppURL
|
setting.AppURL = markup.TestAppURL
|
||||||
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
||||||
test := func(input, expected string) {
|
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.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res))
|
||||||
}
|
}
|
||||||
@ -315,7 +279,7 @@ func TestRender_emoji(t *testing.T) {
|
|||||||
|
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
expected = strings.ReplaceAll(expected, "&", "&")
|
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.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
@ -374,188 +338,133 @@ func TestRender_ShortLinks(t *testing.T) {
|
|||||||
setting.AppURL = markup.TestAppURL
|
setting.AppURL = markup.TestAppURL
|
||||||
tree := util.URLJoin(markup.TestRepoURL, "src", "master")
|
tree := util.URLJoin(markup.TestRepoURL, "src", "master")
|
||||||
|
|
||||||
test := func(input, expected, expectedWiki string) {
|
test := func(input, expected string) {
|
||||||
buffer, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: markup.TestRepoURL, BranchPath: "master"}), input)
|
buffer, err := markdown.RenderString(markup.NewTestRenderContext(tree), input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
|
||||||
buffer, err = markdown.RenderString(markup.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")
|
url := util.URLJoin(tree, "Link")
|
||||||
otherURL := util.URLJoin(tree, "Other-Link")
|
otherURL := util.URLJoin(tree, "Other-Link")
|
||||||
encodedURL := util.URLJoin(tree, "Link%3F")
|
encodedURL := util.URLJoin(tree, "Link%3F")
|
||||||
imgurl := util.URLJoin(mediatree, "Link.jpg")
|
imgurl := util.URLJoin(tree, "Link.jpg")
|
||||||
otherImgurl := util.URLJoin(mediatree, "Link+Other.jpg")
|
otherImgurl := util.URLJoin(tree, "Link+Other.jpg")
|
||||||
encodedImgurl := util.URLJoin(mediatree, "Link+%23.jpg")
|
encodedImgurl := util.URLJoin(tree, "Link+%23.jpg")
|
||||||
notencodedImgurl := util.URLJoin(mediatree, "some", "path", "Link+#.jpg")
|
notencodedImgurl := util.URLJoin(tree, "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")
|
|
||||||
renderableFileURL := util.URLJoin(tree, "markdown_file.md")
|
renderableFileURL := util.URLJoin(tree, "markdown_file.md")
|
||||||
renderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "markdown_file.md")
|
|
||||||
unrenderableFileURL := util.URLJoin(tree, "file.zip")
|
unrenderableFileURL := util.URLJoin(tree, "file.zip")
|
||||||
unrenderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "file.zip")
|
|
||||||
favicon := "http://google.com/favicon.ico"
|
favicon := "http://google.com/favicon.ico"
|
||||||
|
|
||||||
test(
|
test(
|
||||||
"[[Link]]",
|
"[[Link]]",
|
||||||
`<p><a href="`+url+`" rel="nofollow">Link</a></p>`,
|
`<p><a href="`+url+`" rel="nofollow">Link</a></p>`,
|
||||||
`<p><a href="`+urlWiki+`" rel="nofollow">Link</a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[Link.-]]",
|
"[[Link.-]]",
|
||||||
`<p><a href="http://localhost:3000/test-owner/test-repo/src/master/Link.-" rel="nofollow">Link.-</a></p>`,
|
`<p><a href="http://localhost:3000/test-owner/test-repo/src/master/Link.-" rel="nofollow">Link.-</a></p>`,
|
||||||
`<p><a href="http://localhost:3000/test-owner/test-repo/wiki/Link.-" rel="nofollow">Link.-</a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[Link.jpg]]",
|
"[[Link.jpg]]",
|
||||||
`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Link.jpg" alt="Link.jpg"/></a></p>`,
|
`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Link.jpg" alt="Link.jpg"/></a></p>`,
|
||||||
`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Link.jpg" alt="Link.jpg"/></a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[["+favicon+"]]",
|
"[["+favicon+"]]",
|
||||||
`<p><a href="`+favicon+`" rel="nofollow"><img src="`+favicon+`" title="favicon.ico" alt="`+favicon+`"/></a></p>`,
|
`<p><a href="`+favicon+`" rel="nofollow"><img src="`+favicon+`" title="favicon.ico" alt="`+favicon+`"/></a></p>`,
|
||||||
`<p><a href="`+favicon+`" rel="nofollow"><img src="`+favicon+`" title="favicon.ico" alt="`+favicon+`"/></a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[Name|Link]]",
|
"[[Name|Link]]",
|
||||||
`<p><a href="`+url+`" rel="nofollow">Name</a></p>`,
|
`<p><a href="`+url+`" rel="nofollow">Name</a></p>`,
|
||||||
`<p><a href="`+urlWiki+`" rel="nofollow">Name</a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[Name|Link.jpg]]",
|
"[[Name|Link.jpg]]",
|
||||||
`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Name" alt="Name"/></a></p>`,
|
`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Name" alt="Name"/></a></p>`,
|
||||||
`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Name" alt="Name"/></a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[Name|Link.jpg|alt=AltName]]",
|
"[[Name|Link.jpg|alt=AltName]]",
|
||||||
`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="AltName" alt="AltName"/></a></p>`,
|
`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="AltName" alt="AltName"/></a></p>`,
|
||||||
`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="AltName" alt="AltName"/></a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[Name|Link.jpg|title=Title]]",
|
"[[Name|Link.jpg|title=Title]]",
|
||||||
`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Title" alt="Title"/></a></p>`,
|
`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Title" alt="Title"/></a></p>`,
|
||||||
`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Title" alt="Title"/></a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[Name|Link.jpg|alt=AltName|title=Title]]",
|
"[[Name|Link.jpg|alt=AltName|title=Title]]",
|
||||||
`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Title" alt="AltName"/></a></p>`,
|
`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Title" alt="AltName"/></a></p>`,
|
||||||
`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Title" alt="AltName"/></a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[Name|Link.jpg|alt=\"AltName\"|title='Title']]",
|
"[[Name|Link.jpg|alt=\"AltName\"|title='Title']]",
|
||||||
`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Title" alt="AltName"/></a></p>`,
|
`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Title" alt="AltName"/></a></p>`,
|
||||||
`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Title" alt="AltName"/></a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[Name|Link Other.jpg|alt=\"AltName\"|title='Title']]",
|
"[[Name|Link Other.jpg|alt=\"AltName\"|title='Title']]",
|
||||||
`<p><a href="`+otherImgurl+`" rel="nofollow"><img src="`+otherImgurl+`" title="Title" alt="AltName"/></a></p>`,
|
`<p><a href="`+otherImgurl+`" rel="nofollow"><img src="`+otherImgurl+`" title="Title" alt="AltName"/></a></p>`,
|
||||||
`<p><a href="`+otherImgurlWiki+`" rel="nofollow"><img src="`+otherImgurlWiki+`" title="Title" alt="AltName"/></a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[Link]] [[Other Link]]",
|
"[[Link]] [[Other Link]]",
|
||||||
`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherURL+`" rel="nofollow">Other Link</a></p>`,
|
`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherURL+`" rel="nofollow">Other Link</a></p>`,
|
||||||
`<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherURLWiki+`" rel="nofollow">Other Link</a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[Link?]]",
|
"[[Link?]]",
|
||||||
`<p><a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
|
`<p><a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
|
||||||
`<p><a href="`+encodedURLWiki+`" rel="nofollow">Link?</a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[Link]] [[Other Link]] [[Link?]]",
|
"[[Link]] [[Other Link]] [[Link?]]",
|
||||||
`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherURL+`" rel="nofollow">Other Link</a> <a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
|
`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherURL+`" rel="nofollow">Other Link</a> <a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
|
||||||
`<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherURLWiki+`" rel="nofollow">Other Link</a> <a href="`+encodedURLWiki+`" rel="nofollow">Link?</a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[markdown_file.md]]",
|
"[[markdown_file.md]]",
|
||||||
`<p><a href="`+renderableFileURL+`" rel="nofollow">markdown_file.md</a></p>`,
|
`<p><a href="`+renderableFileURL+`" rel="nofollow">markdown_file.md</a></p>`,
|
||||||
`<p><a href="`+renderableFileURLWiki+`" rel="nofollow">markdown_file.md</a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[file.zip]]",
|
"[[file.zip]]",
|
||||||
`<p><a href="`+unrenderableFileURL+`" rel="nofollow">file.zip</a></p>`,
|
`<p><a href="`+unrenderableFileURL+`" rel="nofollow">file.zip</a></p>`,
|
||||||
`<p><a href="`+unrenderableFileURLWiki+`" rel="nofollow">file.zip</a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[Link #.jpg]]",
|
"[[Link #.jpg]]",
|
||||||
`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`" title="Link #.jpg" alt="Link #.jpg"/></a></p>`,
|
`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`" title="Link #.jpg" alt="Link #.jpg"/></a></p>`,
|
||||||
`<p><a href="`+encodedImgurlWiki+`" rel="nofollow"><img src="`+encodedImgurlWiki+`" title="Link #.jpg" alt="Link #.jpg"/></a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]",
|
"[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]",
|
||||||
`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`" title="Title" alt="AltName"/></a></p>`,
|
`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`" title="Title" alt="AltName"/></a></p>`,
|
||||||
`<p><a href="`+encodedImgurlWiki+`" rel="nofollow"><img src="`+encodedImgurlWiki+`" title="Title" alt="AltName"/></a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"[[some/path/Link #.jpg]]",
|
"[[some/path/Link #.jpg]]",
|
||||||
`<p><a href="`+notencodedImgurl+`" rel="nofollow"><img src="`+notencodedImgurl+`" title="Link #.jpg" alt="some/path/Link #.jpg"/></a></p>`,
|
`<p><a href="`+notencodedImgurl+`" rel="nofollow"><img src="`+notencodedImgurl+`" title="Link #.jpg" alt="some/path/Link #.jpg"/></a></p>`,
|
||||||
`<p><a href="`+notencodedImgurlWiki+`" rel="nofollow"><img src="`+notencodedImgurlWiki+`" title="Link #.jpg" alt="some/path/Link #.jpg"/></a></p>`)
|
)
|
||||||
test(
|
test(
|
||||||
"<p><a href=\"https://example.org\">[[foobar]]</a></p>",
|
"<p><a href=\"https://example.org\">[[foobar]]</a></p>",
|
||||||
`<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`,
|
`<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`,
|
||||||
`<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
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(`<img src="LINK">`, false, markup.Links{Base: "/test-owner/test-repo"})
|
|
||||||
assert.Equal(t, `<a href="/test-owner/test-repo/LINK" target="_blank" rel="nofollow noopener"><img src="/test-owner/test-repo/LINK"/></a>`, out)
|
|
||||||
|
|
||||||
out = render(`<img src="LINK">`, true, markup.Links{Base: "/test-owner/test-repo"})
|
|
||||||
assert.Equal(t, `<a href="/test-owner/test-repo/wiki/raw/LINK" target="_blank" rel="nofollow noopener"><img src="/test-owner/test-repo/wiki/raw/LINK"/></a>`, out)
|
|
||||||
|
|
||||||
out = render(`<img src="LINK">`, false, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"})
|
|
||||||
assert.Equal(t, `<a href="/test-owner/test-repo/media/test-branch/LINK" target="_blank" rel="nofollow noopener"><img src="/test-owner/test-repo/media/test-branch/LINK"/></a>`, out)
|
|
||||||
|
|
||||||
out = render(`<img src="LINK">`, true, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"})
|
|
||||||
assert.Equal(t, `<a href="/test-owner/test-repo/wiki/raw/LINK" target="_blank" rel="nofollow noopener"><img src="/test-owner/test-repo/wiki/raw/LINK"/></a>`, out)
|
|
||||||
|
|
||||||
out = render(`<img src="/LINK">`, true, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"})
|
|
||||||
assert.Equal(t, `<img src="/LINK"/>`, out)
|
|
||||||
|
|
||||||
out = render(`<video src="LINK">`, false, markup.Links{Base: "/test-owner/test-repo"})
|
|
||||||
assert.Equal(t, `<video src="/test-owner/test-repo/LINK"></video>`, out)
|
|
||||||
|
|
||||||
out = render(`<video src="LINK">`, true, markup.Links{Base: "/test-owner/test-repo"})
|
|
||||||
assert.Equal(t, `<video src="/test-owner/test-repo/wiki/raw/LINK"></video>`, out)
|
|
||||||
|
|
||||||
out = render(`<video src="/LINK">`, false, markup.Links{Base: "/test-owner/test-repo"})
|
|
||||||
assert.Equal(t, `<video src="/LINK"></video>`, out)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_ParseClusterFuzz(t *testing.T) {
|
func Test_ParseClusterFuzz(t *testing.T) {
|
||||||
setting.AppURL = markup.TestAppURL
|
setting.AppURL = markup.TestAppURL
|
||||||
|
|
||||||
localMetas := map[string]string{
|
localMetas := map[string]string{"user": "go-gitea", "repo": "gitea"}
|
||||||
"user": "go-gitea",
|
|
||||||
"repo": "gitea",
|
|
||||||
}
|
|
||||||
|
|
||||||
data := "<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
|
data := "<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
|
||||||
|
|
||||||
var res strings.Builder
|
var res strings.Builder
|
||||||
err := markup.PostProcess(markup.NewTestRenderContext(markup.Links{Base: "https://example.com"}, localMetas), strings.NewReader(data), &res)
|
err := markup.PostProcess(markup.NewTestRenderContext(localMetas), strings.NewReader(data), &res)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotContains(t, res.String(), "<html")
|
assert.NotContains(t, res.String(), "<html")
|
||||||
|
|
||||||
data = "<!DOCTYPE html>\n<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
|
data = "<!DOCTYPE html>\n<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
|
||||||
|
|
||||||
res.Reset()
|
res.Reset()
|
||||||
err = markup.PostProcess(markup.NewTestRenderContext(markup.Links{Base: "https://example.com"}, localMetas), strings.NewReader(data), &res)
|
err = markup.PostProcess(markup.NewTestRenderContext(localMetas), strings.NewReader(data), &res)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotContains(t, res.String(), "<html")
|
assert.NotContains(t, res.String(), "<html")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPostProcess_RenderDocument(t *testing.T) {
|
func TestPostProcess_RenderDocument(t *testing.T) {
|
||||||
setting.AppURL = markup.TestAppURL
|
|
||||||
setting.StaticURLPrefix = markup.TestAppURL // can't run standalone
|
setting.StaticURLPrefix = markup.TestAppURL // can't run standalone
|
||||||
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
||||||
|
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
var res strings.Builder
|
var res strings.Builder
|
||||||
err := markup.PostProcess(markup.NewTestRenderContext(
|
err := markup.PostProcess(markup.NewTestRenderContext(markup.TestAppURL, map[string]string{"user": "go-gitea", "repo": "gitea"}), strings.NewReader(input), &res)
|
||||||
markup.Links{
|
|
||||||
AbsolutePrefix: true,
|
|
||||||
Base: "https://example.com",
|
|
||||||
},
|
|
||||||
map[string]string{"user": "go-gitea", "repo": "gitea"},
|
|
||||||
), strings.NewReader(input), &res)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String()))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String()))
|
||||||
}
|
}
|
||||||
@ -612,15 +521,7 @@ func BenchmarkEmojiPostprocess(b *testing.B) {
|
|||||||
|
|
||||||
func TestFuzz(t *testing.T) {
|
func TestFuzz(t *testing.T) {
|
||||||
s := "t/l/issues/8#/../../a"
|
s := "t/l/issues/8#/../../a"
|
||||||
renderContext := markup.NewTestRenderContext(
|
renderContext := markup.NewTestRenderContext()
|
||||||
markup.Links{
|
|
||||||
Base: "https://example.com/go-gitea/gitea",
|
|
||||||
},
|
|
||||||
map[string]string{
|
|
||||||
"user": "go-gitea",
|
|
||||||
"repo": "gitea",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
err := markup.PostProcess(renderContext, strings.NewReader(s), io.Discard)
|
err := markup.PostProcess(renderContext, strings.NewReader(s), io.Discard)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,15 @@
|
|||||||
package markup_test
|
package markup_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
unittest.MainTest(m)
|
setting.IsInTesting = true
|
||||||
|
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
||||||
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
@ -37,8 +37,8 @@ func NewASTTransformer(renderInternal *internal.RenderInternal) *ASTTransformer
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *ASTTransformer) applyElementDir(n ast.Node) {
|
func (g *ASTTransformer) applyElementDir(n ast.Node) {
|
||||||
if markup.DefaultProcessorHelper.ElementDir != "" {
|
if !markup.RenderBehaviorForTesting.DisableAdditionalAttributes {
|
||||||
n.SetAttributeString("dir", []byte(markup.DefaultProcessorHelper.ElementDir))
|
n.SetAttributeString("dir", "auto")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,18 +4,15 @@
|
|||||||
package markdown
|
package markdown
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/unittest"
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
markup.Init(&markup.ProcessorHelper{
|
setting.IsInTesting = true
|
||||||
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
||||||
return username == "r-lyeh"
|
os.Exit(m.Run())
|
||||||
},
|
|
||||||
})
|
|
||||||
unittest.MainTest(m)
|
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
package markdown_test
|
package markdown_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"html/template"
|
"html/template"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
@ -35,60 +35,23 @@ var localMetas = map[string]string{
|
|||||||
"repo": testRepoName,
|
"repo": testRepoName,
|
||||||
}
|
}
|
||||||
|
|
||||||
var localWikiMetas = map[string]string{
|
|
||||||
"user": testRepoOwnerName,
|
|
||||||
"repo": testRepoName,
|
|
||||||
"markupContentMode": "wiki",
|
|
||||||
}
|
|
||||||
|
|
||||||
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_StandardLinks(t *testing.T) {
|
func TestRender_StandardLinks(t *testing.T) {
|
||||||
setting.AppURL = AppURL
|
test := func(input, expected string) {
|
||||||
|
buffer, err := markdown.RenderString(markup.NewTestRenderContext(), input)
|
||||||
test := func(input, expected, expectedWiki string) {
|
|
||||||
buffer, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}), input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
|
||||||
|
|
||||||
buffer, err = markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}, localWikiMetas), input)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
googleRendered := `<p><a href="https://google.com/" rel="nofollow">https://google.com/</a></p>`
|
googleRendered := `<p><a href="https://google.com/" rel="nofollow">https://google.com/</a></p>`
|
||||||
test("<https://google.com/>", googleRendered, googleRendered)
|
test("<https://google.com/>", googleRendered)
|
||||||
|
test("[Link](Link)", `<p><a href="/Link" rel="nofollow">Link</a></p>`)
|
||||||
lnk := util.URLJoin(FullURL, "WikiPage")
|
|
||||||
lnkWiki := util.URLJoin(FullURL, "wiki", "WikiPage")
|
|
||||||
test("[WikiPage](WikiPage)",
|
|
||||||
`<p><a href="`+lnk+`" rel="nofollow">WikiPage</a></p>`,
|
|
||||||
`<p><a href="`+lnkWiki+`" rel="nofollow">WikiPage</a></p>`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRender_Images(t *testing.T) {
|
func TestRender_Images(t *testing.T) {
|
||||||
setting.AppURL = AppURL
|
setting.AppURL = AppURL
|
||||||
|
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
buffer, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}), input)
|
buffer, err := markdown.RenderString(markup.NewTestRenderContext(FullURL), input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
|
||||||
}
|
}
|
||||||
@ -122,12 +85,12 @@ func TestRender_Images(t *testing.T) {
|
|||||||
`<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`)
|
`<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAnswers(baseURLContent, baseURLImages string) []string {
|
func testAnswers(baseURL string) []string {
|
||||||
return []string{
|
return []string{
|
||||||
`<p>Wiki! Enjoy :)</p>
|
`<p>Wiki! Enjoy :)</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="` + baseURLContent + `/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
<li><a href="` + baseURL + `/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
||||||
<li><a href="` + baseURLContent + `/Tips" rel="nofollow">Tips</a></li>
|
<li><a href="` + baseURL + `/Tips" rel="nofollow">Tips</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>See commit <a href="/` + testRepoOwnerName + `/` + testRepoName + `/commit/65f1bf27bc" rel="nofollow"><code>65f1bf27bc</code></a></p>
|
<p>See commit <a href="/` + testRepoOwnerName + `/` + testRepoName + `/commit/65f1bf27bc" rel="nofollow"><code>65f1bf27bc</code></a></p>
|
||||||
<p>Ideas and codes</p>
|
<p>Ideas and codes</p>
|
||||||
@ -135,8 +98,8 @@ func testAnswers(baseURLContent, baseURLImages string) []string {
|
|||||||
<li>Bezier widget (by <a href="/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/ocornut/imgui/issues/786" class="ref-issue" rel="nofollow">ocornut/imgui#786</a></li>
|
<li>Bezier widget (by <a href="/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/ocornut/imgui/issues/786" class="ref-issue" rel="nofollow">ocornut/imgui#786</a></li>
|
||||||
<li>Bezier widget (by <a href="/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="` + FullURL + `issues/786" class="ref-issue" rel="nofollow">#786</a></li>
|
<li>Bezier widget (by <a href="/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="` + FullURL + `issues/786" class="ref-issue" rel="nofollow">#786</a></li>
|
||||||
<li>Node graph editors <a href="https://github.com/ocornut/imgui/issues/306" rel="nofollow">https://github.com/ocornut/imgui/issues/306</a></li>
|
<li>Node graph editors <a href="https://github.com/ocornut/imgui/issues/306" rel="nofollow">https://github.com/ocornut/imgui/issues/306</a></li>
|
||||||
<li><a href="` + baseURLContent + `/memory_editor_example" rel="nofollow">Memory Editor</a></li>
|
<li><a href="` + baseURL + `/memory_editor_example" rel="nofollow">Memory Editor</a></li>
|
||||||
<li><a href="` + baseURLContent + `/plot_var_example" rel="nofollow">Plot var helper</a></li>
|
<li><a href="` + baseURL + `/plot_var_example" rel="nofollow">Plot var helper</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
`,
|
`,
|
||||||
`<h2 id="user-content-what-is-wine-staging">What is Wine Staging?</h2>
|
`<h2 id="user-content-what-is-wine-staging">What is Wine Staging?</h2>
|
||||||
@ -146,14 +109,14 @@ func testAnswers(baseURLContent, baseURLImages string) []string {
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><a href="` + baseURLImages + `/images/icon-install.png" rel="nofollow"><img src="` + baseURLImages + `/images/icon-install.png" title="icon-install.png" alt="images/icon-install.png"/></a></th>
|
<th><a href="` + baseURL + `/images/icon-install.png" rel="nofollow"><img src="` + baseURL + `/images/icon-install.png" title="icon-install.png" alt="images/icon-install.png"/></a></th>
|
||||||
<th><a href="` + baseURLContent + `/Installation" rel="nofollow">Installation</a></th>
|
<th><a href="` + baseURL + `/Installation" rel="nofollow">Installation</a></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="` + baseURLImages + `/images/icon-usage.png" rel="nofollow"><img src="` + baseURLImages + `/images/icon-usage.png" title="icon-usage.png" alt="images/icon-usage.png"/></a></td>
|
<td><a href="` + baseURL + `/images/icon-usage.png" rel="nofollow"><img src="` + baseURL + `/images/icon-usage.png" title="icon-usage.png" alt="images/icon-usage.png"/></a></td>
|
||||||
<td><a href="` + baseURLContent + `/Usage" rel="nofollow">Usage</a></td>
|
<td><a href="` + baseURL + `/Usage" rel="nofollow">Usage</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -161,9 +124,9 @@ func testAnswers(baseURLContent, baseURLImages string) []string {
|
|||||||
`<p><a href="http://www.excelsiorjet.com/" rel="nofollow">Excelsior JET</a> allows you to create native executables for Windows, Linux and Mac OS X.</p>
|
`<p><a href="http://www.excelsiorjet.com/" rel="nofollow">Excelsior JET</a> allows you to create native executables for Windows, Linux and Mac OS X.</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li><a href="https://github.com/libgdx/libgdx/wiki/Gradle-on-the-Commandline#packaging-for-the-desktop" rel="nofollow">Package your libGDX application</a><br/>
|
<li><a href="https://github.com/libgdx/libgdx/wiki/Gradle-on-the-Commandline#packaging-for-the-desktop" rel="nofollow">Package your libGDX application</a><br/>
|
||||||
<a href="` + baseURLImages + `/images/1.png" rel="nofollow"><img src="` + baseURLImages + `/images/1.png" title="1.png" alt="images/1.png"/></a></li>
|
<a href="` + baseURL + `/images/1.png" rel="nofollow"><img src="` + baseURL + `/images/1.png" title="1.png" alt="images/1.png"/></a></li>
|
||||||
<li>Perform a test run by hitting the Run! button.<br/>
|
<li>Perform a test run by hitting the Run! button.<br/>
|
||||||
<a href="` + baseURLImages + `/images/2.png" rel="nofollow"><img src="` + baseURLImages + `/images/2.png" title="2.png" alt="images/2.png"/></a></li>
|
<a href="` + baseURL + `/images/2.png" rel="nofollow"><img src="` + baseURL + `/images/2.png" title="2.png" alt="images/2.png"/></a></li>
|
||||||
</ol>
|
</ol>
|
||||||
<h2 id="user-content-custom-id">More tests</h2>
|
<h2 id="user-content-custom-id">More tests</h2>
|
||||||
<p>(from <a href="https://www.markdownguide.org/extended-syntax/" rel="nofollow">https://www.markdownguide.org/extended-syntax/</a>)</p>
|
<p>(from <a href="https://www.markdownguide.org/extended-syntax/" rel="nofollow">https://www.markdownguide.org/extended-syntax/</a>)</p>
|
||||||
@ -284,66 +247,20 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno
|
|||||||
<!-- test-comment -->`,
|
<!-- test-comment -->`,
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
`<p><a href="` + FullURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
|
|
||||||
`,
|
|
||||||
// special syntax
|
|
||||||
`[[Name|Link]]`,
|
|
||||||
// rendered
|
|
||||||
`<p><a href="` + FullURL + `wiki/Link" rel="nofollow">Name</a></p>
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
func TestTotal_RenderString(t *testing.T) {
|
||||||
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)()
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)()
|
||||||
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
||||||
setting.AppURL = AppURL
|
markup.Init(&markup.RenderHelperFuncs{
|
||||||
answers := testAnswers(util.URLJoin(FullURL, "src", "master"), util.URLJoin(FullURL, "media", "master"))
|
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
||||||
for i := 0; i < len(sameCases); i++ {
|
return username == "r-lyeh"
|
||||||
line, err := markdown.RenderString(markup.NewTestRenderContext(
|
|
||||||
markup.Links{
|
|
||||||
Base: FullURL,
|
|
||||||
BranchPath: "master",
|
|
||||||
},
|
},
|
||||||
newMockRepo(testRepoOwnerName, testRepoName),
|
})
|
||||||
localMetas,
|
answers := testAnswers("")
|
||||||
), sameCases[i])
|
for i := 0; i < len(sameCases); i++ {
|
||||||
|
line, err := markdown.RenderString(markup.NewTestRenderContext(localMetas), sameCases[i])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, answers[i], string(line))
|
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) {
|
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
|
input = strings.ReplaceAll(input, "${SPACE}", " ") // replace ${SPACE} with " ", to avoid some editor's auto-trimming
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Links markup.Links
|
|
||||||
IsWiki bool
|
|
||||||
Expected string
|
Expected string
|
||||||
}{
|
}{
|
||||||
{ // 0
|
{
|
||||||
Links: markup.Links{},
|
|
||||||
IsWiki: false,
|
|
||||||
Expected: `<p>space @mention-user<br/>
|
Expected: `<p>space @mention-user<br/>
|
||||||
/just/a/path.bin<br/>
|
/just/a/path.bin<br/>
|
||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
||||||
@ -638,339 +551,14 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
|||||||
@mention-user test<br/>
|
@mention-user test<br/>
|
||||||
#123<br/>
|
#123<br/>
|
||||||
space</p>
|
space</p>
|
||||||
`,
|
|
||||||
},
|
|
||||||
{ // 1
|
|
||||||
Links: markup.Links{},
|
|
||||||
IsWiki: true,
|
|
||||||
Expected: `<p>space @mention-user<br/>
|
|
||||||
/just/a/path.bin<br/>
|
|
||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
|
||||||
<a href="/wiki/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/wiki/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/wiki/raw/image.jpg" alt="local image"/></a><br/>
|
|
||||||
<a href="/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/wiki/raw/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/wiki/raw/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
|
||||||
<a href="/wiki/raw/image.jpg" rel="nofollow"><img src="/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
|
||||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
|
||||||
@mention-user test<br/>
|
|
||||||
#123<br/>
|
|
||||||
space</p>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{ // 2
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: "https://gitea.io/",
|
|
||||||
},
|
|
||||||
IsWiki: false,
|
|
||||||
Expected: `<p>space @mention-user<br/>
|
|
||||||
/just/a/path.bin<br/>
|
|
||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
|
||||||
<a href="https://gitea.io/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="https://gitea.io/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="https://gitea.io/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/image.jpg" alt="local image"/></a><br/>
|
|
||||||
<a href="https://gitea.io/path/file" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="https://gitea.io/path/file" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
|
||||||
<a href="https://gitea.io/image.jpg" rel="nofollow"><img src="https://gitea.io/image.jpg" title="local image" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
|
||||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
|
||||||
@mention-user test<br/>
|
|
||||||
#123<br/>
|
|
||||||
space</p>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{ // 3
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: "https://gitea.io/",
|
|
||||||
},
|
|
||||||
IsWiki: true,
|
|
||||||
Expected: `<p>space @mention-user<br/>
|
|
||||||
/just/a/path.bin<br/>
|
|
||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
|
||||||
<a href="https://gitea.io/wiki/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="https://gitea.io/wiki/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="https://gitea.io/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/wiki/raw/image.jpg" alt="local image"/></a><br/>
|
|
||||||
<a href="https://gitea.io/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/wiki/raw/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="https://gitea.io/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/wiki/raw/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
|
||||||
<a href="https://gitea.io/wiki/raw/image.jpg" rel="nofollow"><img src="https://gitea.io/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
|
||||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
|
||||||
@mention-user test<br/>
|
|
||||||
#123<br/>
|
|
||||||
space</p>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{ // 4
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: "/relative/path",
|
|
||||||
},
|
|
||||||
IsWiki: false,
|
|
||||||
Expected: `<p>space @mention-user<br/>
|
|
||||||
/just/a/path.bin<br/>
|
|
||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
|
||||||
<a href="/relative/path/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/relative/path/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/relative/path/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/image.jpg" alt="local image"/></a><br/>
|
|
||||||
<a href="/relative/path/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="/relative/path/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
|
||||||
<a href="/relative/path/image.jpg" rel="nofollow"><img src="/relative/path/image.jpg" title="local image" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
|
||||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
|
||||||
@mention-user test<br/>
|
|
||||||
#123<br/>
|
|
||||||
space</p>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{ // 5
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: "/relative/path",
|
|
||||||
},
|
|
||||||
IsWiki: true,
|
|
||||||
Expected: `<p>space @mention-user<br/>
|
|
||||||
/just/a/path.bin<br/>
|
|
||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
|
||||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
|
||||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
|
||||||
@mention-user test<br/>
|
|
||||||
#123<br/>
|
|
||||||
space</p>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{ // 6
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: "/user/repo",
|
|
||||||
BranchPath: "branch/main",
|
|
||||||
},
|
|
||||||
IsWiki: false,
|
|
||||||
Expected: `<p>space @mention-user<br/>
|
|
||||||
/just/a/path.bin<br/>
|
|
||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
|
||||||
<a href="/user/repo/src/branch/main/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/user/repo/src/branch/main/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/user/repo/media/branch/main/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/image.jpg" alt="local image"/></a><br/>
|
|
||||||
<a href="/user/repo/media/branch/main/path/file" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="/user/repo/media/branch/main/path/file" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
|
||||||
<a href="/user/repo/media/branch/main/image.jpg" rel="nofollow"><img src="/user/repo/media/branch/main/image.jpg" title="local image" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
|
||||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
|
||||||
@mention-user test<br/>
|
|
||||||
#123<br/>
|
|
||||||
space</p>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{ // 7
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: "/relative/path",
|
|
||||||
BranchPath: "branch/main",
|
|
||||||
},
|
|
||||||
IsWiki: true,
|
|
||||||
Expected: `<p>space @mention-user<br/>
|
|
||||||
/just/a/path.bin<br/>
|
|
||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
|
||||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
|
||||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
|
||||||
@mention-user test<br/>
|
|
||||||
#123<br/>
|
|
||||||
space</p>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{ // 8
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: "/user/repo",
|
|
||||||
TreePath: "sub/folder",
|
|
||||||
},
|
|
||||||
IsWiki: false,
|
|
||||||
Expected: `<p>space @mention-user<br/>
|
|
||||||
/just/a/path.bin<br/>
|
|
||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
|
||||||
<a href="/user/repo/src/sub/folder/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/user/repo/src/sub/folder/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/user/repo/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/image.jpg" alt="local image"/></a><br/>
|
|
||||||
<a href="/user/repo/path/file" target="_blank" rel="nofollow noopener"><img src="/user/repo/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="/user/repo/path/file" target="_blank" rel="nofollow noopener"><img src="/user/repo/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
|
||||||
<a href="/user/repo/image.jpg" rel="nofollow"><img src="/user/repo/image.jpg" title="local image" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
|
||||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
|
||||||
@mention-user test<br/>
|
|
||||||
#123<br/>
|
|
||||||
space</p>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{ // 9
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: "/relative/path",
|
|
||||||
TreePath: "sub/folder",
|
|
||||||
},
|
|
||||||
IsWiki: true,
|
|
||||||
Expected: `<p>space @mention-user<br/>
|
|
||||||
/just/a/path.bin<br/>
|
|
||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
|
||||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
|
||||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
|
||||||
@mention-user test<br/>
|
|
||||||
#123<br/>
|
|
||||||
space</p>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{ // 10
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: "/user/repo",
|
|
||||||
BranchPath: "branch/main",
|
|
||||||
TreePath: "sub/folder",
|
|
||||||
},
|
|
||||||
IsWiki: false,
|
|
||||||
Expected: `<p>space @mention-user<br/>
|
|
||||||
/just/a/path.bin<br/>
|
|
||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
|
||||||
<a href="/user/repo/src/branch/main/sub/folder/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/user/repo/src/branch/main/sub/folder/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/user/repo/media/branch/main/sub/folder/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/sub/folder/image.jpg" alt="local image"/></a><br/>
|
|
||||||
<a href="/user/repo/media/branch/main/sub/folder/path/file" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/sub/folder/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="/user/repo/media/branch/main/sub/folder/path/file" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/sub/folder/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
|
||||||
<a href="/user/repo/media/branch/main/sub/folder/image.jpg" rel="nofollow"><img src="/user/repo/media/branch/main/sub/folder/image.jpg" title="local image" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
|
||||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
|
||||||
@mention-user test<br/>
|
|
||||||
#123<br/>
|
|
||||||
space</p>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{ // 11
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: "/relative/path",
|
|
||||||
BranchPath: "branch/main",
|
|
||||||
TreePath: "sub/folder",
|
|
||||||
},
|
|
||||||
IsWiki: true,
|
|
||||||
Expected: `<p>space @mention-user<br/>
|
|
||||||
/just/a/path.bin<br/>
|
|
||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
|
||||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
|
||||||
<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
|
|
||||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
|
||||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a><br/>
|
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
|
||||||
@mention-user test<br/>
|
|
||||||
#123<br/>
|
|
||||||
space</p>
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)()
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)()
|
||||||
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
||||||
for i, c := range cases {
|
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.NoError(t, err, "Unexpected error in testcase: %v", i)
|
||||||
assert.Equal(t, c.Expected, string(result), "Unexpected result in testcase %v", i)
|
assert.Equal(t, c.Expected, string(result), "Unexpected result in testcase %v", i)
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,7 @@
|
|||||||
package markdown
|
package markdown
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
giteautil "code.gitea.io/gitea/modules/util"
|
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"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
|
// Check if the destination is a real link
|
||||||
if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) {
|
if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) {
|
||||||
v.Destination = []byte(giteautil.URLJoin(
|
v.Destination = []byte(ctx.RenderHelper.ResolveLink(string(v.Destination), markup.LinkTypeMedia))
|
||||||
ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()),
|
|
||||||
strings.TrimLeft(string(v.Destination), "/"),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parent := v.Parent()
|
parent := v.Parent()
|
||||||
|
@ -9,8 +9,19 @@ import (
|
|||||||
"github.com/yuin/goldmark/ast"
|
"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) {
|
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)
|
v.Destination = []byte(link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
|
|
||||||
"github.com/alecthomas/chroma/v2"
|
"github.com/alecthomas/chroma/v2"
|
||||||
"github.com/alecthomas/chroma/v2/lexers"
|
"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
|
// so we need to try to guess the link kind again here
|
||||||
kind = org.RegularLink{URL: link}.Kind()
|
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" {
|
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
|
return link
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@ -22,34 +21,21 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRender_StandardLinks(t *testing.T) {
|
func TestRender_StandardLinks(t *testing.T) {
|
||||||
test := func(input, expected string, isWiki bool) {
|
test := func(input, expected string) {
|
||||||
buffer, err := RenderString(markup.NewTestRenderContext(
|
buffer, err := RenderString(markup.NewTestRenderContext("/relative-path/media/branch/main/"), input)
|
||||||
markup.Links{
|
|
||||||
Base: "/relative-path",
|
|
||||||
BranchPath: "branch/main",
|
|
||||||
},
|
|
||||||
map[string]string{"markupContentMode": util.Iif(isWiki, "wiki", "")},
|
|
||||||
), input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("[[https://google.com/]]",
|
test("[[https://google.com/]]",
|
||||||
`<p><a href="https://google.com/">https://google.com/</a></p>`, false)
|
`<p><a href="https://google.com/">https://google.com/</a></p>`)
|
||||||
test("[[WikiPage][The WikiPage Desc]]",
|
|
||||||
`<p><a href="/relative-path/wiki/WikiPage">The WikiPage Desc</a></p>`, true)
|
|
||||||
test("[[ImageLink.svg][The Image Desc]]",
|
test("[[ImageLink.svg][The Image Desc]]",
|
||||||
`<p><a href="/relative-path/media/branch/main/ImageLink.svg">The Image Desc</a></p>`, false)
|
`<p><a href="/relative-path/media/branch/main/ImageLink.svg">The Image Desc</a></p>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRender_InternalLinks(t *testing.T) {
|
func TestRender_InternalLinks(t *testing.T) {
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
buffer, err := RenderString(markup.NewTestRenderContext(
|
buffer, err := RenderString(markup.NewTestRenderContext("/relative-path/src/branch/main"), input)
|
||||||
markup.Links{
|
|
||||||
Base: "/relative-path",
|
|
||||||
BranchPath: "branch/main",
|
|
||||||
},
|
|
||||||
), input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
@ -66,7 +52,7 @@ func TestRender_InternalLinks(t *testing.T) {
|
|||||||
|
|
||||||
func TestRender_Media(t *testing.T) {
|
func TestRender_Media(t *testing.T) {
|
||||||
test := func(input, expected string) {
|
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.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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/markup/internal"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"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
|
// * However, many places render the content without setting "mode" in Metas, all these places used comment line break setting incorrectly
|
||||||
ForceHardLineBreak bool
|
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.
|
// 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 {
|
type RenderOptions struct {
|
||||||
|
UseAbsoluteLink bool
|
||||||
|
|
||||||
// relative path from tree root of the branch
|
// relative path from tree root of the branch
|
||||||
RelativePath string
|
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
|
// for file mode, it could be left as empty, and will be detected by file extension in RelativePath
|
||||||
MarkupType string
|
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)
|
// user&repo, format&style®exp (for external issue pattern), teams&org (for mention)
|
||||||
// BranchNameSubURL (for iframe&asciicast)
|
// BranchNameSubURL (for iframe&asciicast)
|
||||||
// markupAllowShortIssuePattern, markupContentMode (wiki)
|
// markupAllowShortIssuePattern
|
||||||
// markdownLineBreakStyle (comment, document)
|
// markdownLineBreakStyle (comment, document)
|
||||||
Metas map[string]string
|
Metas map[string]string
|
||||||
|
|
||||||
@ -64,13 +61,6 @@ type RenderOptions struct {
|
|||||||
InStandalonePage bool
|
InStandalonePage bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type RenderHelper struct {
|
|
||||||
gitRepo *git.Repository
|
|
||||||
repoFacade gitrepo.Repository
|
|
||||||
shaExistCache map[string]bool
|
|
||||||
cancelFn func()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenderContext represents a render context
|
// RenderContext represents a render context
|
||||||
type RenderContext struct {
|
type RenderContext struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@ -101,7 +91,7 @@ func (ctx *RenderContext) Value(key any) any {
|
|||||||
var _ context.Context = (*RenderContext)(nil)
|
var _ context.Context = (*RenderContext)(nil)
|
||||||
|
|
||||||
func NewRenderContext(ctx context.Context) *RenderContext {
|
func NewRenderContext(ctx context.Context) *RenderContext {
|
||||||
return &RenderContext{ctx: ctx}
|
return &RenderContext{ctx: ctx, RenderHelper: &SimpleRenderHelper{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *RenderContext) WithMarkupType(typ string) *RenderContext {
|
func (ctx *RenderContext) WithMarkupType(typ string) *RenderContext {
|
||||||
@ -114,11 +104,6 @@ func (ctx *RenderContext) WithRelativePath(path string) *RenderContext {
|
|||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *RenderContext) WithLinks(links Links) *RenderContext {
|
|
||||||
ctx.RenderOptions.Links = links
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *RenderContext) WithMetas(metas map[string]string) *RenderContext {
|
func (ctx *RenderContext) WithMetas(metas map[string]string) *RenderContext {
|
||||||
ctx.RenderOptions.Metas = metas
|
ctx.RenderOptions.Metas = metas
|
||||||
return ctx
|
return ctx
|
||||||
@ -129,48 +114,16 @@ func (ctx *RenderContext) WithInStandalonePage(v bool) *RenderContext {
|
|||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *RenderContext) WithGitRepo(r *git.Repository) *RenderContext {
|
func (ctx *RenderContext) WithUseAbsoluteLink(v bool) *RenderContext {
|
||||||
ctx.RenderHelper.gitRepo = r
|
ctx.RenderOptions.UseAbsoluteLink = v
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *RenderContext) WithRepoFacade(r gitrepo.Repository) *RenderContext {
|
func (ctx *RenderContext) WithHelper(helper RenderHelper) *RenderContext {
|
||||||
ctx.RenderHelper.repoFacade = r
|
ctx.RenderHelper = helper
|
||||||
return ctx
|
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.
|
// Render renders markup file to HTML with all specific handling stuff.
|
||||||
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
|
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
|
||||||
if ctx.RenderOptions.MarkupType == "" && ctx.RenderOptions.RelativePath != "" {
|
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 {
|
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)
|
finalProcessor := ctx.RenderInternal.Init(output)
|
||||||
defer finalProcessor.Close()
|
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
|
// Init initializes the render global variables
|
||||||
func Init(ph *ProcessorHelper) {
|
func Init(renderHelpFuncs *RenderHelperFuncs) {
|
||||||
if ph != nil {
|
DefaultRenderHelperFuncs = renderHelpFuncs
|
||||||
DefaultProcessorHelper = *ph
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(setting.Markdown.CustomURLSchemes) > 0 {
|
if len(setting.Markdown.CustomURLSchemes) > 0 {
|
||||||
CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes)
|
CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes)
|
||||||
}
|
}
|
||||||
@ -300,23 +254,38 @@ func ComposeSimpleDocumentMetas() map[string]string {
|
|||||||
return map[string]string{"markdownLineBreakStyle": "document"}
|
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
|
// NewTestRenderContext is a helper function to create a RenderContext for testing purpose
|
||||||
// It accepts string (RelativePath), Links, map[string]string (Metas), gitrepo.Repository
|
// It accepts string (BaseLink), map[string]string (Metas)
|
||||||
func NewTestRenderContext(a ...any) *RenderContext {
|
func NewTestRenderContext(baseLinkOrMetas ...any) *RenderContext {
|
||||||
if !setting.IsInTesting {
|
if !setting.IsInTesting {
|
||||||
panic("NewTestRenderContext should only be used in testing")
|
panic("NewTestRenderContext should only be used in testing")
|
||||||
}
|
}
|
||||||
ctx := NewRenderContext(context.Background())
|
helper := &TestRenderHelper{}
|
||||||
for _, v := range a {
|
ctx := NewRenderContext(context.Background()).WithHelper(helper)
|
||||||
|
helper.ctx = ctx
|
||||||
|
for _, v := range baseLinkOrMetas {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case string:
|
case string:
|
||||||
ctx = ctx.WithRelativePath(v)
|
helper.BaseLink = v
|
||||||
case Links:
|
|
||||||
ctx = ctx.WithLinks(v)
|
|
||||||
case map[string]string:
|
case map[string]string:
|
||||||
ctx = ctx.WithMetas(v)
|
ctx = ctx.WithMetas(v)
|
||||||
case gitrepo.Repository:
|
|
||||||
ctx = ctx.WithRepoFacade(v)
|
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unknown type %T", v))
|
panic(fmt.Sprintf("unknown type %T", v))
|
||||||
}
|
}
|
||||||
|
@ -6,16 +6,52 @@ package markup
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProcessorHelper is a helper for the rendering processors (it could be renamed to RenderHelper in the future).
|
type LinkType string
|
||||||
// The main purpose of this helper is to decouple some functions which are not directly available in this package.
|
|
||||||
type ProcessorHelper struct {
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
type RenderHelper interface {
|
||||||
|
CleanUp()
|
||||||
|
|
||||||
|
// 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
|
IsUsernameMentionable func(ctx context.Context, username string) bool
|
||||||
|
|
||||||
ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute
|
|
||||||
|
|
||||||
RenderRepoFileCodePreview func(ctx context.Context, options RenderCodePreviewOptions) (template.HTML, error)
|
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)
|
||||||
|
42
modules/markup/render_link.go
Normal file
42
modules/markup/render_link.go
Normal file
@ -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)
|
||||||
|
}
|
27
modules/markup/render_link_test.go
Normal file
27
modules/markup/render_link_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package markup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
@ -6,7 +6,7 @@ package markup
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -55,7 +55,7 @@ func RegisterRenderer(renderer Renderer) {
|
|||||||
|
|
||||||
// GetRendererByFileName get renderer by filename
|
// GetRendererByFileName get renderer by filename
|
||||||
func GetRendererByFileName(filename string) Renderer {
|
func GetRendererByFileName(filename string) Renderer {
|
||||||
extension := strings.ToLower(filepath.Ext(filename))
|
extension := strings.ToLower(path.Ext(filename))
|
||||||
return extRenderers[extension]
|
return extRenderers[extension]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,9 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy {
|
|||||||
policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
|
policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
|
||||||
policy.AllowAttrs("checked", "disabled", "data-source-position").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{0,2}$`)).OnElements("span")
|
||||||
|
|
||||||
// Custom URL-Schemes
|
// Custom URL-Schemes
|
||||||
if len(setting.Markdown.CustomURLSchemes) > 0 {
|
if len(setting.Markdown.CustomURLSchemes) > 0 {
|
||||||
policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
|
policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
|
||||||
|
@ -19,6 +19,7 @@ func TestSanitizer(t *testing.T) {
|
|||||||
// Code highlighting class
|
// Code highlighting class
|
||||||
`<code class="random string"></code>`, `<code></code>`,
|
`<code class="random string"></code>`, `<code></code>`,
|
||||||
`<code class="language-random ui tab active menu attached animating sidebar following bar center"></code>`, `<code></code>`,
|
`<code class="language-random ui tab active menu attached animating sidebar following bar center"></code>`, `<code></code>`,
|
||||||
|
`<span class="k"></span><span class="nb"></span>`, `<span class="k"></span><span class="nb"></span>`,
|
||||||
|
|
||||||
// Input checkbox
|
// Input checkbox
|
||||||
`<input type="hidden">`, ``,
|
`<input type="hidden">`, ``,
|
||||||
|
@ -59,7 +59,7 @@ func TestMain(m *testing.M) {
|
|||||||
if err := git.InitSimple(context.Background()); err != nil {
|
if err := git.InitSimple(context.Background()); err != nil {
|
||||||
log.Fatal("git init failed, err: %v", err)
|
log.Fatal("git init failed, err: %v", err)
|
||||||
}
|
}
|
||||||
markup.Init(&markup.ProcessorHelper{
|
markup.Init(&markup.RenderHelperFuncs{
|
||||||
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
||||||
return username == "mention-user"
|
return username == "mention-user"
|
||||||
},
|
},
|
||||||
@ -74,7 +74,7 @@ func newTestRenderUtils() *RenderUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderCommitBody(t *testing.T) {
|
func TestRenderCommitBody(t *testing.T) {
|
||||||
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
||||||
type args struct {
|
type args struct {
|
||||||
msg string
|
msg string
|
||||||
}
|
}
|
||||||
@ -145,7 +145,7 @@ func TestRenderCommitMessageLinkSubject(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderIssueTitle(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<SPACE><SPACE>
|
expected := ` space @mention-user<SPACE><SPACE>
|
||||||
/just/a/path.bin
|
/just/a/path.bin
|
||||||
https://example.com/file.bin
|
https://example.com/file.bin
|
||||||
@ -172,7 +172,7 @@ mail@domain.com
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderMarkdownToHtml(t *testing.T) {
|
func TestRenderMarkdownToHtml(t *testing.T) {
|
||||||
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
||||||
expected := `<p>space <a href="/mention-user" rel="nofollow">@mention-user</a><br/>
|
expected := `<p>space <a href="/mention-user" rel="nofollow">@mention-user</a><br/>
|
||||||
/just/a/path.bin
|
/just/a/path.bin
|
||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a>
|
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a>
|
||||||
@ -211,6 +211,7 @@ func TestRenderLabels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUserMention(t *testing.T) {
|
func TestUserMention(t *testing.T) {
|
||||||
|
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
||||||
rendered := newTestRenderUtils().MarkdownToHtml("@no-such-user @mention-user @mention-user")
|
rendered := newTestRenderUtils().MarkdownToHtml("@no-such-user @mention-user @mention-user")
|
||||||
assert.EqualValues(t, `<p>@no-such-user <a href="/mention-user" data-markdown-generated-content="" rel="nofollow">@mention-user</a> <a href="/mention-user" data-markdown-generated-content="" rel="nofollow">@mention-user</a></p>`, strings.TrimSpace(string(rendered)))
|
assert.EqualValues(t, `<p>@no-such-user <a href="/mention-user" rel="nofollow">@mention-user</a> <a href="/mention-user" rel="nofollow">@mention-user</a></p>`, strings.TrimSpace(string(rendered)))
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ const AppURL = "http://localhost:3000/"
|
|||||||
|
|
||||||
func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expectedBody string, expectedCode int) {
|
func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expectedBody string, expectedCode int) {
|
||||||
setting.AppURL = AppURL
|
setting.AppURL = AppURL
|
||||||
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
||||||
context := "/gogits/gogs"
|
context := "/gogits/gogs"
|
||||||
if !wiki {
|
if !wiki {
|
||||||
context += path.Join("/src/branch/main", path.Dir(filePath))
|
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) {
|
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
|
setting.AppURL = AppURL
|
||||||
context := "/gogits/gogs"
|
context := "/gogits/gogs"
|
||||||
if !wiki {
|
if !wiki {
|
||||||
@ -67,7 +67,7 @@ func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAPI_RenderGFM(t *testing.T) {
|
func TestAPI_RenderGFM(t *testing.T) {
|
||||||
markup.Init(&markup.ProcessorHelper{
|
markup.Init(&markup.RenderHelperFuncs{
|
||||||
IsUsernameMentionable: func(ctx go_context.Context, username string) bool {
|
IsUsernameMentionable: func(ctx go_context.Context, username string) bool {
|
||||||
return username == "r-lyeh"
|
return username == "r-lyeh"
|
||||||
},
|
},
|
||||||
@ -182,6 +182,7 @@ var simpleCases = []string{
|
|||||||
|
|
||||||
func TestAPI_RenderSimple(t *testing.T) {
|
func TestAPI_RenderSimple(t *testing.T) {
|
||||||
setting.AppURL = AppURL
|
setting.AppURL = AppURL
|
||||||
|
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
||||||
options := api.MarkdownOption{
|
options := api.MarkdownOption{
|
||||||
Mode: "markdown",
|
Mode: "markdown",
|
||||||
Text: "",
|
Text: "",
|
||||||
@ -199,6 +200,7 @@ func TestAPI_RenderSimple(t *testing.T) {
|
|||||||
|
|
||||||
func TestAPI_RenderRaw(t *testing.T) {
|
func TestAPI_RenderRaw(t *testing.T) {
|
||||||
setting.AppURL = AppURL
|
setting.AppURL = AppURL
|
||||||
|
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
||||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
||||||
for i := 0; i < len(simpleCases); i += 2 {
|
for i := 0; i < len(simpleCases); i += 2 {
|
||||||
ctx.Req.Body = io.NopCloser(strings.NewReader(simpleCases[i]))
|
ctx.Req.Body = io.NopCloser(strings.NewReader(simpleCases[i]))
|
||||||
|
@ -11,6 +11,8 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
|
"code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/modules/httplib"
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
@ -20,7 +22,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// RenderMarkup renders markup text for the /markup and /markdown endpoints
|
// 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}"
|
// 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 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
|
// 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"
|
// for example, when previewing file "/gitea/owner/repo/src/branch/features/feat-123/doc/CHANGE.md", then filePath is "doc/CHANGE.md"
|
||||||
// and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc"
|
// and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc"
|
||||||
|
|
||||||
renderCtx := markup.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" {
|
if mode == "" || mode == "markdown" {
|
||||||
// raw markdown doesn't need any special handling
|
// 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())
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
return
|
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 {
|
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":
|
case "comment":
|
||||||
renderCtx = renderCtx.WithMetas(map[string]string{"markdownLineBreakStyle": "comment"})
|
rctx = renderhelper.NewRenderContextRepoComment(ctx, repoModel, renderhelper.RepoCommentOptions{DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName})
|
||||||
case "wiki":
|
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":
|
case "file":
|
||||||
// render the repo file content by its extension
|
rctx = renderhelper.NewRenderContextRepoFile(ctx, repoModel, renderhelper.RepoFileOptions{
|
||||||
renderCtx = renderCtx.WithMetas(map[string]string{"markdownLineBreakStyle": "document"}).
|
DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName,
|
||||||
WithMarkupType("").
|
CurrentRefPath: refPath, CurrentTreePath: treePath,
|
||||||
WithRelativePath(filePath)
|
})
|
||||||
|
rctx = rctx.WithMarkupType("").WithRelativePath(filePath) // render the repo file content by its extension
|
||||||
default:
|
default:
|
||||||
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
|
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
rctx = rctx.WithUseAbsoluteLink(true)
|
||||||
fields := strings.SplitN(strings.TrimPrefix(urlPathContext, setting.AppSubURL+"/"), "/", 5)
|
if err := markup.Render(rctx, strings.NewReader(text), ctx.Resp); err != nil {
|
||||||
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 {
|
|
||||||
if errors.Is(err, util.ErrInvalidArgument) {
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, err.Error())
|
ctx.Error(http.StatusUnprocessableEntity, err.Error())
|
||||||
} else {
|
} else {
|
||||||
|
@ -13,8 +13,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
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/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
@ -48,22 +48,18 @@ func toReleaseLink(ctx *context.Context, act *activities_model.Action) string {
|
|||||||
return act.GetRepoAbsoluteLink(ctx) + "/releases/tag/" + util.PathEscapeSegments(act.GetBranch())
|
return act.GetRepoAbsoluteLink(ctx) + "/releases/tag/" + util.PathEscapeSegments(act.GetBranch())
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderMarkdown creates a minimal markdown render context from an action.
|
// renderCommentMarkdown renders the comment markdown to html
|
||||||
// If rendering fails, the original markdown text is returned
|
func renderCommentMarkdown(ctx *context.Context, act *activities_model.Action, content string) template.HTML {
|
||||||
func renderMarkdown(ctx *context.Context, act *activities_model.Action, content string) template.HTML {
|
act.LoadRepo(ctx)
|
||||||
markdownCtx := markup.NewRenderContext(ctx).
|
if act.Repo == nil {
|
||||||
WithLinks(markup.Links{
|
return ""
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return templates.SanitizeHTML(content) // old code did so: use SanitizeHTML to render in tmpl
|
|
||||||
}
|
}
|
||||||
return markdown
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, act.Repo).WithUseAbsoluteLink(true)
|
||||||
|
rendered, err := markdown.RenderString(rctx, content)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return rendered
|
||||||
}
|
}
|
||||||
|
|
||||||
// feedActionsToFeedItems convert gitea's Action feed to feeds Item
|
// feedActionsToFeedItems convert gitea's Action feed to feeds Item
|
||||||
@ -225,12 +221,12 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
|
|||||||
|
|
||||||
case activities_model.ActionCreateIssue, activities_model.ActionCreatePullRequest:
|
case activities_model.ActionCreateIssue, activities_model.ActionCreatePullRequest:
|
||||||
desc = strings.Join(act.GetIssueInfos(), "#")
|
desc = strings.Join(act.GetIssueInfos(), "#")
|
||||||
content = renderMarkdown(ctx, act, act.GetIssueContent(ctx))
|
content = renderCommentMarkdown(ctx, act, act.GetIssueContent(ctx))
|
||||||
case activities_model.ActionCommentIssue, activities_model.ActionApprovePullRequest, activities_model.ActionRejectPullRequest, activities_model.ActionCommentPull:
|
case activities_model.ActionCommentIssue, activities_model.ActionApprovePullRequest, activities_model.ActionRejectPullRequest, activities_model.ActionCommentPull:
|
||||||
desc = act.GetIssueTitle(ctx)
|
desc = act.GetIssueTitle(ctx)
|
||||||
comment := act.GetIssueInfos()[1]
|
comment := act.GetIssueInfos()[1]
|
||||||
if len(comment) != 0 {
|
if len(comment) != 0 {
|
||||||
desc += "\n\n" + string(renderMarkdown(ctx, act, comment))
|
desc += "\n\n" + string(renderCommentMarkdown(ctx, act, comment))
|
||||||
}
|
}
|
||||||
case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest:
|
case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest:
|
||||||
desc = act.GetIssueInfos()[1]
|
desc = act.GetIssueInfos()[1]
|
||||||
@ -294,12 +290,8 @@ func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
link := &feeds.Link{Href: rel.HTMLURL()}
|
link := &feeds.Link{Href: rel.HTMLURL()}
|
||||||
content, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, rel.Repo).WithUseAbsoluteLink(true)
|
||||||
WithRepoFacade(rel.Repo).
|
content, err = markdown.RenderString(rctx,
|
||||||
WithLinks(markup.Links{
|
|
||||||
Base: rel.Repo.Link(),
|
|
||||||
}).
|
|
||||||
WithMetas(rel.Repo.ComposeMetas(ctx)),
|
|
||||||
rel.Note)
|
rel.Note)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
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/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
|
||||||
@ -41,9 +41,8 @@ func showUserFeed(ctx *context.Context, formatType string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxUserDescription, err := markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextSimpleDocument(ctx, ctx.ContextUser.HTMLURL())
|
||||||
WithLinks(markup.Links{Base: ctx.ContextUser.HTMLURL()}).
|
ctxUserDescription, err := markdown.RenderString(rctx,
|
||||||
WithMetas(markup.ComposeSimpleDocumentMetas()),
|
|
||||||
ctx.ContextUser.Description)
|
ctx.ContextUser.Description)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
|
@ -11,10 +11,10 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@ -180,16 +180,10 @@ func prepareOrgProfileReadme(ctx *context.Context, viewRepositories bool) bool {
|
|||||||
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
||||||
log.Error("failed to GetBlobContent: %v", err)
|
log.Error("failed to GetBlobContent: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if profileContent, err := markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoFile(ctx, profileDbRepo, renderhelper.RepoFileOptions{
|
||||||
WithGitRepo(profileGitRepo).
|
CurrentRefPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
|
||||||
WithLinks(markup.Links{
|
})
|
||||||
// Pass repo link to markdown render for the full link of media elements.
|
if profileContent, err := markdown.RenderString(rctx, bytes); err != nil {
|
||||||
// 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 {
|
|
||||||
log.Error("failed to RenderString: %v", err)
|
log.Error("failed to RenderString: %v", err)
|
||||||
} else {
|
} else {
|
||||||
ctx.Data["ProfileReadme"] = profileContent
|
ctx.Data["ProfileReadme"] = profileContent
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
unit_model "code.gitea.io/gitea/models/unit"
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@ -392,15 +393,8 @@ func Diff(ctx *context.Context) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
ctx.Data["NoteCommit"] = note.Commit
|
ctx.Data["NoteCommit"] = note.Commit
|
||||||
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
|
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
|
||||||
ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{CurrentRefPath: path.Join("commit", util.PathEscapeSegments(commitID))})
|
||||||
WithLinks(markup.Links{
|
ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(rctx, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
|
||||||
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{}))))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderCommitMessage", err)
|
ctx.ServerError("RenderCommitMessage", err)
|
||||||
return
|
return
|
||||||
|
@ -18,12 +18,12 @@ import (
|
|||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
project_model "code.gitea.io/gitea/models/project"
|
project_model "code.gitea.io/gitea/models/project"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
@ -366,12 +366,8 @@ func UpdateIssueContent(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository)
|
||||||
WithLinks(markup.Links{Base: ctx.FormString("context")}).
|
content, err := markdown.RenderString(rctx, issue.Content)
|
||||||
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
|
||||||
WithGitRepo(ctx.Repo.GitRepo).
|
|
||||||
WithRepoFacade(ctx.Repo.Repository),
|
|
||||||
issue.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -10,10 +10,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -267,12 +267,8 @@ func UpdateCommentContent(ctx *context.Context) {
|
|||||||
|
|
||||||
var renderedContent template.HTML
|
var renderedContent template.HTML
|
||||||
if comment.Content != "" {
|
if comment.Content != "" {
|
||||||
renderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository)
|
||||||
WithLinks(markup.Links{Base: ctx.FormString("context")}).
|
renderedContent, err = markdown.RenderString(rctx, comment.Content)
|
||||||
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
|
||||||
WithGitRepo(ctx.Repo.GitRepo).
|
|
||||||
WithRepoFacade(ctx.Repo.Repository),
|
|
||||||
comment.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
project_model "code.gitea.io/gitea/models/project"
|
project_model "code.gitea.io/gitea/models/project"
|
||||||
pull_model "code.gitea.io/gitea/models/pull"
|
pull_model "code.gitea.io/gitea/models/pull"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@ -359,12 +360,8 @@ func ViewIssue(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.Data["IssueWatch"] = iw
|
ctx.Data["IssueWatch"] = iw
|
||||||
issue.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository)
|
||||||
WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
|
issue.RenderedContent, err = markdown.RenderString(rctx, issue.Content)
|
||||||
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
|
||||||
WithGitRepo(ctx.Repo.GitRepo).
|
|
||||||
WithRepoFacade(ctx.Repo.Repository),
|
|
||||||
issue.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
@ -464,14 +461,8 @@ func ViewIssue(ctx *context.Context) {
|
|||||||
comment.Issue = issue
|
comment.Issue = issue
|
||||||
|
|
||||||
if comment.Type == issues_model.CommentTypeComment || comment.Type == issues_model.CommentTypeReview {
|
if comment.Type == issues_model.CommentTypeComment || comment.Type == issues_model.CommentTypeReview {
|
||||||
comment.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx = renderhelper.NewRenderContextRepoComment(ctx, repo)
|
||||||
WithLinks(markup.Links{
|
comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content)
|
||||||
Base: ctx.Repo.RepoLink,
|
|
||||||
}).
|
|
||||||
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
|
||||||
WithGitRepo(ctx.Repo.GitRepo).
|
|
||||||
WithRepoFacade(ctx.Repo.Repository),
|
|
||||||
comment.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
@ -546,12 +537,8 @@ func ViewIssue(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if comment.Type.HasContentSupport() {
|
} else if comment.Type.HasContentSupport() {
|
||||||
comment.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx = renderhelper.NewRenderContextRepoComment(ctx, repo)
|
||||||
WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
|
comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content)
|
||||||
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
|
||||||
WithGitRepo(ctx.Repo.GitRepo).
|
|
||||||
WithRepoFacade(ctx.Repo.Repository),
|
|
||||||
comment.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -10,8 +10,8 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
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/base"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -79,12 +79,8 @@ func Milestones(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, m := range miles {
|
for _, m := range miles {
|
||||||
m.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository)
|
||||||
WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
|
m.RenderedContent, err = markdown.RenderString(rctx, m.Content)
|
||||||
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
|
||||||
WithGitRepo(ctx.Repo.GitRepo).
|
|
||||||
WithRepoFacade(ctx.Repo.Repository),
|
|
||||||
m.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
@ -265,12 +261,8 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
milestone.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository)
|
||||||
WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
|
milestone.RenderedContent, err = markdown.RenderString(rctx, milestone.Content)
|
||||||
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
|
||||||
WithGitRepo(ctx.Repo.GitRepo).
|
|
||||||
WithRepoFacade(ctx.Repo.Repository),
|
|
||||||
milestone.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -13,11 +13,11 @@ import (
|
|||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
project_model "code.gitea.io/gitea/models/project"
|
project_model "code.gitea.io/gitea/models/project"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -92,12 +92,8 @@ func Projects(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range projects {
|
for i := range projects {
|
||||||
projects[i].RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, repo)
|
||||||
WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
|
projects[i].RenderedContent, err = markdown.RenderString(rctx, projects[i].Description)
|
||||||
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
|
||||||
WithGitRepo(ctx.Repo.GitRepo).
|
|
||||||
WithRepoFacade(ctx.Repo.Repository),
|
|
||||||
projects[i].Description)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
@ -422,12 +418,8 @@ func ViewProject(ctx *context.Context) {
|
|||||||
ctx.Data["SelectLabels"] = selectLabels
|
ctx.Data["SelectLabels"] = selectLabels
|
||||||
ctx.Data["AssigneeID"] = assigneeID
|
ctx.Data["AssigneeID"] = assigneeID
|
||||||
|
|
||||||
project.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository)
|
||||||
WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
|
project.RenderedContent, err = markdown.RenderString(rctx, project.Description)
|
||||||
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
|
||||||
WithGitRepo(ctx.Repo.GitRepo).
|
|
||||||
WithRepoFacade(ctx.Repo.Repository),
|
|
||||||
project.Description)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -13,13 +13,13 @@ import (
|
|||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -114,12 +114,8 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions)
|
|||||||
cacheUsers[r.PublisherID] = r.Publisher
|
cacheUsers[r.PublisherID] = r.Publisher
|
||||||
}
|
}
|
||||||
|
|
||||||
r.RenderedNote, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, r.Repo)
|
||||||
WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
|
r.RenderedNote, err = markdown.RenderString(rctx, r.Note)
|
||||||
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
|
||||||
WithGitRepo(ctx.Repo.GitRepo).
|
|
||||||
WithRepoFacade(ctx.Repo.Repository),
|
|
||||||
r.Note)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
"code.gitea.io/gitea/modules/charset"
|
"code.gitea.io/gitea/modules/charset"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@ -56,17 +57,12 @@ func RenderFile(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = markup.Render(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||||
WithRelativePath(ctx.Repo.TreePath).
|
CurrentRefPath: ctx.Repo.BranchNameSubURL(),
|
||||||
WithLinks(markup.Links{
|
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
||||||
Base: ctx.Repo.RepoLink,
|
}).WithRelativePath(ctx.Repo.TreePath).WithInStandalonePage(true)
|
||||||
BranchPath: ctx.Repo.BranchNameSubURL(),
|
|
||||||
TreePath: path.Dir(ctx.Repo.TreePath),
|
err = markup.Render(rctx, rd, ctx.Resp)
|
||||||
}).
|
|
||||||
WithMetas(ctx.Repo.Repository.ComposeDocumentMetas(ctx)).
|
|
||||||
WithGitRepo(ctx.Repo.GitRepo).
|
|
||||||
WithInStandalonePage(true),
|
|
||||||
rd, ctx.Resp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to render file %q: %v", ctx.Repo.TreePath, err)
|
log.Error("Failed to render file %q: %v", ctx.Repo.TreePath, err)
|
||||||
http.Error(ctx.Resp, "Failed to render file", http.StatusInternalServerError)
|
http.Error(ctx.Resp, "Failed to render file", http.StatusInternalServerError)
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issue_model "code.gitea.io/gitea/models/issues"
|
issue_model "code.gitea.io/gitea/models/issues"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
unit_model "code.gitea.io/gitea/models/unit"
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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["IsMarkup"] = true
|
||||||
ctx.Data["MarkupType"] = markupType
|
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{
|
||||||
WithMarkupType(markupType).
|
CurrentRefPath: ctx.Repo.BranchNameSubURL(),
|
||||||
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).
|
CurrentTreePath: path.Join(ctx.Repo.TreePath, subfolder),
|
||||||
WithLinks(markup.Links{
|
|
||||||
Base: ctx.Repo.RepoLink,
|
|
||||||
BranchPath: ctx.Repo.BranchNameSubURL(),
|
|
||||||
TreePath: path.Join(ctx.Repo.TreePath, subfolder),
|
|
||||||
}).
|
}).
|
||||||
WithMetas(ctx.Repo.Repository.ComposeDocumentMetas(ctx)).
|
WithMarkupType(markupType).
|
||||||
WithGitRepo(ctx.Repo.GitRepo),
|
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).
|
||||||
rd)
|
|
||||||
|
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
|
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
|
||||||
delete(ctx.Data, "IsMarkup")
|
delete(ctx.Data, "IsMarkup")
|
||||||
@ -513,17 +511,15 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
|
|||||||
ctx.Data["MarkupType"] = markupType
|
ctx.Data["MarkupType"] = markupType
|
||||||
metas := ctx.Repo.Repository.ComposeDocumentMetas(ctx)
|
metas := ctx.Repo.Repository.ComposeDocumentMetas(ctx)
|
||||||
metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
|
metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
|
||||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||||
|
CurrentRefPath: ctx.Repo.BranchNameSubURL(),
|
||||||
|
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
||||||
|
}).
|
||||||
WithMarkupType(markupType).
|
WithMarkupType(markupType).
|
||||||
WithRelativePath(ctx.Repo.TreePath).
|
WithRelativePath(ctx.Repo.TreePath).
|
||||||
WithLinks(markup.Links{
|
WithMetas(metas)
|
||||||
Base: ctx.Repo.RepoLink,
|
|
||||||
BranchPath: ctx.Repo.BranchNameSubURL(),
|
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
|
||||||
TreePath: path.Dir(ctx.Repo.TreePath),
|
|
||||||
}).
|
|
||||||
WithMetas(metas).
|
|
||||||
WithGitRepo(ctx.Repo.GitRepo),
|
|
||||||
rd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("Render", err)
|
ctx.ServerError("Render", err)
|
||||||
return
|
return
|
||||||
@ -604,17 +600,15 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
|
|||||||
rd := io.MultiReader(bytes.NewReader(buf), dataRc)
|
rd := io.MultiReader(bytes.NewReader(buf), dataRc)
|
||||||
ctx.Data["IsMarkup"] = true
|
ctx.Data["IsMarkup"] = true
|
||||||
ctx.Data["MarkupType"] = markupType
|
ctx.Data["MarkupType"] = markupType
|
||||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, markup.NewRenderContext(ctx).
|
|
||||||
WithMarkupType(markupType).
|
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||||
WithRelativePath(ctx.Repo.TreePath).
|
CurrentRefPath: ctx.Repo.BranchNameSubURL(),
|
||||||
WithLinks(markup.Links{
|
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
||||||
Base: ctx.Repo.RepoLink,
|
|
||||||
BranchPath: ctx.Repo.BranchNameSubURL(),
|
|
||||||
TreePath: path.Dir(ctx.Repo.TreePath),
|
|
||||||
}).
|
}).
|
||||||
WithMetas(ctx.Repo.Repository.ComposeDocumentMetas(ctx)).
|
WithMarkupType(markupType).
|
||||||
WithGitRepo(ctx.Repo.GitRepo),
|
WithRelativePath(ctx.Repo.TreePath)
|
||||||
rd)
|
|
||||||
|
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("Render", err)
|
ctx.ServerError("Render", err)
|
||||||
return
|
return
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
@ -288,11 +289,9 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
|
|||||||
footerContent = data
|
footerContent = data
|
||||||
}
|
}
|
||||||
|
|
||||||
rctx := markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoWiki(ctx, ctx.Repo.Repository)
|
||||||
WithMetas(ctx.Repo.Repository.ComposeWikiMetas(ctx)).
|
|
||||||
WithLinks(markup.Links{Base: ctx.Repo.RepoLink})
|
|
||||||
buf := &strings.Builder{}
|
|
||||||
|
|
||||||
|
buf := &strings.Builder{}
|
||||||
renderFn := func(data []byte) (escaped *charset.EscapeStatus, output string, err error) {
|
renderFn := func(data []byte) (escaped *charset.EscapeStatus, output string, err error) {
|
||||||
markupRd, markupWr := io.Pipe()
|
markupRd, markupWr := io.Pipe()
|
||||||
defer markupWr.Close()
|
defer markupWr.Close()
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@ -27,7 +28,6 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
|
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -257,11 +257,8 @@ func Milestones(ctx *context.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
milestones[i].RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, milestones[i].Repo)
|
||||||
WithLinks(markup.Links{Base: milestones[i].Repo.Link()}).
|
milestones[i].RenderedContent, err = markdown.RenderString(rctx, milestones[i].Content)
|
||||||
WithMetas(milestones[i].Repo.ComposeMetas(ctx)).
|
|
||||||
WithRepoFacade(milestones[i].Repo),
|
|
||||||
milestones[i].Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -12,12 +12,12 @@ import (
|
|||||||
|
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -72,17 +72,17 @@ func userProfile(ctx *context.Context) {
|
|||||||
ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data)
|
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()
|
defer profileClose()
|
||||||
|
|
||||||
showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
|
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
|
// call PrepareContextForProfileBigAvatar later to avoid re-querying the NumFollowers & NumFollowing
|
||||||
shared_user.PrepareContextForProfileBigAvatar(ctx)
|
shared_user.PrepareContextForProfileBigAvatar(ctx)
|
||||||
ctx.HTML(http.StatusOK, tplProfile)
|
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 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
|
// if there is not a profile readme, the overview tab should be treated as the repositories tab
|
||||||
tab := ctx.FormString("tab")
|
tab := ctx.FormString("tab")
|
||||||
@ -246,18 +246,10 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
|
|||||||
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
||||||
log.Error("failed to GetBlobContent: %v", err)
|
log.Error("failed to GetBlobContent: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if profileContent, err := markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoFile(ctx, profileDbRepo, renderhelper.RepoFileOptions{
|
||||||
WithGitRepo(profileGitRepo).
|
CurrentRefPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
|
||||||
WithLinks(markup.Links{
|
})
|
||||||
// Give the repo link to the markdown render for the full link of media element.
|
if profileContent, err := markdown.RenderString(rctx, bytes); err != nil {
|
||||||
// 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 {
|
|
||||||
log.Error("failed to RenderString: %v", err)
|
log.Error("failed to RenderString: %v", err)
|
||||||
} else {
|
} else {
|
||||||
ctx.Data["ProfileReadme"] = profileContent
|
ctx.Data["ProfileReadme"] = profileContent
|
||||||
|
@ -18,12 +18,12 @@ import (
|
|||||||
|
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/emoji"
|
"code.gitea.io/gitea/modules/emoji"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"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
|
// This is the body of the new issue or comment, not the mail body
|
||||||
body, err := markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoComment(ctx.Context, ctx.Issue.Repo).WithUseAbsoluteLink(true)
|
||||||
WithRepoFacade(ctx.Issue.Repo).
|
body, err := markdown.RenderString(rctx,
|
||||||
WithLinks(markup.Links{AbsolutePrefix: true, Base: ctx.Issue.Repo.HTMLURL()}).
|
|
||||||
WithMetas(ctx.Issue.Repo.ComposeMetas(ctx)),
|
|
||||||
ctx.Content)
|
ctx.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -7,11 +7,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"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)
|
locale := translation.NewLocale(lang)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
rel.RenderedNote, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, rel.Repo).WithUseAbsoluteLink(true)
|
||||||
WithRepoFacade(rel.Repo).
|
rel.RenderedNote, err = markdown.RenderString(rctx,
|
||||||
WithLinks(markup.Links{Base: rel.Repo.HTMLURL()}).
|
|
||||||
WithMetas(rel.Repo.ComposeMetas(ctx)),
|
|
||||||
rel.Note)
|
rel.Note)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("markdown.RenderString(%d): %v", rel.RepoID, err)
|
log.Error("markdown.RenderString(%d): %v", rel.RepoID, err)
|
||||||
|
@ -70,7 +70,7 @@ func prepareMailerTest(t *testing.T) (doer *user_model.User, repo *repo_model.Re
|
|||||||
func TestComposeIssueCommentMessage(t *testing.T) {
|
func TestComposeIssueCommentMessage(t *testing.T) {
|
||||||
doer, _, issue, comment := prepareMailerTest(t)
|
doer, _, issue, comment := prepareMailerTest(t)
|
||||||
|
|
||||||
markup.Init(&markup.ProcessorHelper{
|
markup.Init(&markup.RenderHelperFuncs{
|
||||||
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
||||||
return username == doer.Name
|
return username == doer.Name
|
||||||
},
|
},
|
||||||
|
@ -11,10 +11,8 @@ import (
|
|||||||
gitea_context "code.gitea.io/gitea/services/context"
|
gitea_context "code.gitea.io/gitea/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ProcessorHelper() *markup.ProcessorHelper {
|
func ProcessorHelper() *markup.RenderHelperFuncs {
|
||||||
return &markup.ProcessorHelper{
|
return &markup.RenderHelperFuncs{
|
||||||
ElementDir: "auto", // set dir="auto" for necessary (eg: <p>, <h?>, etc) tags
|
|
||||||
|
|
||||||
RenderRepoFileCodePreview: renderRepoFileCodePreview,
|
RenderRepoFileCodePreview: renderRepoFileCodePreview,
|
||||||
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
||||||
mentionedUser, err := user.GetUserByName(ctx, username)
|
mentionedUser, err := user.GetUserByName(ctx, username)
|
||||||
|
@ -5,7 +5,6 @@ package fuzz
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -15,9 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func newFuzzRenderContext() *markup.RenderContext {
|
func newFuzzRenderContext() *markup.RenderContext {
|
||||||
return markup.NewRenderContext(context.Background()).
|
return markup.NewTestRenderContext("https://example.com/go-gitea/gitea", map[string]string{"user": "go-gitea", "repo": "gitea"})
|
||||||
WithLinks(markup.Links{Base: "https://example.com/go-gitea/gitea"}).
|
|
||||||
WithMetas(map[string]string{"user": "go-gitea", "repo": "gitea"})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func FuzzMarkdownRenderRaw(f *testing.F) {
|
func FuzzMarkdownRenderRaw(f *testing.F) {
|
||||||
|
Loading…
Reference in New Issue
Block a user