FIX: allows to have custom emoji translation without static file (#9893)

This commit is contained in:
Joffrey JAFFEUX 2020-05-27 20:11:52 +02:00 committed by GitHub
parent 207b72ade1
commit 77801aa9be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 94 additions and 17 deletions

View File

@ -483,8 +483,15 @@ export default Component.extend({
} }
} }
if (translations[full]) { // note this will only work for emojis starting with :
return resolve([translations[full]]); // eg: :-)
const allTranslations = Object.assign(
{},
translations,
this.getWithDefault("site.custom_emoji_translation", {})
);
if (allTranslations[full]) {
return resolve([allTranslations[full]]);
} }
const match = term.match(/^:?(.*?):t([2-6])?$/); const match = term.match(/^:?(.*?):t([2-6])?$/);

View File

@ -18,6 +18,7 @@ function getOpts(opts) {
getURL: getURLWithCDN, getURL: getURLWithCDN,
currentUser: Discourse.__container__.lookup("current-user:main"), currentUser: Discourse.__container__.lookup("current-user:main"),
censoredRegexp: site.censored_regexp, censoredRegexp: site.censored_regexp,
customEmojiTranslation: site.custom_emoji_translation,
siteSettings, siteSettings,
formatUsername formatUsername
}, },

View File

@ -98,13 +98,18 @@ export function performEmojiUnescape(string, opts) {
const inlineEmoji = opts.inlineEmoji; const inlineEmoji = opts.inlineEmoji;
const regexp = unicodeRegexp(inlineEmoji); const regexp = unicodeRegexp(inlineEmoji);
const allTranslations = Object.assign(
{},
translations,
opts.customEmojiTranslation || {}
);
return string.replace(regexp, (m, index) => { return string.replace(regexp, (m, index) => {
const isEmoticon = opts.enableEmojiShortcuts && !!translations[m]; const isEmoticon = opts.enableEmojiShortcuts && !!allTranslations[m];
const isUnicodeEmoticon = !!replacements[m]; const isUnicodeEmoticon = !!replacements[m];
let emojiVal; let emojiVal;
if (isEmoticon) { if (isEmoticon) {
emojiVal = translations[m]; emojiVal = allTranslations[m];
} else if (isUnicodeEmoticon) { } else if (isUnicodeEmoticon) {
emojiVal = replacements[m]; emojiVal = replacements[m];
} else { } else {
@ -131,11 +136,16 @@ export function performEmojiUnescape(string, opts) {
export function performEmojiEscape(string, opts) { export function performEmojiEscape(string, opts) {
const inlineEmoji = opts.inlineEmoji; const inlineEmoji = opts.inlineEmoji;
const regexp = unicodeRegexp(inlineEmoji); const regexp = unicodeRegexp(inlineEmoji);
const allTranslations = Object.assign(
{},
translations,
opts.customEmojiTranslation || {}
);
return string.replace(regexp, (m, index) => { return string.replace(regexp, (m, index) => {
if (isReplacableInlineEmoji(string, index, inlineEmoji)) { if (isReplacableInlineEmoji(string, index, inlineEmoji)) {
if (!!translations[m]) { if (!!allTranslations[m]) {
return opts.emojiShortcuts ? `:${translations[m]}:` : m; return opts.emojiShortcuts ? `:${allTranslations[m]}:` : m;
} else if (!!replacements[m]) { } else if (!!replacements[m]) {
return `:${replacements[m]}:`; return `:${replacements[m]}:`;
} }

View File

@ -5,15 +5,25 @@ const MAX_NAME_LENGTH = 60;
let translationTree = null; let translationTree = null;
export function resetTranslationTree() {
translationTree = null;
}
// This allows us to efficiently search for aliases // This allows us to efficiently search for aliases
// We build a data structure that allows us to quickly // We build a data structure that allows us to quickly
// search through our N next chars to see if any match // search through our N next chars to see if any match
// one of our alias emojis. // one of our alias emojis.
function buildTranslationTree() { function buildTranslationTree(customEmojiTranslation) {
let tree = []; let tree = [];
let lastNode; let lastNode;
Object.keys(translations).forEach(key => { const allTranslations = Object.assign(
{},
translations,
customEmojiTranslation || {}
);
Object.keys(allTranslations).forEach(key => {
let node = tree; let node = tree;
for (let i = 0; i < key.length; i++) { for (let i = 0; i < key.length; i++) {
@ -37,7 +47,7 @@ function buildTranslationTree() {
} }
} }
lastNode[2] = translations[key]; lastNode[2] = allTranslations[key];
}); });
return tree; return tree;
@ -114,8 +124,14 @@ function getEmojiTokenByName(name, state) {
} }
} }
function getEmojiTokenByTranslation(content, pos, state) { function getEmojiTokenByTranslation(
translationTree = translationTree || buildTranslationTree(); content,
pos,
state,
customEmojiTranslation
) {
translationTree =
translationTree || buildTranslationTree(customEmojiTranslation);
let t = translationTree; let t = translationTree;
let start = pos; let start = pos;
@ -175,7 +191,8 @@ function applyEmoji(
state, state,
emojiUnicodeReplacer, emojiUnicodeReplacer,
enableShortcuts, enableShortcuts,
inlineEmoji inlineEmoji,
customEmojiTranslation
) { ) {
let result = null; let result = null;
let start = 0; let start = 0;
@ -201,7 +218,12 @@ function applyEmoji(
if (enableShortcuts && !token) { if (enableShortcuts && !token) {
// handle aliases (note: we can't do this in inline cause ; is not a split point) // handle aliases (note: we can't do this in inline cause ; is not a split point)
const info = getEmojiTokenByTranslation(content, i, state); const info = getEmojiTokenByTranslation(
content,
i,
state,
customEmojiTranslation
);
if (info) { if (info) {
offset = info.pos - i; offset = info.pos - i;
@ -310,7 +332,8 @@ export function setup(helper) {
s, s,
md.options.discourse.emojiUnicodeReplacer, md.options.discourse.emojiUnicodeReplacer,
md.options.discourse.features.emojiShortcuts, md.options.discourse.features.emojiShortcuts,
md.options.discourse.features.inlineEmoji md.options.discourse.features.inlineEmoji,
md.options.discourse.customEmojiTranslation
) )
) )
); );

View File

@ -30,7 +30,8 @@ export function buildOptions(state) {
previewing, previewing,
linkify, linkify,
censoredRegexp, censoredRegexp,
disableEmojis disableEmojis,
customEmojiTranslation
} = state; } = state;
let features = { let features = {
@ -68,6 +69,7 @@ export function buildOptions(state) {
emojiUnicodeReplacer, emojiUnicodeReplacer,
lookupUploadUrls, lookupUploadUrls,
censoredRegexp, censoredRegexp,
customEmojiTranslation,
allowedHrefSchemes: siteSettings.allowed_href_schemes allowedHrefSchemes: siteSettings.allowed_href_schemes
? siteSettings.allowed_href_schemes.split("|") ? siteSettings.allowed_href_schemes.split("|")
: null, : null,

View File

@ -126,7 +126,7 @@ class Emoji
end end
def self.load_translations def self.load_translations
db["translations"].merge(Plugin::CustomEmoji.translations) db["translations"]
end end
def self.base_directory def self.base_directory

View File

@ -26,7 +26,8 @@ class SiteSerializer < ApplicationSerializer
:topic_featured_link_allowed_category_ids, :topic_featured_link_allowed_category_ids,
:user_themes, :user_themes,
:censored_regexp, :censored_regexp,
:shared_drafts_category_id :shared_drafts_category_id,
:custom_emoji_translation
) )
has_many :categories, serializer: SiteCategorySerializer, embed: :objects has_many :categories, serializer: SiteCategorySerializer, embed: :objects
@ -154,6 +155,10 @@ class SiteSerializer < ApplicationSerializer
WordWatcher.word_matcher_regexp(:censor)&.source WordWatcher.word_matcher_regexp(:censor)&.source
end end
def custom_emoji_translation
Plugin::CustomEmoji.translations
end
def shared_drafts_category_id def shared_drafts_category_id
SiteSetting.shared_drafts_category.to_i SiteSetting.shared_drafts_category.to_i
end end

View File

@ -18,6 +18,7 @@ class Plugin::CustomEmoji
def self.clear_cache def self.clear_cache
@@cache_key = CACHE_KEY @@cache_key = CACHE_KEY
@@emojis = {} @@emojis = {}
@@translations = {}
end end
def self.register(name, url, group = Emoji::DEFAULT_GROUP) def self.register(name, url, group = Emoji::DEFAULT_GROUP)

View File

@ -126,6 +126,10 @@ module PrettyText
@ctx @ctx
end end
def self.reset_translations
v8.eval("__resetTranslationTree()")
end
def self.reset_context def self.reset_context
@ctx_init.synchronize do @ctx_init.synchronize do
@ctx&.dispose @ctx&.dispose
@ -159,6 +163,7 @@ module PrettyText
__optInput.getTopicInfo = __getTopicInfo; __optInput.getTopicInfo = __getTopicInfo;
__optInput.categoryHashtagLookup = __categoryLookup; __optInput.categoryHashtagLookup = __categoryLookup;
__optInput.customEmoji = #{custom_emoji.to_json}; __optInput.customEmoji = #{custom_emoji.to_json};
__optInput.customEmojiTranslation = #{Plugin::CustomEmoji.translations.to_json};
__optInput.emojiUnicodeReplacer = __emojiUnicodeReplacer; __optInput.emojiUnicodeReplacer = __emojiUnicodeReplacer;
__optInput.lookupUploadUrls = __lookupUploadUrls; __optInput.lookupUploadUrls = __lookupUploadUrls;
__optInput.censoredRegexp = #{WordWatcher.word_matcher_regexp(:censor)&.source.to_json}; __optInput.censoredRegexp = #{WordWatcher.word_matcher_regexp(:censor)&.source.to_json};

View File

@ -3,6 +3,8 @@ __buildOptions = require("pretty-text/pretty-text").buildOptions;
__performEmojiUnescape = require("pretty-text/emoji").performEmojiUnescape; __performEmojiUnescape = require("pretty-text/emoji").performEmojiUnescape;
__buildReplacementsList = require("pretty-text/emoji").buildReplacementsList; __buildReplacementsList = require("pretty-text/emoji").buildReplacementsList;
__performEmojiEscape = require("pretty-text/emoji").performEmojiEscape; __performEmojiEscape = require("pretty-text/emoji").performEmojiEscape;
__resetTranslationTree = require("pretty-text/engines/discourse-markdown/emoji")
.resetTranslationTree;
I18n = { I18n = {
t(a, b) { t(a, b) {

View File

@ -1040,6 +1040,27 @@ describe PrettyText do
end end
end end
describe "custom emoji translation" do
before do
PrettyText.reset_translations
SiteSetting.enable_emoji = true
SiteSetting.enable_emoji_shortcuts = true
plugin = Plugin::Instance.new
plugin.translate_emoji "0:)", "otter"
end
after do
Plugin::CustomEmoji.clear_cache
PrettyText.reset_translations
end
it "sets the custom translation" do
expect(PrettyText.cook("hello 0:)")).to match(/otter/)
end
end
it "replaces skin toned emoji" do it "replaces skin toned emoji" do
expect(PrettyText.cook("hello 👱🏿‍♀️")).to eq("<p>hello <img src=\"/images/emoji/twitter/blonde_woman/6.png?v=#{Emoji::EMOJI_VERSION}\" title=\":blonde_woman:t6:\" class=\"emoji\" alt=\":blonde_woman:t6:\"></p>") expect(PrettyText.cook("hello 👱🏿‍♀️")).to eq("<p>hello <img src=\"/images/emoji/twitter/blonde_woman/6.png?v=#{Emoji::EMOJI_VERSION}\" title=\":blonde_woman:t6:\" class=\"emoji\" alt=\":blonde_woman:t6:\"></p>")
expect(PrettyText.cook("hello 👩‍🎤")).to eq("<p>hello <img src=\"/images/emoji/twitter/woman_singer.png?v=#{Emoji::EMOJI_VERSION}\" title=\":woman_singer:\" class=\"emoji\" alt=\":woman_singer:\"></p>") expect(PrettyText.cook("hello 👩‍🎤")).to eq("<p>hello <img src=\"/images/emoji/twitter/woman_singer.png?v=#{Emoji::EMOJI_VERSION}\" title=\":woman_singer:\" class=\"emoji\" alt=\":woman_singer:\"></p>")