2020-04-28 18:05:39 +00:00
|
|
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
|
|
// Copyright 2015 Kenneth Shaw
|
|
|
|
// Use of this source code is governed by a MIT-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package emoji
|
|
|
|
|
|
|
|
import (
|
2020-05-29 21:12:53 +00:00
|
|
|
"sort"
|
2020-04-28 18:05:39 +00:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Gemoji is a set of emoji data.
|
|
|
|
type Gemoji []Emoji
|
|
|
|
|
|
|
|
// Emoji represents a single emoji and associated data.
|
|
|
|
type Emoji struct {
|
|
|
|
Emoji string
|
|
|
|
Description string
|
|
|
|
Aliases []string
|
|
|
|
UnicodeVersion string
|
2020-06-04 18:56:28 +00:00
|
|
|
SkinTones bool
|
2020-04-28 18:05:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
// codeMap provides a map of the emoji unicode code to its emoji data.
|
|
|
|
codeMap map[string]int
|
|
|
|
|
|
|
|
// aliasMap provides a map of the alias to its emoji data.
|
|
|
|
aliasMap map[string]int
|
|
|
|
|
|
|
|
// codeReplacer is the string replacer for emoji codes.
|
|
|
|
codeReplacer *strings.Replacer
|
|
|
|
|
|
|
|
// aliasReplacer is the string replacer for emoji aliases.
|
|
|
|
aliasReplacer *strings.Replacer
|
|
|
|
|
|
|
|
once sync.Once
|
|
|
|
)
|
|
|
|
|
|
|
|
func loadMap() {
|
|
|
|
|
|
|
|
once.Do(func() {
|
|
|
|
|
|
|
|
// initialize
|
|
|
|
codeMap = make(map[string]int, len(GemojiData))
|
|
|
|
aliasMap = make(map[string]int, len(GemojiData))
|
|
|
|
|
|
|
|
// process emoji codes and aliases
|
|
|
|
codePairs := make([]string, 0)
|
|
|
|
aliasPairs := make([]string, 0)
|
2020-05-29 21:12:53 +00:00
|
|
|
|
|
|
|
// sort from largest to small so we match combined emoji first
|
|
|
|
sort.Slice(GemojiData, func(i, j int) bool {
|
|
|
|
return len(GemojiData[i].Emoji) > len(GemojiData[j].Emoji)
|
|
|
|
})
|
|
|
|
|
2020-04-28 18:05:39 +00:00
|
|
|
for i, e := range GemojiData {
|
|
|
|
if e.Emoji == "" || len(e.Aliases) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// setup codes
|
|
|
|
codeMap[e.Emoji] = i
|
|
|
|
codePairs = append(codePairs, e.Emoji, ":"+e.Aliases[0]+":")
|
|
|
|
|
|
|
|
// setup aliases
|
|
|
|
for _, a := range e.Aliases {
|
|
|
|
if a == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
aliasMap[a] = i
|
|
|
|
aliasPairs = append(aliasPairs, ":"+a+":", e.Emoji)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// create replacers
|
|
|
|
codeReplacer = strings.NewReplacer(codePairs...)
|
|
|
|
aliasReplacer = strings.NewReplacer(aliasPairs...)
|
|
|
|
})
|
2020-05-29 21:12:53 +00:00
|
|
|
|
2020-04-28 18:05:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// FromCode retrieves the emoji data based on the provided unicode code (ie,
|
|
|
|
// "\u2618" will return the Gemoji data for "shamrock").
|
|
|
|
func FromCode(code string) *Emoji {
|
|
|
|
loadMap()
|
|
|
|
i, ok := codeMap[code]
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return &GemojiData[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromAlias retrieves the emoji data based on the provided alias in the form
|
|
|
|
// "alias" or ":alias:" (ie, "shamrock" or ":shamrock:" will return the Gemoji
|
|
|
|
// data for "shamrock").
|
|
|
|
func FromAlias(alias string) *Emoji {
|
|
|
|
loadMap()
|
|
|
|
if strings.HasPrefix(alias, ":") && strings.HasSuffix(alias, ":") {
|
|
|
|
alias = alias[1 : len(alias)-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
i, ok := aliasMap[alias]
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return &GemojiData[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReplaceCodes replaces all emoji codes with the first corresponding emoji
|
|
|
|
// alias (in the form of ":alias:") (ie, "\u2618" will be converted to
|
|
|
|
// ":shamrock:").
|
|
|
|
func ReplaceCodes(s string) string {
|
|
|
|
loadMap()
|
|
|
|
return codeReplacer.Replace(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReplaceAliases replaces all aliases of the form ":alias:" with its
|
|
|
|
// corresponding unicode value.
|
|
|
|
func ReplaceAliases(s string) string {
|
|
|
|
loadMap()
|
|
|
|
return aliasReplacer.Replace(s)
|
|
|
|
}
|
2020-05-29 21:12:53 +00:00
|
|
|
|
|
|
|
// FindEmojiSubmatchIndex returns index pair of longest emoji in a string
|
|
|
|
func FindEmojiSubmatchIndex(s string) []int {
|
|
|
|
loadMap()
|
2020-07-25 16:50:57 +00:00
|
|
|
found := make(map[int]int)
|
|
|
|
keys := make([]int, 0)
|
2020-05-29 21:12:53 +00:00
|
|
|
|
2020-06-04 18:56:28 +00:00
|
|
|
//see if there are any emoji in string before looking for position of specific ones
|
|
|
|
//no performance difference when there is a match but 10x faster when there are not
|
|
|
|
if s == ReplaceCodes(s) {
|
2020-05-29 21:12:53 +00:00
|
|
|
return nil
|
|
|
|
}
|
2020-06-04 18:56:28 +00:00
|
|
|
|
2020-07-25 16:50:57 +00:00
|
|
|
// get index of first emoji occurrence while also checking for longest combination
|
2020-05-29 21:12:53 +00:00
|
|
|
for j := range GemojiData {
|
|
|
|
i := strings.Index(s, GemojiData[j].Emoji)
|
|
|
|
if i != -1 {
|
2020-07-25 16:50:57 +00:00
|
|
|
if _, ok := found[i]; !ok {
|
|
|
|
if len(keys) == 0 || i < keys[0] {
|
|
|
|
found[i] = j
|
|
|
|
keys = []int{i}
|
|
|
|
}
|
|
|
|
if i == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2020-05-29 21:12:53 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-25 16:50:57 +00:00
|
|
|
|
|
|
|
if len(keys) > 0 {
|
|
|
|
index := keys[0]
|
|
|
|
return []int{index, index + len(GemojiData[found[index]].Emoji)}
|
|
|
|
}
|
|
|
|
|
2020-05-29 21:12:53 +00:00
|
|
|
return nil
|
|
|
|
}
|