diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 33ff7a62c56b..7cae16cd79ab 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1029,11 +1029,16 @@ PATH = ;; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`. ;THEMES = gitea,arc-green ;; -;;All available reactions users can choose on issues/prs and comments. -;;Values can be emoji alias (:smile:) or a unicode emoji. -;;For custom reactions, add a tightly cropped square image to public/emoji/img/reaction_name.png +;; All available reactions users can choose on issues/prs and comments. +;; Values can be emoji alias (:smile:) or a unicode emoji. +;; For custom reactions, add a tightly cropped square image to public/emoji/img/reaction_name.png ;REACTIONS = +1, -1, laugh, hooray, confused, heart, rocket, eyes ;; +;; Additional Emojis not defined in the utf8 standard +;; By default we support gitea (:gitea:), to add more copy them to public/emoji/img/emoji_name.png and add it to this config. +;; Dont mistake it for Reactions. +;CUSTOM_EMOJIS = gitea +;; ;; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used. ;DEFAULT_SHOW_FULL_NAME = false ;; diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index d1d47bc89301..dc3b36cb4fc1 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -181,6 +181,9 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `REACTIONS`: All available reactions users can choose on issues/prs and comments Values can be emoji alias (:smile:) or a unicode emoji. For custom reactions, add a tightly cropped square image to public/emoji/img/reaction_name.png +- `CUSTOM_EMOJIS`: **gitea**: Additional Emojis not defined in the utf8 standard. + By default we support gitea (:gitea:), to add more copy them to public/emoji/img/emoji_name.png and + add it to this config. - `DEFAULT_SHOW_FULL_NAME`: **false**: Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used. - `SEARCH_REPO_DESCRIPTION`: **true**: Whether to search within description at repository search on explore page. - `USE_SERVICE_WORKER`: **true**: Whether to enable a Service Worker to cache frontend assets. diff --git a/modules/markup/html.go b/modules/markup/html.go index 0cc0e23b5c57..1e55629ab5dd 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -6,7 +6,6 @@ package markup import ( "bytes" - "fmt" "io" "io/ioutil" "net/url" @@ -66,7 +65,7 @@ var ( blackfridayExtRegex = regexp.MustCompile(`[^:]*:user-content-`) // EmojiShortCodeRegex find emoji by alias like :smile: - EmojiShortCodeRegex = regexp.MustCompile(`\:[\w\+\-]+\:{1}`) + EmojiShortCodeRegex = regexp.MustCompile(`:[\w\+\-]+:`) ) // CSS class for action keywords (e.g. "closes: #1") @@ -460,17 +459,14 @@ func createEmoji(content, class, name string) *html.Node { return span } -func createCustomEmoji(alias, class string) *html.Node { - +func createCustomEmoji(alias string) *html.Node { span := &html.Node{ Type: html.ElementNode, Data: atom.Span.String(), Attr: []html.Attribute{}, } - if class != "" { - span.Attr = append(span.Attr, html.Attribute{Key: "class", Val: class}) - span.Attr = append(span.Attr, html.Attribute{Key: "aria-label", Val: alias}) - } + span.Attr = append(span.Attr, html.Attribute{Key: "class", Val: "emoji"}) + span.Attr = append(span.Attr, html.Attribute{Key: "aria-label", Val: alias}) img := &html.Node{ Type: html.ElementNode, @@ -478,10 +474,8 @@ func createCustomEmoji(alias, class string) *html.Node { Data: "img", Attr: []html.Attribute{}, } - if class != "" { - img.Attr = append(img.Attr, html.Attribute{Key: "alt", Val: fmt.Sprintf(`:%s:`, alias)}) - img.Attr = append(img.Attr, html.Attribute{Key: "src", Val: fmt.Sprintf(`%s/assets/img/emoji/%s.png`, setting.StaticURLPrefix, alias)}) - } + img.Attr = append(img.Attr, html.Attribute{Key: "alt", Val: ":" + alias + ":"}) + img.Attr = append(img.Attr, html.Attribute{Key: "src", Val: setting.StaticURLPrefix + "/assets/img/emoji/" + alias + ".png"}) span.AppendChild(img) return span @@ -948,9 +942,8 @@ func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) { converted := emoji.FromAlias(alias) if converted == nil { // check if this is a custom reaction - s := strings.Join(setting.UI.Reactions, " ") + "gitea" - if strings.Contains(s, alias) { - replaceContent(node, m[0], m[1], createCustomEmoji(alias, "emoji")) + if _, exist := setting.UI.CustomEmojisMap[alias]; exist { + replaceContent(node, m[0], m[1], createCustomEmoji(alias)) node = node.NextSibling.NextSibling start = 0 continue diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 8c3d2b5395c1..85418892ef7f 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -284,7 +284,18 @@ func TestRender_emoji(t *testing.T) { test( ":gitea:", `

:gitea:

`) - + test( + ":custom-emoji:", + `

:custom-emoji:

`) + setting.UI.CustomEmojisMap["custom-emoji"] = ":custom-emoji:" + test( + ":custom-emoji:", + `

:custom-emoji:

`) + test( + "θΏ™ζ˜―ε­—η¬¦:1::+1: some🐊 \U0001f44d:custom-emoji: :gitea:", + `

θΏ™ζ˜―ε­—η¬¦:1:πŸ‘ some🐊 `+ + `πŸ‘:custom-emoji: `+ + `:gitea:

`) test( "Some text with πŸ˜„ in the middle", `

Some text with πŸ˜„ in the middle

`) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index de167e288a47..e37b78834202 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -208,7 +208,9 @@ var ( DefaultTheme string Themes []string Reactions []string - ReactionsMap map[string]bool + ReactionsMap map[string]bool `ini:"-"` + CustomEmojis []string + CustomEmojisMap map[string]string `ini:"-"` SearchRepoDescription bool UseServiceWorker bool @@ -256,6 +258,8 @@ var ( DefaultTheme: `gitea`, Themes: []string{`gitea`, `arc-green`}, Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, + CustomEmojis: []string{`gitea`}, + CustomEmojisMap: map[string]string{"gitea": ":gitea:"}, Notification: struct { MinTimeout time.Duration TimeoutStep time.Duration @@ -983,6 +987,10 @@ func NewContext() { for _, reaction := range UI.Reactions { UI.ReactionsMap[reaction] = true } + UI.CustomEmojisMap = make(map[string]string) + for _, emoji := range UI.CustomEmojis { + UI.CustomEmojisMap[emoji] = ":" + emoji + ":" + } } func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) { diff --git a/modules/structs/settings.go b/modules/structs/settings.go index 842b12792d1d..90c4a2107bbd 100644 --- a/modules/structs/settings.go +++ b/modules/structs/settings.go @@ -18,6 +18,7 @@ type GeneralRepoSettings struct { type GeneralUISettings struct { DefaultTheme string `json:"default_theme"` AllowedReactions []string `json:"allowed_reactions"` + CustomEmojis []string `json:"custom_emojis"` } // GeneralAPISettings contains global api settings exposed by it diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 83359a6ef234..f9b2dafd22a1 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -90,6 +90,9 @@ func NewFuncMap() []template.FuncMap { "AllowedReactions": func() []string { return setting.UI.Reactions }, + "CustomEmojis": func() map[string]string { + return setting.UI.CustomEmojisMap + }, "Safe": Safe, "SafeJS": SafeJS, "JSEscape": JSEscape, diff --git a/routers/api/v1/settings/settings.go b/routers/api/v1/settings/settings.go index e6417e40748c..ca2d28fb8bf2 100644 --- a/routers/api/v1/settings/settings.go +++ b/routers/api/v1/settings/settings.go @@ -25,6 +25,7 @@ func GetGeneralUISettings(ctx *context.APIContext) { ctx.JSON(http.StatusOK, api.GeneralUISettings{ DefaultTheme: setting.UI.DefaultTheme, AllowedReactions: setting.UI.Reactions, + CustomEmojis: setting.UI.CustomEmojis, }) } diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 10fc2bad4a02..5091eda1e996 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -30,6 +30,7 @@ AppVer: '{{AppVer}}', AppSubUrl: '{{AppSubUrl}}', AssetUrlPrefix: '{{AssetUrlPrefix}}', + CustomEmojis: {{CustomEmojis}}, UseServiceWorker: {{UseServiceWorker}}, csrf: '{{.CsrfToken}}', HighlightJS: {{if .RequireHighlightJS}}true{{else}}false{{end}}, diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 7f7907b3b0c6..40dddb06a078 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -14481,6 +14481,13 @@ }, "x-go-name": "AllowedReactions" }, + "custom_emojis": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "CustomEmojis" + }, "default_theme": { "type": "string", "x-go-name": "DefaultTheme" diff --git a/web_src/js/features/emoji.js b/web_src/js/features/emoji.js index 7a522b95fc63..254a0b5c4d92 100644 --- a/web_src/js/features/emoji.js +++ b/web_src/js/features/emoji.js @@ -1,8 +1,9 @@ import emojis from '../../../assets/emoji.json'; const {AssetUrlPrefix} = window.config; +const {CustomEmojis} = window.config; -const tempMap = {gitea: ':gitea:'}; +const tempMap = {...CustomEmojis}; for (const {emoji, aliases} of emojis) { for (const alias of aliases || []) { tempMap[alias] = emoji; @@ -23,8 +24,8 @@ for (const key of emojiKeys) { // retrieve HTML for given emoji name export function emojiHTML(name) { let inner; - if (name === 'gitea') { - inner = `:${name}:`; + if (Object.prototype.hasOwnProperty.call(CustomEmojis, name)) { + inner = `:${name}:`; } else { inner = emojiString(name); }