From e73661d40662583a84d34a2cd8929ea4e34e8bfe Mon Sep 17 00:00:00 2001 From: mrdrogdrog Date: Wed, 4 Nov 2020 21:33:52 +0100 Subject: [PATCH] Refactor emoji code (#720) Signed-off-by: Tilman Vatteroth --- package.json | 3 +- .../editor-pane/autocompletion/emoji.ts | 93 +++++++++---------- .../tool-bar/emoji-picker/emoji-picker.tsx | 91 ++++++++++-------- yarn.lock | 8 +- 4 files changed, 102 insertions(+), 93 deletions(-) diff --git a/package.json b/package.json index 4022d1c6b..f6dc951e5 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "copy-webpack-plugin": "6.2.1", "d3-graphviz": "3.1.0", "diff": "4.0.2", - "emoji-picker-element": "1.2.1", + "emoji-picker-element": "1.2.2", "emojibase-data": "5.1.1", "eslint-config-react-app": "6.0.0", "eslint-config-standard": "16.0.1", @@ -127,6 +127,7 @@ }, "rules": { "no-use-before-define": "off", + "no-debugger": "warn", "default-param-last": "off" }, "plugins": [ diff --git a/src/components/editor/editor-pane/autocompletion/emoji.ts b/src/components/editor/editor-pane/autocompletion/emoji.ts index 038161db9..8c562eb67 100644 --- a/src/components/editor/editor-pane/autocompletion/emoji.ts +++ b/src/components/editor/editor-pane/autocompletion/emoji.ts @@ -1,60 +1,57 @@ import { Editor, Hint, Hints, Pos } from 'codemirror' import Database from 'emoji-picker-element/database' import { Emoji, EmojiClickEventDetail, NativeEmoji } from 'emoji-picker-element/shared' -import { customEmojis } from '../tool-bar/emoji-picker/emoji-picker' +import { emojiPickerConfig } from '../tool-bar/emoji-picker/emoji-picker' import { getEmojiIcon, getEmojiShortCode } from '../tool-bar/utils/emojiUtils' import { findWordAtCursor, Hinter } from './index' -const emojiIndex = new Database({ - customEmoji: customEmojis, - dataSource: '/static/js/emoji-data.json' -}) +const emojiIndex = new Database(emojiPickerConfig) const emojiWordRegex = /^:([\w-_+]*)$/ -const generateEmojiHints = (editor: Editor): Promise< Hints| null > => { - return new Promise((resolve) => { - const searchTerm = findWordAtCursor(editor) - const searchResult = emojiWordRegex.exec(searchTerm.text) - if (searchResult === null) { - resolve(null) - return +const findEmojiInDatabase = async (emojiIndex: Database, term: string): Promise => { + try { + if (term === '') { + return await emojiIndex.getTopFavoriteEmoji(7) } - const term = searchResult[1] - let suggestionList: Emoji[] - emojiIndex.getEmojiBySearchQuery(term) - .then(async (result) => { - suggestionList = result - if (result.length === 0) { - suggestionList = await emojiIndex.getTopFavoriteEmoji(7) - } - const cursor = editor.getCursor() - const skinTone = await emojiIndex.getPreferredSkinTone() - const emojiEventDetails: EmojiClickEventDetail[] = suggestionList.map((emoji) => { - return { - emoji, - skinTone: skinTone, - unicode: ((emoji as NativeEmoji).unicode ? (emoji as NativeEmoji).unicode : undefined), - name: emoji.name - } - }) - resolve({ - list: emojiEventDetails.map((emojiData): Hint => ({ - text: getEmojiShortCode(emojiData), - render: (parent: HTMLLIElement) => { - const wrapper = document.createElement('div') - wrapper.innerHTML = `${getEmojiIcon(emojiData)} ${getEmojiShortCode(emojiData)}` - parent.appendChild(wrapper) - } - })), - from: Pos(cursor.line, searchTerm.start), - to: Pos(cursor.line, searchTerm.end) - }) - }) - .catch(error => { - console.error(error) - resolve(null) - }) - }) + const queryResult = await emojiIndex.getEmojiBySearchQuery(term) + if (queryResult.length === 0) { + return await emojiIndex.getTopFavoriteEmoji(7) + } else { + return queryResult + } + } catch (error) { + console.error(error) + return [] + } +} + +const generateEmojiHints = async (editor: Editor): Promise => { + const searchTerm = findWordAtCursor(editor) + const searchResult = emojiWordRegex.exec(searchTerm.text) + if (searchResult === null) { + return null + } + const suggestionList: Emoji[] = await findEmojiInDatabase(emojiIndex, searchResult[1]) + const cursor = editor.getCursor() + const skinTone = await emojiIndex.getPreferredSkinTone() + const emojiEventDetails: EmojiClickEventDetail[] = suggestionList.map((emoji) => ({ + emoji, + skinTone: skinTone, + unicode: ((emoji as NativeEmoji).unicode ? (emoji as NativeEmoji).unicode : undefined), + name: emoji.name + })) + return { + list: emojiEventDetails.map((emojiData): Hint => ({ + text: getEmojiShortCode(emojiData), + render: (parent: HTMLLIElement) => { + const wrapper = document.createElement('div') + wrapper.innerHTML = `${getEmojiIcon(emojiData)} ${getEmojiShortCode(emojiData)}` + parent.appendChild(wrapper) + } + })), + from: Pos(cursor.line, searchTerm.start), + to: Pos(cursor.line, searchTerm.end) + } } export const EmojiHinter: Hinter = { diff --git a/src/components/editor/editor-pane/tool-bar/emoji-picker/emoji-picker.tsx b/src/components/editor/editor-pane/tool-bar/emoji-picker/emoji-picker.tsx index dc5d17918..5b2dcdce1 100644 --- a/src/components/editor/editor-pane/tool-bar/emoji-picker/emoji-picker.tsx +++ b/src/components/editor/editor-pane/tool-bar/emoji-picker/emoji-picker.tsx @@ -1,6 +1,6 @@ import { Picker } from 'emoji-picker-element' import { CustomEmoji, EmojiClickEvent, EmojiClickEventDetail } from 'emoji-picker-element/shared' -import React, { useCallback, useEffect, useMemo, useRef } from 'react' +import React, { useEffect, useRef } from 'react' import { useSelector } from 'react-redux' import { useClickAway } from 'react-use' import { ApplicationState } from '../../../../../redux' @@ -21,61 +21,72 @@ export const customEmojis: CustomEmoji[] = Object.keys(ForkAwesomeIcons).map((na category: 'ForkAwesome' })) +export const EMOJI_DATA_PATH = '/static/js/emoji-data.json' + +export const emojiPickerConfig = { + customEmoji: customEmojis, + dataSource: EMOJI_DATA_PATH +} + +const twemojiStyle = (): HTMLStyleElement => { + const style = document.createElement('style') + style.textContent = 'section.picker { --font-family: "Twemoji Mozilla" !important; }' + return style +} + export const EmojiPicker: React.FC = ({ show, onEmojiSelected, onDismiss }) => { const darkModeEnabled = useSelector((state: ApplicationState) => state.darkMode.darkMode) const pickerContainerRef = useRef(null) - const firstOpened = useRef(false) + const pickerRef = useRef() useClickAway(pickerContainerRef, () => { onDismiss() }) - const emojiClickListener = useCallback((event) => { - onEmojiSelected((event as EmojiClickEvent).detail) - }, [onEmojiSelected]) - - const twemojiStyle = useMemo(() => { - const style = document.createElement('style') - style.textContent = 'section.picker { --font-family: "Twemoji Mozilla" !important; }' - return style - }, []) - - useEffect(() => { - if (!pickerContainerRef.current || firstOpened.current) { - return - } - const picker = new Picker({ - customEmoji: customEmojis, - dataSource: '/static/js/emoji-data.json' - }) - const container = pickerContainerRef.current - picker.addEventListener('emoji-click', emojiClickListener) - if (picker.shadowRoot) { - picker.shadowRoot.appendChild(twemojiStyle) - } - container.appendChild(picker) - firstOpened.current = true - }, [pickerContainerRef, emojiClickListener, darkModeEnabled, twemojiStyle]) - useEffect(() => { if (!pickerContainerRef.current) { return } - const pickerDomList = pickerContainerRef.current.getElementsByTagName('emoji-picker') - if (pickerDomList.length === 0) { + const picker = new Picker(emojiPickerConfig) + if (picker.shadowRoot) { + picker.shadowRoot.appendChild(twemojiStyle()) + } + pickerContainerRef.current.appendChild(picker) + + pickerRef.current = picker + return () => { + picker.remove() + pickerRef.current = undefined + } + }, []) + + useEffect(() => { + if (!pickerRef.current) { return } - const picker = pickerDomList[0] - picker.setAttribute('class', darkModeEnabled ? 'dark' : 'light') - if (darkModeEnabled) { - picker.removeAttribute('style') - } else { - picker.setAttribute('style', '--background: #f8f9fa') + const emojiClick = (event: EmojiClickEvent): void => { + onEmojiSelected(event.detail) } - }, [darkModeEnabled, pickerContainerRef, firstOpened]) + const picker = pickerRef.current + picker.addEventListener('emoji-click', emojiClick, true) + return () => { + picker.removeEventListener('emoji-click', emojiClick, true) + } + }, [onEmojiSelected]) + + useEffect(() => { + if (!pickerRef.current) { + return + } + pickerRef.current.setAttribute('class', darkModeEnabled ? 'dark' : 'light') + if (darkModeEnabled) { + pickerRef.current.removeAttribute('style') + } else { + pickerRef.current.setAttribute('style', '--background: #f8f9fa') + } + }, [darkModeEnabled]) - // noinspection CheckTagEmptyBody return ( -
+
) } diff --git a/yarn.lock b/yarn.lock index 1991f9a31..48a72bb26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5780,10 +5780,10 @@ emittery@^0.7.1: resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== -emoji-picker-element@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/emoji-picker-element/-/emoji-picker-element-1.2.1.tgz#a8eb99035e07f970c16e202a6e0588dce15dda02" - integrity sha512-gk0NBg7G/S6ClfIUjRKchXLrl4o1dcvKmEamFT9GERHfCeAyi+afUeMhwVY168I65RiqjGCJkGpoTV2CVa2QNA== +emoji-picker-element@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/emoji-picker-element/-/emoji-picker-element-1.2.2.tgz#821b2dcdb89183a098dcaa6df10f3dae798b3acb" + integrity sha512-iQDMY+7lGYKQRL5tgC51PKWqf1V5uGNDQgP+tR1ga//4BUP+HVs/A70oo53Q4iuzkd1IVaUbJBL3YY/6ky9KQA== emoji-regex@^7.0.1: version "7.0.3"