FEATURE: Allow plugins to add custom emoji translations

FIX: buildTranslationTree was erroring when translations overlapped (ie. ":-)" and ":-))")
FIX: emoji translations wasn't working properly when translations overlapped
This commit is contained in:
Régis Hanol 2019-01-04 15:14:16 +01:00
parent 88e861e895
commit 95e5f8380d
5 changed files with 98 additions and 94 deletions

View File

@ -1,32 +1,5 @@
export const emojis = <%= Emoji.standard.map(&:name).flatten.inspect %>;
export const tonableEmojis = <%= Emoji.tonable_emojis.flatten.inspect %>;
export const aliases = <%= Emoji.aliases.inspect.gsub("=>", ":") %>;
export const searchAliases = <%= Emoji.searchAliases.inspect.gsub("=>", ":") %>;
export const translations = {
':)' : 'slight_smile',
':-)' : 'slight_smile',
'^_^' : 'slight_smile',
'^__^' : 'slight_smile',
':(' : 'frowning',
':-(' : 'frowning',
';)' : 'wink',
';-)' : 'wink',
':\'(' : 'cry',
':\'-(': 'cry',
':-\'(': 'cry',
':p' : 'stuck_out_tongue',
':P' : 'stuck_out_tongue',
':-P' : 'stuck_out_tongue',
':O' : 'open_mouth',
':-O' : 'open_mouth',
':D' : 'smiley',
':-D' : 'smiley',
':|' : 'expressionless',
':-|' : 'expressionless',
':/' : 'confused',
'8-)' : 'sunglasses',
";P" : 'stuck_out_tongue_winking_eye',
";-P" : 'stuck_out_tongue_winking_eye',
":$" : 'blush',
":-$" : 'blush'
};
export const searchAliases = <%= Emoji.search_aliases.inspect.gsub("=>", ":") %>;
export const translations = <%= Emoji.translations.inspect.gsub("=>", ":") %>;

View File

@ -9,22 +9,18 @@ let translationTree = null;
// We build a data structure that allows us to quickly
// search through our N next chars to see if any match
// one of our alias emojis.
//
function buildTranslationTree() {
let tree = [];
let lastNode;
Object.keys(translations).forEach(function(key) {
let i;
Object.keys(translations).forEach(key => {
let node = tree;
for (i = 0; i < key.length; i++) {
for (let i = 0; i < key.length; i++) {
let code = key.charCodeAt(i);
let j;
let found = false;
for (j = 0; j < node.length; j++) {
for (let j = 0; j < node.length; j++) {
if (node[j][0] === code) {
node = node[j][1];
found = true;
@ -33,7 +29,7 @@ function buildTranslationTree() {
}
if (!found) {
// token, children, value
// code, children, value
let tmp = [code, []];
node.push(tmp);
lastNode = tmp;
@ -41,7 +37,7 @@ function buildTranslationTree() {
}
}
lastNode[1] = translations[key];
lastNode[2] = translations[key];
});
return tree;
@ -121,28 +117,22 @@ function getEmojiTokenByName(name, state) {
function getEmojiTokenByTranslation(content, pos, state) {
translationTree = translationTree || buildTranslationTree();
let currentTree = translationTree;
let i;
let search = true;
let found = false;
let t = translationTree;
let start = pos;
let found = null;
while (search) {
search = false;
while (t.length > 0 && pos < content.length) {
let code = content.charCodeAt(pos);
for (i = 0; i < currentTree.length; i++) {
if (currentTree[i][0] === code) {
currentTree = currentTree[i][1];
pos++;
search = true;
if (typeof currentTree === "string") {
found = currentTree;
}
for (let i = 0; i < t.length; i++) {
if (t[i][0] === code) {
found = t[i][2];
t = t[i][1];
break;
}
}
pos++;
}
if (!found) {
@ -174,17 +164,9 @@ function getEmojiTokenByTranslation(content, pos, state) {
}
}
function applyEmoji(
content,
state,
emojiUnicodeReplacer,
enableShortcuts,
inlineEmoji
) {
let i;
function applyEmoji(content, state, emojiUnicodeReplacer, enableShortcuts, inlineEmoji) {
let result = null;
let contentToken = null;
let start = 0;
if (emojiUnicodeReplacer) {
@ -193,10 +175,10 @@ function applyEmoji(
let endToken = content.length;
for (i = 0; i < content.length - 1; i++) {
for (let i = 0; i < content.length - 1; i++) {
let offset = 0;
const emojiName = getEmojiName(content, i, state, inlineEmoji);
let token = null;
const emojiName = getEmojiName(content, i, state, inlineEmoji);
if (emojiName) {
token = getEmojiTokenByName(emojiName, state);
@ -207,8 +189,7 @@ function applyEmoji(
if (enableShortcuts && !token) {
// handle aliases (note: we can't do this in inline cause ; is not a split point)
//
let info = getEmojiTokenByTranslation(content, i, state);
const info = getEmojiTokenByTranslation(content, i, state);
if (info) {
offset = info.pos - i;
@ -225,7 +206,8 @@ function applyEmoji(
}
result.push(token);
endToken = start = i + offset;
i += offset;
endToken = start = i;
}
}

View File

@ -25,10 +25,14 @@ class Emoji
Discourse.cache.fetch(cache_key("aliases_emojis")) { db['aliases'] }
end
def self.searchAliases
def self.search_aliases
Discourse.cache.fetch(cache_key("search_aliases_emojis")) { db['searchAliases'] }
end
def self.translations
Discourse.cache.fetch(cache_key("translations_emojis")) { load_translations }
end
def self.custom
Discourse.cache.fetch(cache_key("custom_emojis")) { load_custom }
end
@ -63,13 +67,13 @@ class Emoji
end
def self.clear_cache
%w{custom standard aliases search_aliases all tonable}.each do |key|
%w{custom standard aliases search_aliases translations all tonable}.each do |key|
Discourse.cache.delete(cache_key("#{key}_emojis"))
end
end
def self.db_file
"#{Rails.root}/lib/emoji/db.json"
@db_file ||= "#{Rails.root}/lib/emoji/db.json"
end
def self.db
@ -101,6 +105,10 @@ class Emoji
result
end
def self.load_translations
db["translations"].merge(Plugin::CustomEmoji.translations)
end
def self.base_directory
"public#{base_url}"
end
@ -117,35 +125,35 @@ class Emoji
end
def self.unicode_replacements
return @unicode_replacements if @unicode_replacements
@unicode_replacements ||= begin
replacements = {}
is_tonable_emojis = Emoji.tonable_emojis
fitzpatrick_scales = FITZPATRICK_SCALE.map { |scale| scale.to_i(16) }
@unicode_replacements = {}
is_tonable_emojis = Emoji.tonable_emojis
fitzpatrick_scales = FITZPATRICK_SCALE.map { |scale| scale.to_i(16) }
db['emojis'].each do |e|
name = e['name']
next if name == 'tm'.freeze
db['emojis'].each do |e|
name = e['name']
next if name == 'tm'.freeze
code = replacement_code(e['code'])
next unless code
code = replacement_code(e['code'])
next unless code
@unicode_replacements[code] = name
if is_tonable_emojis.include?(name)
fitzpatrick_scales.each_with_index do |scale, index|
toned_code = code.codepoints.insert(1, scale).pack("U*".freeze)
@unicode_replacements[toned_code] = "#{name}:t#{index + 2}"
replacements[code] = name
if is_tonable_emojis.include?(name)
fitzpatrick_scales.each_with_index do |scale, index|
toned_code = code.codepoints.insert(1, scale).pack("U*".freeze)
replacements[toned_code] = "#{name}:t#{index + 2}"
end
end
end
replacements["\u{2639}"] = 'frowning'
replacements["\u{263A}"] = 'slight_smile'
replacements["\u{263B}"] = 'slight_smile'
replacements["\u{2661}"] = 'heart'
replacements["\u{2665}"] = 'heart'
replacements
end
@unicode_replacements["\u{2639}"] = 'frowning'
@unicode_replacements["\u{263A}"] = 'slight_smile'
@unicode_replacements["\u{263B}"] = 'slight_smile'
@unicode_replacements["\u{2661}"] = 'heart'
@unicode_replacements["\u{2665}"] = 'heart'
@unicode_replacements
end
def self.unicode_unescape(string)

View File

@ -7115,5 +7115,33 @@
"cry": [
"sob"
]
},
"translations": {
":)" : "slight_smile",
":-)" : "slight_smile",
"^_^" : "slight_smile",
"^__^": "slight_smile",
":(" : "frowning",
":-(" : "frowning",
";)" : "wink",
";-)" : "wink",
":'(" : "cry",
":'-(": "cry",
":-'(": "cry",
":p" : "stuck_out_tongue",
":P" : "stuck_out_tongue",
":-P" : "stuck_out_tongue",
":O" : "open_mouth",
":-O" : "open_mouth",
":D" : "smiley",
":-D" : "smiley",
":|" : "expressionless",
":-|" : "expressionless",
":/" : "confused",
"8-)" : "sunglasses",
";P" : "stuck_out_tongue_winking_eye",
";-P" : "stuck_out_tongue_winking_eye",
":$" : "blush",
":-$" : "blush"
}
}
}

View File

@ -16,6 +16,15 @@ class Plugin::CustomEmoji
@@cache_key = Digest::SHA1.hexdigest(cache_key + name)[0..10]
emojis[name] = url
end
def self.translations
@@translations ||= {}
end
def self.translate(from, to)
@@cache_key = Digest::SHA1.hexdigest(cache_key + from)[0..10]
translations[from] = to
end
end
class Plugin::Instance
@ -429,6 +438,10 @@ class Plugin::Instance
Plugin::CustomEmoji.register(name, url)
end
def translate_emoji(from, to)
Plugin::CustomEmoji.translate(from, to)
end
def automatic_assets
css = styles.join("\n")
js = javascripts.join("\n")