mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-04-07 14:25:18 +00:00
Refactor emoji code (#720)
Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
This commit is contained in:
parent
0e058e16e2
commit
e73661d406
4 changed files with 102 additions and 93 deletions
|
@ -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": [
|
||||
|
|
|
@ -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<Emoji[]> => {
|
||||
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<Hints | null> => {
|
||||
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 = {
|
||||
|
|
|
@ -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<EmojiPickerProps> = ({ show, onEmojiSelected, onDismiss }) => {
|
||||
const darkModeEnabled = useSelector((state: ApplicationState) => state.darkMode.darkMode)
|
||||
const pickerContainerRef = useRef<HTMLDivElement>(null)
|
||||
const firstOpened = useRef(false)
|
||||
const pickerRef = useRef<Picker>()
|
||||
|
||||
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 (
|
||||
<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}/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Reference in a new issue