Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom emoji support #16004

Merged
merged 17 commits into from
Jun 29, 2021
11 changes: 8 additions & 3 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
;;
Expand Down
3 changes: 3 additions & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 8 additions & 15 deletions modules/markup/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package markup

import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/url"
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -460,28 +459,23 @@ 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,
DataAtom: atom.Img,
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
Expand Down Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion modules/markup/html_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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>`)
Expand Down
10 changes: 9 additions & 1 deletion modules/setting/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions modules/structs/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions modules/templates/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions routers/api/v1/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
}

Expand Down
1 change: 1 addition & 0 deletions templates/base/head.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
AppVer: '{{AppVer}}',
AppSubUrl: '{{AppSubUrl}}',
AssetUrlPrefix: '{{AssetUrlPrefix}}',
CustomEmojis: {{CustomEmojis}},
UseServiceWorker: {{UseServiceWorker}},
csrf: '{{.CsrfToken}}',
HighlightJS: {{if .RequireHighlightJS}}true{{else}}false{{end}},
Expand Down
7 changes: 7 additions & 0 deletions templates/swagger/v1_json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
7 changes: 4 additions & 3 deletions web_src/js/features/emoji.js
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
}
Expand Down