Skip to content

Commit 4563eb8

Browse files
mrsdizziesilverwind6543lafriksguillep2k
authored
Support unicode emojis and remove emojify.js (#11032)
* Support unicode emojis and remove emojify.js This PR replaces all use of emojify.js and adds unicode emoji support to various areas of gitea. This works in a few ways: First it adds emoji parsing support into gitea itself. This allows us to * Render emojis from valid alias (:smile:) * Detect unicode emojis and let us put them in their own class with proper aria-labels and styling * Easily allow for custom "emoji" * Support all emoji rendering and features without javascript * Uses plain unicode and lets the system render in appropriate emoji font * Doesn't leave us relying on external sources for updates/fixes/features That same list of emoji is also used to create a json file which replaces the part of emojify.js that populates the emoji search tribute. This file is about 35KB with GZIP turned on and I've set it to load after the page renders to not hinder page load time (and this removes loading emojify.js also) For custom "emoji" it uses a pretty simple scheme of just looking for /emojis/img/name.png where name is something a user has put in the "allowed reactions" setting we already have. The gitea reaction that was previously hard coded into a forked copy of emojify.js is included and works as a custom reaction under this method. The emoji data sourced here is from https://github.com/github/gemoji which is the gem library Github uses for their emoji rendering (and a data source for other sites). So we should be able to easily render any emoji and :alias: that Github can, removing any errors from migrated content. They also update it as well, so we can sync when there are new unicode emoji lists released. I've included a slimmed down and slightly modified forked copy of https://github.com/knq/emoji to make up our own emoji module. The code is pretty straight forward and again allows us to have a lot of flexibility in what happens. I had seen a few comments about performance in some of the other threads if we render this ourselves, but there doesn't seem to be any issue here. In a test it can parse, convert, and render 1,000 emojis inside of a large markdown table in about 100ms on my laptop (which is many more emojis than will ever be in any normal issue). This also prevents any flickering and other weirdness from using javascript to render some things while using go for others. Not included here are image fall back URLS. I don't really think they are necessary for anything new being written in 2020. However, managing the emoji ourselves would allow us to add these as a feature later on if it seems necessary. Fixes: #9182 Fixes: #8974 Fixes: #8953 Fixes: #6628 Fixes: #5130 * add new shared function emojiHTML * don't increase emoji size in issue title * Update templates/repo/issue/view_content/add_reaction.tmpl Co-Authored-By: 6543 <[email protected]> * Support for emoji rendering in various templates * Render code and review comments as they should be * Better way to handle mail subjects * insert unicode from tribute selection * Add template helper for plain text when needed * Use existing replace function I forgot about * Don't include emoji greater than Unicode Version 12 Only include emoji and aliases in JSON * Update build/generate-emoji.go * Tweak regex slightly to really match everything including random invisible characters. Run tests for every emoji we have * final updates * code review * code review * hard code gitea custom emoji to match previous behavior * Update .eslintrc Co-Authored-By: silverwind <[email protected]> * disable preempt Co-authored-by: silverwind <[email protected]> Co-authored-by: 6543 <[email protected]> Co-authored-by: Lauris BH <[email protected]> Co-authored-by: guillep2k <[email protected]>
1 parent 922a239 commit 4563eb8

File tree

928 files changed

+2713
-167
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

928 files changed

+2713
-167
lines changed

.eslintrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ globals:
2020
__webpack_public_path__: true
2121
CodeMirror: false
2222
Dropzone: false
23-
emojify: false
2423
SimpleMDE: false
2524
u2fApi: false
25+
Tribute: false
2626

2727
overrides:
2828
- files: ["web_src/**/*.worker.js"]

assets/emoji.json

+1
Large diffs are not rendered by default.

build/generate-emoji.go

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Copyright 2015 Kenneth Shaw
3+
// Use of this source code is governed by a MIT-style
4+
// license that can be found in the LICENSE file.
5+
6+
// +build ignore
7+
8+
package main
9+
10+
import (
11+
"encoding/json"
12+
"flag"
13+
"fmt"
14+
"go/format"
15+
"io/ioutil"
16+
"log"
17+
"net/http"
18+
"regexp"
19+
"sort"
20+
"strconv"
21+
"strings"
22+
)
23+
24+
const (
25+
gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json"
26+
maxUnicodeVersion = 12
27+
)
28+
29+
var (
30+
flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")
31+
)
32+
33+
// Gemoji is a set of emoji data.
34+
type Gemoji []Emoji
35+
36+
// Emoji represents a single emoji and associated data.
37+
type Emoji struct {
38+
Emoji string `json:"emoji"`
39+
Description string `json:"description,omitempty"`
40+
Aliases []string `json:"aliases"`
41+
UnicodeVersion string `json:"unicode_version,omitempty"`
42+
}
43+
44+
// Don't include some fields in JSON
45+
func (e Emoji) MarshalJSON() ([]byte, error) {
46+
type emoji Emoji
47+
x := emoji(e)
48+
x.UnicodeVersion = ""
49+
x.Description = ""
50+
return json.Marshal(x)
51+
}
52+
53+
func main() {
54+
var err error
55+
56+
flag.Parse()
57+
58+
// generate data
59+
buf, err := generate()
60+
if err != nil {
61+
log.Fatal(err)
62+
}
63+
64+
// write
65+
err = ioutil.WriteFile(*flagOut, buf, 0644)
66+
if err != nil {
67+
log.Fatal(err)
68+
}
69+
}
70+
71+
var replacer = strings.NewReplacer(
72+
"main.Gemoji", "Gemoji",
73+
"main.Emoji", "\n",
74+
"}}", "},\n}",
75+
", Description:", ", ",
76+
", Aliases:", ", ",
77+
", UnicodeVersion:", ", ",
78+
)
79+
80+
var emojiRE = regexp.MustCompile(`\{Emoji:"([^"]*)"`)
81+
82+
func generate() ([]byte, error) {
83+
var err error
84+
85+
// load gemoji data
86+
res, err := http.Get(gemojiURL)
87+
if err != nil {
88+
return nil, err
89+
}
90+
defer res.Body.Close()
91+
92+
// read all
93+
body, err := ioutil.ReadAll(res.Body)
94+
if err != nil {
95+
return nil, err
96+
}
97+
98+
// unmarshal
99+
var data Gemoji
100+
err = json.Unmarshal(body, &data)
101+
if err != nil {
102+
return nil, err
103+
}
104+
105+
var re = regexp.MustCompile(`keycap|registered|copyright`)
106+
tmp := data[:0]
107+
108+
// filter out emoji that require greater than max unicode version
109+
for i := range data {
110+
val, _ := strconv.ParseFloat(data[i].UnicodeVersion, 64)
111+
if int(val) <= maxUnicodeVersion {
112+
// remove these keycaps for now they really complicate matching since
113+
// they include normal letters in them
114+
if re.MatchString(data[i].Description) {
115+
continue
116+
}
117+
tmp = append(tmp, data[i])
118+
}
119+
}
120+
data = tmp
121+
122+
sort.Slice(data, func(i, j int) bool {
123+
return data[i].Aliases[0] < data[j].Aliases[0]
124+
})
125+
126+
aliasPairs := make([]string, 0)
127+
aliasMap := make(map[string]int, len(data))
128+
129+
for i, e := range data {
130+
if e.Emoji == "" || len(e.Aliases) == 0 {
131+
continue
132+
}
133+
for _, a := range e.Aliases {
134+
if a == "" {
135+
continue
136+
}
137+
aliasMap[a] = i
138+
aliasPairs = append(aliasPairs, ":"+a+":", e.Emoji)
139+
}
140+
}
141+
142+
// gitea customizations
143+
i, ok := aliasMap["tada"]
144+
if ok {
145+
data[i].Aliases = append(data[i].Aliases, "hooray")
146+
}
147+
i, ok = aliasMap["laughing"]
148+
if ok {
149+
data[i].Aliases = append(data[i].Aliases, "laugh")
150+
}
151+
152+
// add header
153+
str := replacer.Replace(fmt.Sprintf(hdr, gemojiURL, data))
154+
155+
// change the format of the unicode string
156+
str = emojiRE.ReplaceAllStringFunc(str, func(s string) string {
157+
var err error
158+
s, err = strconv.Unquote(s[len("{Emoji:"):])
159+
if err != nil {
160+
panic(err)
161+
}
162+
return "{" + strconv.QuoteToASCII(s)
163+
})
164+
165+
// write a JSON file to use with tribute
166+
file, _ := json.Marshal(data)
167+
_ = ioutil.WriteFile("assets/emoji.json", file, 0644)
168+
169+
// format
170+
return format.Source([]byte(str))
171+
}
172+
173+
const hdr = `
174+
// Copyright 2020 The Gitea Authors. All rights reserved.
175+
// Use of this source code is governed by a MIT-style
176+
// license that can be found in the LICENSE file.
177+
178+
package emoji
179+
180+
// Code generated by gen.go. DO NOT EDIT.
181+
// Sourced from %s
182+
//
183+
var GemojiData = %#v
184+
`

custom/conf/app.ini.sample

+3-2
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,9 @@ SHOW_USER_EMAIL = true
171171
DEFAULT_THEME = gitea
172172
; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`.
173173
THEMES = gitea,arc-green
174-
; All available reactions. Allow users react with different emoji's
175-
; For the whole list look at https://gitea.com/gitea/gitea.com/issues/8
174+
;All available reactions users can choose on issues/prs and comments.
175+
;Values can be emoji alias (:smile:) or a unicode emoji.
176+
;For custom reactions, add a tightly cropped square image to public/emoji/img/reaction_name.png
176177
REACTIONS = +1, -1, laugh, hooray, confused, heart, rocket, eyes
177178
; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used.
178179
DEFAULT_SHOW_FULL_NAME = false

docs/content/doc/advanced/config-cheat-sheet.en-us.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
128128
- `DEFAULT_THEME`: **gitea**: \[gitea, arc-green\]: Set the default theme for the Gitea install.
129129
- `THEMES`: **gitea,arc-green**: All available themes. Allow users select personalized themes
130130
regardless of the value of `DEFAULT_THEME`.
131-
- `REACTIONS`: All available reactions. Allow users react with different emoji's.
131+
- `REACTIONS`: All available reactions users can choose on issues/prs and comments
132+
Values can be emoji alias (:smile:) or a unicode emoji.
133+
For custom reactions, add a tightly cropped square image to public/emoji/img/reaction_name.png
132134
- `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.
133135
- `SEARCH_REPO_DESCRIPTION`: **true**: Whether to search within description at repository search on explore page.
134136
- `USE_SERVICE_WORKER`: **true**: Whether to enable a Service Worker to cache frontend assets.

docs/content/page/index.en-us.md

-1
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,6 @@ Windows, on architectures like amd64, i386, ARM, PowerPC, and others.
274274
* [DropzoneJS](http://www.dropzonejs.com/)
275275
* [Highlight](https://highlightjs.org/)
276276
* [Clipboard](https://zenorocha.github.io/clipboard.js/)
277-
* [Emojify](https://github.com/Ranks/emojify.js)
278277
* [CodeMirror](https://codemirror.net/)
279278
* [jQuery Date Time Picker](https://github.com/xdan/datetimepicker)
280279
* [jQuery MiniColors](https://github.com/claviska/jquery-minicolors)

docs/content/page/index.fr-fr.md

-1
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,6 @@ Le but de ce projet est de fournir de la manière la plus simple, la plus rapide
263263
* [DropzoneJS](http://www.dropzonejs.com/)
264264
* [Highlight](https://highlightjs.org/)
265265
* [Clipboard](https://zenorocha.github.io/clipboard.js/)
266-
* [Emojify](https://github.com/Ranks/emojify.js)
267266
* [CodeMirror](https://codemirror.net/)
268267
* [jQuery Date Time Picker](https://github.com/xdan/datetimepicker)
269268
* [jQuery MiniColors](https://github.com/claviska/jquery-minicolors)

docs/content/page/index.zh-cn.md

-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ Gitea的首要目标是创建一个极易安装,运行非常快速,安装和
5656
* [DropzoneJS](http://www.dropzonejs.com/)
5757
* [Highlight](https://highlightjs.org/)
5858
* [Clipboard](https://zenorocha.github.io/clipboard.js/)
59-
* [Emojify](https://github.com/Ranks/emojify.js)
6059
* [CodeMirror](https://codemirror.net/)
6160
* [jQuery Date Time Picker](https://github.com/xdan/datetimepicker)
6261
* [jQuery MiniColors](https://github.com/claviska/jquery-minicolors)

docs/content/page/index.zh-tw.md

-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ Gitea 的首要目標是建立一個容易安裝,運行快速,安装和使
5656
* [DropzoneJS](http://www.dropzonejs.com/)
5757
* [Highlight](https://highlightjs.org/)
5858
* [Clipboard](https://zenorocha.github.io/clipboard.js/)
59-
* [Emojify](https://github.com/Ranks/emojify.js)
6059
* [CodeMirror](https://codemirror.net/)
6160
* [jQuery Date Time Picker](https://github.com/xdan/datetimepicker)
6261
* [jQuery MiniColors](https://github.com/claviska/jquery-minicolors)

modules/emoji/emoji.go

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Copyright 2015 Kenneth Shaw
3+
// Use of this source code is governed by a MIT-style
4+
// license that can be found in the LICENSE file.
5+
6+
package emoji
7+
8+
import (
9+
"strings"
10+
"sync"
11+
)
12+
13+
// Gemoji is a set of emoji data.
14+
type Gemoji []Emoji
15+
16+
// Emoji represents a single emoji and associated data.
17+
type Emoji struct {
18+
Emoji string
19+
Description string
20+
Aliases []string
21+
UnicodeVersion string
22+
}
23+
24+
var (
25+
// codeMap provides a map of the emoji unicode code to its emoji data.
26+
codeMap map[string]int
27+
28+
// aliasMap provides a map of the alias to its emoji data.
29+
aliasMap map[string]int
30+
31+
// codeReplacer is the string replacer for emoji codes.
32+
codeReplacer *strings.Replacer
33+
34+
// aliasReplacer is the string replacer for emoji aliases.
35+
aliasReplacer *strings.Replacer
36+
37+
once sync.Once
38+
)
39+
40+
func loadMap() {
41+
42+
once.Do(func() {
43+
44+
// initialize
45+
codeMap = make(map[string]int, len(GemojiData))
46+
aliasMap = make(map[string]int, len(GemojiData))
47+
48+
// process emoji codes and aliases
49+
codePairs := make([]string, 0)
50+
aliasPairs := make([]string, 0)
51+
for i, e := range GemojiData {
52+
if e.Emoji == "" || len(e.Aliases) == 0 {
53+
continue
54+
}
55+
56+
// setup codes
57+
codeMap[e.Emoji] = i
58+
codePairs = append(codePairs, e.Emoji, ":"+e.Aliases[0]+":")
59+
60+
// setup aliases
61+
for _, a := range e.Aliases {
62+
if a == "" {
63+
continue
64+
}
65+
66+
aliasMap[a] = i
67+
aliasPairs = append(aliasPairs, ":"+a+":", e.Emoji)
68+
}
69+
}
70+
71+
// create replacers
72+
codeReplacer = strings.NewReplacer(codePairs...)
73+
aliasReplacer = strings.NewReplacer(aliasPairs...)
74+
})
75+
}
76+
77+
// FromCode retrieves the emoji data based on the provided unicode code (ie,
78+
// "\u2618" will return the Gemoji data for "shamrock").
79+
func FromCode(code string) *Emoji {
80+
loadMap()
81+
i, ok := codeMap[code]
82+
if !ok {
83+
return nil
84+
}
85+
86+
return &GemojiData[i]
87+
}
88+
89+
// FromAlias retrieves the emoji data based on the provided alias in the form
90+
// "alias" or ":alias:" (ie, "shamrock" or ":shamrock:" will return the Gemoji
91+
// data for "shamrock").
92+
func FromAlias(alias string) *Emoji {
93+
loadMap()
94+
if strings.HasPrefix(alias, ":") && strings.HasSuffix(alias, ":") {
95+
alias = alias[1 : len(alias)-1]
96+
}
97+
98+
i, ok := aliasMap[alias]
99+
if !ok {
100+
return nil
101+
}
102+
103+
return &GemojiData[i]
104+
}
105+
106+
// ReplaceCodes replaces all emoji codes with the first corresponding emoji
107+
// alias (in the form of ":alias:") (ie, "\u2618" will be converted to
108+
// ":shamrock:").
109+
func ReplaceCodes(s string) string {
110+
loadMap()
111+
return codeReplacer.Replace(s)
112+
}
113+
114+
// ReplaceAliases replaces all aliases of the form ":alias:" with its
115+
// corresponding unicode value.
116+
func ReplaceAliases(s string) string {
117+
loadMap()
118+
return aliasReplacer.Replace(s)
119+
}

0 commit comments

Comments
 (0)