Refactor emoji code (#720)

Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
This commit is contained in:
mrdrogdrog 2020-11-04 21:33:52 +01:00 committed by GitHub
parent 0e058e16e2
commit e73661d406
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 102 additions and 93 deletions

View file

@ -40,7 +40,7 @@
"copy-webpack-plugin": "6.2.1", "copy-webpack-plugin": "6.2.1",
"d3-graphviz": "3.1.0", "d3-graphviz": "3.1.0",
"diff": "4.0.2", "diff": "4.0.2",
"emoji-picker-element": "1.2.1", "emoji-picker-element": "1.2.2",
"emojibase-data": "5.1.1", "emojibase-data": "5.1.1",
"eslint-config-react-app": "6.0.0", "eslint-config-react-app": "6.0.0",
"eslint-config-standard": "16.0.1", "eslint-config-standard": "16.0.1",
@ -127,6 +127,7 @@
}, },
"rules": { "rules": {
"no-use-before-define": "off", "no-use-before-define": "off",
"no-debugger": "warn",
"default-param-last": "off" "default-param-last": "off"
}, },
"plugins": [ "plugins": [

View file

@ -1,60 +1,57 @@
import { Editor, Hint, Hints, Pos } from 'codemirror' import { Editor, Hint, Hints, Pos } from 'codemirror'
import Database from 'emoji-picker-element/database' import Database from 'emoji-picker-element/database'
import { Emoji, EmojiClickEventDetail, NativeEmoji } from 'emoji-picker-element/shared' 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 { getEmojiIcon, getEmojiShortCode } from '../tool-bar/utils/emojiUtils'
import { findWordAtCursor, Hinter } from './index' import { findWordAtCursor, Hinter } from './index'
const emojiIndex = new Database({ const emojiIndex = new Database(emojiPickerConfig)
customEmoji: customEmojis,
dataSource: '/static/js/emoji-data.json'
})
const emojiWordRegex = /^:([\w-_+]*)$/ const emojiWordRegex = /^:([\w-_+]*)$/
const generateEmojiHints = (editor: Editor): Promise< Hints| null > => { const findEmojiInDatabase = async (emojiIndex: Database, term: string): Promise<Emoji[]> => {
return new Promise((resolve) => { try {
const searchTerm = findWordAtCursor(editor) if (term === '') {
const searchResult = emojiWordRegex.exec(searchTerm.text) return await emojiIndex.getTopFavoriteEmoji(7)
if (searchResult === null) {
resolve(null)
return
} }
const term = searchResult[1] const queryResult = await emojiIndex.getEmojiBySearchQuery(term)
let suggestionList: Emoji[] if (queryResult.length === 0) {
emojiIndex.getEmojiBySearchQuery(term) return await emojiIndex.getTopFavoriteEmoji(7)
.then(async (result) => { } else {
suggestionList = result return queryResult
if (result.length === 0) { }
suggestionList = await emojiIndex.getTopFavoriteEmoji(7) } catch (error) {
} console.error(error)
const cursor = editor.getCursor() return []
const skinTone = await emojiIndex.getPreferredSkinTone() }
const emojiEventDetails: EmojiClickEventDetail[] = suggestionList.map((emoji) => { }
return {
emoji, const generateEmojiHints = async (editor: Editor): Promise<Hints | null> => {
skinTone: skinTone, const searchTerm = findWordAtCursor(editor)
unicode: ((emoji as NativeEmoji).unicode ? (emoji as NativeEmoji).unicode : undefined), const searchResult = emojiWordRegex.exec(searchTerm.text)
name: emoji.name if (searchResult === null) {
} return null
}) }
resolve({ const suggestionList: Emoji[] = await findEmojiInDatabase(emojiIndex, searchResult[1])
list: emojiEventDetails.map((emojiData): Hint => ({ const cursor = editor.getCursor()
text: getEmojiShortCode(emojiData), const skinTone = await emojiIndex.getPreferredSkinTone()
render: (parent: HTMLLIElement) => { const emojiEventDetails: EmojiClickEventDetail[] = suggestionList.map((emoji) => ({
const wrapper = document.createElement('div') emoji,
wrapper.innerHTML = `${getEmojiIcon(emojiData)} ${getEmojiShortCode(emojiData)}` skinTone: skinTone,
parent.appendChild(wrapper) unicode: ((emoji as NativeEmoji).unicode ? (emoji as NativeEmoji).unicode : undefined),
} name: emoji.name
})), }))
from: Pos(cursor.line, searchTerm.start), return {
to: Pos(cursor.line, searchTerm.end) list: emojiEventDetails.map((emojiData): Hint => ({
}) text: getEmojiShortCode(emojiData),
}) render: (parent: HTMLLIElement) => {
.catch(error => { const wrapper = document.createElement('div')
console.error(error) wrapper.innerHTML = `${getEmojiIcon(emojiData)} ${getEmojiShortCode(emojiData)}`
resolve(null) parent.appendChild(wrapper)
}) }
}) })),
from: Pos(cursor.line, searchTerm.start),
to: Pos(cursor.line, searchTerm.end)
}
} }
export const EmojiHinter: Hinter = { export const EmojiHinter: Hinter = {

View file

@ -1,6 +1,6 @@
import { Picker } from 'emoji-picker-element' import { Picker } from 'emoji-picker-element'
import { CustomEmoji, EmojiClickEvent, EmojiClickEventDetail } from 'emoji-picker-element/shared' 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 { useSelector } from 'react-redux'
import { useClickAway } from 'react-use' import { useClickAway } from 'react-use'
import { ApplicationState } from '../../../../../redux' import { ApplicationState } from '../../../../../redux'
@ -21,61 +21,72 @@ export const customEmojis: CustomEmoji[] = Object.keys(ForkAwesomeIcons).map((na
category: 'ForkAwesome' 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<EmojiPickerProps> = ({ show, onEmojiSelected, onDismiss }) => { export const EmojiPicker: React.FC<EmojiPickerProps> = ({ show, onEmojiSelected, onDismiss }) => {
const darkModeEnabled = useSelector((state: ApplicationState) => state.darkMode.darkMode) const darkModeEnabled = useSelector((state: ApplicationState) => state.darkMode.darkMode)
const pickerContainerRef = useRef<HTMLDivElement>(null) const pickerContainerRef = useRef<HTMLDivElement>(null)
const firstOpened = useRef(false) const pickerRef = useRef<Picker>()
useClickAway(pickerContainerRef, () => { useClickAway(pickerContainerRef, () => {
onDismiss() 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(() => { useEffect(() => {
if (!pickerContainerRef.current) { if (!pickerContainerRef.current) {
return return
} }
const pickerDomList = pickerContainerRef.current.getElementsByTagName('emoji-picker') const picker = new Picker(emojiPickerConfig)
if (pickerDomList.length === 0) { 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 return
} }
const picker = pickerDomList[0] const emojiClick = (event: EmojiClickEvent): void => {
picker.setAttribute('class', darkModeEnabled ? 'dark' : 'light') onEmojiSelected(event.detail)
if (darkModeEnabled) {
picker.removeAttribute('style')
} else {
picker.setAttribute('style', '--background: #f8f9fa')
} }
}, [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 ( return (
<div className={`position-absolute emoji-picker-container ${!show ? 'd-none' : ''}`} ref={pickerContainerRef}></div> <div className={`position-absolute emoji-picker-container ${!show ? 'd-none' : ''}`} ref={pickerContainerRef}/>
) )
} }

View file

@ -5780,10 +5780,10 @@ emittery@^0.7.1:
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82"
integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==
emoji-picker-element@1.2.1: emoji-picker-element@1.2.2:
version "1.2.1" version "1.2.2"
resolved "https://registry.yarnpkg.com/emoji-picker-element/-/emoji-picker-element-1.2.1.tgz#a8eb99035e07f970c16e202a6e0588dce15dda02" resolved "https://registry.yarnpkg.com/emoji-picker-element/-/emoji-picker-element-1.2.2.tgz#821b2dcdb89183a098dcaa6df10f3dae798b3acb"
integrity sha512-gk0NBg7G/S6ClfIUjRKchXLrl4o1dcvKmEamFT9GERHfCeAyi+afUeMhwVY168I65RiqjGCJkGpoTV2CVa2QNA== integrity sha512-iQDMY+7lGYKQRL5tgC51PKWqf1V5uGNDQgP+tR1ga//4BUP+HVs/A70oo53Q4iuzkd1IVaUbJBL3YY/6ky9KQA==
emoji-regex@^7.0.1: emoji-regex@^7.0.1:
version "7.0.3" version "7.0.3"