FIX: Respect `enable_inline_emoji_translation` setting in titles

This commit is contained in:
Gerhard Schlager 2019-12-03 17:32:33 +01:00
parent 86b81b5f63
commit 9ebb69e8eb
8 changed files with 141 additions and 17 deletions

View File

@ -68,7 +68,8 @@ function emojiOptions() {
return {
getURL: Discourse.getURLWithCDN,
emojiSet: Discourse.SiteSettings.emoji_set,
enableEmojiShortcuts: Discourse.SiteSettings.enable_emoji_shortcuts
enableEmojiShortcuts: Discourse.SiteSettings.enable_emoji_shortcuts,
inlineEmoji: Discourse.SiteSettings.enable_inline_emoji_translation
};
}

View File

@ -42,10 +42,31 @@ export function buildReplacementsList(emojiReplacements) {
.join("|");
}
const unicodeRegexp = new RegExp(
buildReplacementsList(replacements) + "|\\B:[^\\s:]+(?::t\\d)?:?\\B",
"g"
);
let replacementListCache;
const unicodeRegexpCache = {};
function replacementList() {
if (replacementListCache === undefined) {
replacementListCache = buildReplacementsList(replacements);
}
return replacementListCache;
}
function unicodeRegexp(inlineEmoji) {
if (unicodeRegexpCache[inlineEmoji] === undefined) {
const emojiExpression = inlineEmoji
? "|:[^\\s:]+(?::t\\d)?:?"
: "|\\B:[^\\s:]+(?::t\\d)?:?\\B";
unicodeRegexpCache[inlineEmoji] = new RegExp(
replacementList() + emojiExpression,
"g"
);
}
return unicodeRegexpCache[inlineEmoji];
}
// add all default emojis
emojis.forEach(code => (emojiHash[code] = true));
@ -56,12 +77,29 @@ Object.keys(aliases).forEach(name => {
aliases[name].forEach(alias => (aliasHash[alias] = name));
});
function isReplacableInlineEmoji(string, index, inlineEmoji) {
if (inlineEmoji) return true;
// index depends on regex; when `inlineEmoji` is false, the regex starts
// with a `\B` character, so there's no need to subtract from the index
const beforeEmoji = string.slice(0, index - (inlineEmoji ? 1 : 0));
return (
beforeEmoji.length === 0 ||
/(?:\s|[>.,\/#!$%^&*;:{}=\-_`~()])$/.test(beforeEmoji) ||
new RegExp(`(?:${replacementList()})$`).test(beforeEmoji)
);
}
export function performEmojiUnescape(string, opts) {
if (!string) {
return;
}
return string.replace(unicodeRegexp, m => {
const inlineEmoji = opts.inlineEmoji;
const regexp = unicodeRegexp(inlineEmoji);
return string.replace(regexp, (m, index) => {
const isEmoticon = opts.enableEmojiShortcuts && !!translations[m];
const isUnicodeEmoticon = !!replacements[m];
let emojiVal;
@ -78,7 +116,11 @@ export function performEmojiUnescape(string, opts) {
? "emoji emoji-custom"
: "emoji";
return url && (isEmoticon || hasEndingColon || isUnicodeEmoticon)
const isReplacable =
(isEmoticon || hasEndingColon || isUnicodeEmoticon) &&
isReplacableInlineEmoji(string, index, inlineEmoji);
return url && isReplacable
? `<img src='${url}' ${
opts.skipTitle ? "" : `title='${emojiVal}'`
} alt='${emojiVal}' class='${classes}'>`
@ -89,14 +131,19 @@ export function performEmojiUnescape(string, opts) {
}
export function performEmojiEscape(string, opts) {
return string.replace(unicodeRegexp, m => {
if (!!translations[m]) {
return opts.emojiShortcuts ? `:${translations[m]}:` : m;
} else if (!!replacements[m]) {
return `:${replacements[m]}:`;
} else {
return m;
const inlineEmoji = opts.inlineEmoji;
const regexp = unicodeRegexp(inlineEmoji);
return string.replace(regexp, (m, index) => {
if (isReplacableInlineEmoji(string, index, inlineEmoji)) {
if (!!translations[m]) {
return opts.emojiShortcuts ? `:${translations[m]}:` : m;
} else if (!!replacements[m]) {
return `:${replacements[m]}:`;
}
}
return m;
});
return string;

View File

@ -120,7 +120,8 @@ const rule = {
title = performEmojiUnescape(topicInfo.title, {
getURL: options.getURL,
emojiSet: options.emojiSet,
enableEmojiShortcuts: options.enableEmojiShortcuts
enableEmojiShortcuts: options.enableEmojiShortcuts,
inlineEmoji: options.inlineEmoji
});
}
@ -156,6 +157,7 @@ export function setup(helper) {
opts.enableEmoji = siteSettings.enable_emoji;
opts.emojiSet = siteSettings.emoji_set;
opts.enableEmojiShortcuts = siteSettings.enable_emoji_shortcuts;
opts.inlineEmoji = siteSettings.enable_inline_emoji_translation;
});
helper.registerPlugin(md => {

View File

@ -218,6 +218,7 @@ module PrettyText
set = SiteSetting.emoji_set.inspect
custom = Emoji.custom.map { |e| [e.name, e.url] }.to_h.to_json
protect do
v8.eval(<<~JS)
__paths = #{paths_json};
@ -225,7 +226,8 @@ module PrettyText
getURL: __getURL,
emojiSet: #{set},
customEmoji: #{custom},
enableEmojiShortcuts: #{SiteSetting.enable_emoji_shortcuts}
enableEmojiShortcuts: #{SiteSetting.enable_emoji_shortcuts},
inlineEmoji: #{SiteSetting.enable_inline_emoji_translation}
});
JS
end
@ -238,7 +240,10 @@ module PrettyText
protect do
v8.eval(<<~JS)
__performEmojiEscape(#{title.inspect}, { emojiShortcuts: #{replace_emoji_shortcuts} });
__performEmojiEscape(#{title.inspect}, {
emojiShortcuts: #{replace_emoji_shortcuts},
inlineEmoji: #{SiteSetting.enable_inline_emoji_translation}
});
JS
end
end

View File

@ -308,6 +308,7 @@ describe Topic do
let(:topic_emoji) { build_topic_with_title("I 💖 candy alot") }
let(:topic_modifier_emoji) { build_topic_with_title("I 👨‍🌾 candy alot") }
let(:topic_shortcut_emoji) { build_topic_with_title("I love candy :)") }
let(:topic_inline_emoji) { build_topic_with_title("Hello😊World") }
it "escapes script contents" do
expect(topic_script.fancy_title).to eq("Topic with &lt;script&gt;alert(&lsquo;title&rsquo;)&lt;/script&gt; script in its title")
@ -359,6 +360,16 @@ describe Topic do
expect(topic_shortcut_emoji.fancy_title).to eq("I love candy :)")
end
end
it "keeps inline emojis if inline emoji setting disabled" do
SiteSetting.enable_inline_emoji_translation = false
expect(topic_inline_emoji.fancy_title).to eq("Hello😊World")
end
it "expands inline emojis if inline emoji setting enabled" do
SiteSetting.enable_inline_emoji_translation = true
expect(topic_inline_emoji.fancy_title).to eq("Hello:blush:World")
end
end
context 'fancy title' do

View File

@ -186,6 +186,27 @@ QUnit.test("Updating the topic title with unicode emojis", async assert => {
);
});
QUnit.test(
"Updating the topic title with unicode emojis without whitespaces",
async assert => {
Discourse.SiteSettings.enable_inline_emoji_translation = true;
await visit("/t/internationalization-localization/280");
await click("#topic-title .d-icon-pencil-alt");
await fillIn("#edit-title", "Test🙂Title");
await click("#topic-title .submit-edit");
assert.equal(
find(".fancy-title")
.html()
.trim(),
`Test<img src="/images/emoji/emoji_one/slightly_smiling_face.png?v=${v}" title="slightly_smiling_face" alt="slightly_smiling_face" class="emoji">Title`,
"it displays the new title with escaped unicode emojis"
);
}
);
acceptance("Topic featured links", {
loggedIn: true,
settings: {

View File

@ -93,6 +93,8 @@ Discourse.SiteSettingsOriginal = {
enable_emoji: true,
enable_emoji_shortcuts: true,
emoji_set: "emoji_one",
enable_emoji_shortcuts: true,
enable_inline_emoji_translation: false,
desktop_category_page_style: "categories_and_latest_topics",
enable_mentions: true,
enable_personal_messages: true,

View File

@ -92,6 +92,41 @@ QUnit.test("emojiUnescape", assert => {
"no emoticons when emoji shortcuts are disabled",
{ enable_emoji_shortcuts: false }
);
testUnescape(
"Hello 😊 World",
`Hello <img src='/images/emoji/emoji_one/blush.png?v=${v}' title='blush' alt='blush' class='emoji'> World`,
"emoji from Unicode emoji"
);
testUnescape(
"Hello😊World",
"Hello😊World",
"keeps Unicode emoji when inline translation disabled",
{
enable_inline_emoji_translation: false
}
);
testUnescape(
"Hello😊World",
`Hello<img src='/images/emoji/emoji_one/blush.png?v=${v}' title='blush' alt='blush' class='emoji'>World`,
"emoji from Unicode emoji when inline translation enabled",
{
enable_inline_emoji_translation: true
}
);
testUnescape(
"hi:smile:",
"hi:smile:",
"no emojis when inline translation disabled",
{
enable_inline_emoji_translation: false
}
);
testUnescape(
"hi:smile:",
`hi<img src='/images/emoji/emoji_one/smile.png?v=${v}' title='smile' alt='smile' class='emoji'>`,
"emoji when inline translation enabled",
{ enable_inline_emoji_translation: true }
);
});
QUnit.test("Emoji search", assert => {