Avoid emojifying on invisible text (#5558)

pull/5615/head
MIYAGI Hikaru 2017-11-07 22:48:13 +09:00 committed by Eugen Rochko
parent 84cfee2488
commit 782224c991
3 changed files with 60 additions and 9 deletions

View File

@ -57,5 +57,21 @@ describe('emoji', () => {
it('does an emoji whose filename is irregular', () => { it('does an emoji whose filename is irregular', () => {
expect(emojify('↙️')).toEqual('<img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg" />'); expect(emojify('↙️')).toEqual('<img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg" />');
}); });
it('avoid emojifying on invisible text', () => {
expect(emojify('<a href="http://example.com/test%F0%9F%98%84"><span class="invisible">http://</span><span class="ellipsis">example.com/te</span><span class="invisible">st😄</span></a>'))
.toEqual('<a href="http://example.com/test%F0%9F%98%84"><span class="invisible">http://</span><span class="ellipsis">example.com/te</span><span class="invisible">st😄</span></a>');
expect(emojify('<span class="invisible">:luigi:</span>', { ':luigi:': { static_url: 'luigi.exe' } }))
.toEqual('<span class="invisible">:luigi:</span>');
});
it('avoid emojifying on invisible text with nested tags', () => {
expect(emojify('<span class="invisible">😄<span class="foo">bar</span>😴</span>😇'))
.toEqual('<span class="invisible">😄<span class="foo">bar</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />');
expect(emojify('<span class="invisible">😄<span class="invisible">😕</span>😴</span>😇'))
.toEqual('<span class="invisible">😄<span class="invisible">😕</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />');
expect(emojify('<span class="invisible">😄<br/>😴</span>😇'))
.toEqual('<span class="invisible">😄<br/>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />');
});
}); });
}); });

View File

@ -7,10 +7,12 @@ const trie = new Trie(Object.keys(unicodeMapping));
const assetHost = process.env.CDN_HOST || ''; const assetHost = process.env.CDN_HOST || '';
const emojify = (str, customEmojis = {}) => { const emojify = (str, customEmojis = {}) => {
let rtn = ''; const tagCharsWithoutEmojis = '<&';
const tagCharsWithEmojis = Object.keys(customEmojis).length ? '<&:' : '<&';
let rtn = '', tagChars = tagCharsWithEmojis, invisible = 0;
for (;;) { for (;;) {
let match, i = 0, tag; let match, i = 0, tag;
while (i < str.length && (tag = '<&:'.indexOf(str[i])) === -1 && !(match = trie.search(str.slice(i)))) { while (i < str.length && (tag = tagChars.indexOf(str[i])) === -1 && (invisible || !(match = trie.search(str.slice(i))))) {
i += str.codePointAt(i) < 65536 ? 1 : 2; i += str.codePointAt(i) < 65536 ? 1 : 2;
} }
let rend, replacement = ''; let rend, replacement = '';
@ -34,7 +36,26 @@ const emojify = (str, customEmojis = {}) => {
})()) rend = ++i; })()) rend = ++i;
} else if (tag >= 0) { // <, & } else if (tag >= 0) { // <, &
rend = str.indexOf('>;'[tag], i + 1) + 1; rend = str.indexOf('>;'[tag], i + 1) + 1;
if (!rend) break; if (!rend) {
break;
}
if (tag === 0) {
if (invisible) {
if (str[i + 1] === '/') { // closing tag
if (!--invisible) {
tagChars = tagCharsWithEmojis;
}
} else if (str[rend - 2] !== '/') { // opening tag
invisible++;
}
} else {
if (str.startsWith('<span class="invisible">', i)) {
// avoid emojifying on invisible text
invisible = 1;
tagChars = tagCharsWithoutEmojis;
}
}
}
i = rend; i = rend;
} else { // matched to unicode emoji } else { // matched to unicode emoji
const { filename, shortCode } = unicodeMapping[match]; const { filename, shortCode } = unicodeMapping[match];

View File

@ -89,20 +89,28 @@ class Formatter
end end
end end
def count_tag_nesting(tag)
if tag[1] == '/' then -1
elsif tag[-2] == '/' then 0
else 1
end
end
def encode_custom_emojis(html, emojis) def encode_custom_emojis(html, emojis)
return html if emojis.empty? return html if emojis.empty?
emoji_map = emojis.map { |e| [e.shortcode, full_asset_url(e.image.url(:static))] }.to_h emoji_map = emojis.map { |e| [e.shortcode, full_asset_url(e.image.url(:static))] }.to_h
i = -1 i = -1
inside_tag = false tag_open_index = nil
inside_shortname = false inside_shortname = false
shortname_start_index = -1 shortname_start_index = -1
invisible_depth = 0
while i + 1 < html.size while i + 1 < html.size
i += 1 i += 1
if inside_shortname && html[i] == ':' if invisible_depth.zero? && inside_shortname && html[i] == ':'
shortcode = html[shortname_start_index + 1..i - 1] shortcode = html[shortname_start_index + 1..i - 1]
emoji = emoji_map[shortcode] emoji = emoji_map[shortcode]
@ -116,12 +124,18 @@ class Formatter
end end
inside_shortname = false inside_shortname = false
elsif inside_tag && html[i] == '>' elsif tag_open_index && html[i] == '>'
inside_tag = false tag = html[tag_open_index..i]
tag_open_index = nil
if invisible_depth.positive?
invisible_depth += count_tag_nesting(tag)
elsif tag == '<span class="invisible">'
invisible_depth = 1
end
elsif html[i] == '<' elsif html[i] == '<'
inside_tag = true tag_open_index = i
inside_shortname = false inside_shortname = false
elsif !inside_tag && html[i] == ':' elsif !tag_open_index && html[i] == ':'
inside_shortname = true inside_shortname = true
shortname_start_index = i shortname_start_index = i
end end