mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-23 11:37:02 -04: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",
|
"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": [
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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}/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue