forked from treehouse/mastodon
Avoid emojifying on invisible text (#5558)
parent
84cfee2488
commit
782224c991
|
@ -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" />');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue