]> source.dussan.org Git - gitea.git/commitdiff
Fix and refactor markdown rendering (#32522)
authorwxiaoguang <wxiaoguang@gmail.com>
Sat, 16 Nov 2024 08:41:44 +0000 (16:41 +0800)
committerGitHub <noreply@github.com>
Sat, 16 Nov 2024 08:41:44 +0000 (08:41 +0000)
27 files changed:
models/repo/repo.go
models/repo/repo_test.go
modules/markup/html.go
modules/markup/html_commit.go
modules/markup/html_email.go
modules/markup/html_emoji.go
modules/markup/html_internal_test.go
modules/markup/html_issue.go
modules/markup/html_link.go
modules/markup/html_node.go
modules/markup/html_test.go
modules/markup/markdown/goldmark.go
modules/markup/markdown/markdown_test.go
modules/markup/markdown/transform_image.go
modules/markup/orgmode/orgmode.go
modules/markup/orgmode/orgmode_test.go
modules/markup/render.go
modules/markup/render_links.go
modules/templates/util_render.go
modules/templates/util_render_test.go
routers/common/markup.go
routers/web/feed/convert.go
routers/web/feed/profile.go
routers/web/org/home.go
routers/web/repo/wiki.go
routers/web/shared/user/header.go
templates/repo/view_file.tmpl

index 4776ff0b9ca25c8216693cad955cf115d9e0cdb3..7d78cee2877d46a01c5b6da828efd13842c8fd16 100644 (file)
@@ -7,6 +7,7 @@ import (
        "context"
        "fmt"
        "html/template"
+       "maps"
        "net"
        "net/url"
        "path/filepath"
@@ -165,10 +166,10 @@ type Repository struct {
 
        Status RepositoryStatus `xorm:"NOT NULL DEFAULT 0"`
 
-       RenderingMetas         map[string]string `xorm:"-"`
-       DocumentRenderingMetas map[string]string `xorm:"-"`
-       Units                  []*RepoUnit       `xorm:"-"`
-       PrimaryLanguage        *LanguageStat     `xorm:"-"`
+       commonRenderingMetas map[string]string `xorm:"-"`
+
+       Units           []*RepoUnit   `xorm:"-"`
+       PrimaryLanguage *LanguageStat `xorm:"-"`
 
        IsFork                          bool               `xorm:"INDEX NOT NULL DEFAULT false"`
        ForkID                          int64              `xorm:"INDEX"`
@@ -473,9 +474,8 @@ func (repo *Repository) MustOwner(ctx context.Context) *user_model.User {
        return repo.Owner
 }
 
-// ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers.
-func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string {
-       if len(repo.RenderingMetas) == 0 {
+func (repo *Repository) composeCommonMetas(ctx context.Context) map[string]string {
+       if len(repo.commonRenderingMetas) == 0 {
                metas := map[string]string{
                        "user": repo.OwnerName,
                        "repo": repo.Name,
@@ -508,21 +508,34 @@ func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string {
                        metas["org"] = strings.ToLower(repo.OwnerName)
                }
 
-               repo.RenderingMetas = metas
+               repo.commonRenderingMetas = metas
        }
-       return repo.RenderingMetas
+       return repo.commonRenderingMetas
+}
+
+// ComposeMetas composes a map of metas for properly rendering comments or comment-like contents (commit message)
+func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string {
+       metas := maps.Clone(repo.composeCommonMetas(ctx))
+       metas["markdownLineBreakStyle"] = "comment"
+       metas["markupAllowShortIssuePattern"] = "true"
+       return metas
 }
 
-// ComposeDocumentMetas composes a map of metas for properly rendering documents
+// ComposeWikiMetas composes a map of metas for properly rendering wikis
+func (repo *Repository) ComposeWikiMetas(ctx context.Context) map[string]string {
+       // does wiki need the "teams" and "org" from common metas?
+       metas := maps.Clone(repo.composeCommonMetas(ctx))
+       metas["markdownLineBreakStyle"] = "document"
+       metas["markupAllowShortIssuePattern"] = "true"
+       return metas
+}
+
+// ComposeDocumentMetas composes a map of metas for properly rendering documents (repo files)
 func (repo *Repository) ComposeDocumentMetas(ctx context.Context) map[string]string {
-       if len(repo.DocumentRenderingMetas) == 0 {
-               metas := map[string]string{}
-               for k, v := range repo.ComposeMetas(ctx) {
-                       metas[k] = v
-               }
-               repo.DocumentRenderingMetas = metas
-       }
-       return repo.DocumentRenderingMetas
+       // does document(file) need the "teams" and "org" from common metas?
+       metas := maps.Clone(repo.composeCommonMetas(ctx))
+       metas["markdownLineBreakStyle"] = "document"
+       return metas
 }
 
 // GetBaseRepo populates repo.BaseRepo for a fork repository and
index c13b698abf14858787c916a3221e738508f45780..6468e0f605889f159883bb545fe2c6d37c80a904 100644 (file)
@@ -1,13 +1,12 @@
 // Copyright 2017 The Gitea Authors. All rights reserved.
 // SPDX-License-Identifier: MIT
 
-package repo_test
+package repo
 
 import (
        "testing"
 
        "code.gitea.io/gitea/models/db"
-       repo_model "code.gitea.io/gitea/models/repo"
        "code.gitea.io/gitea/models/unit"
        "code.gitea.io/gitea/models/unittest"
        user_model "code.gitea.io/gitea/models/user"
@@ -20,18 +19,18 @@ import (
 )
 
 var (
-       countRepospts        = repo_model.CountRepositoryOptions{OwnerID: 10}
-       countReposptsPublic  = repo_model.CountRepositoryOptions{OwnerID: 10, Private: optional.Some(false)}
-       countReposptsPrivate = repo_model.CountRepositoryOptions{OwnerID: 10, Private: optional.Some(true)}
+       countRepospts        = CountRepositoryOptions{OwnerID: 10}
+       countReposptsPublic  = CountRepositoryOptions{OwnerID: 10, Private: optional.Some(false)}
+       countReposptsPrivate = CountRepositoryOptions{OwnerID: 10, Private: optional.Some(true)}
 )
 
 func TestGetRepositoryCount(t *testing.T) {
        assert.NoError(t, unittest.PrepareTestDatabase())
 
        ctx := db.DefaultContext
-       count, err1 := repo_model.CountRepositories(ctx, countRepospts)
-       privateCount, err2 := repo_model.CountRepositories(ctx, countReposptsPrivate)
-       publicCount, err3 := repo_model.CountRepositories(ctx, countReposptsPublic)
+       count, err1 := CountRepositories(ctx, countRepospts)
+       privateCount, err2 := CountRepositories(ctx, countReposptsPrivate)
+       publicCount, err3 := CountRepositories(ctx, countReposptsPublic)
        assert.NoError(t, err1)
        assert.NoError(t, err2)
        assert.NoError(t, err3)
@@ -42,7 +41,7 @@ func TestGetRepositoryCount(t *testing.T) {
 func TestGetPublicRepositoryCount(t *testing.T) {
        assert.NoError(t, unittest.PrepareTestDatabase())
 
-       count, err := repo_model.CountRepositories(db.DefaultContext, countReposptsPublic)
+       count, err := CountRepositories(db.DefaultContext, countReposptsPublic)
        assert.NoError(t, err)
        assert.Equal(t, int64(1), count)
 }
@@ -50,14 +49,14 @@ func TestGetPublicRepositoryCount(t *testing.T) {
 func TestGetPrivateRepositoryCount(t *testing.T) {
        assert.NoError(t, unittest.PrepareTestDatabase())
 
-       count, err := repo_model.CountRepositories(db.DefaultContext, countReposptsPrivate)
+       count, err := CountRepositories(db.DefaultContext, countReposptsPrivate)
        assert.NoError(t, err)
        assert.Equal(t, int64(2), count)
 }
 
 func TestRepoAPIURL(t *testing.T) {
        assert.NoError(t, unittest.PrepareTestDatabase())
-       repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
+       repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 10})
 
        assert.Equal(t, "https://try.gitea.io/api/v1/repos/user12/repo10", repo.APIURL())
 }
@@ -65,22 +64,22 @@ func TestRepoAPIURL(t *testing.T) {
 func TestWatchRepo(t *testing.T) {
        assert.NoError(t, unittest.PrepareTestDatabase())
 
-       repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
+       repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 3})
        user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 
-       assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, user, repo, true))
-       unittest.AssertExistsAndLoadBean(t, &repo_model.Watch{RepoID: repo.ID, UserID: user.ID})
-       unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
+       assert.NoError(t, WatchRepo(db.DefaultContext, user, repo, true))
+       unittest.AssertExistsAndLoadBean(t, &Watch{RepoID: repo.ID, UserID: user.ID})
+       unittest.CheckConsistencyFor(t, &Repository{ID: repo.ID})
 
-       assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, user, repo, false))
-       unittest.AssertNotExistsBean(t, &repo_model.Watch{RepoID: repo.ID, UserID: user.ID})
-       unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
+       assert.NoError(t, WatchRepo(db.DefaultContext, user, repo, false))
+       unittest.AssertNotExistsBean(t, &Watch{RepoID: repo.ID, UserID: user.ID})
+       unittest.CheckConsistencyFor(t, &Repository{ID: repo.ID})
 }
 
 func TestMetas(t *testing.T) {
        assert.NoError(t, unittest.PrepareTestDatabase())
 
-       repo := &repo_model.Repository{Name: "testRepo"}
+       repo := &Repository{Name: "testRepo"}
        repo.Owner = &user_model.User{Name: "testOwner"}
        repo.OwnerName = repo.Owner.Name
 
@@ -90,16 +89,16 @@ func TestMetas(t *testing.T) {
        assert.Equal(t, "testRepo", metas["repo"])
        assert.Equal(t, "testOwner", metas["user"])
 
-       externalTracker := repo_model.RepoUnit{
+       externalTracker := RepoUnit{
                Type: unit.TypeExternalTracker,
-               Config: &repo_model.ExternalTrackerConfig{
+               Config: &ExternalTrackerConfig{
                        ExternalTrackerFormat: "https://someurl.com/{user}/{repo}/{issue}",
                },
        }
 
        testSuccess := func(expectedStyle string) {
-               repo.Units = []*repo_model.RepoUnit{&externalTracker}
-               repo.RenderingMetas = nil
+               repo.Units = []*RepoUnit{&externalTracker}
+               repo.commonRenderingMetas = nil
                metas := repo.ComposeMetas(db.DefaultContext)
                assert.Equal(t, expectedStyle, metas["style"])
                assert.Equal(t, "testRepo", metas["repo"])
@@ -118,7 +117,7 @@ func TestMetas(t *testing.T) {
        externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleRegexp
        testSuccess(markup.IssueNameStyleRegexp)
 
-       repo, err := repo_model.GetRepositoryByID(db.DefaultContext, 3)
+       repo, err := GetRepositoryByID(db.DefaultContext, 3)
        assert.NoError(t, err)
 
        metas = repo.ComposeMetas(db.DefaultContext)
@@ -132,7 +131,7 @@ func TestGetRepositoryByURL(t *testing.T) {
        assert.NoError(t, unittest.PrepareTestDatabase())
 
        t.Run("InvalidPath", func(t *testing.T) {
-               repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, "something")
+               repo, err := GetRepositoryByURL(db.DefaultContext, "something")
 
                assert.Nil(t, repo)
                assert.Error(t, err)
@@ -140,7 +139,7 @@ func TestGetRepositoryByURL(t *testing.T) {
 
        t.Run("ValidHttpURL", func(t *testing.T) {
                test := func(t *testing.T, url string) {
-                       repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, url)
+                       repo, err := GetRepositoryByURL(db.DefaultContext, url)
 
                        assert.NotNil(t, repo)
                        assert.NoError(t, err)
@@ -155,7 +154,7 @@ func TestGetRepositoryByURL(t *testing.T) {
 
        t.Run("ValidGitSshURL", func(t *testing.T) {
                test := func(t *testing.T, url string) {
-                       repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, url)
+                       repo, err := GetRepositoryByURL(db.DefaultContext, url)
 
                        assert.NotNil(t, repo)
                        assert.NoError(t, err)
@@ -173,7 +172,7 @@ func TestGetRepositoryByURL(t *testing.T) {
 
        t.Run("ValidImplicitSshURL", func(t *testing.T) {
                test := func(t *testing.T, url string) {
-                       repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, url)
+                       repo, err := GetRepositoryByURL(db.DefaultContext, url)
 
                        assert.NotNil(t, repo)
                        assert.NoError(t, err)
@@ -200,21 +199,21 @@ func TestComposeSSHCloneURL(t *testing.T) {
        setting.SSH.Domain = "domain"
        setting.SSH.Port = 22
        setting.Repository.UseCompatSSHURI = false
-       assert.Equal(t, "git@domain:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
+       assert.Equal(t, "git@domain:user/repo.git", ComposeSSHCloneURL("user", "repo"))
        setting.Repository.UseCompatSSHURI = true
-       assert.Equal(t, "ssh://git@domain/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
+       assert.Equal(t, "ssh://git@domain/user/repo.git", ComposeSSHCloneURL("user", "repo"))
        // test SSH_DOMAIN while use non-standard SSH port
        setting.SSH.Port = 123
        setting.Repository.UseCompatSSHURI = false
-       assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
+       assert.Equal(t, "ssh://git@domain:123/user/repo.git", ComposeSSHCloneURL("user", "repo"))
        setting.Repository.UseCompatSSHURI = true
-       assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
+       assert.Equal(t, "ssh://git@domain:123/user/repo.git", ComposeSSHCloneURL("user", "repo"))
 
        // test IPv6 SSH_DOMAIN
        setting.Repository.UseCompatSSHURI = false
        setting.SSH.Domain = "::1"
        setting.SSH.Port = 22
-       assert.Equal(t, "git@[::1]:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
+       assert.Equal(t, "git@[::1]:user/repo.git", ComposeSSHCloneURL("user", "repo"))
        setting.SSH.Port = 123
-       assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
+       assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", ComposeSSHCloneURL("user", "repo"))
 }
index 54c65c95d27239513a245ca4ad85118b2493cb1c..16ccd4b40672f7a7873a9f484d29ba8b0bd86e33 100644 (file)
@@ -7,11 +7,11 @@ import (
        "bytes"
        "io"
        "regexp"
+       "slices"
        "strings"
        "sync"
 
        "code.gitea.io/gitea/modules/markup/common"
-       "code.gitea.io/gitea/modules/setting"
 
        "golang.org/x/net/html"
        "golang.org/x/net/html/atom"
@@ -25,7 +25,27 @@ const (
        IssueNameStyleRegexp       = "regexp"
 )
 
-var (
+// CSS class for action keywords (e.g. "closes: #1")
+const keywordClass = "issue-keyword"
+
+type globalVarsType struct {
+       hashCurrentPattern      *regexp.Regexp
+       shortLinkPattern        *regexp.Regexp
+       anyHashPattern          *regexp.Regexp
+       comparePattern          *regexp.Regexp
+       fullURLPattern          *regexp.Regexp
+       emailRegex              *regexp.Regexp
+       blackfridayExtRegex     *regexp.Regexp
+       emojiShortCodeRegex     *regexp.Regexp
+       issueFullPattern        *regexp.Regexp
+       filesChangedFullPattern *regexp.Regexp
+
+       tagCleaner *regexp.Regexp
+       nulCleaner *strings.Replacer
+}
+
+var globalVars = sync.OnceValue[*globalVarsType](func() *globalVarsType {
+       v := &globalVarsType{}
        // NOTE: All below regex matching do not perform any extra validation.
        // Thus a link is produced even if the linked entity does not exist.
        // While fast, this is also incorrect and lead to false positives.
@@ -36,79 +56,56 @@ var (
        // hashCurrentPattern matches string that represents a commit SHA, e.g. d8a994ef243349f321568f9e36d5c3f444b99cae
        // Although SHA1 hashes are 40 chars long, SHA256 are 64, the regex matches the hash from 7 to 64 chars in length
        // so that abbreviated hash links can be used as well. This matches git and GitHub usability.
-       hashCurrentPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-f]{7,64})(?:\s|$|\)|\]|[.,:](\s|$))`)
+       v.hashCurrentPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-f]{7,64})(?:\s|$|\)|\]|[.,:](\s|$))`)
 
        // shortLinkPattern matches short but difficult to parse [[name|link|arg=test]] syntax
-       shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`)
+       v.shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`)
 
        // anyHashPattern splits url containing SHA into parts
-       anyHashPattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~%./\w]+)?(\?[-+~%.\w&=]+)?(#[-+~%.\w]+)?`)
+       v.anyHashPattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~%./\w]+)?(\?[-+~%.\w&=]+)?(#[-+~%.\w]+)?`)
 
        // comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash"
-       comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?`)
+       v.comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?`)
 
        // fullURLPattern matches full URL like "mailto:...", "https://..." and "ssh+git://..."
-       fullURLPattern = regexp.MustCompile(`^[a-z][-+\w]+:`)
+       v.fullURLPattern = regexp.MustCompile(`^[a-z][-+\w]+:`)
 
        // emailRegex is definitely not perfect with edge cases,
        // it is still accepted by the CommonMark specification, as well as the HTML5 spec:
        //   http://spec.commonmark.org/0.28/#email-address
        //   https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail)
-       emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|;|,|\\?|!|\\.(\\s|$))")
+       v.emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|;|,|\\?|!|\\.(\\s|$))")
 
        // blackfridayExtRegex is for blackfriday extensions create IDs like fn:user-content-footnote
-       blackfridayExtRegex = regexp.MustCompile(`[^:]*:user-content-`)
+       v.blackfridayExtRegex = regexp.MustCompile(`[^:]*:user-content-`)
 
        // emojiShortCodeRegex find emoji by alias like :smile:
-       emojiShortCodeRegex = regexp.MustCompile(`:[-+\w]+:`)
-)
+       v.emojiShortCodeRegex = regexp.MustCompile(`:[-+\w]+:`)
 
-// CSS class for action keywords (e.g. "closes: #1")
-const keywordClass = "issue-keyword"
+       // example: https://domain/org/repo/pulls/27#hash
+       v.issueFullPattern = regexp.MustCompile(`https?://(?:\S+/)[\w_.-]+/[\w_.-]+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b`)
+
+       // example: https://domain/org/repo/pulls/27/files#hash
+       v.filesChangedFullPattern = regexp.MustCompile(`https?://(?:\S+/)[\w_.-]+/[\w_.-]+/pulls/((?:\w{1,10}-)?[1-9][0-9]*)/files([\?|#](\S+)?)?\b`)
+
+       v.tagCleaner = regexp.MustCompile(`<((?:/?\w+/\w+)|(?:/[\w ]+/)|(/?[hH][tT][mM][lL]\b)|(/?[hH][eE][aA][dD]\b))`)
+       v.nulCleaner = strings.NewReplacer("\000", "")
+       return v
+})
 
 // IsFullURLBytes reports whether link fits valid format.
 func IsFullURLBytes(link []byte) bool {
-       return fullURLPattern.Match(link)
+       return globalVars().fullURLPattern.Match(link)
 }
 
 func IsFullURLString(link string) bool {
-       return fullURLPattern.MatchString(link)
+       return globalVars().fullURLPattern.MatchString(link)
 }
 
 func IsNonEmptyRelativePath(link string) bool {
        return link != "" && !IsFullURLString(link) && link[0] != '/' && link[0] != '?' && link[0] != '#'
 }
 
-// regexp for full links to issues/pulls
-var issueFullPattern *regexp.Regexp
-
-// Once for to prevent races
-var issueFullPatternOnce sync.Once
-
-// regexp for full links to hash comment in pull request files changed tab
-var filesChangedFullPattern *regexp.Regexp
-
-// Once for to prevent races
-var filesChangedFullPatternOnce sync.Once
-
-func getIssueFullPattern() *regexp.Regexp {
-       issueFullPatternOnce.Do(func() {
-               // example: https://domain/org/repo/pulls/27#hash
-               issueFullPattern = regexp.MustCompile(regexp.QuoteMeta(setting.AppURL) +
-                       `[\w_.-]+/[\w_.-]+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b`)
-       })
-       return issueFullPattern
-}
-
-func getFilesChangedFullPattern() *regexp.Regexp {
-       filesChangedFullPatternOnce.Do(func() {
-               // example: https://domain/org/repo/pulls/27/files#hash
-               filesChangedFullPattern = regexp.MustCompile(regexp.QuoteMeta(setting.AppURL) +
-                       `[\w_.-]+/[\w_.-]+/pulls/((?:\w{1,10}-)?[1-9][0-9]*)/files([\?|#](\S+)?)?\b`)
-       })
-       return filesChangedFullPattern
-}
-
 // CustomLinkURLSchemes allows for additional schemes to be detected when parsing links within text
 func CustomLinkURLSchemes(schemes []string) {
        schemes = append(schemes, "http", "https")
@@ -197,13 +194,6 @@ func RenderCommitMessage(
        content string,
 ) (string, error) {
        procs := commitMessageProcessors
-       if ctx.DefaultLink != "" {
-               // we don't have to fear data races, because being
-               // commitMessageProcessors of fixed len and cap, every time we append
-               // something to it the slice is realloc+copied, so append always
-               // generates the slice ex-novo.
-               procs = append(procs, genDefaultLinkProcessor(ctx.DefaultLink))
-       }
        return renderProcessString(ctx, procs, content)
 }
 
@@ -231,16 +221,17 @@ var emojiProcessors = []processor{
 // which changes every text node into a link to the passed default link.
 func RenderCommitMessageSubject(
        ctx *RenderContext,
-       content string,
+       defaultLink, content string,
 ) (string, error) {
-       procs := commitMessageSubjectProcessors
-       if ctx.DefaultLink != "" {
-               // we don't have to fear data races, because being
-               // commitMessageSubjectProcessors of fixed len and cap, every time we
-               // append something to it the slice is realloc+copied, so append always
-               // generates the slice ex-novo.
-               procs = append(procs, genDefaultLinkProcessor(ctx.DefaultLink))
-       }
+       procs := slices.Clone(commitMessageSubjectProcessors)
+       procs = append(procs, func(ctx *RenderContext, node *html.Node) {
+               ch := &html.Node{Parent: node, Type: html.TextNode, Data: node.Data}
+               node.Type = html.ElementNode
+               node.Data = "a"
+               node.DataAtom = atom.A
+               node.Attr = []html.Attribute{{Key: "href", Val: defaultLink}, {Key: "class", Val: "muted"}}
+               node.FirstChild, node.LastChild = ch, ch
+       })
        return renderProcessString(ctx, procs, content)
 }
 
@@ -249,10 +240,8 @@ func RenderIssueTitle(
        ctx *RenderContext,
        title string,
 ) (string, error) {
+       // do not render other issue/commit links in an issue's title - which in most cases is already a link.
        return renderProcessString(ctx, []processor{
-               issueIndexPatternProcessor,
-               commitCrossReferencePatternProcessor,
-               hashCurrentPatternProcessor,
                emojiShortCodeProcessor,
                emojiProcessor,
        }, title)
@@ -288,11 +277,6 @@ func RenderEmoji(
        return renderProcessString(ctx, emojiProcessors, content)
 }
 
-var (
-       tagCleaner = regexp.MustCompile(`<((?:/?\w+/\w+)|(?:/[\w ]+/)|(/?[hH][tT][mM][lL]\b)|(/?[hH][eE][aA][dD]\b))`)
-       nulCleaner = strings.NewReplacer("\000", "")
-)
-
 func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output io.Writer) error {
        defer ctx.Cancel()
        // FIXME: don't read all content to memory
@@ -306,7 +290,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output
                // prepend "<html><body>"
                strings.NewReader("<html><body>"),
                // Strip out nuls - they're always invalid
-               bytes.NewReader(tagCleaner.ReplaceAll([]byte(nulCleaner.Replace(string(rawHTML))), []byte("&lt;$1"))),
+               bytes.NewReader(globalVars().tagCleaner.ReplaceAll([]byte(globalVars().nulCleaner.Replace(string(rawHTML))), []byte("&lt;$1"))),
                // close the tags
                strings.NewReader("</body></html>"),
        ))
@@ -353,7 +337,7 @@ func visitNode(ctx *RenderContext, procs []processor, node *html.Node) *html.Nod
        // Add user-content- to IDs and "#" links if they don't already have them
        for idx, attr := range node.Attr {
                val := strings.TrimPrefix(attr.Val, "#")
-               notHasPrefix := !(strings.HasPrefix(val, "user-content-") || blackfridayExtRegex.MatchString(val))
+               notHasPrefix := !(strings.HasPrefix(val, "user-content-") || globalVars().blackfridayExtRegex.MatchString(val))
 
                if attr.Key == "id" && notHasPrefix {
                        node.Attr[idx].Val = "user-content-" + attr.Val
index 86d70746d470c23f7bc84f7c9783895b6a5be32d..0e674c83e173677ec11f2fbcbcdc19a51dde119c 100644 (file)
@@ -54,7 +54,7 @@ func createCodeLink(href, content, class string) *html.Node {
 }
 
 func anyHashPatternExtract(s string) (ret anyHashPatternResult, ok bool) {
-       m := anyHashPattern.FindStringSubmatchIndex(s)
+       m := globalVars().anyHashPattern.FindStringSubmatchIndex(s)
        if m == nil {
                return ret, false
        }
@@ -120,7 +120,7 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
                        node = node.NextSibling
                        continue
                }
-               m := comparePattern.FindStringSubmatchIndex(node.Data)
+               m := globalVars().comparePattern.FindStringSubmatchIndex(node.Data)
                if m == nil || slices.Contains(m[:8], -1) { // ensure that every group (m[0]...m[7]) has a match
                        node = node.NextSibling
                        continue
@@ -173,7 +173,7 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
                ctx.ShaExistCache = make(map[string]bool)
        }
        for node != nil && node != next && start < len(node.Data) {
-               m := hashCurrentPattern.FindStringSubmatchIndex(node.Data[start:])
+               m := globalVars().hashCurrentPattern.FindStringSubmatchIndex(node.Data[start:])
                if m == nil {
                        return
                }
index a062789b35888aabf421f088f889ed2fcc84ec73..32d0285eb4c464f3c8e5ca83e730ef849f303d86 100644 (file)
@@ -9,7 +9,7 @@ import "golang.org/x/net/html"
 func emailAddressProcessor(ctx *RenderContext, node *html.Node) {
        next := node.NextSibling
        for node != nil && node != next {
-               m := emailRegex.FindStringSubmatchIndex(node.Data)
+               m := globalVars().emailRegex.FindStringSubmatchIndex(node.Data)
                if m == nil {
                        return
                }
index c60d06b82322304ac3fec0c78be974c532cc9bec..6eacb2067f02801cf62d95da618320d60cf88472 100644 (file)
@@ -62,7 +62,7 @@ func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) {
        start := 0
        next := node.NextSibling
        for node != nil && node != next && start < len(node.Data) {
-               m := emojiShortCodeRegex.FindStringSubmatchIndex(node.Data[start:])
+               m := globalVars().emojiShortCodeRegex.FindStringSubmatchIndex(node.Data[start:])
                if m == nil {
                        return
                }
index 2fb657f56b6ff5782a801015c1e27d3877a56c57..cdcc94d5631312d77398abb5cbe54e8e6ed89519 100644 (file)
@@ -40,17 +40,19 @@ func link(href, class, contents string) string {
 }
 
 var numericMetas = map[string]string{
-       "format": "https://someurl.com/{user}/{repo}/{index}",
-       "user":   "someUser",
-       "repo":   "someRepo",
-       "style":  IssueNameStyleNumeric,
+       "format":                       "https://someurl.com/{user}/{repo}/{index}",
+       "user":                         "someUser",
+       "repo":                         "someRepo",
+       "style":                        IssueNameStyleNumeric,
+       "markupAllowShortIssuePattern": "true",
 }
 
 var alphanumericMetas = map[string]string{
-       "format": "https://someurl.com/{user}/{repo}/{index}",
-       "user":   "someUser",
-       "repo":   "someRepo",
-       "style":  IssueNameStyleAlphanumeric,
+       "format":                       "https://someurl.com/{user}/{repo}/{index}",
+       "user":                         "someUser",
+       "repo":                         "someRepo",
+       "style":                        IssueNameStyleAlphanumeric,
+       "markupAllowShortIssuePattern": "true",
 }
 
 var regexpMetas = map[string]string{
@@ -62,8 +64,15 @@ var regexpMetas = map[string]string{
 
 // these values should match the TestOrgRepo const above
 var localMetas = map[string]string{
-       "user": "test-owner",
-       "repo": "test-repo",
+       "user":                         "test-owner",
+       "repo":                         "test-repo",
+       "markupAllowShortIssuePattern": "true",
+}
+
+var localWikiMetas = map[string]string{
+       "user":              "test-owner",
+       "repo":              "test-repo",
+       "markupContentMode": "wiki",
 }
 
 func TestRender_IssueIndexPattern(t *testing.T) {
@@ -124,9 +133,8 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
                }
                expectedNil := fmt.Sprintf(expectedFmt, links...)
                testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{
-                       Ctx:         git.DefaultContext,
-                       Metas:       localMetas,
-                       ContentMode: RenderContentAsComment,
+                       Ctx:   git.DefaultContext,
+                       Metas: localMetas,
                })
 
                class := "ref-issue"
@@ -139,9 +147,8 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
                }
                expectedNum := fmt.Sprintf(expectedFmt, links...)
                testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{
-                       Ctx:         git.DefaultContext,
-                       Metas:       numericMetas,
-                       ContentMode: RenderContentAsComment,
+                       Ctx:   git.DefaultContext,
+                       Metas: numericMetas,
                })
        }
 
@@ -262,7 +269,7 @@ func TestRender_IssueIndexPattern5(t *testing.T) {
        })
 }
 
-func TestRender_IssueIndexPattern_Document(t *testing.T) {
+func TestRender_IssueIndexPattern_NoShortPattern(t *testing.T) {
        setting.AppURL = TestAppURL
        metas := map[string]string{
                "format": "https://someurl.com/{user}/{repo}/{index}",
@@ -285,6 +292,22 @@ func TestRender_IssueIndexPattern_Document(t *testing.T) {
        })
 }
 
+func TestRender_RenderIssueTitle(t *testing.T) {
+       setting.AppURL = TestAppURL
+       metas := map[string]string{
+               "format": "https://someurl.com/{user}/{repo}/{index}",
+               "user":   "someUser",
+               "repo":   "someRepo",
+               "style":  IssueNameStyleNumeric,
+       }
+       actual, err := RenderIssueTitle(&RenderContext{
+               Ctx:   git.DefaultContext,
+               Metas: metas,
+       }, "#1")
+       assert.NoError(t, err)
+       assert.Equal(t, "#1", actual)
+}
+
 func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
        ctx.Links.AbsolutePrefix = true
        if ctx.Links.Base == "" {
@@ -318,8 +341,7 @@ func TestRender_AutoLink(t *testing.T) {
                        Links: Links{
                                Base: TestRepoURL,
                        },
-                       Metas:       localMetas,
-                       ContentMode: RenderContentAsWiki,
+                       Metas: localWikiMetas,
                }, strings.NewReader(input), &buffer)
                assert.Equal(t, err, nil)
                assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
@@ -391,10 +413,10 @@ func TestRegExp_sha1CurrentPattern(t *testing.T) {
        }
 
        for _, testCase := range trueTestCases {
-               assert.True(t, hashCurrentPattern.MatchString(testCase))
+               assert.True(t, globalVars().hashCurrentPattern.MatchString(testCase))
        }
        for _, testCase := range falseTestCases {
-               assert.False(t, hashCurrentPattern.MatchString(testCase))
+               assert.False(t, globalVars().hashCurrentPattern.MatchString(testCase))
        }
 }
 
@@ -474,9 +496,9 @@ func TestRegExp_shortLinkPattern(t *testing.T) {
        }
 
        for _, testCase := range trueTestCases {
-               assert.True(t, shortLinkPattern.MatchString(testCase))
+               assert.True(t, globalVars().shortLinkPattern.MatchString(testCase))
        }
        for _, testCase := range falseTestCases {
-               assert.False(t, shortLinkPattern.MatchString(testCase))
+               assert.False(t, globalVars().shortLinkPattern.MatchString(testCase))
        }
 }
index fa630656cef41a3effded84389d926e36e4f95cd..2acf154ad2ad7607539ae81ecd6a899fea19a728 100644 (file)
@@ -7,6 +7,7 @@ import (
        "strings"
 
        "code.gitea.io/gitea/modules/base"
+       "code.gitea.io/gitea/modules/httplib"
        "code.gitea.io/gitea/modules/log"
        "code.gitea.io/gitea/modules/references"
        "code.gitea.io/gitea/modules/regexplru"
@@ -23,18 +24,21 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
        }
        next := node.NextSibling
        for node != nil && node != next {
-               m := getIssueFullPattern().FindStringSubmatchIndex(node.Data)
+               m := globalVars().issueFullPattern.FindStringSubmatchIndex(node.Data)
                if m == nil {
                        return
                }
 
-               mDiffView := getFilesChangedFullPattern().FindStringSubmatchIndex(node.Data)
+               mDiffView := globalVars().filesChangedFullPattern.FindStringSubmatchIndex(node.Data)
                // leave it as it is if the link is from "Files Changed" tab in PR Diff View https://domain/org/repo/pulls/27/files
                if mDiffView != nil {
                        return
                }
 
                link := node.Data[m[0]:m[1]]
+               if !httplib.IsCurrentGiteaSiteURL(ctx.Ctx, link) {
+                       return
+               }
                text := "#" + node.Data[m[2]:m[3]]
                // if m[4] and m[5] is not -1, then link is to a comment
                // indicate that in the text by appending (comment)
@@ -67,8 +71,10 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
                return
        }
 
-       // crossLinkOnly if not comment and not wiki
-       crossLinkOnly := ctx.ContentMode != RenderContentAsTitle && ctx.ContentMode != RenderContentAsComment && ctx.ContentMode != RenderContentAsWiki
+       // crossLinkOnly: do not parse "#123", only parse "owner/repo#123"
+       // if there is no repo in the context, then the "#123" format can't be parsed
+       // old logic: crossLinkOnly := ctx.Metas["mode"] == "document" && !ctx.IsWiki
+       crossLinkOnly := ctx.Metas["markupAllowShortIssuePattern"] != "true"
 
        var (
                found bool
index 30564da548a66c213cc5c029a1acf9acf74df4da..b7562d0aa6d26a4cb5d840c9d15bd64b633ea86d 100644 (file)
@@ -20,9 +20,9 @@ func ResolveLink(ctx *RenderContext, link, userContentAnchorPrefix string) (resu
        isAnchorFragment := link != "" && link[0] == '#'
        if !isAnchorFragment && !IsFullURLString(link) {
                linkBase := ctx.Links.Base
-               if ctx.ContentMode == RenderContentAsWiki {
+               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
+                       // just use wiki link here, and it will be redirected to a wiki raw link if necessary
                        linkBase = ctx.Links.WikiLink()
                } else if ctx.Links.BranchPath != "" || ctx.Links.TreePath != "" {
                        // if there is no BranchPath, then the link will be something like "/owner/repo/src/{the-file-path}"
@@ -40,7 +40,7 @@ func ResolveLink(ctx *RenderContext, link, userContentAnchorPrefix string) (resu
 func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
        next := node.NextSibling
        for node != nil && node != next {
-               m := shortLinkPattern.FindStringSubmatchIndex(node.Data)
+               m := globalVars().shortLinkPattern.FindStringSubmatchIndex(node.Data)
                if m == nil {
                        return
                }
@@ -147,7 +147,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
                }
                if image {
                        if !absoluteLink {
-                               link = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.ContentMode == RenderContentAsWiki), link)
+                               link = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), link)
                        }
                        title := props["title"]
                        if title == "" {
@@ -200,25 +200,6 @@ func linkProcessor(ctx *RenderContext, node *html.Node) {
        }
 }
 
-func genDefaultLinkProcessor(defaultLink string) processor {
-       return func(ctx *RenderContext, node *html.Node) {
-               ch := &html.Node{
-                       Parent: node,
-                       Type:   html.TextNode,
-                       Data:   node.Data,
-               }
-
-               node.Type = html.ElementNode
-               node.Data = "a"
-               node.DataAtom = atom.A
-               node.Attr = []html.Attribute{
-                       {Key: "href", Val: defaultLink},
-                       {Key: "class", Val: "default-link muted"},
-               }
-               node.FirstChild, node.LastChild = ch, ch
-       }
-}
-
 // descriptionLinkProcessor creates links for DescriptionHTML
 func descriptionLinkProcessor(ctx *RenderContext, node *html.Node) {
        next := node.NextSibling
index c499854053fa225b57deaf1bc9b1f2757dee820e..234adba2bfa8dfd571fa880a3874d4f834b657e1 100644 (file)
@@ -17,7 +17,7 @@ func visitNodeImg(ctx *RenderContext, img *html.Node) (next *html.Node) {
                }
 
                if IsNonEmptyRelativePath(attr.Val) {
-                       attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.ContentMode == RenderContentAsWiki), attr.Val)
+                       attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val)
 
                        // By default, the "<img>" tag should also be clickable,
                        // because frontend use `<img>` to paste the re-scaled image into the markdown,
@@ -53,7 +53,7 @@ func visitNodeVideo(ctx *RenderContext, node *html.Node) (next *html.Node) {
                        continue
                }
                if IsNonEmptyRelativePath(attr.Val) {
-                       attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.ContentMode == RenderContentAsWiki), attr.Val)
+                       attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val)
                }
                attr.Val = camoHandleLink(attr.Val)
                node.Attr[i] = attr
index 262d0fc4dd5d14b524a4eeb224dcd58237445a95..67ac2758a31fba7fd4e93b47af4ae1515e66a4a1 100644 (file)
@@ -27,6 +27,11 @@ var (
                "user": testRepoOwnerName,
                "repo": testRepoName,
        }
+       localWikiMetas = map[string]string{
+               "user":              testRepoOwnerName,
+               "repo":              testRepoName,
+               "markupContentMode": "wiki",
+       }
 )
 
 type mockRepo struct {
@@ -413,8 +418,7 @@ func TestRender_ShortLinks(t *testing.T) {
                        Links: markup.Links{
                                Base: markup.TestRepoURL,
                        },
-                       Metas:       localMetas,
-                       ContentMode: markup.RenderContentAsWiki,
+                       Metas: localWikiMetas,
                }, input)
                assert.NoError(t, err)
                assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
@@ -526,10 +530,9 @@ func TestRender_ShortLinks(t *testing.T) {
 func TestRender_RelativeMedias(t *testing.T) {
        render := func(input string, isWiki bool, links markup.Links) string {
                buffer, err := markdown.RenderString(&markup.RenderContext{
-                       Ctx:         git.DefaultContext,
-                       Links:       links,
-                       Metas:       localMetas,
-                       ContentMode: util.Iif(isWiki, markup.RenderContentAsWiki, markup.RenderContentAsComment),
+                       Ctx:   git.DefaultContext,
+                       Links: links,
+                       Metas: util.Iif(isWiki, localWikiMetas, localMetas),
                }, input)
                assert.NoError(t, err)
                return strings.TrimSpace(string(buffer))
index c8488cfb50c375c8eea1956c9b7c1a920e35bf0c..c837b21e7804ce37766000b12d48777c938ec84f 100644 (file)
@@ -75,11 +75,12 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
                                // TODO: this was a quite unclear part, old code: `if metas["mode"] != "document" { use comment link break setting }`
                                // many places render non-comment contents with no mode=document, then these contents also use comment's hard line break setting
                                // especially in many tests.
+                               markdownLineBreakStyle := ctx.Metas["markdownLineBreakStyle"]
                                if markup.RenderBehaviorForTesting.ForceHardLineBreak {
                                        v.SetHardLineBreak(true)
-                               } else if ctx.ContentMode == markup.RenderContentAsComment {
+                               } else if markdownLineBreakStyle == "comment" {
                                        v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInComments)
-                               } else {
+                               } else if markdownLineBreakStyle == "document" {
                                        v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInDocuments)
                                }
                        }
index 315eed2e62e7cca5a19467c204c9c45e2c5c59cc..780df8727f0c879259a287f8a1e1830e652c3f0d 100644 (file)
@@ -37,6 +37,12 @@ var localMetas = map[string]string{
        "repo": testRepoName,
 }
 
+var localWikiMetas = map[string]string{
+       "user":              testRepoOwnerName,
+       "repo":              testRepoName,
+       "markupContentMode": "wiki",
+}
+
 type mockRepo struct {
        OwnerName string
        RepoName  string
@@ -75,7 +81,7 @@ func TestRender_StandardLinks(t *testing.T) {
                        Links: markup.Links{
                                Base: FullURL,
                        },
-                       ContentMode: markup.RenderContentAsWiki,
+                       Metas: localWikiMetas,
                }, input)
                assert.NoError(t, err)
                assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
@@ -307,9 +313,8 @@ func TestTotal_RenderWiki(t *testing.T) {
                        Links: markup.Links{
                                Base: FullURL,
                        },
-                       Repo:        newMockRepo(testRepoOwnerName, testRepoName),
-                       Metas:       localMetas,
-                       ContentMode: markup.RenderContentAsWiki,
+                       Repo:  newMockRepo(testRepoOwnerName, testRepoName),
+                       Metas: localWikiMetas,
                }, sameCases[i])
                assert.NoError(t, err)
                assert.Equal(t, answers[i], string(line))
@@ -334,7 +339,7 @@ func TestTotal_RenderWiki(t *testing.T) {
                        Links: markup.Links{
                                Base: FullURL,
                        },
-                       ContentMode: markup.RenderContentAsWiki,
+                       Metas: localWikiMetas,
                }, testCases[i])
                assert.NoError(t, err)
                assert.EqualValues(t, testCases[i+1], string(line))
@@ -657,9 +662,9 @@ mail@domain.com
 <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="/image.jpg" rel="nofollow"><img src="/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">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</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">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><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/>
@@ -684,9 +689,9 @@ space</p>
 <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">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</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">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><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/>
@@ -713,9 +718,9 @@ space</p>
 <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">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</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">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><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/>
@@ -742,9 +747,9 @@ space</p>
 <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">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</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">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><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/>
@@ -771,9 +776,9 @@ space</p>
 <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">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</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">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><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/>
@@ -800,9 +805,9 @@ space</p>
 <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">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</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">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><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/>
@@ -830,9 +835,9 @@ space</p>
 <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">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</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">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><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/>
@@ -860,9 +865,9 @@ space</p>
 <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">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</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">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><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/>
@@ -890,9 +895,9 @@ space</p>
 <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">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</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">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><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/>
@@ -920,9 +925,9 @@ space</p>
 <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">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</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">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><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/>
@@ -951,9 +956,9 @@ space</p>
 <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">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</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">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><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/>
@@ -982,9 +987,9 @@ space</p>
 <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">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</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">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><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/>
@@ -999,9 +1004,9 @@ space</p>
        defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
        for i, c := range cases {
                result, err := markdown.RenderString(&markup.RenderContext{
-                       Ctx:         context.Background(),
-                       Links:       c.Links,
-                       ContentMode: util.Iif(c.IsWiki, markup.RenderContentAsWiki, markup.RenderContentAsDefault),
+                       Ctx:   context.Background(),
+                       Links: c.Links,
+                       Metas: util.Iif(c.IsWiki, map[string]string{"markupContentMode": "wiki"}, map[string]string{}),
                }, input)
                assert.NoError(t, err, "Unexpected error in testcase: %v", i)
                assert.Equal(t, c.Expected, string(result), "Unexpected result in testcase %v", i)
index 4ed4118854386b66e40efed06d39643ae1199bb8..b2262c1c7885d77fe0680ef8d602104e1b554d02 100644 (file)
@@ -21,7 +21,7 @@ func (g *ASTTransformer) transformImage(ctx *markup.RenderContext, v *ast.Image)
        // Check if the destination is a real link
        if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) {
                v.Destination = []byte(giteautil.URLJoin(
-                       ctx.Links.ResolveMediaLink(ctx.ContentMode == markup.RenderContentAsWiki),
+                       ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()),
                        strings.TrimLeft(string(v.Destination), "/"),
                ))
        }
index 6b9c9631575beb0a16cf57a0a2b450ed194ec78c..c587a6ada5ef07505b417e91300913b8bc16fd4d 100644 (file)
@@ -144,15 +144,14 @@ func (r *Writer) resolveLink(kind, link string) string {
                }
 
                base := r.Ctx.Links.Base
-               isWiki := r.Ctx.ContentMode == markup.RenderContentAsWiki
-               if isWiki {
+               if r.Ctx.IsMarkupContentWiki() {
                        base = r.Ctx.Links.WikiLink()
                } else if r.Ctx.Links.HasBranchInfo() {
                        base = r.Ctx.Links.SrcLink()
                }
 
                if kind == "image" || kind == "video" {
-                       base = r.Ctx.Links.ResolveMediaLink(isWiki)
+                       base = r.Ctx.Links.ResolveMediaLink(r.Ctx.IsMarkupContentWiki())
                }
 
                link = util.URLJoin(base, link)
index b882678c7e20ed6eac1512ada9b798bdec1e12dc..a3eefc3db3df923ef0adfe264f0a2ba451325ec0 100644 (file)
@@ -27,7 +27,7 @@ func TestRender_StandardLinks(t *testing.T) {
                                Base:       "/relative-path",
                                BranchPath: "branch/main",
                        },
-                       ContentMode: util.Iif(isWiki, markup.RenderContentAsWiki, markup.RenderContentAsDefault),
+                       Metas: map[string]string{"markupContentMode": util.Iif(isWiki, "wiki", "")},
                }, input)
                assert.NoError(t, err)
                assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
index add50f438272a14630bb688d9eca0e392e5e3ce0..1977dc73f55ef415be4b931801d400fa5bb92b14 100644 (file)
@@ -27,15 +27,6 @@ const (
        RenderMetaAsTable   RenderMetaMode = "table"
 )
 
-type RenderContentMode string
-
-const (
-       RenderContentAsDefault RenderContentMode = "" // empty means "default", no special handling, maybe just a simple "document"
-       RenderContentAsComment RenderContentMode = "comment"
-       RenderContentAsTitle   RenderContentMode = "title"
-       RenderContentAsWiki    RenderContentMode = "wiki"
-)
-
 var RenderBehaviorForTesting struct {
        // Markdown line break rendering has 2 default behaviors:
        // * Use hard: replace "\n" with "<br>" for comments, setting.Markdown.EnableHardLineBreakInComments=true
@@ -59,12 +50,14 @@ type RenderContext struct {
        // for file mode, it could be left as empty, and will be detected by file extension in RelativePath
        MarkupType string
 
-       // what the content will be used for: eg: for comment or for wiki? or just render a file?
-       ContentMode RenderContentMode
+       Links Links // special link references for rendering, especially when there is a branch/tree path
+
+       // user&repo, format&style&regexp (for external issue pattern), teams&org (for mention)
+       // BranchNameSubURL (for iframe&asciicast)
+       // markupAllowShortIssuePattern, markupContentMode (wiki)
+       // markdownLineBreakStyle (comment, document)
+       Metas map[string]string
 
-       Links            Links             // special link references for rendering, especially when there is a branch/tree path
-       Metas            map[string]string // user&repo, format&style&regexp (for external issue pattern), teams&org (for mention), BranchNameSubURL(for iframe&asciicast)
-       DefaultLink      string            // TODO: need to figure out
        GitRepo          *git.Repository
        Repo             gitrepo.Repository
        ShaExistCache    map[string]bool
@@ -102,6 +95,10 @@ func (ctx *RenderContext) AddCancel(fn func()) {
        }
 }
 
+func (ctx *RenderContext) IsMarkupContentWiki() bool {
+       return ctx.Metas != nil && ctx.Metas["markupContentMode"] == "wiki"
+}
+
 // Render renders markup file to HTML with all specific handling stuff.
 func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
        if ctx.MarkupType == "" && ctx.RelativePath != "" {
@@ -232,3 +229,7 @@ func Init(ph *ProcessorHelper) {
                }
        }
 }
+
+func ComposeSimpleDocumentMetas() map[string]string {
+       return map[string]string{"markdownLineBreakStyle": "document"}
+}
index 3e1aa7ce3a5df6f6a3b152e999e7d716af373728..c8339d8f8b3f2868dd09b69c56d84d000e17e23b 100644 (file)
@@ -10,7 +10,7 @@ import (
 
 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)
+       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
 }
index 1db1e4a111919c29b7e1472949d85d1ca7d50216..8e443446bd69cc43a51eb62f8f42f756c854f6a0 100644 (file)
@@ -62,19 +62,18 @@ func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, me
        }
        msgLine = strings.TrimRightFunc(msgLine, unicode.IsSpace)
        if len(msgLine) == 0 {
-               return template.HTML("")
+               return ""
        }
 
        // we can safely assume that it will not return any error, since there
        // shouldn't be any special HTML.
        renderedMessage, err := markup.RenderCommitMessageSubject(&markup.RenderContext{
-               Ctx:         ut.ctx,
-               DefaultLink: urlDefault,
-               Metas:       metas,
-       }, template.HTMLEscapeString(msgLine))
+               Ctx:   ut.ctx,
+               Metas: metas,
+       }, urlDefault, template.HTMLEscapeString(msgLine))
        if err != nil {
                log.Error("RenderCommitMessageSubject: %v", err)
-               return template.HTML("")
+               return ""
        }
        return renderCodeBlock(template.HTML(renderedMessage))
 }
@@ -94,9 +93,8 @@ func (ut *RenderUtils) RenderCommitBody(msg string, metas map[string]string) tem
        }
 
        renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
-               Ctx:         ut.ctx,
-               Metas:       metas,
-               ContentMode: markup.RenderContentAsComment,
+               Ctx:   ut.ctx,
+               Metas: metas,
        }, template.HTMLEscapeString(msgLine))
        if err != nil {
                log.Error("RenderCommitMessage: %v", err)
@@ -117,9 +115,8 @@ func renderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
 // RenderIssueTitle renders issue/pull title with defined post processors
 func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML {
        renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{
-               Ctx:         ut.ctx,
-               ContentMode: markup.RenderContentAsTitle,
-               Metas:       metas,
+               Ctx:   ut.ctx,
+               Metas: metas,
        }, template.HTMLEscapeString(text))
        if err != nil {
                log.Error("RenderIssueTitle: %v", err)
@@ -212,7 +209,7 @@ func reactionToEmoji(reaction string) template.HTML {
 func (ut *RenderUtils) MarkdownToHtml(input string) template.HTML { //nolint:revive
        output, err := markdown.RenderString(&markup.RenderContext{
                Ctx:   ut.ctx,
-               Metas: map[string]string{"mode": "document"},
+               Metas: markup.ComposeSimpleDocumentMetas(),
        }, input)
        if err != nil {
                log.Error("RenderString: %v", err)
index 2d331b83171970b9c3355ebd8adb6e7bbc39afe2..529507e7eab828ac4da176191305c0fd146db55d 100644 (file)
@@ -47,10 +47,11 @@ mail@domain.com
 }
 
 var testMetas = map[string]string{
-       "user":     "user13",
-       "repo":     "repo11",
-       "repoPath": "../../tests/gitea-repositories-meta/user13/repo11.git/",
-       "mode":     "comment",
+       "user":                         "user13",
+       "repo":                         "repo11",
+       "repoPath":                     "../../tests/gitea-repositories-meta/user13/repo11.git/",
+       "markdownLineBreakStyle":       "comment",
+       "markupAllowShortIssuePattern": "true",
 }
 
 func TestMain(m *testing.M) {
@@ -75,8 +76,7 @@ func newTestRenderUtils() *RenderUtils {
 func TestRenderCommitBody(t *testing.T) {
        defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
        type args struct {
-               msg   string
-               metas map[string]string
+               msg string
        }
        tests := []struct {
                name string
@@ -108,7 +108,7 @@ func TestRenderCommitBody(t *testing.T) {
        ut := newTestRenderUtils()
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
-                       assert.Equalf(t, tt.want, ut.RenderCommitBody(tt.args.msg, tt.args.metas), "RenderCommitBody(%v, %v)", tt.args.msg, tt.args.metas)
+                       assert.Equalf(t, tt.want, ut.RenderCommitBody(tt.args.msg, nil), "RenderCommitBody(%v, %v)", tt.args.msg, nil)
                })
        }
 
@@ -140,7 +140,7 @@ func TestRenderCommitMessage(t *testing.T) {
 }
 
 func TestRenderCommitMessageLinkSubject(t *testing.T) {
-       expected := `<a href="https://example.com/link" class="default-link muted">space </a><a href="/mention-user" data-markdown-generated-content="" class="mention">@mention-user</a>`
+       expected := `<a href="https://example.com/link" class="muted">space </a><a href="/mention-user" data-markdown-generated-content="" class="mention">@mention-user</a>`
        assert.EqualValues(t, expected, newTestRenderUtils().RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", testMetas))
 }
 
@@ -164,11 +164,11 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
 <span class="emoji" aria-label="thumbs up">👍</span>
 mail@domain.com
 @mention-user test
-<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
+#123
   space<SPACE><SPACE>
 `
        expected = strings.ReplaceAll(expected, "<SPACE>", " ")
-       assert.EqualValues(t, expected, string(newTestRenderUtils().RenderIssueTitle(testInput(), testMetas)))
+       assert.EqualValues(t, expected, string(newTestRenderUtils().RenderIssueTitle(testInput(), nil)))
 }
 
 func TestRenderMarkdownToHtml(t *testing.T) {
index c8cc1a5ff12719ff6f8532e73c0db1c9f0791618..dd6b286109f80287684b6a9fc581bc4b10f9d7e8 100644 (file)
@@ -47,11 +47,12 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPa
        switch mode {
        case "gfm": // legacy mode, do nothing
        case "comment":
-               renderCtx.ContentMode = markup.RenderContentAsComment
+               renderCtx.Metas = map[string]string{"markdownLineBreakStyle": "comment"}
        case "wiki":
-               renderCtx.ContentMode = markup.RenderContentAsWiki
+               renderCtx.Metas = map[string]string{"markdownLineBreakStyle": "document", "markupContentMode": "wiki"}
        case "file":
                // render the repo file content by its extension
+               renderCtx.Metas = map[string]string{"markdownLineBreakStyle": "document"}
                renderCtx.MarkupType = ""
                renderCtx.RelativePath = filePath
                renderCtx.InStandalonePage = true
@@ -74,10 +75,12 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPa
 
        if repo != nil && repo.Repository != nil {
                renderCtx.Repo = repo.Repository
-               if renderCtx.ContentMode == markup.RenderContentAsComment {
-                       renderCtx.Metas = repo.Repository.ComposeMetas(ctx)
-               } else {
+               if mode == "file" {
                        renderCtx.Metas = repo.Repository.ComposeDocumentMetas(ctx)
+               } else if mode == "wiki" {
+                       renderCtx.Metas = repo.Repository.ComposeWikiMetas(ctx)
+               } else if mode == "comment" {
+                       renderCtx.Metas = repo.Repository.ComposeMetas(ctx)
                }
        }
        if err := markup.Render(renderCtx, strings.NewReader(text), ctx.Resp); err != nil {
index a273515c8a3b0f5b2284c12334d8029c340894f0..afc2c343a68b3ae42014d7ca3d2625ea7c08bdf4 100644 (file)
@@ -56,7 +56,7 @@ func renderMarkdown(ctx *context.Context, act *activities_model.Action, content
                Links: markup.Links{
                        Base: act.GetRepoLink(ctx),
                },
-               Metas: map[string]string{
+               Metas: map[string]string{ // FIXME: not right here, it should use issue to compose the metas
                        "user": act.GetRepoUserName(ctx),
                        "repo": act.GetRepoName(ctx),
                },
index 08cbcd9e12bc35992ef2c1ac3ee5dde4fb81f2b0..6dd2d14cc6bcf274f113f479a6f19c60a12a4d13 100644 (file)
@@ -46,9 +46,7 @@ func showUserFeed(ctx *context.Context, formatType string) {
                Links: markup.Links{
                        Base: ctx.ContextUser.HTMLURL(),
                },
-               Metas: map[string]string{
-                       "user": ctx.ContextUser.GetDisplayName(),
-               },
+               Metas: markup.ComposeSimpleDocumentMetas(),
        }, ctx.ContextUser.Description)
        if err != nil {
                ctx.ServerError("RenderString", err)
index 544f5362b897d81370017f627ddf41be88756532..18648d33cd79f8aafd4e41d191545d7dddab6dd0 100644 (file)
@@ -189,7 +189,7 @@ func prepareOrgProfileReadme(ctx *context.Context, viewRepositories bool) bool {
                                Base:       profileDbRepo.Link(),
                                BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
                        },
-                       Metas: map[string]string{"mode": "document"},
+                       Metas: markup.ComposeSimpleDocumentMetas(),
                }, bytes); err != nil {
                        log.Error("failed to RenderString: %v", err)
                } else {
index 2b8312f10acdfb0cd1111eaadd55b7e7959ed1e2..13f6b69493e096a1ca2d92a3933312bff692a106 100644 (file)
@@ -289,9 +289,8 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
        }
 
        rctx := &markup.RenderContext{
-               Ctx:         ctx,
-               ContentMode: markup.RenderContentAsWiki,
-               Metas:       ctx.Repo.Repository.ComposeDocumentMetas(ctx),
+               Ctx:   ctx,
+               Metas: ctx.Repo.Repository.ComposeWikiMetas(ctx),
                Links: markup.Links{
                        Base: ctx.Repo.RepoLink,
                },
index ef111cff806fc436664f8cb333fe1bdca23aa4f5..9467b0986b3d5441927dcb4471923266d3004b67 100644 (file)
@@ -50,7 +50,7 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
        ctx.Data["OpenIDs"] = openIDs
        if len(ctx.ContextUser.Description) != 0 {
                content, err := markdown.RenderString(&markup.RenderContext{
-                       Metas: map[string]string{"mode": "document"},
+                       Metas: markup.ComposeSimpleDocumentMetas(),
                        Ctx:   ctx,
                }, ctx.ContextUser.Description)
                if err != nil {
index fb5bc14fe2a79dd0623750432be2ad2654d06052..89bb371e7c7cd6fa539e704727553c133f2805ed 100644 (file)
@@ -29,7 +29,7 @@
                <div class="file-header-left tw-flex tw-items-center tw-py-2 tw-pr-4">
                        {{if .ReadmeInList}}
                                {{svg "octicon-book" 16 "tw-mr-2"}}
-                               <strong><a class="default-link muted" href="#readme">{{.FileName}}</a></strong>
+                               <strong><a class="muted" href="#readme">{{.FileName}}</a></strong>
                        {{else}}
                                {{template "repo/file_info" .}}
                        {{end}}