diff --git a/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx b/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx index a7a4dff28..834b556f2 100644 --- a/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx +++ b/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx @@ -4,7 +4,7 @@ import { List as ImmutableList, Map as ImmutableMap } from 'immutable'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { buildCustomEmojis } from '../../../emoji/emoji'; +import { buildCustomEmojis, categoriesFromEmojis } from '../../../emoji/emoji'; import { EmojiPicker } from './emoji-picker-dropdown'; import ModifierPicker from './modifier-picker'; @@ -14,19 +14,6 @@ import type { Emoji } from 'soapbox/components/autosuggest-emoji'; const backgroundImageFn = () => require('emoji-datasource/img/twitter/sheets/32.png'); const listenerOptions = supportsPassiveEvents ? { passive: true } : false; -const categoriesSort = [ - 'recent', - 'custom', - 'people', - 'nature', - 'foods', - 'activity', - 'places', - 'objects', - 'symbols', - 'flags', -]; - const messages = defineMessages({ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search…' }, @@ -71,6 +58,20 @@ const EmojiPickerMenu: React.FC = ({ const [modifierOpen, setModifierOpen] = useState(false); + const categoriesSort = [ + 'recent', + 'people', + 'nature', + 'foods', + 'activity', + 'places', + 'objects', + 'symbols', + 'flags', + ]; + + categoriesSort.splice(1, 0, ...Array.from(categoriesFromEmojis(customEmojis) as Set).sort()); + const handleDocumentClick = useCallback(e => { if (node.current && !node.current.contains(e.target)) { onClose(); diff --git a/app/soapbox/features/emoji/__tests__/emoji.test.ts b/app/soapbox/features/emoji/__tests__/emoji.test.ts index 83b6ef14d..df6798ef7 100644 --- a/app/soapbox/features/emoji/__tests__/emoji.test.ts +++ b/app/soapbox/features/emoji/__tests__/emoji.test.ts @@ -88,5 +88,10 @@ describe('emoji', () => { expect(emojify('πŸ’‚β€β™€οΈπŸ’‚β€β™‚οΈ')) .toEqual('πŸ’‚\u200Dβ™€οΈπŸ’‚\u200D♂️'); }); + + it('keeps ordering as expected (issue fixed by PR 20677)', () => { + expect(emojify('

πŸ’• #foo test: foo.

')) + .toEqual('

πŸ’• #foo test: foo.

'); + }); }); }); diff --git a/app/soapbox/features/emoji/emoji.js b/app/soapbox/features/emoji/emoji.js index 9eadddbc6..e9b4caa35 100644 --- a/app/soapbox/features/emoji/emoji.js +++ b/app/soapbox/features/emoji/emoji.js @@ -6,8 +6,6 @@ import unicodeMapping from './emoji-unicode-mapping-light'; const trie = new Trie(Object.keys(unicodeMapping)); -const domParser = new DOMParser(); - const emojifyTextNode = (node, customEmojis, autoPlayGif = false) => { let str = node.textContent; @@ -26,7 +24,7 @@ const emojifyTextNode = (node, customEmojis, autoPlayGif = false) => { } } - let rend, replacement = ''; + let rend, replacement = null; if (i === str.length) { break; } else if (str[i] === ':') { @@ -39,7 +37,14 @@ const emojifyTextNode = (node, customEmojis, autoPlayGif = false) => { // 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 = `${shortname}`; + 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; @@ -47,8 +52,12 @@ const emojifyTextNode = (node, customEmojis, autoPlayGif = false) => { } else { // matched to unicode emoji const { filename, shortCode } = unicodeMapping[match]; const title = shortCode ? `:${shortCode}:` : ''; - const src = joinPublicPath(`packs/emoji/${filename}.svg`); - replacement = `${match}`; + replacement = document.createElement('img'); + replacement.setAttribute('draggable', false); + replacement.setAttribute('class', 'emojione'); + replacement.setAttribute('alt', match); + replacement.setAttribute('title', title); + replacement.setAttribute('src', joinPublicPath(`packs/emoji/${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) { @@ -58,7 +67,7 @@ const emojifyTextNode = (node, customEmojis, autoPlayGif = false) => { fragment.append(document.createTextNode(str.slice(0, i))); if (replacement) { - fragment.append(domParser.parseFromString(replacement, 'text/html').documentElement.getElementsByTagName('img')[0]); + fragment.append(replacement); } node.textContent = str.slice(0, i); str = str.slice(rend); diff --git a/app/styles/emoji-picker.scss b/app/styles/emoji-picker.scss index 90830d576..7408e85ab 100644 --- a/app/styles/emoji-picker.scss +++ b/app/styles/emoji-picker.scss @@ -9,7 +9,7 @@ } .emoji-mart .emoji-mart-emoji { - @apply p-1.5; + @apply p-1.5 align-middle; } .emoji-mart-bar {