FIX: allows to have custom emoji translation without static file (#9893)
This commit is contained in:
parent
207b72ade1
commit
77801aa9be
|
@ -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])?$/);
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
|
@ -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]}:`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>")
|
||||||
|
|
Loading…
Reference in New Issue