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
22 changes: 8 additions & 14 deletions modules/markup/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ var (
blackfridayExtRegex = regexp.MustCompile(`[^:]*:user-content-`)

// EmojiShortCodeRegex find emoji by alias like :smile:
EmojiShortCodeRegex = regexp.MustCompile(`\:[\w\+\-]+\:{1}`)
EmojiShortCodeRegex = regexp.MustCompile(`\:[\w\+\-]+\:`)
6543 marked this conversation as resolved.
Show resolved Hide resolved
)

// CSS class for action keywords (e.g. "closes: #1")
Expand Down Expand Up @@ -460,28 +460,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: 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)})
6543 marked this conversation as resolved.
Show resolved Hide resolved

span.AppendChild(img)
return span
Expand Down Expand Up @@ -948,9 +943,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(
"a :+1: some🐊 \U0001f44d:custom-emoji: :gitea:",
6543 marked this conversation as resolved.
Show resolved Hide resolved
`<p>a <span class="emoji" aria-label="thumbs up">👍</span> some<span class="emoji" aria-label="crocodile">🐊</span> `+
6543 marked this conversation as resolved.
Show resolved Hide resolved
`<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`, `codeberg`},
CustomEmojisMap: map[string]string{"gitea": ":gitea:", "codeberg": ":codeberg:"},
6543 marked this conversation as resolved.
Show resolved Hide resolved
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] = fmt.Sprintf(":%s:", 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
Binary file added public/img/emoji/codeberg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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