mirror of https://github.com/go-gitea/gitea.git
Fix and refactor markdown rendering (#32522)
This commit is contained in:
parent
e546480d0a
commit
5eebe1dc5f
|
@ -7,6 +7,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -165,10 +166,10 @@ type Repository struct {
|
||||||
|
|
||||||
Status RepositoryStatus `xorm:"NOT NULL DEFAULT 0"`
|
Status RepositoryStatus `xorm:"NOT NULL DEFAULT 0"`
|
||||||
|
|
||||||
RenderingMetas map[string]string `xorm:"-"`
|
commonRenderingMetas map[string]string `xorm:"-"`
|
||||||
DocumentRenderingMetas map[string]string `xorm:"-"`
|
|
||||||
Units []*RepoUnit `xorm:"-"`
|
Units []*RepoUnit `xorm:"-"`
|
||||||
PrimaryLanguage *LanguageStat `xorm:"-"`
|
PrimaryLanguage *LanguageStat `xorm:"-"`
|
||||||
|
|
||||||
IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
||||||
ForkID int64 `xorm:"INDEX"`
|
ForkID int64 `xorm:"INDEX"`
|
||||||
|
@ -473,9 +474,8 @@ func (repo *Repository) MustOwner(ctx context.Context) *user_model.User {
|
||||||
return repo.Owner
|
return repo.Owner
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers.
|
func (repo *Repository) composeCommonMetas(ctx context.Context) map[string]string {
|
||||||
func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string {
|
if len(repo.commonRenderingMetas) == 0 {
|
||||||
if len(repo.RenderingMetas) == 0 {
|
|
||||||
metas := map[string]string{
|
metas := map[string]string{
|
||||||
"user": repo.OwnerName,
|
"user": repo.OwnerName,
|
||||||
"repo": repo.Name,
|
"repo": repo.Name,
|
||||||
|
@ -508,21 +508,34 @@ func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string {
|
||||||
metas["org"] = strings.ToLower(repo.OwnerName)
|
metas["org"] = strings.ToLower(repo.OwnerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.RenderingMetas = metas
|
repo.commonRenderingMetas = metas
|
||||||
}
|
}
|
||||||
return repo.RenderingMetas
|
return repo.commonRenderingMetas
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComposeDocumentMetas composes a map of metas for properly rendering documents
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
func (repo *Repository) ComposeDocumentMetas(ctx context.Context) map[string]string {
|
||||||
if len(repo.DocumentRenderingMetas) == 0 {
|
// does document(file) need the "teams" and "org" from common metas?
|
||||||
metas := map[string]string{}
|
metas := maps.Clone(repo.composeCommonMetas(ctx))
|
||||||
for k, v := range repo.ComposeMetas(ctx) {
|
metas["markdownLineBreakStyle"] = "document"
|
||||||
metas[k] = v
|
return metas
|
||||||
}
|
|
||||||
repo.DocumentRenderingMetas = metas
|
|
||||||
}
|
|
||||||
return repo.DocumentRenderingMetas
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBaseRepo populates repo.BaseRepo for a fork repository and
|
// GetBaseRepo populates repo.BaseRepo for a fork repository and
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package repo_test
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"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/unit"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -20,18 +19,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
countRepospts = repo_model.CountRepositoryOptions{OwnerID: 10}
|
countRepospts = CountRepositoryOptions{OwnerID: 10}
|
||||||
countReposptsPublic = repo_model.CountRepositoryOptions{OwnerID: 10, Private: optional.Some(false)}
|
countReposptsPublic = CountRepositoryOptions{OwnerID: 10, Private: optional.Some(false)}
|
||||||
countReposptsPrivate = repo_model.CountRepositoryOptions{OwnerID: 10, Private: optional.Some(true)}
|
countReposptsPrivate = CountRepositoryOptions{OwnerID: 10, Private: optional.Some(true)}
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetRepositoryCount(t *testing.T) {
|
func TestGetRepositoryCount(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
ctx := db.DefaultContext
|
ctx := db.DefaultContext
|
||||||
count, err1 := repo_model.CountRepositories(ctx, countRepospts)
|
count, err1 := CountRepositories(ctx, countRepospts)
|
||||||
privateCount, err2 := repo_model.CountRepositories(ctx, countReposptsPrivate)
|
privateCount, err2 := CountRepositories(ctx, countReposptsPrivate)
|
||||||
publicCount, err3 := repo_model.CountRepositories(ctx, countReposptsPublic)
|
publicCount, err3 := CountRepositories(ctx, countReposptsPublic)
|
||||||
assert.NoError(t, err1)
|
assert.NoError(t, err1)
|
||||||
assert.NoError(t, err2)
|
assert.NoError(t, err2)
|
||||||
assert.NoError(t, err3)
|
assert.NoError(t, err3)
|
||||||
|
@ -42,7 +41,7 @@ func TestGetRepositoryCount(t *testing.T) {
|
||||||
func TestGetPublicRepositoryCount(t *testing.T) {
|
func TestGetPublicRepositoryCount(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
count, err := repo_model.CountRepositories(db.DefaultContext, countReposptsPublic)
|
count, err := CountRepositories(db.DefaultContext, countReposptsPublic)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(1), count)
|
assert.Equal(t, int64(1), count)
|
||||||
}
|
}
|
||||||
|
@ -50,14 +49,14 @@ func TestGetPublicRepositoryCount(t *testing.T) {
|
||||||
func TestGetPrivateRepositoryCount(t *testing.T) {
|
func TestGetPrivateRepositoryCount(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
count, err := repo_model.CountRepositories(db.DefaultContext, countReposptsPrivate)
|
count, err := CountRepositories(db.DefaultContext, countReposptsPrivate)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(2), count)
|
assert.Equal(t, int64(2), count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepoAPIURL(t *testing.T) {
|
func TestRepoAPIURL(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
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())
|
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) {
|
func TestWatchRepo(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
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})
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, user, repo, true))
|
assert.NoError(t, WatchRepo(db.DefaultContext, user, repo, true))
|
||||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Watch{RepoID: repo.ID, UserID: user.ID})
|
unittest.AssertExistsAndLoadBean(t, &Watch{RepoID: repo.ID, UserID: user.ID})
|
||||||
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
|
unittest.CheckConsistencyFor(t, &Repository{ID: repo.ID})
|
||||||
|
|
||||||
assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, user, repo, false))
|
assert.NoError(t, WatchRepo(db.DefaultContext, user, repo, false))
|
||||||
unittest.AssertNotExistsBean(t, &repo_model.Watch{RepoID: repo.ID, UserID: user.ID})
|
unittest.AssertNotExistsBean(t, &Watch{RepoID: repo.ID, UserID: user.ID})
|
||||||
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
|
unittest.CheckConsistencyFor(t, &Repository{ID: repo.ID})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMetas(t *testing.T) {
|
func TestMetas(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
repo := &repo_model.Repository{Name: "testRepo"}
|
repo := &Repository{Name: "testRepo"}
|
||||||
repo.Owner = &user_model.User{Name: "testOwner"}
|
repo.Owner = &user_model.User{Name: "testOwner"}
|
||||||
repo.OwnerName = repo.Owner.Name
|
repo.OwnerName = repo.Owner.Name
|
||||||
|
|
||||||
|
@ -90,16 +89,16 @@ func TestMetas(t *testing.T) {
|
||||||
assert.Equal(t, "testRepo", metas["repo"])
|
assert.Equal(t, "testRepo", metas["repo"])
|
||||||
assert.Equal(t, "testOwner", metas["user"])
|
assert.Equal(t, "testOwner", metas["user"])
|
||||||
|
|
||||||
externalTracker := repo_model.RepoUnit{
|
externalTracker := RepoUnit{
|
||||||
Type: unit.TypeExternalTracker,
|
Type: unit.TypeExternalTracker,
|
||||||
Config: &repo_model.ExternalTrackerConfig{
|
Config: &ExternalTrackerConfig{
|
||||||
ExternalTrackerFormat: "https://someurl.com/{user}/{repo}/{issue}",
|
ExternalTrackerFormat: "https://someurl.com/{user}/{repo}/{issue}",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testSuccess := func(expectedStyle string) {
|
testSuccess := func(expectedStyle string) {
|
||||||
repo.Units = []*repo_model.RepoUnit{&externalTracker}
|
repo.Units = []*RepoUnit{&externalTracker}
|
||||||
repo.RenderingMetas = nil
|
repo.commonRenderingMetas = nil
|
||||||
metas := repo.ComposeMetas(db.DefaultContext)
|
metas := repo.ComposeMetas(db.DefaultContext)
|
||||||
assert.Equal(t, expectedStyle, metas["style"])
|
assert.Equal(t, expectedStyle, metas["style"])
|
||||||
assert.Equal(t, "testRepo", metas["repo"])
|
assert.Equal(t, "testRepo", metas["repo"])
|
||||||
|
@ -118,7 +117,7 @@ func TestMetas(t *testing.T) {
|
||||||
externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleRegexp
|
externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleRegexp
|
||||||
testSuccess(markup.IssueNameStyleRegexp)
|
testSuccess(markup.IssueNameStyleRegexp)
|
||||||
|
|
||||||
repo, err := repo_model.GetRepositoryByID(db.DefaultContext, 3)
|
repo, err := GetRepositoryByID(db.DefaultContext, 3)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
metas = repo.ComposeMetas(db.DefaultContext)
|
metas = repo.ComposeMetas(db.DefaultContext)
|
||||||
|
@ -132,7 +131,7 @@ func TestGetRepositoryByURL(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
t.Run("InvalidPath", func(t *testing.T) {
|
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.Nil(t, repo)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
@ -140,7 +139,7 @@ func TestGetRepositoryByURL(t *testing.T) {
|
||||||
|
|
||||||
t.Run("ValidHttpURL", func(t *testing.T) {
|
t.Run("ValidHttpURL", func(t *testing.T) {
|
||||||
test := func(t *testing.T, url string) {
|
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.NotNil(t, repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -155,7 +154,7 @@ func TestGetRepositoryByURL(t *testing.T) {
|
||||||
|
|
||||||
t.Run("ValidGitSshURL", func(t *testing.T) {
|
t.Run("ValidGitSshURL", func(t *testing.T) {
|
||||||
test := func(t *testing.T, url string) {
|
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.NotNil(t, repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -173,7 +172,7 @@ func TestGetRepositoryByURL(t *testing.T) {
|
||||||
|
|
||||||
t.Run("ValidImplicitSshURL", func(t *testing.T) {
|
t.Run("ValidImplicitSshURL", func(t *testing.T) {
|
||||||
test := func(t *testing.T, url string) {
|
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.NotNil(t, repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -200,21 +199,21 @@ func TestComposeSSHCloneURL(t *testing.T) {
|
||||||
setting.SSH.Domain = "domain"
|
setting.SSH.Domain = "domain"
|
||||||
setting.SSH.Port = 22
|
setting.SSH.Port = 22
|
||||||
setting.Repository.UseCompatSSHURI = false
|
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
|
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
|
// test SSH_DOMAIN while use non-standard SSH port
|
||||||
setting.SSH.Port = 123
|
setting.SSH.Port = 123
|
||||||
setting.Repository.UseCompatSSHURI = false
|
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
|
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
|
// test IPv6 SSH_DOMAIN
|
||||||
setting.Repository.UseCompatSSHURI = false
|
setting.Repository.UseCompatSSHURI = false
|
||||||
setting.SSH.Domain = "::1"
|
setting.SSH.Domain = "::1"
|
||||||
setting.SSH.Port = 22
|
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
|
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"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,11 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/markup/common"
|
"code.gitea.io/gitea/modules/markup/common"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
"golang.org/x/net/html/atom"
|
"golang.org/x/net/html/atom"
|
||||||
|
@ -25,7 +25,27 @@ const (
|
||||||
IssueNameStyleRegexp = "regexp"
|
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.
|
// NOTE: All below regex matching do not perform any extra validation.
|
||||||
// Thus a link is produced even if the linked entity does not exist.
|
// Thus a link is produced even if the linked entity does not exist.
|
||||||
// While fast, this is also incorrect and lead to false positives.
|
// 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
|
// 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
|
// 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.
|
// 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 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 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 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 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,
|
// emailRegex is definitely not perfect with edge cases,
|
||||||
// it is still accepted by the CommonMark specification, as well as the HTML5 spec:
|
// it is still accepted by the CommonMark specification, as well as the HTML5 spec:
|
||||||
// http://spec.commonmark.org/0.28/#email-address
|
// http://spec.commonmark.org/0.28/#email-address
|
||||||
// https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail)
|
// 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 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 find emoji by alias like :smile:
|
||||||
emojiShortCodeRegex = regexp.MustCompile(`:[-+\w]+:`)
|
v.emojiShortCodeRegex = regexp.MustCompile(`:[-+\w]+:`)
|
||||||
)
|
|
||||||
|
|
||||||
// CSS class for action keywords (e.g. "closes: #1")
|
// example: https://domain/org/repo/pulls/27#hash
|
||||||
const keywordClass = "issue-keyword"
|
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.
|
// IsFullURLBytes reports whether link fits valid format.
|
||||||
func IsFullURLBytes(link []byte) bool {
|
func IsFullURLBytes(link []byte) bool {
|
||||||
return fullURLPattern.Match(link)
|
return globalVars().fullURLPattern.Match(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsFullURLString(link string) bool {
|
func IsFullURLString(link string) bool {
|
||||||
return fullURLPattern.MatchString(link)
|
return globalVars().fullURLPattern.MatchString(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsNonEmptyRelativePath(link string) bool {
|
func IsNonEmptyRelativePath(link string) bool {
|
||||||
return link != "" && !IsFullURLString(link) && link[0] != '/' && link[0] != '?' && link[0] != '#'
|
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
|
// CustomLinkURLSchemes allows for additional schemes to be detected when parsing links within text
|
||||||
func CustomLinkURLSchemes(schemes []string) {
|
func CustomLinkURLSchemes(schemes []string) {
|
||||||
schemes = append(schemes, "http", "https")
|
schemes = append(schemes, "http", "https")
|
||||||
|
@ -197,13 +194,6 @@ func RenderCommitMessage(
|
||||||
content string,
|
content string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
procs := commitMessageProcessors
|
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)
|
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.
|
// which changes every text node into a link to the passed default link.
|
||||||
func RenderCommitMessageSubject(
|
func RenderCommitMessageSubject(
|
||||||
ctx *RenderContext,
|
ctx *RenderContext,
|
||||||
content string,
|
defaultLink, content string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
procs := commitMessageSubjectProcessors
|
procs := slices.Clone(commitMessageSubjectProcessors)
|
||||||
if ctx.DefaultLink != "" {
|
procs = append(procs, func(ctx *RenderContext, node *html.Node) {
|
||||||
// we don't have to fear data races, because being
|
ch := &html.Node{Parent: node, Type: html.TextNode, Data: node.Data}
|
||||||
// commitMessageSubjectProcessors of fixed len and cap, every time we
|
node.Type = html.ElementNode
|
||||||
// append something to it the slice is realloc+copied, so append always
|
node.Data = "a"
|
||||||
// generates the slice ex-novo.
|
node.DataAtom = atom.A
|
||||||
procs = append(procs, genDefaultLinkProcessor(ctx.DefaultLink))
|
node.Attr = []html.Attribute{{Key: "href", Val: defaultLink}, {Key: "class", Val: "muted"}}
|
||||||
}
|
node.FirstChild, node.LastChild = ch, ch
|
||||||
|
})
|
||||||
return renderProcessString(ctx, procs, content)
|
return renderProcessString(ctx, procs, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,10 +240,8 @@ func RenderIssueTitle(
|
||||||
ctx *RenderContext,
|
ctx *RenderContext,
|
||||||
title string,
|
title string,
|
||||||
) (string, error) {
|
) (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{
|
return renderProcessString(ctx, []processor{
|
||||||
issueIndexPatternProcessor,
|
|
||||||
commitCrossReferencePatternProcessor,
|
|
||||||
hashCurrentPatternProcessor,
|
|
||||||
emojiShortCodeProcessor,
|
emojiShortCodeProcessor,
|
||||||
emojiProcessor,
|
emojiProcessor,
|
||||||
}, title)
|
}, title)
|
||||||
|
@ -288,11 +277,6 @@ func RenderEmoji(
|
||||||
return renderProcessString(ctx, emojiProcessors, content)
|
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 {
|
func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output io.Writer) error {
|
||||||
defer ctx.Cancel()
|
defer ctx.Cancel()
|
||||||
// FIXME: don't read all content to memory
|
// 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>"
|
// prepend "<html><body>"
|
||||||
strings.NewReader("<html><body>"),
|
strings.NewReader("<html><body>"),
|
||||||
// Strip out nuls - they're always invalid
|
// Strip out nuls - they're always invalid
|
||||||
bytes.NewReader(tagCleaner.ReplaceAll([]byte(nulCleaner.Replace(string(rawHTML))), []byte("<$1"))),
|
bytes.NewReader(globalVars().tagCleaner.ReplaceAll([]byte(globalVars().nulCleaner.Replace(string(rawHTML))), []byte("<$1"))),
|
||||||
// close the tags
|
// close the tags
|
||||||
strings.NewReader("</body></html>"),
|
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
|
// Add user-content- to IDs and "#" links if they don't already have them
|
||||||
for idx, attr := range node.Attr {
|
for idx, attr := range node.Attr {
|
||||||
val := strings.TrimPrefix(attr.Val, "#")
|
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 {
|
if attr.Key == "id" && notHasPrefix {
|
||||||
node.Attr[idx].Val = "user-content-" + attr.Val
|
node.Attr[idx].Val = "user-content-" + attr.Val
|
||||||
|
|
|
@ -54,7 +54,7 @@ func createCodeLink(href, content, class string) *html.Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
func anyHashPatternExtract(s string) (ret anyHashPatternResult, ok bool) {
|
func anyHashPatternExtract(s string) (ret anyHashPatternResult, ok bool) {
|
||||||
m := anyHashPattern.FindStringSubmatchIndex(s)
|
m := globalVars().anyHashPattern.FindStringSubmatchIndex(s)
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return ret, false
|
return ret, false
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
node = node.NextSibling
|
node = node.NextSibling
|
||||||
continue
|
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
|
if m == nil || slices.Contains(m[:8], -1) { // ensure that every group (m[0]...m[7]) has a match
|
||||||
node = node.NextSibling
|
node = node.NextSibling
|
||||||
continue
|
continue
|
||||||
|
@ -173,7 +173,7 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
ctx.ShaExistCache = make(map[string]bool)
|
ctx.ShaExistCache = make(map[string]bool)
|
||||||
}
|
}
|
||||||
for node != nil && node != next && start < len(node.Data) {
|
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 {
|
if m == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import "golang.org/x/net/html"
|
||||||
func emailAddressProcessor(ctx *RenderContext, node *html.Node) {
|
func emailAddressProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
next := node.NextSibling
|
next := node.NextSibling
|
||||||
for node != nil && node != next {
|
for node != nil && node != next {
|
||||||
m := emailRegex.FindStringSubmatchIndex(node.Data)
|
m := globalVars().emailRegex.FindStringSubmatchIndex(node.Data)
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
start := 0
|
start := 0
|
||||||
next := node.NextSibling
|
next := node.NextSibling
|
||||||
for node != nil && node != next && start < len(node.Data) {
|
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 {
|
if m == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,17 +40,19 @@ func link(href, class, contents string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
var numericMetas = map[string]string{
|
var numericMetas = map[string]string{
|
||||||
"format": "https://someurl.com/{user}/{repo}/{index}",
|
"format": "https://someurl.com/{user}/{repo}/{index}",
|
||||||
"user": "someUser",
|
"user": "someUser",
|
||||||
"repo": "someRepo",
|
"repo": "someRepo",
|
||||||
"style": IssueNameStyleNumeric,
|
"style": IssueNameStyleNumeric,
|
||||||
|
"markupAllowShortIssuePattern": "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
var alphanumericMetas = map[string]string{
|
var alphanumericMetas = map[string]string{
|
||||||
"format": "https://someurl.com/{user}/{repo}/{index}",
|
"format": "https://someurl.com/{user}/{repo}/{index}",
|
||||||
"user": "someUser",
|
"user": "someUser",
|
||||||
"repo": "someRepo",
|
"repo": "someRepo",
|
||||||
"style": IssueNameStyleAlphanumeric,
|
"style": IssueNameStyleAlphanumeric,
|
||||||
|
"markupAllowShortIssuePattern": "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
var regexpMetas = map[string]string{
|
var regexpMetas = map[string]string{
|
||||||
|
@ -62,8 +64,15 @@ var regexpMetas = map[string]string{
|
||||||
|
|
||||||
// these values should match the TestOrgRepo const above
|
// these values should match the TestOrgRepo const above
|
||||||
var localMetas = map[string]string{
|
var localMetas = map[string]string{
|
||||||
"user": "test-owner",
|
"user": "test-owner",
|
||||||
"repo": "test-repo",
|
"repo": "test-repo",
|
||||||
|
"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) {
|
||||||
|
@ -124,9 +133,8 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
|
||||||
}
|
}
|
||||||
expectedNil := fmt.Sprintf(expectedFmt, links...)
|
expectedNil := fmt.Sprintf(expectedFmt, links...)
|
||||||
testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{
|
testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
Ctx: git.DefaultContext,
|
||||||
Metas: localMetas,
|
Metas: localMetas,
|
||||||
ContentMode: RenderContentAsComment,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
class := "ref-issue"
|
class := "ref-issue"
|
||||||
|
@ -139,9 +147,8 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
|
||||||
}
|
}
|
||||||
expectedNum := fmt.Sprintf(expectedFmt, links...)
|
expectedNum := fmt.Sprintf(expectedFmt, links...)
|
||||||
testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{
|
testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
Ctx: git.DefaultContext,
|
||||||
Metas: numericMetas,
|
Metas: numericMetas,
|
||||||
ContentMode: RenderContentAsComment,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
setting.AppURL = TestAppURL
|
||||||
metas := map[string]string{
|
metas := map[string]string{
|
||||||
"format": "https://someurl.com/{user}/{repo}/{index}",
|
"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) {
|
func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
|
||||||
ctx.Links.AbsolutePrefix = true
|
ctx.Links.AbsolutePrefix = true
|
||||||
if ctx.Links.Base == "" {
|
if ctx.Links.Base == "" {
|
||||||
|
@ -318,8 +341,7 @@ func TestRender_AutoLink(t *testing.T) {
|
||||||
Links: Links{
|
Links: Links{
|
||||||
Base: TestRepoURL,
|
Base: TestRepoURL,
|
||||||
},
|
},
|
||||||
Metas: localMetas,
|
Metas: localWikiMetas,
|
||||||
ContentMode: RenderContentAsWiki,
|
|
||||||
}, strings.NewReader(input), &buffer)
|
}, strings.NewReader(input), &buffer)
|
||||||
assert.Equal(t, err, nil)
|
assert.Equal(t, err, nil)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
|
||||||
|
@ -391,10 +413,10 @@ func TestRegExp_sha1CurrentPattern(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range trueTestCases {
|
for _, testCase := range trueTestCases {
|
||||||
assert.True(t, hashCurrentPattern.MatchString(testCase))
|
assert.True(t, globalVars().hashCurrentPattern.MatchString(testCase))
|
||||||
}
|
}
|
||||||
for _, testCase := range falseTestCases {
|
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 {
|
for _, testCase := range trueTestCases {
|
||||||
assert.True(t, shortLinkPattern.MatchString(testCase))
|
assert.True(t, globalVars().shortLinkPattern.MatchString(testCase))
|
||||||
}
|
}
|
||||||
for _, testCase := range falseTestCases {
|
for _, testCase := range falseTestCases {
|
||||||
assert.False(t, shortLinkPattern.MatchString(testCase))
|
assert.False(t, globalVars().shortLinkPattern.MatchString(testCase))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/references"
|
"code.gitea.io/gitea/modules/references"
|
||||||
"code.gitea.io/gitea/modules/regexplru"
|
"code.gitea.io/gitea/modules/regexplru"
|
||||||
|
@ -23,18 +24,21 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
}
|
}
|
||||||
next := node.NextSibling
|
next := node.NextSibling
|
||||||
for node != nil && node != next {
|
for node != nil && node != next {
|
||||||
m := getIssueFullPattern().FindStringSubmatchIndex(node.Data)
|
m := globalVars().issueFullPattern.FindStringSubmatchIndex(node.Data)
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return
|
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
|
// 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 {
|
if mDiffView != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
link := node.Data[m[0]:m[1]]
|
link := node.Data[m[0]:m[1]]
|
||||||
|
if !httplib.IsCurrentGiteaSiteURL(ctx.Ctx, link) {
|
||||||
|
return
|
||||||
|
}
|
||||||
text := "#" + node.Data[m[2]:m[3]]
|
text := "#" + node.Data[m[2]:m[3]]
|
||||||
// if m[4] and m[5] is not -1, then link is to a comment
|
// if m[4] and m[5] is not -1, then link is to a comment
|
||||||
// indicate that in the text by appending (comment)
|
// indicate that in the text by appending (comment)
|
||||||
|
@ -67,8 +71,10 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// crossLinkOnly if not comment and not wiki
|
// crossLinkOnly: do not parse "#123", only parse "owner/repo#123"
|
||||||
crossLinkOnly := ctx.ContentMode != RenderContentAsTitle && ctx.ContentMode != RenderContentAsComment && ctx.ContentMode != RenderContentAsWiki
|
// 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 (
|
var (
|
||||||
found bool
|
found bool
|
||||||
|
|
|
@ -20,9 +20,9 @@ func ResolveLink(ctx *RenderContext, link, userContentAnchorPrefix string) (resu
|
||||||
isAnchorFragment := link != "" && link[0] == '#'
|
isAnchorFragment := link != "" && link[0] == '#'
|
||||||
if !isAnchorFragment && !IsFullURLString(link) {
|
if !isAnchorFragment && !IsFullURLString(link) {
|
||||||
linkBase := ctx.Links.Base
|
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
|
// no need to check if the link should be resolved as a wiki link or a wiki raw link
|
||||||
// just use wiki link here and it will be redirected to a wiki raw link if necessary
|
// just use wiki link here, and it will be redirected to a wiki raw link if necessary
|
||||||
linkBase = ctx.Links.WikiLink()
|
linkBase = ctx.Links.WikiLink()
|
||||||
} else if ctx.Links.BranchPath != "" || ctx.Links.TreePath != "" {
|
} 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}"
|
// 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) {
|
func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
next := node.NextSibling
|
next := node.NextSibling
|
||||||
for node != nil && node != next {
|
for node != nil && node != next {
|
||||||
m := shortLinkPattern.FindStringSubmatchIndex(node.Data)
|
m := globalVars().shortLinkPattern.FindStringSubmatchIndex(node.Data)
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
}
|
}
|
||||||
if image {
|
if image {
|
||||||
if !absoluteLink {
|
if !absoluteLink {
|
||||||
link = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.ContentMode == RenderContentAsWiki), link)
|
link = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), link)
|
||||||
}
|
}
|
||||||
title := props["title"]
|
title := props["title"]
|
||||||
if 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
|
// descriptionLinkProcessor creates links for DescriptionHTML
|
||||||
func descriptionLinkProcessor(ctx *RenderContext, node *html.Node) {
|
func descriptionLinkProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
next := node.NextSibling
|
next := node.NextSibling
|
||||||
|
|
|
@ -17,7 +17,7 @@ func visitNodeImg(ctx *RenderContext, img *html.Node) (next *html.Node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if IsNonEmptyRelativePath(attr.Val) {
|
if IsNonEmptyRelativePath(attr.Val) {
|
||||||
attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.ContentMode == RenderContentAsWiki), attr.Val)
|
attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val)
|
||||||
|
|
||||||
// By default, the "<img>" tag should also be clickable,
|
// By default, the "<img>" tag should also be clickable,
|
||||||
// because frontend use `<img>` to paste the re-scaled image into the markdown,
|
// because frontend use `<img>` to paste the re-scaled image into the markdown,
|
||||||
|
@ -53,7 +53,7 @@ func visitNodeVideo(ctx *RenderContext, node *html.Node) (next *html.Node) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if IsNonEmptyRelativePath(attr.Val) {
|
if IsNonEmptyRelativePath(attr.Val) {
|
||||||
attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.ContentMode == RenderContentAsWiki), attr.Val)
|
attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val)
|
||||||
}
|
}
|
||||||
attr.Val = camoHandleLink(attr.Val)
|
attr.Val = camoHandleLink(attr.Val)
|
||||||
node.Attr[i] = attr
|
node.Attr[i] = attr
|
||||||
|
|
|
@ -27,6 +27,11 @@ var (
|
||||||
"user": testRepoOwnerName,
|
"user": testRepoOwnerName,
|
||||||
"repo": testRepoName,
|
"repo": testRepoName,
|
||||||
}
|
}
|
||||||
|
localWikiMetas = map[string]string{
|
||||||
|
"user": testRepoOwnerName,
|
||||||
|
"repo": testRepoName,
|
||||||
|
"markupContentMode": "wiki",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockRepo struct {
|
type mockRepo struct {
|
||||||
|
@ -413,8 +418,7 @@ func TestRender_ShortLinks(t *testing.T) {
|
||||||
Links: markup.Links{
|
Links: markup.Links{
|
||||||
Base: markup.TestRepoURL,
|
Base: markup.TestRepoURL,
|
||||||
},
|
},
|
||||||
Metas: localMetas,
|
Metas: localWikiMetas,
|
||||||
ContentMode: markup.RenderContentAsWiki,
|
|
||||||
}, input)
|
}, input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
|
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
|
||||||
|
@ -526,10 +530,9 @@ func TestRender_ShortLinks(t *testing.T) {
|
||||||
func TestRender_RelativeMedias(t *testing.T) {
|
func TestRender_RelativeMedias(t *testing.T) {
|
||||||
render := func(input string, isWiki bool, links markup.Links) string {
|
render := func(input string, isWiki bool, links markup.Links) string {
|
||||||
buffer, err := markdown.RenderString(&markup.RenderContext{
|
buffer, err := markdown.RenderString(&markup.RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
Ctx: git.DefaultContext,
|
||||||
Links: links,
|
Links: links,
|
||||||
Metas: localMetas,
|
Metas: util.Iif(isWiki, localWikiMetas, localMetas),
|
||||||
ContentMode: util.Iif(isWiki, markup.RenderContentAsWiki, markup.RenderContentAsComment),
|
|
||||||
}, input)
|
}, input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return strings.TrimSpace(string(buffer))
|
return strings.TrimSpace(string(buffer))
|
||||||
|
|
|
@ -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 }`
|
// TODO: this was a quite unclear part, old code: `if metas["mode"] != "document" { use comment link break setting }`
|
||||||
// many places render non-comment contents with no mode=document, then these contents also use comment's hard line break setting
|
// many places render non-comment contents with no mode=document, then these contents also use comment's hard line break setting
|
||||||
// especially in many tests.
|
// especially in many tests.
|
||||||
|
markdownLineBreakStyle := ctx.Metas["markdownLineBreakStyle"]
|
||||||
if markup.RenderBehaviorForTesting.ForceHardLineBreak {
|
if markup.RenderBehaviorForTesting.ForceHardLineBreak {
|
||||||
v.SetHardLineBreak(true)
|
v.SetHardLineBreak(true)
|
||||||
} else if ctx.ContentMode == markup.RenderContentAsComment {
|
} else if markdownLineBreakStyle == "comment" {
|
||||||
v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInComments)
|
v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInComments)
|
||||||
} else {
|
} else if markdownLineBreakStyle == "document" {
|
||||||
v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInDocuments)
|
v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInDocuments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,12 @@ var localMetas = map[string]string{
|
||||||
"repo": testRepoName,
|
"repo": testRepoName,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var localWikiMetas = map[string]string{
|
||||||
|
"user": testRepoOwnerName,
|
||||||
|
"repo": testRepoName,
|
||||||
|
"markupContentMode": "wiki",
|
||||||
|
}
|
||||||
|
|
||||||
type mockRepo struct {
|
type mockRepo struct {
|
||||||
OwnerName string
|
OwnerName string
|
||||||
RepoName string
|
RepoName string
|
||||||
|
@ -75,7 +81,7 @@ func TestRender_StandardLinks(t *testing.T) {
|
||||||
Links: markup.Links{
|
Links: markup.Links{
|
||||||
Base: FullURL,
|
Base: FullURL,
|
||||||
},
|
},
|
||||||
ContentMode: markup.RenderContentAsWiki,
|
Metas: localWikiMetas,
|
||||||
}, input)
|
}, input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
|
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
|
||||||
|
@ -307,9 +313,8 @@ func TestTotal_RenderWiki(t *testing.T) {
|
||||||
Links: markup.Links{
|
Links: markup.Links{
|
||||||
Base: FullURL,
|
Base: FullURL,
|
||||||
},
|
},
|
||||||
Repo: newMockRepo(testRepoOwnerName, testRepoName),
|
Repo: newMockRepo(testRepoOwnerName, testRepoName),
|
||||||
Metas: localMetas,
|
Metas: localWikiMetas,
|
||||||
ContentMode: markup.RenderContentAsWiki,
|
|
||||||
}, sameCases[i])
|
}, sameCases[i])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, answers[i], string(line))
|
assert.Equal(t, answers[i], string(line))
|
||||||
|
@ -334,7 +339,7 @@ func TestTotal_RenderWiki(t *testing.T) {
|
||||||
Links: markup.Links{
|
Links: markup.Links{
|
||||||
Base: FullURL,
|
Base: FullURL,
|
||||||
},
|
},
|
||||||
ContentMode: markup.RenderContentAsWiki,
|
Metas: localWikiMetas,
|
||||||
}, testCases[i])
|
}, testCases[i])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, testCases[i+1], string(line))
|
assert.EqualValues(t, testCases[i+1], string(line))
|
||||||
|
@ -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="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="/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/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/>
|
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/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><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="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="/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/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/>
|
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/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><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://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://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/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/>
|
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/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><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://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://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/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/>
|
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/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><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="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="/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/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/>
|
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/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><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="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="/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/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/>
|
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/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><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="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="/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/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/>
|
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/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><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="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="/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/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/>
|
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/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><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="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="/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/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/>
|
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/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><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="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="/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/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/>
|
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/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><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="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="/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/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/>
|
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/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><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="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="/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/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/>
|
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/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><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)()
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
||||||
for i, c := range cases {
|
for i, c := range cases {
|
||||||
result, err := markdown.RenderString(&markup.RenderContext{
|
result, err := markdown.RenderString(&markup.RenderContext{
|
||||||
Ctx: context.Background(),
|
Ctx: context.Background(),
|
||||||
Links: c.Links,
|
Links: c.Links,
|
||||||
ContentMode: util.Iif(c.IsWiki, markup.RenderContentAsWiki, markup.RenderContentAsDefault),
|
Metas: util.Iif(c.IsWiki, map[string]string{"markupContentMode": "wiki"}, map[string]string{}),
|
||||||
}, input)
|
}, 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)
|
||||||
|
|
|
@ -21,7 +21,7 @@ func (g *ASTTransformer) transformImage(ctx *markup.RenderContext, v *ast.Image)
|
||||||
// Check if the destination is a real link
|
// Check if the destination is a real link
|
||||||
if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) {
|
if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) {
|
||||||
v.Destination = []byte(giteautil.URLJoin(
|
v.Destination = []byte(giteautil.URLJoin(
|
||||||
ctx.Links.ResolveMediaLink(ctx.ContentMode == markup.RenderContentAsWiki),
|
ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()),
|
||||||
strings.TrimLeft(string(v.Destination), "/"),
|
strings.TrimLeft(string(v.Destination), "/"),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,15 +144,14 @@ func (r *Writer) resolveLink(kind, link string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
base := r.Ctx.Links.Base
|
base := r.Ctx.Links.Base
|
||||||
isWiki := r.Ctx.ContentMode == markup.RenderContentAsWiki
|
if r.Ctx.IsMarkupContentWiki() {
|
||||||
if isWiki {
|
|
||||||
base = r.Ctx.Links.WikiLink()
|
base = r.Ctx.Links.WikiLink()
|
||||||
} else if r.Ctx.Links.HasBranchInfo() {
|
} else if r.Ctx.Links.HasBranchInfo() {
|
||||||
base = r.Ctx.Links.SrcLink()
|
base = r.Ctx.Links.SrcLink()
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind == "image" || kind == "video" {
|
if kind == "image" || kind == "video" {
|
||||||
base = r.Ctx.Links.ResolveMediaLink(isWiki)
|
base = r.Ctx.Links.ResolveMediaLink(r.Ctx.IsMarkupContentWiki())
|
||||||
}
|
}
|
||||||
|
|
||||||
link = util.URLJoin(base, link)
|
link = util.URLJoin(base, link)
|
||||||
|
|
|
@ -27,7 +27,7 @@ func TestRender_StandardLinks(t *testing.T) {
|
||||||
Base: "/relative-path",
|
Base: "/relative-path",
|
||||||
BranchPath: "branch/main",
|
BranchPath: "branch/main",
|
||||||
},
|
},
|
||||||
ContentMode: util.Iif(isWiki, markup.RenderContentAsWiki, markup.RenderContentAsDefault),
|
Metas: map[string]string{"markupContentMode": util.Iif(isWiki, "wiki", "")},
|
||||||
}, input)
|
}, input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
|
|
|
@ -27,15 +27,6 @@ const (
|
||||||
RenderMetaAsTable RenderMetaMode = "table"
|
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 {
|
var RenderBehaviorForTesting struct {
|
||||||
// Markdown line break rendering has 2 default behaviors:
|
// Markdown line break rendering has 2 default behaviors:
|
||||||
// * Use hard: replace "\n" with "<br>" for comments, setting.Markdown.EnableHardLineBreakInComments=true
|
// * 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
|
// for file mode, it could be left as empty, and will be detected by file extension in RelativePath
|
||||||
MarkupType string
|
MarkupType string
|
||||||
|
|
||||||
// what the content will be used for: eg: for comment or for wiki? or just render a file?
|
Links Links // special link references for rendering, especially when there is a branch/tree path
|
||||||
ContentMode RenderContentMode
|
|
||||||
|
// user&repo, format&style®exp (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®exp (for external issue pattern), teams&org (for mention), BranchNameSubURL(for iframe&asciicast)
|
|
||||||
DefaultLink string // TODO: need to figure out
|
|
||||||
GitRepo *git.Repository
|
GitRepo *git.Repository
|
||||||
Repo gitrepo.Repository
|
Repo gitrepo.Repository
|
||||||
ShaExistCache map[string]bool
|
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.
|
// Render renders markup file to HTML with all specific handling stuff.
|
||||||
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
|
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
|
||||||
if ctx.MarkupType == "" && ctx.RelativePath != "" {
|
if ctx.MarkupType == "" && ctx.RelativePath != "" {
|
||||||
|
@ -232,3 +229,7 @@ func Init(ph *ProcessorHelper) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ComposeSimpleDocumentMetas() map[string]string {
|
||||||
|
return map[string]string{"markdownLineBreakStyle": "document"}
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
type Links struct {
|
type Links struct {
|
||||||
AbsolutePrefix bool // add absolute URL prefix to auto-resolved links like "#issue", but not for pre-provided links and medias
|
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"
|
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
|
TreePath string // the dir of the file, eg: "doc" if the file "doc/CHANGE.md" is being rendered
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,19 +62,18 @@ func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, me
|
||||||
}
|
}
|
||||||
msgLine = strings.TrimRightFunc(msgLine, unicode.IsSpace)
|
msgLine = strings.TrimRightFunc(msgLine, unicode.IsSpace)
|
||||||
if len(msgLine) == 0 {
|
if len(msgLine) == 0 {
|
||||||
return template.HTML("")
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// we can safely assume that it will not return any error, since there
|
// we can safely assume that it will not return any error, since there
|
||||||
// shouldn't be any special HTML.
|
// shouldn't be any special HTML.
|
||||||
renderedMessage, err := markup.RenderCommitMessageSubject(&markup.RenderContext{
|
renderedMessage, err := markup.RenderCommitMessageSubject(&markup.RenderContext{
|
||||||
Ctx: ut.ctx,
|
Ctx: ut.ctx,
|
||||||
DefaultLink: urlDefault,
|
Metas: metas,
|
||||||
Metas: metas,
|
}, urlDefault, template.HTMLEscapeString(msgLine))
|
||||||
}, template.HTMLEscapeString(msgLine))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderCommitMessageSubject: %v", err)
|
log.Error("RenderCommitMessageSubject: %v", err)
|
||||||
return template.HTML("")
|
return ""
|
||||||
}
|
}
|
||||||
return renderCodeBlock(template.HTML(renderedMessage))
|
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{
|
renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
||||||
Ctx: ut.ctx,
|
Ctx: ut.ctx,
|
||||||
Metas: metas,
|
Metas: metas,
|
||||||
ContentMode: markup.RenderContentAsComment,
|
|
||||||
}, template.HTMLEscapeString(msgLine))
|
}, template.HTMLEscapeString(msgLine))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderCommitMessage: %v", err)
|
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
|
// RenderIssueTitle renders issue/pull title with defined post processors
|
||||||
func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML {
|
func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML {
|
||||||
renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{
|
renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{
|
||||||
Ctx: ut.ctx,
|
Ctx: ut.ctx,
|
||||||
ContentMode: markup.RenderContentAsTitle,
|
Metas: metas,
|
||||||
Metas: metas,
|
|
||||||
}, template.HTMLEscapeString(text))
|
}, template.HTMLEscapeString(text))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderIssueTitle: %v", err)
|
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
|
func (ut *RenderUtils) MarkdownToHtml(input string) template.HTML { //nolint:revive
|
||||||
output, err := markdown.RenderString(&markup.RenderContext{
|
output, err := markdown.RenderString(&markup.RenderContext{
|
||||||
Ctx: ut.ctx,
|
Ctx: ut.ctx,
|
||||||
Metas: map[string]string{"mode": "document"},
|
Metas: markup.ComposeSimpleDocumentMetas(),
|
||||||
}, input)
|
}, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderString: %v", err)
|
log.Error("RenderString: %v", err)
|
||||||
|
|
|
@ -47,10 +47,11 @@ mail@domain.com
|
||||||
}
|
}
|
||||||
|
|
||||||
var testMetas = map[string]string{
|
var testMetas = map[string]string{
|
||||||
"user": "user13",
|
"user": "user13",
|
||||||
"repo": "repo11",
|
"repo": "repo11",
|
||||||
"repoPath": "../../tests/gitea-repositories-meta/user13/repo11.git/",
|
"repoPath": "../../tests/gitea-repositories-meta/user13/repo11.git/",
|
||||||
"mode": "comment",
|
"markdownLineBreakStyle": "comment",
|
||||||
|
"markupAllowShortIssuePattern": "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
@ -75,8 +76,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.DisableInternalAttributes, true)()
|
||||||
type args struct {
|
type args struct {
|
||||||
msg string
|
msg string
|
||||||
metas map[string]string
|
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -108,7 +108,7 @@ func TestRenderCommitBody(t *testing.T) {
|
||||||
ut := newTestRenderUtils()
|
ut := newTestRenderUtils()
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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) {
|
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))
|
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>
|
<span class="emoji" aria-label="thumbs up">👍</span>
|
||||||
mail@domain.com
|
mail@domain.com
|
||||||
@mention-user test
|
@mention-user test
|
||||||
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
#123
|
||||||
space<SPACE><SPACE>
|
space<SPACE><SPACE>
|
||||||
`
|
`
|
||||||
expected = strings.ReplaceAll(expected, "<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) {
|
func TestRenderMarkdownToHtml(t *testing.T) {
|
||||||
|
|
|
@ -47,11 +47,12 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPa
|
||||||
switch mode {
|
switch mode {
|
||||||
case "gfm": // legacy mode, do nothing
|
case "gfm": // legacy mode, do nothing
|
||||||
case "comment":
|
case "comment":
|
||||||
renderCtx.ContentMode = markup.RenderContentAsComment
|
renderCtx.Metas = map[string]string{"markdownLineBreakStyle": "comment"}
|
||||||
case "wiki":
|
case "wiki":
|
||||||
renderCtx.ContentMode = markup.RenderContentAsWiki
|
renderCtx.Metas = map[string]string{"markdownLineBreakStyle": "document", "markupContentMode": "wiki"}
|
||||||
case "file":
|
case "file":
|
||||||
// render the repo file content by its extension
|
// render the repo file content by its extension
|
||||||
|
renderCtx.Metas = map[string]string{"markdownLineBreakStyle": "document"}
|
||||||
renderCtx.MarkupType = ""
|
renderCtx.MarkupType = ""
|
||||||
renderCtx.RelativePath = filePath
|
renderCtx.RelativePath = filePath
|
||||||
renderCtx.InStandalonePage = true
|
renderCtx.InStandalonePage = true
|
||||||
|
@ -74,10 +75,12 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPa
|
||||||
|
|
||||||
if repo != nil && repo.Repository != nil {
|
if repo != nil && repo.Repository != nil {
|
||||||
renderCtx.Repo = repo.Repository
|
renderCtx.Repo = repo.Repository
|
||||||
if renderCtx.ContentMode == markup.RenderContentAsComment {
|
if mode == "file" {
|
||||||
renderCtx.Metas = repo.Repository.ComposeMetas(ctx)
|
|
||||||
} else {
|
|
||||||
renderCtx.Metas = repo.Repository.ComposeDocumentMetas(ctx)
|
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 {
|
if err := markup.Render(renderCtx, strings.NewReader(text), ctx.Resp); err != nil {
|
||||||
|
|
|
@ -56,7 +56,7 @@ func renderMarkdown(ctx *context.Context, act *activities_model.Action, content
|
||||||
Links: markup.Links{
|
Links: markup.Links{
|
||||||
Base: act.GetRepoLink(ctx),
|
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),
|
"user": act.GetRepoUserName(ctx),
|
||||||
"repo": act.GetRepoName(ctx),
|
"repo": act.GetRepoName(ctx),
|
||||||
},
|
},
|
||||||
|
|
|
@ -46,9 +46,7 @@ func showUserFeed(ctx *context.Context, formatType string) {
|
||||||
Links: markup.Links{
|
Links: markup.Links{
|
||||||
Base: ctx.ContextUser.HTMLURL(),
|
Base: ctx.ContextUser.HTMLURL(),
|
||||||
},
|
},
|
||||||
Metas: map[string]string{
|
Metas: markup.ComposeSimpleDocumentMetas(),
|
||||||
"user": ctx.ContextUser.GetDisplayName(),
|
|
||||||
},
|
|
||||||
}, ctx.ContextUser.Description)
|
}, ctx.ContextUser.Description)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
|
|
|
@ -189,7 +189,7 @@ func prepareOrgProfileReadme(ctx *context.Context, viewRepositories bool) bool {
|
||||||
Base: profileDbRepo.Link(),
|
Base: profileDbRepo.Link(),
|
||||||
BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
|
BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
|
||||||
},
|
},
|
||||||
Metas: map[string]string{"mode": "document"},
|
Metas: markup.ComposeSimpleDocumentMetas(),
|
||||||
}, bytes); err != nil {
|
}, bytes); err != nil {
|
||||||
log.Error("failed to RenderString: %v", err)
|
log.Error("failed to RenderString: %v", err)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -289,9 +289,8 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
|
||||||
}
|
}
|
||||||
|
|
||||||
rctx := &markup.RenderContext{
|
rctx := &markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
ContentMode: markup.RenderContentAsWiki,
|
Metas: ctx.Repo.Repository.ComposeWikiMetas(ctx),
|
||||||
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
|
|
||||||
Links: markup.Links{
|
Links: markup.Links{
|
||||||
Base: ctx.Repo.RepoLink,
|
Base: ctx.Repo.RepoLink,
|
||||||
},
|
},
|
||||||
|
|
|
@ -50,7 +50,7 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
|
||||||
ctx.Data["OpenIDs"] = openIDs
|
ctx.Data["OpenIDs"] = openIDs
|
||||||
if len(ctx.ContextUser.Description) != 0 {
|
if len(ctx.ContextUser.Description) != 0 {
|
||||||
content, err := markdown.RenderString(&markup.RenderContext{
|
content, err := markdown.RenderString(&markup.RenderContext{
|
||||||
Metas: map[string]string{"mode": "document"},
|
Metas: markup.ComposeSimpleDocumentMetas(),
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
}, ctx.ContextUser.Description)
|
}, ctx.ContextUser.Description)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<div class="file-header-left tw-flex tw-items-center tw-py-2 tw-pr-4">
|
<div class="file-header-left tw-flex tw-items-center tw-py-2 tw-pr-4">
|
||||||
{{if .ReadmeInList}}
|
{{if .ReadmeInList}}
|
||||||
{{svg "octicon-book" 16 "tw-mr-2"}}
|
{{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}}
|
{{else}}
|
||||||
{{template "repo/file_info" .}}
|
{{template "repo/file_info" .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
Loading…
Reference in New Issue