]> source.dussan.org Git - gitea.git/commitdiff
Add custom emoji support (#16004)
author6543 <6543@obermui.de>
Tue, 29 Jun 2021 14:28:38 +0000 (16:28 +0200)
committerGitHub <noreply@github.com>
Tue, 29 Jun 2021 14:28:38 +0000 (16:28 +0200)
custom/conf/app.example.ini
docs/content/doc/advanced/config-cheat-sheet.en-us.md
modules/markup/html.go
modules/markup/html_test.go
modules/setting/setting.go
modules/structs/settings.go
modules/templates/helper.go
routers/api/v1/settings/settings.go
templates/base/head.tmpl
templates/swagger/v1_json.tmpl
web_src/js/features/emoji.js

index 33ff7a62c56b346e5376ab54d8c49c05503c2336..7cae16cd79ab0d0ed7b589a98269e7499aa637d4 100644 (file)
@@ -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
 ;;
index d1d47bc89301d005cbb95ba60e795f5f4c6ea153..dc3b36cb4fc1f77753889f6192a7da89fbce0a7e 100644 (file)
@@ -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.
index 0cc0e23b5c57ddd3376da3d3ab68961db95eb6b5..1e55629ab5dd721cb2fed66d4236799bba8973b1 100644 (file)
@@ -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
index 8c3d2b5395c11f931fe6094c76b1657b47fca21d..85418892ef7f1c09815a8dcce9d1f80bebd0b88e 100644 (file)
@@ -284,7 +284,18 @@ func TestRender_emoji(t *testing.T) {
        test(
                ":gitea:",
                `<p><span class="emoji" aria-label="gitea"><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>`)
+       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>`)
        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>`)
index de167e288a47ae4c0a4abd78b5b83a1f5936e422..e37b7883420227d9db83f4975739de520744ea5c 100644 (file)
@@ -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) {
index 842b12792d1d94d5eda3e99709e968e0191366f8..90c4a2107bbdf52d8677039437739b1fa1b482f9 100644 (file)
@@ -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
index 83359a6ef234b0cb71c5252d4232cba18a1adf82..f9b2dafd22a14c64ff13ae69bfcdca455cdb3234 100644 (file)
@@ -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,
index e6417e40748c02e85d5bfed599468569db905b70..ca2d28fb8bf2561cd55aea7b2ef5ee1e46a89a9c 100644 (file)
@@ -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,
        })
 }
 
index 10fc2bad4a027d8a875877454c9584688c604dc7..5091eda1e9969d81803614e3d14422889b9e4ecd 100644 (file)
@@ -30,6 +30,7 @@
                        AppVer: '{{AppVer}}',
                        AppSubUrl: '{{AppSubUrl}}',
                        AssetUrlPrefix: '{{AssetUrlPrefix}}',
+                       CustomEmojis: {{CustomEmojis}},
                        UseServiceWorker: {{UseServiceWorker}},
                        csrf: '{{.CsrfToken}}',
                        HighlightJS: {{if .RequireHighlightJS}}true{{else}}false{{end}},
index 7f7907b3b0c64952860f69bcd652a5bf6ec4c371..40dddb06a07858c9e0352d0babda096090c19108 100644 (file)
           },
           "x-go-name": "AllowedReactions"
         },
+        "custom_emojis": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "CustomEmojis"
+        },
         "default_theme": {
           "type": "string",
           "x-go-name": "DefaultTheme"
index 7a522b95fc63b6806749d0a565012aeeeb5d2050..254a0b5c4d9282f8505d5b822f56782f38607f44 100644 (file)
@@ -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 = `<img alt=":${name}:" src="${AssetUrlPrefix}/img/emoji/gitea.png">`;
+  if (Object.prototype.hasOwnProperty.call(CustomEmojis, name)) {
+    inner = `<img alt=":${name}:" src="${AssetUrlPrefix}/img/emoji/${name}.png">`;
   } else {
     inner = emojiString(name);
   }