Add custom emoji support (#16004)
This commit is contained in:
		
							parent
							
								
									aac663e0da
								
							
						
					
					
						commit
						65548359cc
					
				
					 11 changed files with 57 additions and 23 deletions
				
			
		|  | @ -1034,6 +1034,11 @@ PATH = | |||
| ;; 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 | ||||
| ;; | ||||
|  |  | |||
|  | @ -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. | ||||
|  |  | |||
|  | @ -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: "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 | ||||
|  |  | |||
|  | @ -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>`) | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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, | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -30,6 +30,7 @@ | |||
| 			AppVer: '{{AppVer}}', | ||||
| 			AppSubUrl: '{{AppSubUrl}}', | ||||
| 			AssetUrlPrefix: '{{AssetUrlPrefix}}', | ||||
| 			CustomEmojis: {{CustomEmojis}}, | ||||
| 			UseServiceWorker: {{UseServiceWorker}}, | ||||
| 			csrf: '{{.CsrfToken}}', | ||||
| 			HighlightJS: {{if .RequireHighlightJS}}true{{else}}false{{end}}, | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
|  | @ -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); | ||||
|   } | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue