diff --git a/Gemfile.lock b/Gemfile.lock index 6923522097..15e34c9365 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -565,7 +565,7 @@ GEM redis (>= 4) redlock (1.3.2) redis (>= 3.0.0, < 6.0) - regexp_parser (2.7.0) + regexp_parser (2.8.0) request_store (1.5.1) rack (>= 1.4) responders (3.1.0) @@ -601,7 +601,7 @@ GEM rspec_chunked (0.6) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.49.0) + rubocop (1.50.2) json (~> 2.3) parallel (~> 1.10) parser (>= 3.2.0.0) diff --git a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js index 72a732e3bc..7b917ac43b 100644 --- a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js +++ b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js @@ -74,9 +74,9 @@ describe('emoji', () => { .toEqual('😇'); }); - it('skips the textual presentation VS15 character', () => { + it('does not emojify emojis with textual presentation VS15 character', () => { expect(emojify('✴︎')) // This is U+2734 EIGHT POINTED BLACK STAR then U+FE0E VARIATION SELECTOR-15 - .toEqual('✴'); + .toEqual('✴︎'); }); it('does an simple emoji properly', () => { diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js index 6ae4066242..fe4f6f2869 100644 --- a/app/javascript/mastodon/features/emoji/emoji.js +++ b/app/javascript/mastodon/features/emoji/emoji.js @@ -20,13 +20,18 @@ const emojiFilename = (filename) => { }; const emojifyTextNode = (node, customEmojis) => { + const VS15 = 0xFE0E; + const VS16 = 0xFE0F; + let str = node.textContent; const fragment = new DocumentFragment(); + let i = 0; for (;;) { - let match, i = 0; + let match; + // Skip to the next potential emoji to replace if (customEmojis === null) { while (i < str.length && !(match = trie.search(str.slice(i)))) { i += str.codePointAt(i) < 65536 ? 1 : 2; @@ -37,51 +42,58 @@ const emojifyTextNode = (node, customEmojis) => { } } - let rend, replacement = null; + // We reached the end of the string, nothing to replace if (i === str.length) { break; - } else if (str[i] === ':') { - if (!(() => { - rend = str.indexOf(':', i + 1) + 1; - if (!rend) return false; // no pair of ':' - const shortname = str.slice(i, rend); - // 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 = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url; - replacement = document.createElement('img'); - replacement.setAttribute('draggable', 'false'); - replacement.setAttribute('class', 'emojione custom-emoji'); - replacement.setAttribute('alt', shortname); - replacement.setAttribute('title', shortname); - replacement.setAttribute('src', filename); - replacement.setAttribute('data-original', customEmojis[shortname].url); - replacement.setAttribute('data-static', customEmojis[shortname].static_url); - return true; - } - return false; - })()) rend = ++i; + } + + let rend, replacement = null; + if (str[i] === ':') { // Potentially the start of a custom emoji shortcode + if (!(rend = str.indexOf(':', i + 1) + 1)) { + continue; // no pair of ':' + } + + const shortname = str.slice(i, rend); + // 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 = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url; + replacement = document.createElement('img'); + replacement.setAttribute('draggable', 'false'); + replacement.setAttribute('class', 'emojione custom-emoji'); + replacement.setAttribute('alt', shortname); + replacement.setAttribute('title', shortname); + replacement.setAttribute('src', filename); + replacement.setAttribute('data-original', customEmojis[shortname].url); + replacement.setAttribute('data-static', customEmojis[shortname].static_url); + } else { + continue; + } } else { // matched to unicode emoji + rend = i + match.length; + + // If the matched character was followed by VS15 (for selecting text presentation), skip it. + if (str.codePointAt(rend - 1) !== VS16 && str.codePointAt(rend) === VS15) { + i = rend + 1; + continue; + } + const { filename, shortCode } = unicodeMapping[match]; const title = shortCode ? `:${shortCode}:` : ''; + replacement = document.createElement('img'); replacement.setAttribute('draggable', 'false'); replacement.setAttribute('class', 'emojione'); replacement.setAttribute('alt', match); replacement.setAttribute('title', title); replacement.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename)}.svg`); - rend = i + match.length; - // If the matched character was followed by VS15 (for selecting text presentation), skip it. - if (str.codePointAt(rend) === 65038) { - rend += 1; - } } + // Add the processed-up-to-now string and the emoji replacement fragment.append(document.createTextNode(str.slice(0, i))); - if (replacement) { - fragment.append(replacement); - } + fragment.append(replacement); str = str.slice(rend); + i = 0; } fragment.append(document.createTextNode(str)); diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb index 5194cd80a8..e990f5f192 100644 --- a/lib/mastodon/accounts_cli.rb +++ b/lib/mastodon/accounts_cli.rb @@ -543,7 +543,7 @@ module Mastodon if options[:all] User.pending.find_each(&:approve!) say('OK', :green) - elsif options[:number] + elsif options[:number]&.positive? User.pending.limit(options[:number]).each(&:approve!) say('OK', :green) elsif username.present? @@ -557,6 +557,7 @@ module Mastodon account.user&.approve! say('OK', :green) else + say('Number must be positive', :red) if options[:number] exit(1) end end