Replace emoji-mart with emoji-picker-element (#620)

* Change dependencies

* Use emoji-picker-element instead of emoji-mart

* Optimize emoji-picker appeareance and data-source

* Add twemoji font to emoji-picker

* Add missing useEffect dependency

* Add emoji-shortcode map

* Include emoji-data into bundle and remove dynamic fetch

* Rename shortcode-map

* Fix emoji-picker being hidden on second attempt to open it

* Add support for skin-tone short-codes

* Remove whitespace line

* Don't reinitialize the picker on every open

* Fixed linting and test issues

* Update CHANGELOG entry
This commit is contained in:
Erik Michelson 2020-10-10 23:12:17 +02:00 committed by GitHub
parent fe40d7247d
commit 5574f09ef5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 203 additions and 167 deletions

View file

@ -35,7 +35,7 @@
- HedgeDoc instances can now be branded either with a '@ <custom string>' or '@ <custom logo>' after the HedgeDoc logo and text - HedgeDoc instances can now be branded either with a '@ <custom string>' or '@ <custom logo>' after the HedgeDoc logo and text
- Images will be loaded via proxy if an image proxy is configured in the backend - Images will be loaded via proxy if an image proxy is configured in the backend
- Asciinema videos may now be embedded by pasting the URL of one video into a single line - Asciinema videos may now be embedded by pasting the URL of one video into a single line
- The toolbar includes an EmojiPicker - The toolbar includes an emoji and fork-awesome icon picker.
- Collapsable blocks can be added via a toolbar button or via autocompletion of "<details" - Collapsable blocks can be added via a toolbar button or via autocompletion of "<details"
- Added shortcodes for [fork-awesome icons](https://forkaweso.me/Fork-Awesome/icons/) (e.g. `:fa-picture-o:`) - Added shortcodes for [fork-awesome icons](https://forkaweso.me/Fork-Awesome/icons/) (e.g. `:fa-picture-o:`)
- The code button now adds code fences even if the user selected nothing beforehand - The code button now adds code fences even if the user selected nothing beforehand

View file

@ -8,7 +8,8 @@ module.exports = {
new CopyPlugin({ new CopyPlugin({
patterns: [ patterns: [
{ from: 'node_modules/@hpcc-js/wasm/dist/graphvizlib.wasm', to: 'static/js' }, { from: 'node_modules/@hpcc-js/wasm/dist/graphvizlib.wasm', to: 'static/js' },
{ from: 'node_modules/@hpcc-js/wasm/dist/expatlib.wasm', to: 'static/js' } { from: 'node_modules/@hpcc-js/wasm/dist/expatlib.wasm', to: 'static/js' },
{ from: 'node_modules/emojibase-data/en/data.json', to: 'static/js/emoji-data.json' }
], ],
}), }),
...when(Boolean(process.env.ANALYZE), () => [ ...when(Boolean(process.env.ANALYZE), () => [

View file

@ -80,7 +80,7 @@ describe('Autocompletion', () => {
describe('normal emoji', () => { describe('normal emoji', () => {
it('via Enter', () => { it('via Enter', () => {
cy.get('.CodeMirror textarea') cy.get('.CodeMirror textarea')
.type(':book') .type(':hedg')
cy.get('.CodeMirror-hints') cy.get('.CodeMirror-hints')
.should('exist') .should('exist')
cy.get('.CodeMirror textarea') cy.get('.CodeMirror textarea')
@ -88,29 +88,29 @@ describe('Autocompletion', () => {
cy.get('.CodeMirror-hints') cy.get('.CodeMirror-hints')
.should('not.exist') .should('not.exist')
cy.get('.CodeMirror-activeline > .CodeMirror-line > span') cy.get('.CodeMirror-activeline > .CodeMirror-line > span')
.should('have.text', ':book:') .should('have.text', ':hedgehog:')
cy.get('.markdown-body') cy.get('.markdown-body')
.should('have.text', '📖') .should('have.text', '🦔')
}) })
it('via doubleclick', () => { it('via doubleclick', () => {
cy.get('.CodeMirror textarea') cy.get('.CodeMirror textarea')
.type(':book') .type(':hedg')
cy.get('.CodeMirror-hints > li') cy.get('.CodeMirror-hints > li')
.first() .first()
.dblclick() .dblclick()
cy.get('.CodeMirror-hints') cy.get('.CodeMirror-hints')
.should('not.exist') .should('not.exist')
cy.get('.CodeMirror-activeline > .CodeMirror-line > span') cy.get('.CodeMirror-activeline > .CodeMirror-line > span')
.should('have.text', ':book:') .should('have.text', ':hedgehog:')
cy.get('.markdown-body') cy.get('.markdown-body')
.should('have.text', '📖') .should('have.text', '🦔')
}) })
}) })
describe('fork-awesome-icon', () => { describe('fork-awesome-icon', () => {
it('via Enter', () => { it('via Enter', () => {
cy.get('.CodeMirror textarea') cy.get('.CodeMirror textarea')
.type(':facebook') .type(':fa-face')
cy.get('.CodeMirror-hints') cy.get('.CodeMirror-hints')
.should('exist') .should('exist')
cy.get('.CodeMirror textarea') cy.get('.CodeMirror textarea')
@ -124,7 +124,7 @@ describe('Autocompletion', () => {
}) })
it('via doubleclick', () => { it('via doubleclick', () => {
cy.get('.CodeMirror textarea') cy.get('.CodeMirror textarea')
.type(':facebook') .type(':fa-face')
cy.get('.CodeMirror-hints > li') cy.get('.CodeMirror-hints > li')
.first() .first()
.dblclick() .dblclick()

View file

@ -275,28 +275,14 @@ describe('Toolbar', () => {
.should('have.text', '> []') .should('have.text', '> []')
}) })
describe('emoji', () => { describe('emoji-picker', () => {
it('picker is show when clicked', () => { it('show when clicked', () => {
cy.get('.emoji-mart') cy.get('emoji-picker')
.should('not.exist') .should('not.be.visible')
cy.get('.fa-smile-o') cy.get('.fa-smile-o')
.click() .click()
cy.get('.emoji-mart') cy.get('emoji-picker')
.should('exist') .should('be.visible')
})
it('picker is show when clicked', () => {
cy.get('.fa-smile-o')
.click()
cy.get('.emoji-mart')
.should('exist')
cy.get('.emoji-mart-emoji-native')
.first()
.click()
cy.get('.markdown-body')
.should('have.text', '👍')
cy.get('.CodeMirror-activeline > .CodeMirror-line > span ')
.should('have.text', ':+1:')
}) })
}) })

View file

@ -13,7 +13,6 @@
"@types/d3-graphviz": "2.6.6", "@types/d3-graphviz": "2.6.6",
"@types/diff": "4.0.2", "@types/diff": "4.0.2",
"@types/domhandler": "2.4.1", "@types/domhandler": "2.4.1",
"@types/emoji-mart": "3.0.2",
"@types/highlight.js": "9.12.4", "@types/highlight.js": "9.12.4",
"@types/jest": "26.0.14", "@types/jest": "26.0.14",
"@types/js-yaml": "3.12.5", "@types/js-yaml": "3.12.5",
@ -41,7 +40,8 @@
"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-mart": "3.0.0", "emoji-picker-element": "^1.2.1",
"emojibase-data": "5",
"eslint-config-react-app": "5.2.1", "eslint-config-react-app": "5.2.1",
"eslint-config-standard": "14.1.1", "eslint-config-standard": "14.1.1",
"eslint-plugin-flowtype": "5.2.0", "eslint-plugin-flowtype": "5.2.0",

View file

@ -1,11 +1,14 @@
import { Editor, Hint, Hints, Pos } from 'codemirror' import { Editor, Hint, Hints, Pos } from 'codemirror'
import { Data, EmojiData, NimbleEmojiIndex } from 'emoji-mart' import Database from 'emoji-picker-element/database'
import data from 'emoji-mart/data/twitter.json' import { Emoji, EmojiClickEventDetail, NativeEmoji } from 'emoji-picker-element/shared'
import { customEmojis } from '../tool-bar/emoji-picker/emoji-picker' import { customEmojis } 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 NimbleEmojiIndex(data as unknown as Data) const emojiIndex = new Database({
customEmoji: customEmojis,
dataSource: '/static/js/emoji-data.json'
})
const emojiWordRegex = /^:([\w-_+]*)$/ const emojiWordRegex = /^:([\w-_+]*)$/
const generateEmojiHints = (editor: Editor): Promise< Hints| null > => { const generateEmojiHints = (editor: Editor): Promise< Hints| null > => {
@ -17,23 +20,25 @@ const generateEmojiHints = (editor: Editor): Promise< Hints| null > => {
return return
} }
const term = searchResult[1] const term = searchResult[1]
let search: EmojiData[] | null = emojiIndex.search(term, { let suggestionList: Emoji[]
emojisToShowFilter: () => true, emojiIndex.getEmojiBySearchQuery(term)
maxResults: 7, .then(async (result) => {
include: [], suggestionList = result
exclude: [], if (result.length === 0) {
custom: customEmojis as EmojiData[] suggestionList = await emojiIndex.getTopFavoriteEmoji(7)
})
if (search === null) {
// set search to the first seven emojis in data
search = Object.values(emojiIndex.emojis).slice(0, 7)
} }
const cursor = editor.getCursor() const cursor = editor.getCursor()
if (!search) { const skinTone = await emojiIndex.getPreferredSkinTone()
resolve(null) const emojiEventDetails: EmojiClickEventDetail[] = suggestionList.map((emoji) => {
} else { return {
emoji,
skinTone: skinTone,
unicode: ((emoji as NativeEmoji).unicode ? (emoji as NativeEmoji).unicode : undefined),
name: emoji.name
}
})
resolve({ resolve({
list: search.map((emojiData): Hint => ({ list: emojiEventDetails.map((emojiData): Hint => ({
text: getEmojiShortCode(emojiData), text: getEmojiShortCode(emojiData),
render: (parent: HTMLLIElement) => { render: (parent: HTMLLIElement) => {
const wrapper = document.createElement('div') const wrapper = document.createElement('div')
@ -44,7 +49,11 @@ const generateEmojiHints = (editor: Editor): Promise< Hints| null > => {
from: Pos(cursor.line, searchTerm.start), from: Pos(cursor.line, searchTerm.start),
to: Pos(cursor.line, searchTerm.end) to: Pos(cursor.line, searchTerm.end)
}) })
} })
.catch(error => {
console.error(error)
resolve(null)
})
}) })
} }

View file

@ -16,10 +16,13 @@ export const EmojiPickerButton: React.FC<EmojiPickerButtonProps> = ({ editor })
return ( return (
<Fragment> <Fragment>
<EmojiPicker show={showEmojiPicker} onEmojiSelected={(emoji) => { <EmojiPicker
show={showEmojiPicker}
onEmojiSelected={(emoji) => {
setShowEmojiPicker(false) setShowEmojiPicker(false)
addEmoji(emoji, editor) addEmoji(emoji, editor)
}} onDismiss={() => setShowEmojiPicker(false)}/> }}
onDismiss={() => setShowEmojiPicker(false)}/>
<Button variant='light' onClick={() => setShowEmojiPicker(old => !old)} title={t('editor.editorToolbar.emoji')}> <Button variant='light' onClick={() => setShowEmojiPicker(old => !old)} title={t('editor.editorToolbar.emoji')}>
<ForkAwesomeIcon icon="smile-o"/> <ForkAwesomeIcon icon="smile-o"/>
</Button> </Button>

View file

@ -1,10 +1,3 @@
@import '../../../../../../node_modules/emoji-mart/css/emoji-mart'; .emoji-picker-container {
z-index: 1111;
.emoji-mart {
position: absolute;
z-index: 10000;
}
.emoji-mart-emoji-native {
font-family: "twemoji", monospace;
} }

View file

@ -1,47 +1,81 @@
import { CustomEmoji, Data, EmojiData, NimblePicker } from 'emoji-mart' import { Picker } from 'emoji-picker-element'
import emojiData from 'emoji-mart/data/twitter.json' import { CustomEmoji, EmojiClickEvent, EmojiClickEventDetail } from 'emoji-picker-element/shared'
import React, { useRef } from 'react' import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import { useSelector } from 'react-redux'
import { useClickAway } from 'react-use' import { useClickAway } from 'react-use'
import { ShowIf } from '../../../../common/show-if/show-if' import { ApplicationState } from '../../../../../redux'
import './emoji-picker.scss' import './emoji-picker.scss'
import forkawesomeIcon from './forkawesome.png' import forkawesomeIcon from './forkawesome.png'
import { ForkAwesomeIcons } from './icon-names' import { ForkAwesomeIcons } from './icon-names'
export interface EmojiPickerProps { export interface EmojiPickerProps {
show: boolean show: boolean
onEmojiSelected: (emoji: EmojiData) => void onEmojiSelected: (emoji: EmojiClickEventDetail) => void
onDismiss: () => void onDismiss: () => void
} }
export const customEmojis: CustomEmoji[] = Object.keys(ForkAwesomeIcons).map((name) => ({ export const customEmojis: CustomEmoji[] = Object.keys(ForkAwesomeIcons).map((name) => ({
name: `fa-${name}`, name: `fa-${name}`,
short_names: [`fa-${name.toLowerCase()}`], shortcodes: [`fa-${name.toLowerCase()}`],
text: '', url: forkawesomeIcon,
emoticons: [], category: 'ForkAwesome'
keywords: ['fork awesome'],
imageUrl: forkawesomeIcon,
customCategory: 'ForkAwesome'
})) }))
export const EmojiPicker: React.FC<EmojiPickerProps> = ({ show, onEmojiSelected, onDismiss }) => { export const EmojiPicker: React.FC<EmojiPickerProps> = ({ show, onEmojiSelected, onDismiss }) => {
const pickerRef = useRef(null) const darkModeEnabled = useSelector((state: ApplicationState) => state.darkMode.darkMode)
const pickerContainerRef = useRef<HTMLDivElement>(null)
const firstOpened = useRef(false)
useClickAway(pickerRef, () => { 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" !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) {
return
}
const picker = pickerDomList[0]
picker.setAttribute('class', darkModeEnabled ? 'dark' : 'light')
if (darkModeEnabled) {
picker.removeAttribute('style')
} else {
picker.setAttribute('style', '--background: #f8f9fa')
}
}, [darkModeEnabled, pickerContainerRef, firstOpened])
// noinspection CheckTagEmptyBody
return ( return (
<ShowIf condition={show}> <div className={`position-absolute emoji-picker-container ${!show ? 'd-none' : ''}`} ref={pickerContainerRef}></div>
<div className={'position-relative'} ref={pickerRef}>
<NimblePicker
data={emojiData as unknown as Data}
native={true}
onSelect={onEmojiSelected}
theme={'auto'}
title=''
custom={customEmojis}
/>
</div>
</ShowIf>
) )
} }

View file

@ -1,15 +1,20 @@
import { BaseEmoji, CustomEmoji, EmojiData } from 'emoji-mart' import { EmojiClickEventDetail, NativeEmoji } from 'emoji-picker-element/shared'
export const getEmojiIcon = (emoji: EmojiData):string => { export const getEmojiIcon = (emoji: EmojiClickEventDetail): string => {
if ((emoji as BaseEmoji).native) { if (emoji.unicode) {
return (emoji as BaseEmoji).native return emoji.unicode
} else if ((emoji as CustomEmoji).imageUrl) { }
if (emoji.name) {
// noinspection CheckTagEmptyBody // noinspection CheckTagEmptyBody
return `<i class="fa ${(emoji as CustomEmoji).name}"></i>` return `<i class="fa ${emoji.name}"></i>`
} }
return '' return ''
} }
export const getEmojiShortCode = (emoji: EmojiData):string => { export const getEmojiShortCode = (emoji: EmojiClickEventDetail): string => {
return (emoji as BaseEmoji).colons let skinToneModifier = ''
if ((emoji.emoji as NativeEmoji).skins && emoji.skinTone !== 0) {
skinToneModifier = `:skin-tone-${emoji.skinTone as number}:`
}
return `:${emoji.emoji.shortcodes[0]}:${skinToneModifier}`
} }

View file

@ -1,5 +1,5 @@
import { Editor, Position, Range } from 'codemirror' import CodeMirror, { Editor, Position, Range } from 'codemirror'
import { EmojiData } from 'emoji-mart' import { EmojiClickEventDetail } from 'emoji-picker-element/shared'
import { Mock } from 'ts-mockery' import { Mock } from 'ts-mockery'
import { import {
addCodeFences, addCodeFences,
@ -1762,8 +1762,23 @@ describe('test addTable', () => {
describe('test addEmoji with native emoji', () => { describe('test addEmoji with native emoji', () => {
const { cursor, firstLine, multiline, multilineOffset } = buildRanges() const { cursor, firstLine, multiline, multilineOffset } = buildRanges()
const textFirstLine = testContent.split('\n')[0] const textFirstLine = testContent.split('\n')[0]
const emoji = Mock.of<EmojiData>({ const emoji = Mock.of<EmojiClickEventDetail>({
colons: ':+1:' emoji: {
annotation: 'input numbers',
group: 8,
order: 3809,
shortcodes: [
'1234'
],
tags: [
'1234',
'input',
'numbers'
],
unicode: '🔢',
version: 0.6
},
unicode: '🔢'
}) })
it('just cursor', done => { it('just cursor', done => {
Mock.extend(editor).with({ Mock.extend(editor).with({
@ -1778,7 +1793,7 @@ describe('test addEmoji with native emoji', () => {
), ),
getLine: (): string => (textFirstLine), getLine: (): string => (textFirstLine),
replaceRange: (replacement: string | string[]) => { replaceRange: (replacement: string | string[]) => {
expect(replacement).toEqual(':+1:') expect(replacement).toEqual(':1234:')
done() done()
} }
}) })
@ -1800,7 +1815,7 @@ describe('test addEmoji with native emoji', () => {
replaceRange: (replacement: string | string[], from: CodeMirror.Position, to?: CodeMirror.Position) => { replaceRange: (replacement: string | string[], from: CodeMirror.Position, to?: CodeMirror.Position) => {
expect(from).toEqual(firstLine.from) expect(from).toEqual(firstLine.from)
expect(to).toEqual(firstLine.to) expect(to).toEqual(firstLine.to)
expect(replacement).toEqual(':+1:') expect(replacement).toEqual(':1234:')
done() done()
} }
}) })
@ -1822,7 +1837,7 @@ describe('test addEmoji with native emoji', () => {
replaceRange: (replacement: string | string[], from: CodeMirror.Position, to?: CodeMirror.Position) => { replaceRange: (replacement: string | string[], from: CodeMirror.Position, to?: CodeMirror.Position) => {
expect(from).toEqual(multiline.from) expect(from).toEqual(multiline.from)
expect(to).toEqual(multiline.to) expect(to).toEqual(multiline.to)
expect(replacement).toEqual(':+1:') expect(replacement).toEqual(':1234:')
done() done()
} }
}) })
@ -1844,7 +1859,7 @@ describe('test addEmoji with native emoji', () => {
replaceRange: (replacement: string | string[], from: CodeMirror.Position, to?: CodeMirror.Position) => { replaceRange: (replacement: string | string[], from: CodeMirror.Position, to?: CodeMirror.Position) => {
expect(from).toEqual(multilineOffset.from) expect(from).toEqual(multilineOffset.from)
expect(to).toEqual(multilineOffset.to) expect(to).toEqual(multilineOffset.to)
expect(replacement).toEqual(':+1:') expect(replacement).toEqual(':1234:')
done() done()
} }
}) })
@ -1856,10 +1871,16 @@ describe('test addEmoji with native emoji', () => {
const { cursor, firstLine, multiline, multilineOffset } = buildRanges() const { cursor, firstLine, multiline, multilineOffset } = buildRanges()
const textFirstLine = testContent.split('\n')[0] const textFirstLine = testContent.split('\n')[0]
const forkAwesomeIcon = ':fa-star:' const forkAwesomeIcon = ':fa-star:'
const emoji = Mock.of<EmojiData>({ const emoji = Mock.of<EmojiClickEventDetail>({
name: 'star', emoji: {
colons: ':fa-star:', name: 'fa-star',
imageUrl: '/img/forkawesome.png' shortcodes: [
'fa-star'
],
url: '/img/forkawesome.png'
},
skinTone: 0,
name: 'fa-star'
}) })
it('just cursor', done => { it('just cursor', done => {
Mock.extend(editor).with({ Mock.extend(editor).with({

View file

@ -1,5 +1,5 @@
import { Editor } from 'codemirror' import { Editor } from 'codemirror'
import { EmojiData } from 'emoji-mart' import { EmojiClickEventDetail } from 'emoji-picker-element/shared'
import { getEmojiShortCode } from './emojiUtils' import { getEmojiShortCode } from './emojiUtils'
export const makeSelectionBold = (editor: Editor): void => wrapTextWith(editor, '**') export const makeSelectionBold = (editor: Editor): void => wrapTextWith(editor, '**')
@ -25,7 +25,7 @@ export const addCollapsableBlock = (editor: Editor): void => changeLines(editor,
export const addComment = (editor: Editor): void => changeLines(editor, line => `${line}\n> []`) export const addComment = (editor: Editor): void => changeLines(editor, line => `${line}\n> []`)
export const addTable = (editor: Editor): void => changeLines(editor, line => `${line}\n| # 1 | # 2 | # 3 |\n| ---- | ---- | ---- |\n| Text | Text | Text |`) export const addTable = (editor: Editor): void => changeLines(editor, line => `${line}\n| # 1 | # 2 | # 3 |\n| ---- | ---- | ---- |\n| Text | Text | Text |`)
export const addEmoji = (emoji: EmojiData, editor: Editor): void => { export const addEmoji = (emoji: EmojiClickEventDetail, editor: Editor): void => {
insertAtCursor(editor, getEmojiShortCode(emoji)) insertAtCursor(editor, getEmojiShortCode(emoji))
} }

View file

@ -1,36 +1,40 @@
import emojiData from 'emoji-mart/data/twitter.json'
import { Data } from 'emoji-mart/dist-es/utils/data'
import { ForkAwesomeIcons } from '../../../editor/editor-pane/tool-bar/emoji-picker/icon-names' import { ForkAwesomeIcons } from '../../../editor/editor-pane/tool-bar/emoji-picker/icon-names'
import emojiData from 'emojibase-data/en/compact.json'
export const markdownItTwitterEmojis = Object.keys((emojiData as unknown as Data).emojis) interface EmojiEntry {
.reduce((reduceObject, emojiIdentifier) => { shortcodes: string[]
const emoji = (emojiData as unknown as Data).emojis[emojiIdentifier] unicode: string
const emojiCodes = emoji.unified ?? emoji.b }
if (emojiCodes) {
reduceObject[emojiIdentifier] = emojiCodes.split('-').map(char => `&#x${char};`).join('') type ShortCodeMap = { [key: string]: string }
}
const shortCodeMap = (emojiData as unknown as EmojiEntry[])
.reduce((reduceObject, emoji) => {
emoji.shortcodes.forEach(shortcode => {
reduceObject[shortcode] = emoji.unicode
})
return reduceObject return reduceObject
}, {} as { [key: string]: string }) }, {} as ShortCodeMap)
export const emojiSkinToneModifierMap = [2, 3, 4, 5, 6] const emojiSkinToneModifierMap = [1, 2, 3, 4, 5]
.reduce((reduceObject, modifierValue) => { .reduce((reduceObject, modifierValue) => {
const lightSkinCode = 127995 const lightSkinCode = 127995
const codepoint = lightSkinCode + (modifierValue - 2) const codepoint = lightSkinCode + (modifierValue - 1)
const shortcode = `skin-tone-${modifierValue}` const shortcode = `skin-tone-${modifierValue}`
reduceObject[shortcode] = `&#${codepoint};` reduceObject[shortcode] = `&#${codepoint};`
return reduceObject return reduceObject
}, {} as { [key: string]: string }) }, {} as ShortCodeMap)
export const forkAwesomeIconMap = Object.keys(ForkAwesomeIcons) const forkAwesomeIconMap = Object.keys(ForkAwesomeIcons)
.reduce((reduceObject, icon) => { .reduce((reduceObject, icon) => {
const shortcode = `fa-${icon}` const shortcode = `fa-${icon}`
// noinspection CheckTagEmptyBody // noinspection CheckTagEmptyBody
reduceObject[shortcode] = `<i class="fa fa-${icon}"></i>` reduceObject[shortcode] = `<i class="fa fa-${icon}"></i>`
return reduceObject return reduceObject
}, {} as { [key: string]: string }) }, {} as ShortCodeMap)
export const combinedEmojiData = { export const combinedEmojiData = {
...markdownItTwitterEmojis, ...shortCodeMap,
...emojiSkinToneModifierMap, ...emojiSkinToneModifierMap,
...forkAwesomeIconMap ...forkAwesomeIconMap
} }

View file

@ -1,15 +0,0 @@
import 'emoji-mart'
declare module 'emoji-mart' {
export interface SearchOption {
emojisToShowFilter: (emoji: EmojiData) => boolean
maxResults: number,
include: EmojiData[]
exclude: EmojiData[]
custom: EmojiData[]
}
export class NimbleEmojiIndex {
search (query: string, options: SearchOption): EmojiData[] | null;
}
}

View file

@ -2066,13 +2066,6 @@
dependencies: dependencies:
domhandler "^2.4.0" domhandler "^2.4.0"
"@types/emoji-mart@3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/emoji-mart/-/emoji-mart-3.0.2.tgz#5814064ce7c622069adf1583e17b3851a00802cb"
integrity sha512-Cmq8xpPK5Va+fjQE7ZaE5oykXzACBQ64CpNnYOIU7gWcR6nYTxWjMR3yPhnAMzw4yQn9R9761FpTvAyi/SH9MQ==
dependencies:
"@types/react" "*"
"@types/eslint-visitor-keys@^1.0.0": "@types/eslint-visitor-keys@^1.0.0":
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
@ -5755,13 +5748,10 @@ elliptic@^6.5.3:
minimalistic-assert "^1.0.0" minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0" minimalistic-crypto-utils "^1.0.0"
emoji-mart@3.0.0: emoji-picker-element@^1.2.1:
version "3.0.0" version "1.2.1"
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-3.0.0.tgz#eca24a04881e27752a6921e09f65a86ce8539a50" resolved "https://registry.yarnpkg.com/emoji-picker-element/-/emoji-picker-element-1.2.1.tgz#a8eb99035e07f970c16e202a6e0588dce15dda02"
integrity sha512-r5DXyzOLJttdwRYfJmPq/XL3W5tiAE/VsRnS0Hqyn27SqPA/GOYwVUSx50px/dXdJyDSnvmoPbuJ/zzhwSaU4A== integrity sha512-gk0NBg7G/S6ClfIUjRKchXLrl4o1dcvKmEamFT9GERHfCeAyi+afUeMhwVY168I65RiqjGCJkGpoTV2CVa2QNA==
dependencies:
"@babel/runtime" "^7.0.0"
prop-types "^15.6.0"
emoji-regex@^7.0.1, emoji-regex@^7.0.2: emoji-regex@^7.0.1, emoji-regex@^7.0.2:
version "7.0.3" version "7.0.3"
@ -5778,6 +5768,11 @@ emoji-regex@^9.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.0.0.tgz#48a2309cc8a1d2e9d23bc6a67c39b63032e76ea4" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.0.0.tgz#48a2309cc8a1d2e9d23bc6a67c39b63032e76ea4"
integrity sha512-6p1NII1Vm62wni/VR/cUMauVQoxmLVb9csqQlvLz+hO2gk8U2UYDfXHQSUYIBKmZwAKz867IDqG7B+u0mj+M6w== integrity sha512-6p1NII1Vm62wni/VR/cUMauVQoxmLVb9csqQlvLz+hO2gk8U2UYDfXHQSUYIBKmZwAKz867IDqG7B+u0mj+M6w==
emojibase-data@5:
version "5.1.1"
resolved "https://registry.yarnpkg.com/emojibase-data/-/emojibase-data-5.1.1.tgz#0a0d63dd07ce1376b3d27642f28cafa46f651de6"
integrity sha512-za/ma5SfogHjwUmGFnDbTvSfm8GGFvFaPS27GPti16YZSp5EPgz+UDsZCATXvJGit+oRNBbG/FtybXHKi2UQgQ==
emojis-list@^2.0.0: emojis-list@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
@ -11513,7 +11508,7 @@ prop-types-extra@^1.1.0:
react-is "^16.3.2" react-is "^16.3.2"
warning "^4.0.0" warning "^4.0.0"
prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2" version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==