diff options
Diffstat (limited to 'app/javascript/mastodon/features/emoji')
3 files changed, 46 insertions, 12 deletions
diff --git a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js index 636402172..372459c78 100644 --- a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js +++ b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js @@ -57,5 +57,21 @@ describe('emoji', () => { 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" />'); }); + + 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" />'); + }); }); }); diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js index b70fc2b37..0f005dd50 100644 --- a/app/javascript/mastodon/features/emoji/emoji.js +++ b/app/javascript/mastodon/features/emoji/emoji.js @@ -1,3 +1,4 @@ +import { autoPlayGif } from '../../initial_state'; import unicodeMapping from './emoji_unicode_mapping_light'; import Trie from 'substring-trie'; @@ -5,13 +6,13 @@ const trie = new Trie(Object.keys(unicodeMapping)); const assetHost = process.env.CDN_HOST || ''; -let allowAnimations = false; - const emojify = (str, customEmojis = {}) => { - let rtn = ''; + const tagCharsWithoutEmojis = '<&'; + const tagCharsWithEmojis = Object.keys(customEmojis).length ? '<&:' : '<&'; + let rtn = '', tagChars = tagCharsWithEmojis, invisible = 0; for (;;) { 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; } let rend, replacement = ''; @@ -27,7 +28,7 @@ const emojify = (str, customEmojis = {}) => { // now got a replacee as ':shortname:' // if you want additional emoji handler, add statements below which set replacement and return true. if (shortname in customEmojis) { - const filename = allowAnimations ? customEmojis[shortname].url : customEmojis[shortname].static_url; + const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url; replacement = `<img draggable="false" class="emojione" alt="${shortname}" title="${shortname}" src="${filename}" />`; return true; } @@ -35,7 +36,26 @@ const emojify = (str, customEmojis = {}) => { })()) rend = ++i; } else if (tag >= 0) { // <, & 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; } else { // matched to unicode emoji const { filename, shortCode } = unicodeMapping[match]; @@ -51,14 +71,12 @@ const emojify = (str, customEmojis = {}) => { export default emojify; -export const buildCustomEmojis = (customEmojis, overrideAllowAnimations = false) => { +export const buildCustomEmojis = (customEmojis) => { const emojis = []; - allowAnimations = overrideAllowAnimations; - customEmojis.forEach(emoji => { const shortcode = emoji.get('shortcode'); - const url = allowAnimations ? emoji.get('url') : emoji.get('static_url'); + const url = autoPlayGif ? emoji.get('url') : emoji.get('static_url'); const name = shortcode.replace(':', ''); emojis.push({ diff --git a/app/javascript/mastodon/features/emoji/emoji_compressed.js b/app/javascript/mastodon/features/emoji/emoji_compressed.js index c0cba952a..e5b834a74 100644 --- a/app/javascript/mastodon/features/emoji/emoji_compressed.js +++ b/app/javascript/mastodon/features/emoji/emoji_compressed.js @@ -64,14 +64,14 @@ Object.keys(emojiMap).forEach(key => { Object.keys(emojiIndex.emojis).forEach(key => { const { native } = emojiIndex.emojis[key]; - const { short_names, search, unified } = emojiMartData.emojis[key]; + let { short_names, search, unified } = emojiMartData.emojis[key]; if (short_names[0] !== key) { throw new Error('The compresser expects the first short_code to be the ' + 'key. It may need to be rewritten if the emoji change such that this ' + 'is no longer the case.'); } - short_names.splice(0, 1); // first short name can be inferred from the key + short_names = short_names.slice(1); // first short name can be inferred from the key const searchData = [native, short_names, search]; if (unicodeToUnifiedName(native) !== unified) { |