mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-25 22:23:42 +09:00
enhance(emoji): update to Unicode 17, unify and lazy-load emoji data (#39363)
Generate emoji data from Unicode 17's `emoji-test.txt`, keeping existing aliases. `public/assets/emoji.json` is now the single emoji data file, also loaded by the backend. Rendered emoji drop their `aria-label`, the dark theme inverts key on a new `data-alias` attribute instead. Skin tone variants and their Gitea-only aliases are removed, GitHub has none either. Emoji autocompletion is now lazy-loaded with the markdown editor, shrinking the index JS chunk from 653KB to 563KB. --------- Signed-off-by: silverwind <me@silverwind.io> Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
co-authored by
wxiaoguang
bircni
parent
05f049e8bb
commit
64f31d9b70
+57
-20
@@ -8,24 +8,23 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"unicode/utf8"
|
||||
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/public"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
// Gemoji is a set of emoji data.
|
||||
type Gemoji []Emoji
|
||||
|
||||
// Emoji represents a single emoji and associated data.
|
||||
type Emoji struct {
|
||||
Emoji string
|
||||
Description string
|
||||
Aliases []string
|
||||
UnicodeVersion string
|
||||
SkinTones bool
|
||||
Emoji string `json:"emoji"`
|
||||
Aliases []string `json:"aliases"`
|
||||
}
|
||||
|
||||
type globalVarsStruct struct {
|
||||
emojis []Emoji
|
||||
codeMap map[string]int // emoji Unicode code to its emoji data.
|
||||
aliasMap map[string]int // the alias to its emoji data.
|
||||
trie *util.TrieNode // trie for finding emoji positions.
|
||||
@@ -43,8 +42,13 @@ func globalVars() *globalVarsStruct {
|
||||
}
|
||||
// although there can be concurrent calls, the result should be the same, and there is no performance problem
|
||||
vars = &globalVarsStruct{}
|
||||
vars.codeMap = make(map[string]int, len(GemojiData))
|
||||
vars.aliasMap = make(map[string]int, len(GemojiData))
|
||||
if data, err := public.AssetFS().ReadFile("assets/emoji.json"); err != nil {
|
||||
log.Error("Unable to read assets/emoji.json: %v", err)
|
||||
} else if err = json.Unmarshal(data, &vars.emojis); err != nil {
|
||||
log.Error("Unable to parse assets/emoji.json: %v", err)
|
||||
}
|
||||
vars.codeMap = make(map[string]int, len(vars.emojis))
|
||||
vars.aliasMap = make(map[string]int, len(vars.emojis))
|
||||
vars.trie = &util.TrieNode{}
|
||||
|
||||
// process emoji codes and aliases
|
||||
@@ -52,11 +56,11 @@ func globalVars() *globalVarsStruct {
|
||||
aliasPairs := make([]string, 0)
|
||||
|
||||
// sort from largest to small so we match combined emoji first
|
||||
sort.Slice(GemojiData, func(i, j int) bool {
|
||||
return len(GemojiData[i].Emoji) > len(GemojiData[j].Emoji)
|
||||
sort.Slice(vars.emojis, func(i, j int) bool {
|
||||
return len(vars.emojis[i].Emoji) > len(vars.emojis[j].Emoji)
|
||||
})
|
||||
|
||||
for idx, emoji := range GemojiData {
|
||||
for idx, emoji := range vars.emojis {
|
||||
if emoji.Emoji == "" || len(emoji.Aliases) == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -95,29 +99,34 @@ func globalVars() *globalVarsStruct {
|
||||
}
|
||||
|
||||
// FromCode retrieves the emoji data based on the provided Unicode code
|
||||
// e.g.: "\u2618" will return the Gemoji data for "shamrock".
|
||||
// e.g.: "\u2618" will return the emoji data for "shamrock".
|
||||
func FromCode(code string) *Emoji {
|
||||
i, ok := globalVars().codeMap[code]
|
||||
vars := globalVars()
|
||||
i, ok := vars.codeMap[code]
|
||||
if !ok {
|
||||
i, ok = vars.codeMap[removeSkinTones(code)]
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &GemojiData[i]
|
||||
return &vars.emojis[i]
|
||||
}
|
||||
|
||||
// FromAlias retrieves the emoji data based on the provided alias in the form "alias" or ":alias:"
|
||||
// e.g.: "shamrock" or ":shamrock:" will return the Gemoji data for "shamrock".
|
||||
// e.g.: "shamrock" or ":shamrock:" will return the emoji data for "shamrock".
|
||||
func FromAlias(alias string) *Emoji {
|
||||
if strings.HasPrefix(alias, ":") && strings.HasSuffix(alias, ":") {
|
||||
alias = alias[1 : len(alias)-1]
|
||||
}
|
||||
|
||||
i, ok := globalVars().aliasMap[alias]
|
||||
vars := globalVars()
|
||||
i, ok := vars.aliasMap[alias]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &GemojiData[i]
|
||||
return &vars.emojis[i]
|
||||
}
|
||||
|
||||
// ReplaceCodes replaces all emoji codes with the first corresponding emoji alias in the form of ":alias:"
|
||||
@@ -139,8 +148,36 @@ func FindEmojiSubmatchIndex(s string) []int {
|
||||
continue
|
||||
}
|
||||
if matchLen := vars.trie.Match(s, i); matchLen > 0 {
|
||||
return []int{i, i + matchLen}
|
||||
return []int{i, i + skinTonedLen(vars, s[i:], matchLen)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSkinTone(r rune) bool {
|
||||
return r >= 0x1f3fb && r <= 0x1f3ff
|
||||
}
|
||||
|
||||
func removeSkinTones(s string) string {
|
||||
return strings.Map(func(r rune) rune { return util.Iif(isSkinTone(r), -1, r) }, s)
|
||||
}
|
||||
|
||||
// skinTonedLen extends a match at the start of s over skin tones, which the emoji data omits
|
||||
func skinTonedLen(vars *globalVarsStruct, s string, matchLen int) int {
|
||||
if r, _ := utf8.DecodeRuneInString(s[matchLen:]); !isSkinTone(r) {
|
||||
return matchLen
|
||||
}
|
||||
tonelessLen := vars.trie.Match(removeSkinTones(s[:min(len(s), 2*len(vars.emojis[0].Emoji))]), 0)
|
||||
end := 0
|
||||
for end < len(s) {
|
||||
r, size := utf8.DecodeRuneInString(s[end:])
|
||||
if !isSkinTone(r) {
|
||||
if tonelessLen == 0 {
|
||||
break
|
||||
}
|
||||
tonelessLen -= size
|
||||
}
|
||||
end += size
|
||||
}
|
||||
return end
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -61,6 +61,11 @@ func TestReplacers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setting.SetupGiteaTestEnv()
|
||||
m.Run()
|
||||
}
|
||||
|
||||
const (
|
||||
testInputWithEmojis = "This is a test string containing some emojis like \U0001f44d and \U0001f37a and some text in between."
|
||||
testInputNoEmojis = "This is a test string containing no emojis at all, just plain old ASCII text, which should ideally be scanned very quickly by our trie implementation."
|
||||
@@ -89,6 +94,14 @@ func TestFindEmojiSubmatchIndex(t *testing.T) {
|
||||
"\u0001\U0001f44d",
|
||||
[]int{1, 1 + len("\U0001f44d")},
|
||||
},
|
||||
{
|
||||
"👩🏿❤️👩🏿",
|
||||
[]int{0, len("👩🏿❤️👩🏿")},
|
||||
},
|
||||
{
|
||||
"🏽👍",
|
||||
[]int{len("🏽"), len("🏽👍")},
|
||||
},
|
||||
{
|
||||
// This package can handle keycap emoji if it is registered in the emoji data.
|
||||
// However, many other places (e.g.: markup rendering) also might not handle such cases correctly.
|
||||
|
||||
@@ -14,16 +14,14 @@ import (
|
||||
"golang.org/x/net/html/atom"
|
||||
)
|
||||
|
||||
func createEmoji(ctx *RenderContext, content, name string) *html.Node {
|
||||
func createEmoji(ctx *RenderContext, content, alias string) *html.Node {
|
||||
span := &html.Node{
|
||||
Type: html.ElementNode,
|
||||
Data: atom.Span.String(),
|
||||
Attr: []html.Attribute{},
|
||||
}
|
||||
span.Attr = append(span.Attr, ctx.RenderInternal.NodeSafeAttr("class", "emoji"))
|
||||
if name != "" {
|
||||
span.Attr = append(span.Attr, html.Attribute{Key: "aria-label", Val: name})
|
||||
}
|
||||
span.Attr = append(span.Attr, html.Attribute{Key: "data-alias", Val: alias})
|
||||
|
||||
text := &html.Node{
|
||||
Type: html.TextNode,
|
||||
@@ -41,7 +39,6 @@ func createCustomEmoji(ctx *RenderContext, alias string) *html.Node {
|
||||
Attr: []html.Attribute{},
|
||||
}
|
||||
span.Attr = append(span.Attr, ctx.RenderInternal.NodeSafeAttr("class", "emoji"))
|
||||
span.Attr = append(span.Attr, html.Attribute{Key: "aria-label", Val: alias})
|
||||
|
||||
img := &html.Node{
|
||||
Type: html.ElementNode,
|
||||
@@ -88,7 +85,7 @@ func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) {
|
||||
converted := emoji.FromAlias(alias)
|
||||
if converted != nil {
|
||||
// standard emoji
|
||||
replaceContent(node, m[0], m[1], createEmoji(ctx, converted.Emoji, converted.Description))
|
||||
replaceContent(node, m[0], m[1], createEmoji(ctx, converted.Emoji, converted.Aliases[0]))
|
||||
node = node.NextSibling.NextSibling
|
||||
start = 0 // restart searching start since node has changed
|
||||
} else if _, exist := setting.UI.CustomEmojisMap[alias]; exist {
|
||||
@@ -116,7 +113,7 @@ func emojiProcessor(ctx *RenderContext, node *html.Node) {
|
||||
start = m[1]
|
||||
val := emoji.FromCode(codepoint)
|
||||
if val != nil {
|
||||
replaceContent(node, m[0], m[1], createEmoji(ctx, codepoint, val.Description))
|
||||
replaceContent(node, m[0], m[1], createEmoji(ctx, codepoint, val.Aliases[0]))
|
||||
node = node.NextSibling.NextSibling
|
||||
start = 0
|
||||
}
|
||||
|
||||
+26
-21
@@ -9,14 +9,17 @@ import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modules/emoji"
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/markup"
|
||||
"gitea.dev/modules/markup/common"
|
||||
"gitea.dev/modules/markup/markdown"
|
||||
"gitea.dev/modules/public"
|
||||
"gitea.dev/modules/setting"
|
||||
testModule "gitea.dev/modules/test"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -334,45 +337,47 @@ func TestRender_emoji(t *testing.T) {
|
||||
}
|
||||
|
||||
// Make sure we can successfully match every emoji in our dataset with regex
|
||||
for i := range emoji.GemojiData {
|
||||
test(
|
||||
emoji.GemojiData[i].Emoji,
|
||||
`<p><span class="emoji" aria-label="`+emoji.GemojiData[i].Description+`">`+emoji.GemojiData[i].Emoji+`</span></p>`)
|
||||
}
|
||||
for i := range emoji.GemojiData {
|
||||
test(
|
||||
":"+emoji.GemojiData[i].Aliases[0]+":",
|
||||
`<p><span class="emoji" aria-label="`+emoji.GemojiData[i].Description+`">`+emoji.GemojiData[i].Emoji+`</span></p>`)
|
||||
data, err := public.AssetFS().ReadFile("assets", "emoji.json")
|
||||
require.NoError(t, err)
|
||||
var emojis []emoji.Emoji
|
||||
require.NoError(t, json.Unmarshal(data, &emojis))
|
||||
for _, e := range emojis {
|
||||
expected := `<p><span class="emoji" data-alias="` + e.Aliases[0] + `">` + e.Emoji + `</span></p>`
|
||||
test(e.Emoji, expected)
|
||||
test(":"+e.Aliases[0]+":", expected)
|
||||
}
|
||||
|
||||
// Text that should be turned into or recognized as emoji
|
||||
test(
|
||||
":gitea:",
|
||||
`<p><span class="emoji" aria-label="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`)
|
||||
`<p><span class="emoji"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`)
|
||||
test(
|
||||
":custom-emoji:",
|
||||
`<p>:custom-emoji:</p>`)
|
||||
setting.UI.CustomEmojisMap["custom-emoji"] = ":custom-emoji:"
|
||||
test(
|
||||
":custom-emoji:",
|
||||
`<p><span class="emoji" aria-label="custom-emoji"><img alt=":custom-emoji:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/custom-emoji.png"/></span></p>`)
|
||||
`<p><span class="emoji"><img alt=":custom-emoji:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/custom-emoji.png"/></span></p>`)
|
||||
test(
|
||||
"这是字符:1::+1: some🐊 \U0001f44d:custom-emoji: :gitea:",
|
||||
`<p>这是字符:1:<span class="emoji" aria-label="thumbs up">👍</span> some<span class="emoji" aria-label="crocodile">🐊</span> `+
|
||||
`<span class="emoji" aria-label="thumbs up">👍</span><span class="emoji" aria-label="custom-emoji"><img alt=":custom-emoji:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/custom-emoji.png"/></span> `+
|
||||
`<span class="emoji" aria-label="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`)
|
||||
`<p>这是字符:1:<span class="emoji" data-alias="+1">👍</span> some<span class="emoji" data-alias="crocodile">🐊</span> `+
|
||||
`<span class="emoji" data-alias="+1">👍</span><span class="emoji"><img alt=":custom-emoji:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/custom-emoji.png"/></span> `+
|
||||
`<span class="emoji"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`)
|
||||
test(
|
||||
"Some text with 😄 in the middle",
|
||||
`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span> in the middle</p>`)
|
||||
`<p>Some text with <span class="emoji" data-alias="smile">😄</span> in the middle</p>`)
|
||||
test(
|
||||
"Some text with :smile: in the middle",
|
||||
`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span> in the middle</p>`)
|
||||
`<p>Some text with <span class="emoji" data-alias="smile">😄</span> in the middle</p>`)
|
||||
test(
|
||||
"Some text with 😄😄 2 emoji next to each other",
|
||||
`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span><span class="emoji" aria-label="grinning face with smiling eyes">😄</span> 2 emoji next to each other</p>`)
|
||||
`<p>Some text with <span class="emoji" data-alias="smile">😄</span><span class="emoji" data-alias="smile">😄</span> 2 emoji next to each other</p>`)
|
||||
test(
|
||||
"😎🤪🔐🤑❓",
|
||||
`<p><span class="emoji" aria-label="smiling face with sunglasses">😎</span><span class="emoji" aria-label="zany face">🤪</span><span class="emoji" aria-label="locked with key">🔐</span><span class="emoji" aria-label="money-mouth face">🤑</span><span class="emoji" aria-label="red question mark">❓</span></p>`)
|
||||
`<p><span class="emoji" data-alias="sunglasses">😎</span><span class="emoji" data-alias="zany_face">🤪</span><span class="emoji" data-alias="closed_lock_with_key">🔐</span><span class="emoji" data-alias="money_mouth_face">🤑</span><span class="emoji" data-alias="question">❓</span></p>`)
|
||||
test(
|
||||
"👍🏽🧑🏽💻👩🏿❤️👩🏿",
|
||||
`<p><span class="emoji" data-alias="+1">👍🏽</span><span class="emoji" data-alias="technologist">🧑🏽💻</span><span class="emoji" data-alias="couple_with_heart_woman_woman">👩🏿❤️👩🏿</span></p>`)
|
||||
|
||||
// should match nothing
|
||||
test(":100:200", `<p>:100:200</p>`)
|
||||
@@ -380,7 +385,7 @@ func TestRender_emoji(t *testing.T) {
|
||||
test(":not exist:", `<p>:not exist:</p>`)
|
||||
test("foo `:smile:", "<p>foo `:smile:</p>")
|
||||
test("foo `:smile:`", `<p>foo <code>:smile:</code></p>`)
|
||||
test("foo ` :smile:", "<p>foo ` <span class=\"emoji\" aria-label=\"grinning face with smiling eyes\">😄</span></p>")
|
||||
test("foo ` :smile:", "<p>foo ` <span class=\"emoji\" data-alias=\"smile\">😄</span></p>")
|
||||
}
|
||||
|
||||
func TestRender_ShortLinks(t *testing.T) {
|
||||
@@ -532,10 +537,10 @@ func TestPostProcess(t *testing.T) {
|
||||
// Test that other post-processing still works.
|
||||
test(
|
||||
":gitea:",
|
||||
`<span class="emoji" aria-label="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span>`)
|
||||
`<span class="emoji"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span>`)
|
||||
test(
|
||||
"Some text with 😄 in the middle",
|
||||
`Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span> in the middle`)
|
||||
`Some text with <span class="emoji" data-alias="smile">😄</span> in the middle`)
|
||||
test("http://localhost:3000/person/repo/issues/4#issuecomment-1234",
|
||||
`<a href="http://localhost:3000/person/repo/issues/4#issuecomment-1234" class="ref-issue">person/repo#4 (comment)</a>`)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setting.IsInTesting = true
|
||||
setting.SetupGiteaTestEnv()
|
||||
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
||||
markup.RefreshFileNamePatterns()
|
||||
os.Exit(m.Run())
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package markdown
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modules/markup"
|
||||
@@ -12,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setting.IsInTesting = true
|
||||
setting.SetupGiteaTestEnv()
|
||||
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
||||
os.Exit(m.Run())
|
||||
m.Run()
|
||||
}
|
||||
|
||||
@@ -323,8 +323,8 @@ func TestRenderSiblingImages_Issue12925(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenderEmojiInLinks_Issue12331(t *testing.T) {
|
||||
testcase := `[Link with emoji :moon: in text](https://gitea.io)`
|
||||
expected := `<p><a href="https://gitea.io" rel="nofollow">Link with emoji <span class="emoji" aria-label="waxing gibbous moon">🌔</span> in text</a></p>
|
||||
testcase := `[Link with emoji :moon: in text](https://gitea.com)`
|
||||
expected := `<p><a href="https://gitea.com" rel="nofollow">Link with emoji <span class="emoji" data-alias="moon">🌔</span> in text</a></p>
|
||||
`
|
||||
res, err := markdown.RenderString(markup.NewTestRenderContext(), testcase)
|
||||
assert.NoError(t, err)
|
||||
@@ -542,7 +542,7 @@ mail@domain.com
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
||||
<span class="emoji" data-alias="+1">👍</span>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a>
|
||||
@mention-user test
|
||||
#123
|
||||
|
||||
@@ -34,6 +34,9 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy {
|
||||
// Line numbers on codepreview
|
||||
policy.AllowAttrs("data-line-number").OnElements("span")
|
||||
|
||||
// emoji aliases for dark theme inversion
|
||||
policy.AllowAttrs("data-alias").OnElements("span")
|
||||
|
||||
// HINT: CUSTOM-URL-SCHEMES-ALLOW: setting custom means also allow them besides http/https, no custom means "allow all"
|
||||
if len(setting.Markdown.CustomURLSchemes) > 0 {
|
||||
policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
|
||||
|
||||
@@ -183,7 +183,7 @@ func IsViteDevRequest(req *http.Request) bool {
|
||||
// - "{RepoRoot}/assets/*.json" just happens to live under the dir name "assets"; it is not related to frontend assets
|
||||
// - BAD DESIGN: indeed it is a "conflicted and polluted name" sample
|
||||
switch path {
|
||||
case "/assets/emoji.json", "/assets/codemirror-languages.json":
|
||||
case "/assets/codemirror-languages.json":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -38,9 +38,9 @@ https://example.com/file.bin
|
||||

|
||||
[[local image|image.jpg]]
|
||||
[[remote link|https://example.com/image.jpg]]
|
||||
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
|
||||
http://localhost:3000/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
|
||||
http://localhost:3000/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
:+1:
|
||||
mail@domain.com
|
||||
@@ -52,6 +52,7 @@ mail@domain.com
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setting.SetupGiteaTestEnv()
|
||||
setting.Markdown.RenderOptionsComment.ShortIssuePattern = true
|
||||
markup.Init(&markup.RenderHelperFuncs{
|
||||
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
||||
@@ -123,11 +124,11 @@ func TestRenderRepoComment(t *testing.T) {
|
||||

|
||||
[[local image|image.jpg]]
|
||||
[[remote link|<a href="https://example.com/image.jpg">https://example.com/image.jpg</a>]]
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" class="compare"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a>
|
||||
<a href="http://localhost:3000/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" class="compare"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" class="commit"><code>88fc37a3c0</code></a>
|
||||
<a href="http://localhost:3000/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" class="commit"><code>88fc37a3c0</code></a>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
||||
<span class="emoji" data-alias="+1">👍</span>
|
||||
<a href="mailto:mail@domain.com">mail@domain.com</a>
|
||||
<a href="/mention-user">@mention-user</a> test
|
||||
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||
@@ -170,11 +171,11 @@ https://example.com/file.bin
|
||||

|
||||
[[local image|image.jpg]]
|
||||
[[remote link|https://example.com/image.jpg]]
|
||||
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
|
||||
http://localhost:3000/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
|
||||
http://localhost:3000/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
||||
<span class="emoji" data-alias="+1">👍</span>
|
||||
mail@domain.com
|
||||
@mention-user test
|
||||
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||
@@ -203,10 +204,10 @@ func TestRenderIssueTitleCodeSpan(t *testing.T) {
|
||||
{"`#123`", `<code class="inline-code-block">#123</code>`, false},
|
||||
{"`88fc37a3c0a4dda553bdcfc80c178a58247f42fb`", `<code class="inline-code-block">88fc37a3c0a4dda553bdcfc80c178a58247f42fb</code>`, false},
|
||||
{"foo `:100:", "foo `:100:", true},
|
||||
{"foo ` :100:", `foo ` + "`" + ` <span class="emoji" aria-label="hundred points">💯</span>`, true},
|
||||
{":100:", `<span class="emoji" aria-label="hundred points">💯</span>`, true},
|
||||
{"foo ` :100:", `foo ` + "`" + ` <span class="emoji" data-alias="100">💯</span>`, true},
|
||||
{":100:", `<span class="emoji" data-alias="100">💯</span>`, true},
|
||||
{"#123", `<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>`, false},
|
||||
{"`x`:100:", `<code class="inline-code-block">x</code><span class="emoji" aria-label="hundred points">💯</span>`, true},
|
||||
{"`x`:100:", `<code class="inline-code-block">x</code><span class="emoji" data-alias="100">💯</span>`, true},
|
||||
{"a `:100:` b `:+1:` c", `a <code class="inline-code-block">:100:</code> b <code class="inline-code-block">:+1:</code> c`, true},
|
||||
}
|
||||
for _, c := range cases {
|
||||
@@ -230,11 +231,11 @@ func TestRenderMarkdownToHtml(t *testing.T) {
|
||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a>
|
||||
<a href="/image.jpg" rel="nofollow"><img src="/image.jpg" title="local image" alt="local image"/></a>
|
||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a>
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a>
|
||||
<a href="http://localhost:3000/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a>
|
||||
<a href="http://localhost:3000/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
||||
<span class="emoji" data-alias="+1">👍</span>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a>
|
||||
<a href="/mention-user" rel="nofollow">@mention-user</a> test
|
||||
#123
|
||||
|
||||
Reference in New Issue
Block a user