aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.babelrc2
-rw-r--r--.gitignore1
-rw-r--r--build/build.js3
-rw-r--r--build/dev-server.js3
-rw-r--r--build/update-emoji.js27
-rw-r--r--build/webpack.base.conf.js3
-rw-r--r--package.json2
-rw-r--r--src/components/emoji_input/emoji_input.js50
-rw-r--r--src/components/emoji_input/emoji_input.vue3
-rw-r--r--src/components/emoji_input/suggestor.js34
-rw-r--r--src/components/emoji_picker/emoji_picker.js302
-rw-r--r--src/components/emoji_picker/emoji_picker.scss52
-rw-r--r--src/components/emoji_picker/emoji_picker.vue52
-rw-r--r--src/components/post_status_form/post_status_form.js6
-rw-r--r--src/components/react_button/react_button.js4
-rw-r--r--src/components/settings_modal/tabs/profile_tab.js4
-rw-r--r--src/components/still-image/still-image.js27
-rw-r--r--src/components/still-image/still-image.vue5
-rw-r--r--src/i18n/en.json14
-rw-r--r--src/i18n/languages.js53
-rw-r--r--src/i18n/messages.js46
-rw-r--r--src/modules/config.js1
-rw-r--r--src/modules/instance.js149
-rw-r--r--static/emoji.json1431
-rw-r--r--yarn.lock10
25 files changed, 655 insertions, 1629 deletions
diff --git a/.babelrc b/.babelrc
index 373d2c59..4ec10416 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,5 +1,5 @@
{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-jsx"],
- "comments": false
+ "comments": true
}
diff --git a/.gitignore b/.gitignore
index 479d57c4..4df5ec83 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ test/e2e/reports
selenium-debug.log
.idea/
config/local.json
+static/emoji.json
diff --git a/build/build.js b/build/build.js
index b3c9aad4..35969eb6 100644
--- a/build/build.js
+++ b/build/build.js
@@ -18,6 +18,9 @@ console.log(
var spinner = ora('building for production...')
spinner.start()
+var updateEmoji = require('./update-emoji').updateEmoji
+updateEmoji()
+
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
rm('-rf', assetsPath)
mkdir('-p', assetsPath)
diff --git a/build/dev-server.js b/build/dev-server.js
index 854efa0b..e51ba948 100644
--- a/build/dev-server.js
+++ b/build/dev-server.js
@@ -10,6 +10,9 @@ var webpackConfig = process.env.NODE_ENV === 'testing'
? require('./webpack.prod.conf')
: require('./webpack.dev.conf')
+var updateEmoji = require('./update-emoji').updateEmoji
+updateEmoji()
+
// default port where dev server listens for incoming traffic
var port = process.env.PORT || config.dev.port
// Define HTTP proxies to your custom API backend
diff --git a/build/update-emoji.js b/build/update-emoji.js
new file mode 100644
index 00000000..9f4b4e67
--- /dev/null
+++ b/build/update-emoji.js
@@ -0,0 +1,27 @@
+
+module.exports = {
+ updateEmoji () {
+ const emojis = require('@kazvmoe-infra/unicode-emoji-json/data-by-group')
+ const fs = require('fs')
+
+ Object.keys(emojis)
+ .map(k => {
+ emojis[k].map(e => {
+ delete e.unicode_version
+ delete e.emoji_version
+ delete e.skin_tone_support_unicode_version
+ })
+ })
+
+ const res = {}
+ Object.keys(emojis)
+ .map(k => {
+ const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
+ res[groupId] = emojis[k]
+ })
+
+ console.info('Updating emojis...')
+ fs.writeFileSync('static/emoji.json', JSON.stringify(res))
+ console.info('Done.')
+ }
+}
diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
index 78b75e3f..bf946922 100644
--- a/build/webpack.base.conf.js
+++ b/build/webpack.base.conf.js
@@ -24,7 +24,8 @@ module.exports = {
output: {
path: config.build.assetsRoot,
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
- filename: '[name].js'
+ filename: '[name].js',
+ chunkFilename: '[name].js'
},
optimization: {
splitChunks: {
diff --git a/package.json b/package.json
index 50a6b55f..260df573 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
"@fortawesome/free-solid-svg-icons": "6.2.0",
"@fortawesome/vue-fontawesome": "3.0.1",
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
+ "@kazvmoe-infra/unicode-emoji-json": "^0.4.0",
"@ruffle-rs/ruffle": "0.1.0-nightly.2022.7.12",
"@vuelidate/core": "2.0.0-alpha.44",
"@vuelidate/validators": "2.0.0-alpha.31",
@@ -34,6 +35,7 @@
"escape-html": "1.0.3",
"js-cookie": "3.0.1",
"localforage": "1.10.0",
+ "lozad": "^1.16.0",
"parse-link-header": "2.0.0",
"phoenix": "1.6.2",
"punycode.js": "2.1.0",
diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
index b664d6b3..ffc0ffac 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -3,7 +3,7 @@ import EmojiPicker from '../emoji_picker/emoji_picker.vue'
import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
import { take } from 'lodash'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
-
+import { ensureFinalFallback } from '../../i18n/languages.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faSmileBeam
@@ -143,6 +143,51 @@ const EmojiInput = {
const word = Completion.wordAtPosition(this.modelValue, this.caret - 1) || {}
return word
}
+ },
+ languages () {
+ return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
+ },
+ maybeLocalizedEmojiNamesAndKeywords () {
+ return emoji => {
+ const names = [emoji.displayText]
+ const keywords = []
+
+ if (emoji.displayTextI18n) {
+ names.push(this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args))
+ }
+
+ if (emoji.annotations) {
+ this.languages.forEach(lang => {
+ names.push(emoji.annotations[lang]?.name)
+
+ keywords.push(...(emoji.annotations[lang]?.keywords || []))
+ })
+ }
+
+ return {
+ names: names.filter(k => k),
+ keywords: keywords.filter(k => k)
+ }
+ }
+ },
+ maybeLocalizedEmojiName () {
+ return emoji => {
+ if (!emoji.annotations) {
+ return emoji.displayText
+ }
+
+ if (emoji.displayTextI18n) {
+ return this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args)
+ }
+
+ for (const lang of this.languages) {
+ if (emoji.annotations[lang]?.name) {
+ return emoji.annotations[lang].name
+ }
+ }
+
+ return emoji.displayText
+ }
}
},
mounted () {
@@ -181,7 +226,7 @@ const EmojiInput = {
const firstchar = newWord.charAt(0)
this.suggestions = []
if (newWord === firstchar) return
- const matchedSuggestions = await this.suggest(newWord)
+ const matchedSuggestions = await this.suggest(newWord, this.maybeLocalizedEmojiNamesAndKeywords)
// Async: cancel if textAtCaret has changed during wait
if (this.textAtCaret !== newWord) return
if (matchedSuggestions.length <= 0) return
@@ -207,7 +252,6 @@ const EmojiInput = {
},
triggerShowPicker () {
this.showPicker = true
- this.$refs.picker.startEmojiLoad()
this.$nextTick(() => {
this.scrollIntoView()
this.focusPickerInput()
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 81b81913..43581dbf 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -19,6 +19,7 @@
v-if="enableEmojiPicker"
ref="picker"
:class="{ hide: !showPicker }"
+ :showing="showPicker"
:enable-sticker-picker="enableStickerPicker"
class="emoji-picker-panel"
@emoji="insert"
@@ -63,7 +64,7 @@
v-if="!suggestion.user"
class="displayText"
>
- {{ suggestion.displayText }}
+ {{ maybeLocalizedEmojiName(suggestion) }}
</span>
<span class="detailText">{{ suggestion.detailText }}</span>
</div>
diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js
index 0ddb4d68..adaa879e 100644
--- a/src/components/emoji_input/suggestor.js
+++ b/src/components/emoji_input/suggestor.js
@@ -2,7 +2,7 @@
* suggest - generates a suggestor function to be used by emoji-input
* data: object providing source information for specific types of suggestions:
* data.emoji - optional, an array of all emoji available i.e.
- * (state.instance.emoji + state.instance.customEmoji)
+ * (getters.standardEmojiList + state.instance.customEmoji)
* data.users - optional, an array of all known users
* updateUsersList - optional, a function to search and append to users
*
@@ -13,10 +13,10 @@
export default data => {
const emojiCurry = suggestEmoji(data.emoji)
const usersCurry = data.store && suggestUsers(data.store)
- return input => {
+ return (input, nameKeywordLocalizer) => {
const firstChar = input[0]
if (firstChar === ':' && data.emoji) {
- return emojiCurry(input)
+ return emojiCurry(input, nameKeywordLocalizer)
}
if (firstChar === '@' && usersCurry) {
return usersCurry(input)
@@ -25,34 +25,34 @@ export default data => {
}
}
-export const suggestEmoji = emojis => input => {
+export const suggestEmoji = emojis => (input, nameKeywordLocalizer) => {
const noPrefix = input.toLowerCase().substr(1)
return emojis
- .filter(({ displayText }) => displayText.toLowerCase().match(noPrefix))
- .sort((a, b) => {
- let aScore = 0
- let bScore = 0
+ .map(emoji => ({ ...emoji, ...nameKeywordLocalizer(emoji) }))
+ .filter((emoji) => (emoji.names.concat(emoji.keywords)).filter(kw => kw.toLowerCase().match(noPrefix)).length)
+ .map(k => {
+ let score = 0
// An exact match always wins
- aScore += a.displayText.toLowerCase() === noPrefix ? 200 : 0
- bScore += b.displayText.toLowerCase() === noPrefix ? 200 : 0
+ score += Math.max(...k.names.map(name => name.toLowerCase() === noPrefix ? 200 : 0), 0)
// Prioritize custom emoji a lot
- aScore += a.imageUrl ? 100 : 0
- bScore += b.imageUrl ? 100 : 0
+ score += k.imageUrl ? 100 : 0
// Prioritize prefix matches somewhat
- aScore += a.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
- bScore += b.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
+ score += Math.max(...k.names.map(kw => kw.toLowerCase().startsWith(noPrefix) ? 10 : 0), 0)
// Sort by length
- aScore -= a.displayText.length
- bScore -= b.displayText.length
+ score -= k.displayText.length
+ k.score = score
+ return k
+ })
+ .sort((a, b) => {
// Break ties alphabetically
const alphabetically = a.displayText > b.displayText ? 0.5 : -0.5
- return bScore - aScore + alphabetically
+ return b.score - a.score + alphabetically
})
}
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js
index f6920208..fafc2af1 100644
--- a/src/components/emoji_picker/emoji_picker.js
+++ b/src/components/emoji_picker/emoji_picker.js
@@ -1,33 +1,76 @@
import { defineAsyncComponent } from 'vue'
import Checkbox from '../checkbox/checkbox.vue'
+import StillImage from '../still-image/still-image.vue'
+import { ensureFinalFallback } from '../../i18n/languages.js'
+import lozad from 'lozad'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faBoxOpen,
faStickyNote,
- faSmileBeam
+ faSmileBeam,
+ faSmile,
+ faUser,
+ faPaw,
+ faIceCream,
+ faBus,
+ faBasketballBall,
+ faLightbulb,
+ faCode,
+ faFlag
} from '@fortawesome/free-solid-svg-icons'
-import { trim } from 'lodash'
+import { debounce, trim } from 'lodash'
library.add(
faBoxOpen,
faStickyNote,
- faSmileBeam
+ faSmileBeam,
+ faSmile,
+ faUser,
+ faPaw,
+ faIceCream,
+ faBus,
+ faBasketballBall,
+ faLightbulb,
+ faCode,
+ faFlag
)
-// At widest, approximately 20 emoji are visible in a row,
-// loading 3 rows, could be overkill for narrow picker
-const LOAD_EMOJI_BY = 60
+const UNICODE_EMOJI_GROUP_ICON = {
+ 'smileys-and-emotion': 'smile',
+ 'people-and-body': 'user',
+ 'animals-and-nature': 'paw',
+ 'food-and-drink': 'ice-cream',
+ 'travel-and-places': 'bus',
+ activities: 'basketball-ball',
+ objects: 'lightbulb',
+ symbols: 'code',
+ flags: 'flag'
+}
-// When to start loading new batch emoji, in pixels
-const LOAD_EMOJI_MARGIN = 64
+const maybeLocalizedKeywords = (emoji, languages, nameLocalizer) => {
+ const res = [emoji.displayText, nameLocalizer(emoji)]
+ if (emoji.annotations) {
+ languages.forEach(lang => {
+ const keywords = emoji.annotations[lang]?.keywords || []
+ const name = emoji.annotations[lang]?.name
+ res.push(...(keywords.concat([name]).filter(k => k)))
+ })
+ }
+ return res
+}
-const filterByKeyword = (list, keyword = '') => {
+const filterByKeyword = (list, keyword = '', languages, nameLocalizer) => {
if (keyword === '') return list
const keywordLowercase = keyword.toLowerCase()
const orderedEmojiList = []
for (const emoji of list) {
- const indexOfKeyword = emoji.displayText.toLowerCase().indexOf(keywordLowercase)
+ const indices = maybeLocalizedKeywords(emoji, languages, nameLocalizer)
+ .map(k => k.toLowerCase().indexOf(keywordLowercase))
+ .filter(k => k > -1)
+
+ const indexOfKeyword = indices.length ? Math.min(...indices) : -1
+
if (indexOfKeyword > -1) {
if (!Array.isArray(orderedEmojiList[indexOfKeyword])) {
orderedEmojiList[indexOfKeyword] = []
@@ -44,6 +87,10 @@ const EmojiPicker = {
required: false,
type: Boolean,
default: false
+ },
+ showing: {
+ required: true,
+ type: Boolean
}
},
data () {
@@ -53,16 +100,26 @@ const EmojiPicker = {
showingStickers: false,
groupsScrolledClass: 'scrolled-top',
keepOpen: false,
- customEmojiBufferSlice: LOAD_EMOJI_BY,
customEmojiTimeout: null,
- customEmojiLoadAllConfirmed: false
+ // Lazy-load only after the first time `showing` becomes true.
+ contentLoaded: false,
+ groupRefs: {},
+ emojiRefs: {},
+ filteredEmojiGroups: []
}
},
components: {
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
- Checkbox
+ Checkbox,
+ StillImage
},
methods: {
+ setGroupRef (name) {
+ return el => { this.groupRefs[name] = el }
+ },
+ setEmojiRef (name) {
+ return el => { this.emojiRefs[name] = el }
+ },
onStickerUploaded (e) {
this.$emit('sticker-uploaded', e)
},
@@ -77,10 +134,38 @@ const EmojiPicker = {
const target = (e && e.target) || this.$refs['emoji-groups']
this.updateScrolledClass(target)
this.scrolledGroup(target)
- this.triggerLoadMore(target)
+ },
+ scrolledGroup (target) {
+ const top = target.scrollTop + 5
+ this.$nextTick(() => {
+ this.allEmojiGroups.forEach(group => {
+ const ref = this.groupRefs['group-' + group.id]
+ if (ref && ref.offsetTop <= top) {
+ this.activeGroup = group.id
+ }
+ })
+ this.scrollHeader()
+ })
+ },
+ scrollHeader () {
+ // Scroll the active tab's header into view
+ const headerRef = this.groupRefs['group-header-' + this.activeGroup]
+ const left = headerRef.offsetLeft
+ const right = left + headerRef.offsetWidth
+ const headerCont = this.$refs.header
+ const currentScroll = headerCont.scrollLeft
+ const currentScrollRight = currentScroll + headerCont.clientWidth
+ const setScroll = s => { headerCont.scrollLeft = s }
+
+ const margin = 7 // .emoji-tabs-item: padding
+ if (left - margin < currentScroll) {
+ setScroll(left - margin)
+ } else if (right + margin > currentScrollRight) {
+ setScroll(right + margin - headerCont.clientWidth)
+ }
},
highlight (key) {
- const ref = this.$refs['group-' + key]
+ const ref = this.groupRefs['group-' + key]
const top = ref.offsetTop
this.setShowStickers(false)
this.activeGroup = key
@@ -97,73 +182,90 @@ const EmojiPicker = {
this.groupsScrolledClass = 'scrolled-middle'
}
},
- triggerLoadMore (target) {
- const ref = this.$refs['group-end-custom']
- if (!ref) return
- const bottom = ref.offsetTop + ref.offsetHeight
-
- const scrollerBottom = target.scrollTop + target.clientHeight
- const scrollerTop = target.scrollTop
- const scrollerMax = target.scrollHeight
-
- // Loads more emoji when they come into view
- const approachingBottom = bottom - scrollerBottom < LOAD_EMOJI_MARGIN
- // Always load when at the very top in case there's no scroll space yet
- const atTop = scrollerTop < 5
- // Don't load when looking at unicode category or at the very bottom
- const bottomAboveViewport = bottom < scrollerTop || scrollerBottom === scrollerMax
- if (!bottomAboveViewport && (approachingBottom || atTop)) {
- this.loadEmoji()
- }
+ toggleStickers () {
+ this.showingStickers = !this.showingStickers
},
- scrolledGroup (target) {
- const top = target.scrollTop + 5
+ setShowStickers (value) {
+ this.showingStickers = value
+ },
+ filterByKeyword (list, keyword) {
+ return filterByKeyword(list, keyword, this.languages, this.maybeLocalizedEmojiName)
+ },
+ initializeLazyLoad () {
+ this.destroyLazyLoad()
this.$nextTick(() => {
- this.emojisView.forEach(group => {
- const ref = this.$refs['group-' + group.id]
- if (ref.offsetTop <= top) {
- this.activeGroup = group.id
+ this.$lozad = lozad('.still-image.emoji-picker-emoji', {
+ load: el => {
+ const name = el.getAttribute('data-emoji-name')
+ const vn = this.emojiRefs[name]
+ if (!vn) {
+ return
+ }
+
+ vn.loadLazy()
}
})
+ this.$lozad.observe()
})
},
- loadEmoji () {
- const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length
-
- if (allLoaded) {
- return
- }
-
- this.customEmojiBufferSlice += LOAD_EMOJI_BY
+ waitForDomAndInitializeLazyLoad () {
+ this.$nextTick(() => this.initializeLazyLoad())
},
- startEmojiLoad (forceUpdate = false) {
- if (!forceUpdate) {
- this.keyword = ''
- }
- this.$nextTick(() => {
- this.$refs['emoji-groups'].scrollTop = 0
- })
- const bufferSize = this.customEmojiBuffer.length
- const bufferPrefilledAll = bufferSize === this.filteredEmoji.length
- if (bufferPrefilledAll && !forceUpdate) {
- return
+ destroyLazyLoad () {
+ if (this.$lozad) {
+ if (this.$lozad.observer) {
+ this.$lozad.observer.disconnect()
+ }
+ if (this.$lozad.mutationObserver) {
+ this.$lozad.mutationObserver.disconnect()
+ }
}
- this.customEmojiBufferSlice = LOAD_EMOJI_BY
},
- toggleStickers () {
- this.showingStickers = !this.showingStickers
+ onShowing () {
+ const oldContentLoaded = this.contentLoaded
+ this.contentLoaded = true
+ this.waitForDomAndInitializeLazyLoad()
+ this.filteredEmojiGroups = this.getFilteredEmojiGroups()
+ if (!oldContentLoaded) {
+ this.$nextTick(() => {
+ if (this.defaultGroup) {
+ this.highlight(this.defaultGroup)
+ }
+ })
+ }
},
- setShowStickers (value) {
- this.showingStickers = value
+ getFilteredEmojiGroups () {
+ return this.allEmojiGroups
+ .map(group => ({
+ ...group,
+ emojis: this.filterByKeyword(group.emojis, trim(this.keyword))
+ }))
+ .filter(group => group.emojis.length > 0)
}
},
watch: {
keyword () {
- this.customEmojiLoadAllConfirmed = false
this.onScroll()
- this.startEmojiLoad(true)
+ this.debouncedHandleKeywordChange()
+ },
+ allCustomGroups () {
+ this.waitForDomAndInitializeLazyLoad()
+ this.filteredEmojiGroups = this.getFilteredEmojiGroups()
+ },
+ showing (val) {
+ if (val) {
+ this.onShowing()
+ }
}
},
+ mounted () {
+ if (this.showing) {
+ this.onShowing()
+ }
+ },
+ destroyed () {
+ this.destroyLazyLoad()
+ },
computed: {
activeGroupView () {
return this.showingStickers ? '' : this.activeGroup
@@ -174,39 +276,55 @@ const EmojiPicker = {
}
return 0
},
- filteredEmoji () {
- return filterByKeyword(
- this.$store.state.instance.customEmoji || [],
- trim(this.keyword)
- )
+ allCustomGroups () {
+ return this.$store.getters.groupedCustomEmojis
},
- customEmojiBuffer () {
- return this.filteredEmoji.slice(0, this.customEmojiBufferSlice)
+ defaultGroup () {
+ return Object.keys(this.allCustomGroups)[0]
},
- emojis () {
- const standardEmojis = this.$store.state.instance.emoji || []
- const customEmojis = this.customEmojiBuffer
-
- return [
- {
- id: 'custom',
- text: this.$t('emoji.custom'),
- icon: 'smile-beam',
- emojis: customEmojis
- },
- {
- id: 'standard',
- text: this.$t('emoji.unicode'),
- icon: 'box-open',
- emojis: filterByKeyword(standardEmojis, trim(this.keyword))
- }
- ]
+ unicodeEmojiGroups () {
+ return this.$store.getters.standardEmojiGroupList.map(group => ({
+ id: `standard-${group.id}`,
+ text: this.$t(`emoji.unicode_groups.${group.id}`),
+ icon: UNICODE_EMOJI_GROUP_ICON[group.id],
+ emojis: group.emojis
+ }))
},
- emojisView () {
- return this.emojis.filter(value => value.emojis.length > 0)
+ allEmojiGroups () {
+ return Object.entries(this.allCustomGroups)
+ .map(([_, v]) => v)
+ .concat(this.unicodeEmojiGroups)
},
stickerPickerEnabled () {
return (this.$store.state.instance.stickers || []).length !== 0
+ },
+ debouncedHandleKeywordChange () {
+ return debounce(() => {
+ this.waitForDomAndInitializeLazyLoad()
+ this.filteredEmojiGroups = this.getFilteredEmojiGroups()
+ }, 500)
+ },
+ languages () {
+ return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
+ },
+ maybeLocalizedEmojiName () {
+ return emoji => {
+ if (!emoji.annotations) {
+ return emoji.displayText
+ }
+
+ if (emoji.displayTextI18n) {
+ return this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args)
+ }
+
+ for (const lang of this.languages) {
+ if (emoji.annotations[lang]?.name) {
+ return emoji.annotations[lang].name
+ }
+ }
+
+ return emoji.displayText
+ }
}
}
}
diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
index a2f17c51..016c46d7 100644
--- a/src/components/emoji_picker/emoji_picker.scss
+++ b/src/components/emoji_picker/emoji_picker.scss
@@ -1,5 +1,10 @@
@import '../../_variables.scss';
+$emoji-picker-header-height: 36px;
+$emoji-picker-header-picture-width: 32px;
+$emoji-picker-header-picture-height: 32px;
+$emoji-picker-emoji-size: 32px;
+
.emoji-picker {
display: flex;
flex-direction: column;
@@ -19,6 +24,23 @@
--lightText: var(--popoverLightText, $fallback--lightText);
--icon: var(--popoverIcon, $fallback--icon);
+ &-header-image {
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ width: $emoji-picker-header-picture-width;
+ max-width: $emoji-picker-header-picture-width;
+ height: $emoji-picker-header-picture-height;
+ max-height: $emoji-picker-header-picture-height;
+ .still-image {
+ max-width: 100%;
+ max-height: 100%;
+ height: 100%;
+ width: 100%;
+ object-fit: contain;
+ }
+ }
+
.keep-open,
.too-many-emoji {
padding: 7px;
@@ -37,7 +59,6 @@
.heading {
display: flex;
- height: 32px;
padding: 10px 7px 5px;
}
@@ -50,6 +71,10 @@
.emoji-tabs {
flex-grow: 1;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ overflow-x: auto;
}
.emoji-groups {
@@ -57,6 +82,8 @@
}
.additional-tabs {
+ display: flex;
+ flex: 1;
border-left: 1px solid;
border-left-color: $fallback--icon;
border-left-color: var(--icon, $fallback--icon);
@@ -66,15 +93,20 @@
.additional-tabs,
.emoji-tabs {
- display: block;
- min-width: 0;
flex-basis: auto;
- flex-shrink: 1;
+ display: flex;
+ align-content: center;
&-item {
padding: 0 7px;
cursor: pointer;
font-size: 1.85em;
+ width: $emoji-picker-header-picture-width;
+ max-width: $emoji-picker-header-picture-width;
+ height: $emoji-picker-header-picture-height;
+ max-height: $emoji-picker-header-picture-height;
+ display: flex;
+ align-items: center;
&.disabled {
opacity: 0.5;
@@ -164,22 +196,26 @@
}
&-item {
- width: 32px;
- height: 32px;
+ width: $emoji-picker-emoji-size;
+ height: $emoji-picker-emoji-size;
box-sizing: border-box;
display: flex;
- font-size: 32px;
+ line-height: $emoji-picker-emoji-size;
align-items: center;
justify-content: center;
margin: 4px;
cursor: pointer;
- img {
+ .emoji-picker-emoji.-custom {
object-fit: contain;
max-width: 100%;
max-height: 100%;
}
+ .emoji-picker-emoji.-unicode {
+ font-size: 24px;
+ overflow: hidden;
+ }
}
}
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
index a7269120..57bb0037 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -1,19 +1,34 @@
<template>
- <div class="emoji-picker panel panel-default panel-body">
+ <div
+ class="emoji-picker panel panel-default panel-body"
+ >
<div class="heading">
- <span class="emoji-tabs">
+ <span
+ ref="header"
+ class="emoji-tabs"
+ >
<span
- v-for="group in emojis"
+ v-for="group in filteredEmojiGroups"
+ :ref="setGroupRef('group-header-' + group.id)"
:key="group.id"
class="emoji-tabs-item"
:class="{
- active: activeGroupView === group.id,
- disabled: group.emojis.length === 0
+ active: activeGroupView === group.id
}"
:title="group.text"
@click.prevent="highlight(group.id)"
>
+ <span
+ v-if="group.image"
+ class="emoji-picker-header-image"
+ >
+ <still-image
+ :alt="group.text"
+ :src="group.image"
+ />
+ </span>
<FAIcon
+ v-else
:icon="group.icon"
fixed-width
/>
@@ -36,7 +51,10 @@
</span>
</span>
</div>
- <div class="content">
+ <div
+ v-if="contentLoaded"
+ class="content"
+ >
<div
class="emoji-content"
:class="{hidden: showingStickers}"
@@ -57,12 +75,12 @@
@scroll="onScroll"
>
<div
- v-for="group in emojisView"
+ v-for="group in filteredEmojiGroups"
:key="group.id"
class="emoji-group"
>
<h6
- :ref="'group-' + group.id"
+ :ref="setGroupRef('group-' + group.id)"
class="emoji-group-title"
>
{{ group.text }}
@@ -70,17 +88,23 @@
<span
v-for="emoji in group.emojis"
:key="group.id + emoji.displayText"
- :title="emoji.displayText"
+ :title="maybeLocalizedEmojiName(emoji)"
class="emoji-item"
@click.stop.prevent="onEmoji(emoji)"
>
- <span v-if="!emoji.imageUrl">{{ emoji.replacement }}</span>
- <img
+ <span
+ v-if="!emoji.imageUrl"
+ class="emoji-picker-emoji -unicode"
+ >{{ emoji.replacement }}</span>
+ <still-image
v-else
- :src="emoji.imageUrl"
- >
+ :ref="setEmojiRef(group.id + emoji.displayText)"
+ class="emoji-picker-emoji -custom"
+ :data-src="emoji.imageUrl"
+ :data-emoji-name="group.id + emoji.displayText"
+ />
</span>
- <span :ref="'group-end-' + group.id" />
+ <span :ref="setGroupRef('group-end-' + group.id)" />
</div>
</div>
<div class="keep-open">
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 77f73d04..5c536b74 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -189,7 +189,7 @@ const PostStatusForm = {
emojiUserSuggestor () {
return suggestor({
emoji: [
- ...this.$store.state.instance.emoji,
+ ...this.$store.getters.standardEmojiList,
...this.$store.state.instance.customEmoji
],
store: this.$store
@@ -198,13 +198,13 @@ const PostStatusForm = {
emojiSuggestor () {
return suggestor({
emoji: [
- ...this.$store.state.instance.emoji,
+ ...this.$store.getters.standardEmojiList,
...this.$store.state.instance.customEmoji
]
})
},
emoji () {
- return this.$store.state.instance.emoji || []
+ return this.$store.getters.standardEmojiList || []
},
customEmoji () {
return this.$store.state.instance.customEmoji || []
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index 5e052e1e..e65bfd93 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -59,7 +59,7 @@ const ReactButton = {
if (this.filterWord !== '') {
const filterWordLowercase = trim(this.filterWord.toLowerCase())
const orderedEmojiList = []
- for (const emoji of this.$store.state.instance.emoji) {
+ for (const emoji of this.$store.getters.standardEmojiList) {
if (emoji.replacement === this.filterWord) return [emoji]
const indexOfFilterWord = emoji.displayText.toLowerCase().indexOf(filterWordLowercase)
@@ -72,7 +72,7 @@ const ReactButton = {
}
return orderedEmojiList.flat()
}
- return this.$store.state.instance.emoji || []
+ return this.$store.getters.standardEmojiList || []
},
mergedConfig () {
return this.$store.getters.mergedConfig
diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js
index 376248ef..b86faef0 100644
--- a/src/components/settings_modal/tabs/profile_tab.js
+++ b/src/components/settings_modal/tabs/profile_tab.js
@@ -64,7 +64,7 @@ const ProfileTab = {
emojiUserSuggestor () {
return suggestor({
emoji: [
- ...this.$store.state.instance.emoji,
+ ...this.$store.getters.standardEmojiList,
...this.$store.state.instance.customEmoji
],
store: this.$store
@@ -73,7 +73,7 @@ const ProfileTab = {
emojiSuggestor () {
return suggestor({
emoji: [
- ...this.$store.state.instance.emoji,
+ ...this.$store.getters.standardEmojiList,
...this.$store.state.instance.customEmoji
]
})
diff --git a/src/components/still-image/still-image.js b/src/components/still-image/still-image.js
index d7abbcb5..200ef147 100644
--- a/src/components/still-image/still-image.js
+++ b/src/components/still-image/still-image.js
@@ -7,16 +7,23 @@ const StillImage = {
'imageLoadHandler',
'alt',
'height',
- 'width'
+ 'width',
+ 'dataSrc'
],
data () {
return {
+ // for lazy loading, see loadLazy()
+ realSrc: this.src,
stopGifs: this.$store.getters.mergedConfig.stopGifs
}
},
computed: {
animated () {
- return this.stopGifs && (this.mimetype === 'image/gif' || this.src.endsWith('.gif'))
+ if (!this.realSrc) {
+ return false
+ }
+
+ return this.stopGifs && (this.mimetype === 'image/gif' || this.realSrc.endsWith('.gif'))
},
style () {
const appendPx = (str) => /\d$/.test(str) ? str + 'px' : str
@@ -27,7 +34,15 @@ const StillImage = {
}
},
methods: {
+ loadLazy () {
+ if (this.dataSrc) {
+ this.realSrc = this.dataSrc
+ }
+ },
onLoad () {
+ if (!this.realSrc) {
+ return
+ }
const image = this.$refs.src
if (!image) return
this.imageLoadHandler && this.imageLoadHandler(image)
@@ -42,6 +57,14 @@ const StillImage = {
onError () {
this.imageLoadError && this.imageLoadError()
}
+ },
+ watch: {
+ src () {
+ this.realSrc = this.src
+ },
+ dataSrc () {
+ this.$el.removeAttribute('data-loaded')
+ }
}
}
diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue
index ab3080c8..633fb229 100644
--- a/src/components/still-image/still-image.vue
+++ b/src/components/still-image/still-image.vue
@@ -11,10 +11,11 @@
<!-- NOTE: key is required to force to re-render img tag when src is changed -->
<img
ref="src"
- :key="src"
+ :key="realSrc"
:alt="alt"
:title="alt"
- :src="src"
+ :data-src="dataSrc"
+ :src="realSrc"
:referrerpolicy="referrerpolicy"
@load="onLoad"
@error="onError"
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 22ec42a9..30b59e82 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -199,8 +199,20 @@
"add_emoji": "Insert emoji",
"custom": "Custom emoji",
"unicode": "Unicode emoji",
+ "unicode_groups": {
+ "activities": "Activities",
+ "animals-and-nature": "Animals & Nature",
+ "flags": "Flags",
+ "food-and-drink": "Food & Drink",
+ "objects": "Objects",
+ "people-and-body": "People & Body",
+ "smileys-and-emotion": "Smileys & Emotion",
+ "symbols": "Symbols",
+ "travel-and-places": "Travel & Places"
+ },
"load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.",
- "load_all": "Loading all {emojiAmount} emoji"
+ "load_all": "Loading all {emojiAmount} emoji",
+ "regional_indicator": "Regional indicator {letter}"
},
"errors": {
"storage_unavailable": "Pleroma could not access browser storage. Your login or your local settings won't be saved and you might encounter unexpected issues. Try enabling cookies."
diff --git a/src/i18n/languages.js b/src/i18n/languages.js
new file mode 100644
index 00000000..250b3b1a
--- /dev/null
+++ b/src/i18n/languages.js
@@ -0,0 +1,53 @@
+
+const languages = [
+ 'ar',
+ 'ca',
+ 'cs',
+ 'de',
+ 'eo',
+ 'en',
+ 'es',
+ 'et',
+ 'eu',
+ 'fi',
+ 'fr',
+ 'ga',
+ 'he',
+ 'hu',
+ 'it',
+ 'ja',
+ 'ja_easy',
+ 'ko',
+ 'nb',
+ 'nl',
+ 'oc',
+ 'pl',
+ 'pt',
+ 'ro',
+ 'ru',
+ 'sk',
+ 'te',
+ 'uk',
+ 'zh',
+ 'zh_Hant'
+]
+
+const specialJsonName = {
+ ja: 'ja_pedantic'
+}
+
+const langCodeToJsonName = (code) => specialJsonName[code] || code
+
+const langCodeToCldrName = (code) => code
+
+const ensureFinalFallback = codes => {
+ const codeList = Array.isArray(codes) ? codes : [codes]
+ return codeList.includes('en') ? codeList : codeList.concat(['en'])
+}
+
+module.exports = {
+ languages,
+ langCodeToJsonName,
+ langCodeToCldrName,
+ ensureFinalFallback
+}
diff --git a/src/i18n/messages.js b/src/i18n/messages.js
index eae75c80..74a89ca8 100644
--- a/src/i18n/messages.js
+++ b/src/i18n/messages.js
@@ -7,46 +7,26 @@
// sed -i -e "s/'//gm" -e 's/"/\\"/gm' -re 's/^( +)(.+?): ((.+?))?(,?)(\{?)$/\1"\2": "\4"/gm' -e 's/\"\{\"/{/g' -e 's/,"$/",/g' file.json
// There's only problem that apostrophe character ' gets replaced by \\ so you have to fix it manually, sorry.
-const loaders = {
- ar: () => import('./ar.json'),
- ca: () => import('./ca.json'),
- cs: () => import('./cs.json'),
- de: () => import('./de.json'),
- eo: () => import('./eo.json'),
- es: () => import('./es.json'),
- et: () => import('./et.json'),
- eu: () => import('./eu.json'),
- fi: () => import('./fi.json'),
- fr: () => import('./fr.json'),
- ga: () => import('./ga.json'),
- he: () => import('./he.json'),
- hu: () => import('./hu.json'),
- it: () => import('./it.json'),
- ja: () => import('./ja_pedantic.json'),
- ja_easy: () => import('./ja_easy.json'),
- ko: () => import('./ko.json'),
- nb: () => import('./nb.json'),
- nl: () => import('./nl.json'),
- oc: () => import('./oc.json'),
- pl: () => import('./pl.json'),
- pt: () => import('./pt.json'),
- ro: () => import('./ro.json'),
- ru: () => import('./ru.json'),
- sk: () => import('./sk.json'),
- te: () => import('./te.json'),
- uk: () => import('./uk.json'),
- zh: () => import('./zh.json'),
- zh_Hant: () => import('./zh_Hant.json')
+import { languages, langCodeToJsonName } from './languages.js'
+
+const hasLanguageFile = (code) => languages.includes(code)
+
+const loadLanguageFile = (code) => {
+ return import(
+ /* webpackInclude: /\.json$/ */
+ /* webpackChunkName: "i18n/[request]" */
+ `./${langCodeToJsonName(code)}.json`
+ )
}
const messages = {
- languages: ['en', ...Object.keys(loaders)],
+ languages,
default: {
en: require('./en.json').default
},
setLanguage: async (i18n, language) => {
- if (loaders[language]) {
- const messages = await loaders[language]()
+ if (hasLanguageFile(language)) {
+ const messages = await loadLanguageFile(language)
i18n.setLocaleMessage(language, messages.default)
}
i18n.locale = language
diff --git a/src/modules/config.js b/src/modules/config.js
index eeaac917..c966602e 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -183,6 +183,7 @@ const config = {
break
case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value)
+ dispatch('loadUnicodeEmojiData', value)
Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value))
break
case 'thirdColumnMode':
diff --git a/src/modules/instance.js b/src/modules/instance.js
index bfce6f38..b1bc9779 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -2,6 +2,39 @@ import { getPreset, applyTheme } from '../services/style_setter/style_setter.js'
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
import apiService from '../services/api/api.service.js'
import { instanceDefaultProperties } from './config.js'
+import { langCodeToCldrName, ensureFinalFallback } from '../i18n/languages.js'
+
+const SORTED_EMOJI_GROUP_IDS = [
+ 'smileys-and-emotion',
+ 'people-and-body',
+ 'animals-and-nature',
+ 'food-and-drink',
+ 'travel-and-places',
+ 'activities',
+ 'objects',
+ 'symbols',
+ 'flags'
+]
+
+const REGIONAL_INDICATORS = (() => {
+ const start = 0x1F1E6
+ const end = 0x1F1FF
+ const A = 'A'.codePointAt(0)
+ const res = new Array(end - start + 1)
+ for (let i = start; i <= end; ++i) {
+ const letter = String.fromCodePoint(A + i - start)
+ res[i - start] = {
+ replacement: String.fromCodePoint(i),
+ imageUrl: false,
+ displayText: 'regional_indicator_' + letter,
+ displayTextI18n: {
+ key: 'emoji.regional_indicator',
+ args: { letter }
+ }
+ }
+ }
+ return res
+})()
const defaultState = {
// Stuff from apiConfig
@@ -64,8 +97,9 @@ const defaultState = {
// Nasty stuff
customEmoji: [],
customEmojiFetched: false,
- emoji: [],
+ emoji: {},
emojiFetched: false,
+ unicodeEmojiAnnotations: {},
pleromaBackend: true,
postFormats: [],
restrictedNicknames: [],
@@ -97,6 +131,31 @@ const defaultState = {
}
}
+const loadAnnotations = (lang) => {
+ return import(
+ /* webpackChunkName: "emoji-annotations/[request]" */
+ `@kazvmoe-infra/unicode-emoji-json/annotations/${langCodeToCldrName(lang)}.json`
+ )
+ .then(k => k.default)
+}
+
+const injectAnnotations = (emoji, annotations) => {
+ const availableLangs = Object.keys(annotations)
+
+ return {
+ ...emoji,
+ annotations: availableLangs.reduce((acc, cur) => {
+ acc[cur] = annotations[cur][emoji.replacement]
+ return acc
+ }, {})
+ }
+}
+
+const injectRegionalIndicators = groups => {
+ groups.symbols.push(...REGIONAL_INDICATORS)
+ return groups
+}
+
const instance = {
state: defaultState,
mutations: {
@@ -107,6 +166,9 @@ const instance = {
},
setKnownDomains (state, domains) {
state.knownDomains = domains
+ },
+ setUnicodeEmojiAnnotations (state, { lang, annotations }) {
+ state.unicodeEmojiAnnotations[lang] = annotations
}
},
getters: {
@@ -115,6 +177,41 @@ const instance = {
.map(key => [key, state[key]])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
},
+ groupedCustomEmojis (state) {
+ const packsOf = emoji => {
+ return emoji.tags
+ .filter(k => k.startsWith('pack:'))
+ .map(k => k.slice(5)) // remove 'pack:' prefix
+ }
+
+ return state.customEmoji
+ .reduce((res, emoji) => {
+ packsOf(emoji).forEach(packName => {
+ const packId = `custom-${packName}`
+ if (!res[packId]) {
+ res[packId] = ({
+ id: packId,
+ text: packName,
+ image: emoji.imageUrl,
+ emojis: []
+ })
+ }
+ res[packId].emojis.push(emoji)
+ })
+ return res
+ }, {})
+ },
+ standardEmojiList (state) {
+ return SORTED_EMOJI_GROUP_IDS
+ .map(groupId => (state.emoji[groupId] || []).map(k => injectAnnotations(k, state.unicodeEmojiAnnotations)))
+ .reduce((a, b) => a.concat(b), [])
+ },
+ standardEmojiGroupList (state) {
+ return SORTED_EMOJI_GROUP_IDS.map(groupId => ({
+ id: groupId,
+ emojis: (state.emoji[groupId] || []).map(k => injectAnnotations(k, state.unicodeEmojiAnnotations))
+ }))
+ },
instanceDomain (state) {
return new URL(state.server).hostname
}
@@ -138,32 +235,52 @@ const instance = {
},
async getStaticEmoji ({ commit }) {
try {
- const res = await window.fetch('/static/emoji.json')
- if (res.ok) {
- const values = await res.json()
- const emoji = Object.keys(values).map((key) => {
- return {
- displayText: key,
- imageUrl: false,
- replacement: values[key]
- }
- }).sort((a, b) => a.name > b.name ? 1 : -1)
- commit('setInstanceOption', { name: 'emoji', value: emoji })
- } else {
- throw (res)
- }
+ const values = (await import(/* webpackChunkName: 'emoji' */ '../../static/emoji.json')).default
+
+ const emoji = Object.keys(values).reduce((res, groupId) => {
+ res[groupId] = values[groupId].map(e => ({
+ displayText: e.slug,
+ imageUrl: false,
+ replacement: e.emoji
+ }))
+ return res
+ }, {})
+ commit('setInstanceOption', { name: 'emoji', value: injectRegionalIndicators(emoji) })
} catch (e) {
console.warn("Can't load static emoji")
console.warn(e)
}
},
+ loadUnicodeEmojiData ({ commit, state }, language) {
+ const langList = ensureFinalFallback(language)
+
+ return Promise.all(
+ langList
+ .map(async lang => {
+ if (!state.unicodeEmojiAnnotations[lang]) {
+ const annotations = await loadAnnotations(lang)
+ commit('setUnicodeEmojiAnnotations', { lang, annotations })
+ }
+ }))
+ },
+
async getCustomEmoji ({ commit, state }) {
try {
const res = await window.fetch('/api/pleroma/emoji.json')
if (res.ok) {
const result = await res.json()
const values = Array.isArray(result) ? Object.assign({}, ...result) : result
+ const caseInsensitiveStrCmp = (a, b) => {
+ const la = a.toLowerCase()
+ const lb = b.toLowerCase()
+ return la > lb ? 1 : (la < lb ? -1 : 0)
+ }
+ const byPackThenByName = (a, b) => {
+ const packOf = emoji => (emoji.tags.filter(k => k.startsWith('pack:'))[0] || '').slice(5)
+ return caseInsensitiveStrCmp(packOf(a), packOf(b)) || caseInsensitiveStrCmp(a.displayText, b.displayText)
+ }
+
const emoji = Object.entries(values).map(([key, value]) => {
const imageUrl = value.image_url
return {
@@ -174,7 +291,7 @@ const instance = {
}
// Technically could use tags but those are kinda useless right now,
// should have been "pack" field, that would be more useful
- }).sort((a, b) => a.displayText.toLowerCase() > b.displayText.toLowerCase() ? 1 : -1)
+ }).sort(byPackThenByName)
commit('setInstanceOption', { name: 'customEmoji', value: emoji })
} else {
throw (res)
diff --git a/static/emoji.json b/static/emoji.json
deleted file mode 100644
index 12b91b3f..00000000
--- a/static/emoji.json
+++ /dev/null
@@ -1,1431 +0,0 @@
-{
- "100": "๐Ÿ’ฏ",
- "1234": "๐Ÿ”ข",
- "1st_place_medal": "๐Ÿฅ‡",
- "2nd_place_medal": "๐Ÿฅˆ",
- "3rd_place_medal": "๐Ÿฅ‰",
- "8ball": "๐ŸŽฑ",
- "a_button_blood_type": "๐Ÿ…ฐ",
- "ab": "๐Ÿ†Ž",
- "abacus": "๐Ÿงฎ",
- "abc": "๐Ÿ”ค",
- "abcd": "๐Ÿ”ก",
- "accept": "๐Ÿ‰‘",
- "adhesive_bandage": "๐Ÿฉน",
- "admission_tickets": "๐ŸŽŸ",
- "adult": "๐Ÿง‘",
- "aerial_tramway": "๐Ÿšก",
- "airplane": "โœˆ",
- "airplane_arriving": "๐Ÿ›ฌ",
- "airplane_departure": "๐Ÿ›ซ",
- "alarm_clock": "โฐ",
- "alembic": "โš—๏ธ",
- "alien": "๐Ÿ‘ฝ",
- "ambulance": "๐Ÿš‘",
- "amphora": "๐Ÿบ",
- "anchor": "โš“",
- "angel": "๐Ÿ‘ผ",
- "anger": "๐Ÿ’ข",
- "anger_right": "๐Ÿ—ฏ",
- "angry": "๐Ÿ˜ ",
- "anguished": "๐Ÿ˜ง",
- "ant": "๐Ÿœ",
- "apple": "๐ŸŽ",
- "aquarius": "โ™’",
- "aries": "โ™ˆ",
- "arrow_backward": "โ—€๏ธ",
- "arrow_double_down": "โฌ",
- "arrow_double_up": "โซ",
- "arrow_down": "โฌ‡๏ธ",
- "arrow_down_small": "๐Ÿ”ฝ",
- "arrow_forward": "โ–ถ๏ธ",
- "arrow_heading_down": "โคต๏ธ",
- "arrow_heading_up": "โคด๏ธ",
- "arrow_left": "โฌ…๏ธ",
- "arrow_lower_left": "โ†™๏ธ",
- "arrow_lower_right": "โ†˜๏ธ",
- "arrow_right": "โžก",
- "arrow_right_hook": "โ†ช๏ธ",
- "arrow_up": "โฌ†๏ธ",
- "arrow_up_down": "โ†•",
- "arrow_up_small": "๐Ÿ”ผ",
- "arrow_upper_left": "โ†–",
- "arrow_upper_right": "โ†—๏ธ",
- "arrows_clockwise": "๐Ÿ”ƒ",
- "arrows_counterclockwise": "๐Ÿ”„",
- "art": "๐ŸŽจ",
- "articulated_lorry": "๐Ÿš›",
- "artist_palette": "๐ŸŽจ",
- "asterisk": "*โƒฃ",
- "astonished": "๐Ÿ˜ฒ",
- "athletic_shoe": "๐Ÿ‘Ÿ",
- "atm": "๐Ÿง",
- "atom": "โš›",
- "atom_symbol": "โš›๏ธ",
- "auto_rickshaw": "๐Ÿ›บ",
- "automobile": "๐Ÿš—",
- "avocado": "๐Ÿฅ‘",
- "axe": "๐Ÿช“",
- "b_button_blood_type": "๐Ÿ…ฑ",
- "baby": "๐Ÿ‘ถ",
- "baby_bottle": "๐Ÿผ",
- "baby_chick": "๐Ÿค",
- "baby_symbol": "๐Ÿšผ",
- "back": "๐Ÿ”™",
- "bacon": "๐Ÿฅ“",
- "badger": "๐Ÿฆก",
- "badminton": "๐Ÿธ",
- "bagel": "๐Ÿฅฏ",
- "baggage_claim": "๐Ÿ›„",
- "baguette_bread": "๐Ÿฅ–",
- "balance_scale": "โš–๏ธ",
- "bald": "๐Ÿฆฒ",
- "ballet_shoes": "๐Ÿฉฐ",
- "balloon": "๐ŸŽˆ",
- "ballot_box": "๐Ÿ—ณ",
- "ballot_box_with_check": "โ˜‘๏ธ",
- "bamboo": "๐ŸŽ",
- "banana": "๐ŸŒ",
- "bangbang": "โ€ผ๏ธ",
- "banjo": "๐Ÿช•",
- "bank": "๐Ÿฆ",
- "bar_chart": "๐Ÿ“Š",
- "barber": "๐Ÿ’ˆ",
- "baseball": "โšพ",
- "basket": "๐Ÿงบ",
- "basketball": "๐Ÿ€",
- "basketballer": "โ›น",
- "bat": "๐Ÿฆ‡",
- "bath": "๐Ÿ›€",
- "bathtub": "๐Ÿ›",
- "battery": "๐Ÿ”‹",
- "beach_umbrella": "โ›ฑ",
- "beach_with_umbrella": "๐Ÿ–",
- "bear": "๐Ÿป",
- "beard": "๐Ÿง”",
- "bearded_person": "๐Ÿง”",
- "bed": "๐Ÿ›",
- "bee": "๐Ÿ",
- "beer": "๐Ÿบ",
- "beers": "๐Ÿป",
- "beetle": "๐Ÿž",
- "beginner": "๐Ÿ”ฐ",
- "bell": "๐Ÿ””",
- "bellhop_bell": "๐Ÿ›Ž",
- "bento": "๐Ÿฑ",
- "beverage_box": "๐Ÿงƒ",
- "bicyclist": "๐Ÿšด",
- "bike": "๐Ÿšฒ",
- "bikini": "๐Ÿ‘™",
- "billed_cap": "๐Ÿงข",
- "biohazard": "โ˜ฃ๏ธ",
- "bird": "๐Ÿฆ",
- "birthday": "๐ŸŽ‚",
- "black_circle": "โšซ",
- "black_heart": "๐Ÿ–ค",
- "black_joker": "๐Ÿƒ",
- "black_large_square": "โฌ›",
- "black_medium_small_square": "โ—พ",
- "black_medium_square": "โ—ผ",
- "black_nib": "โœ’๏ธ",
- "black_small_square": "โ–ช",
- "black_square_button": "๐Ÿ”ฒ",
- "blond_haired_person": "๐Ÿ‘ฑ",
- "blossom": "๐ŸŒผ",
- "blowfish": "๐Ÿก",
- "blue_book": "๐Ÿ“˜",
- "blue_car": "๐Ÿš™",
- "blue_circle": "๐Ÿ”ต",
- "blue_heart": "๐Ÿ’™",
- "blue_square": "๐ŸŸฆ",
- "blush": "๐Ÿ˜Š",
- "boar": "๐Ÿ—",
- "bomb": "๐Ÿ’ฃ",
- "bone": "๐Ÿฆด",
- "book": "๐Ÿ“–",
- "bookmark": "๐Ÿ”–",
- "bookmark_tabs": "๐Ÿ“‘",
- "books": "๐Ÿ“š",
- "boom": "๐Ÿ’ฅ",
- "boot": "๐Ÿ‘ข",
- "bouquet": "๐Ÿ’",
- "bow": "๐Ÿ™‡",
- "bow_and_arrow": "๐Ÿน",
- "bowl_with_spoon": "๐Ÿฅฃ",
- "bowling": "๐ŸŽณ",
- "boxing_glove": "๐ŸฅŠ",
- "boy": "๐Ÿ‘ฆ",
- "brain": "๐Ÿง ",
- "bread": "๐Ÿž",
- "breast_feeding": "๐Ÿคฑ",
- "breastfeeding": "๐Ÿคฑ",
- "brick": "๐Ÿงฑ",
- "bride_with_veil": "๐Ÿ‘ฐ",
- "bridge_at_night": "๐ŸŒ‰",
- "briefcase": "๐Ÿ’ผ",
- "briefs": "๐Ÿฉฒ",
- "broccoli": "๐Ÿฅฆ",
- "broken_heart": "๐Ÿ’”",
- "broom": "๐Ÿงน",
- "brown_circle": "๐ŸŸค",
- "brown_heart": "๐ŸคŽ",
- "bug": "๐Ÿ›",
- "building_construction": "๐Ÿ—",
- "bulb": "๐Ÿ’ก",
- "bullettrain_front": "๐Ÿš…",
- "bullettrain_side": "๐Ÿš„",
- "burrito": "๐ŸŒฏ",
- "bus": "๐ŸšŒ",
- "busstop": "๐Ÿš",
- "bust_in_silhouette": "๐Ÿ‘ค",
- "busts_in_silhouette": "๐Ÿ‘ฅ",
- "butter": "๐Ÿงˆ",
- "butterfly": "๐Ÿฆ‹",
- "cactus": "๐ŸŒต",
- "cake": "๐Ÿฐ",
- "calendar": "๐Ÿ“†",
- "call_me": "๐Ÿค™",
- "call_me_hand": "๐Ÿค™",
- "calling": "๐Ÿ“ฒ",
- "camel": "๐Ÿซ",
- "camera": "๐Ÿ“ท",
- "camera_with_flash": "๐Ÿ“ธ",
- "camping": "๐Ÿ•",
- "cancer": "โ™‹",
- "candle": "๐Ÿ•ฏ",
- "candy": "๐Ÿฌ",
- "canned_food": "๐Ÿฅซ",
- "canoe": "๐Ÿ›ถ",
- "capital_abcd": "๐Ÿ” ",
- "capricorn": "โ™‘",
- "card_file_box": "๐Ÿ—ƒ",
- "card_index": "๐Ÿ“‡",
- "card_index_dividers": "๐Ÿ—‚",
- "carousel_horse": "๐ŸŽ ",
- "carrot": "๐Ÿฅ•",
- "cat": "๐Ÿฑ",
- "cat2": "๐Ÿˆ",
- "cd": "๐Ÿ’ฟ",
- "chains": "โ›“๏ธ",
- "chair": "๐Ÿช‘",
- "champagne": "๐Ÿพ",
- "champagne_glass": "๐Ÿฅ‚",
- "chart": "๐Ÿ’น",
- "chart_with_downwards_trend": "๐Ÿ“‰",
- "chart_with_upwards_trend": "๐Ÿ“ˆ",
- "check_box_with_check": "โ˜‘",
- "check_mark": "โœ”",
- "checkered_flag": "๐Ÿ",
- "cheese": "๐Ÿง€",
- "cheese_wedge": "๐Ÿง€",
- "cherries": "๐Ÿ’",
- "cherry_blossom": "๐ŸŒธ",
- "chess_pawn": "โ™Ÿ",
- "chestnut": "๐ŸŒฐ",
- "chicken": "๐Ÿ”",
- "child": "๐Ÿง’",
- "children_crossing": "๐Ÿšธ",
- "chipmunk": "๐Ÿฟ",
- "chocolate_bar": "๐Ÿซ",
- "chopsticks": "๐Ÿฅข",
- "christmas_tree": "๐ŸŽ„",
- "church": "โ›ช",
- "cinema": "๐ŸŽฆ",
- "circled_m": "โ“‚",
- "circus_tent": "๐ŸŽช",
- "city_dusk": "๐ŸŒ†",
- "city_sunset": "๐ŸŒ‡",
- "cityscape": "๐Ÿ™",
- "cityscape_at_dusk": "๐ŸŒ†",
- "cl": "๐Ÿ†‘",
- "clap": "๐Ÿ‘",
- "clapper": "๐ŸŽฌ",
- "classical_building": "๐Ÿ›",
- "clinking_glasses": "๐Ÿฅ‚",
- "clipboard": "๐Ÿ“‹",
- "clock1": "๐Ÿ•",
- "clock10": "๐Ÿ•™",
- "clock1030": "๐Ÿ•ฅ",
- "clock11": "๐Ÿ•š",
- "clock1130": "๐Ÿ•ฆ",
- "clock12": "๐Ÿ•›",
- "clock1230": "๐Ÿ•ง",
- "clock130": "๐Ÿ•œ",
- "clock2": "๐Ÿ•‘",
- "clock230": "๐Ÿ•",
- "clock3": "๐Ÿ•’",
- "clock330": "๐Ÿ•ž",
- "clock4": "๐Ÿ•“",
- "clock430": "๐Ÿ•Ÿ",
- "clock5": "๐Ÿ•”",
- "clock530": "๐Ÿ• ",
- "clock6": "๐Ÿ••",
- "clock630": "๐Ÿ•ก",
- "clock7": "๐Ÿ•–",
- "clock730": "๐Ÿ•ข",
- "clock8": "๐Ÿ•—",
- "clock830": "๐Ÿ•ฃ",
- "clock9": "๐Ÿ•˜",
- "clock930": "๐Ÿ•ค",
- "closed_book": "๐Ÿ“•",
- "closed_lock_with_key": "๐Ÿ”",
- "closed_umbrella": "๐ŸŒ‚",
- "cloud": "โ˜๏ธ",
- "cloud_with_lightning": "๐ŸŒฉ",
- "cloud_with_lightning_and_rain": "โ›ˆ๏ธ",
- "cloud_with_rain": "๐ŸŒง",
- "cloud_with_snow": "๐ŸŒจ",
- "clown": "๐Ÿคก",
- "clown_face": "๐Ÿคก",
- "club_suit": "โ™ฃ๏ธ",
- "clubs": "โ™ฃ",
- "coat": "๐Ÿงฅ",
- "cocktail": "๐Ÿธ",
- "coconut": "๐Ÿฅฅ",
- "coffee": "โ˜•",
- "coffin": "โšฐ๏ธ",
- "cold_face": "๐Ÿฅถ",
- "cold_sweat": "๐Ÿ˜ฐ",
- "comet": "โ˜„๏ธ",
- "compass": "๐Ÿงญ",
- "compression": "๐Ÿ—œ",
- "computer": "๐Ÿ’ป",
- "computer_mouse": "๐Ÿ–ฑ",
- "confetti_ball": "๐ŸŽŠ",
- "confounded": "๐Ÿ˜–",
- "confused": "๐Ÿ˜•",
- "congratulations": "ใŠ—",
- "construction": "๐Ÿšง",
- "construction_worker": "๐Ÿ‘ท",
- "control_knobs": "๐ŸŽ›",
- "convenience_store": "๐Ÿช",
- "cookie": "๐Ÿช",
- "cooking": "๐Ÿณ",
- "cool": "๐Ÿ†’",
- "cop": "๐Ÿ‘ฎ",
- "copyright": "ยฉ",
- "corn": "๐ŸŒฝ",
- "couch_and_lamp": "๐Ÿ›‹",
- "couple": "๐Ÿ‘ซ",
- "couple_with_heart": "๐Ÿ’‘",
- "couplekiss": "๐Ÿ’",
- "cow": "๐Ÿฎ",
- "cow2": "๐Ÿ„",
- "cowboy": "๐Ÿค ",
- "cowboy_hat_face": "๐Ÿค ",
- "crab": "๐Ÿฆ€",
- "crayon": "๐Ÿ–",
- "crazy_face": "๐Ÿคช",
- "credit_card": "๐Ÿ’ณ",
- "crescent_moon": "๐ŸŒ™",
- "cricket": "๐Ÿฆ—",
- "cricket_game": "๐Ÿ",
- "crocodile": "๐ŸŠ",
- "croissant": "๐Ÿฅ",
- "cross": "โœ๏ธ",
- "crossed_fingers": "๐Ÿคž",
- "crossed_flags": "๐ŸŽŒ",
- "crossed_swords": "โš”๏ธ",
- "crown": "๐Ÿ‘‘",
- "cry": "๐Ÿ˜ข",
- "crying_cat_face": "๐Ÿ˜ฟ",
- "crystal_ball": "๐Ÿ”ฎ",
- "cucumber": "๐Ÿฅ’",
- "cup_with_straw": "๐Ÿฅค",
- "cupcake": "๐Ÿง",
- "cupid": "๐Ÿ’˜",
- "curling_stone": "๐ŸฅŒ",
- "curly_hair": "๐Ÿฆฑ",
- "curly_loop": "โžฐ",
- "currency_exchange": "๐Ÿ’ฑ",
- "curry": "๐Ÿ›",
- "custard": "๐Ÿฎ",
- "customs": "๐Ÿ›ƒ",
- "cut_of_meat": "๐Ÿฅฉ",
- "cyclone": "๐ŸŒ€",
- "dagger": "๐Ÿ—ก",
- "dancer": "๐Ÿ’ƒ",
- "dancers": "๐Ÿ‘ฏ",
- "dango": "๐Ÿก",
- "dark_skin_tone": "๐Ÿฟ",
- "dark_sunglasses": "๐Ÿ•ถ",
- "dart": "๐ŸŽฏ",
- "dash": "๐Ÿ’จ",
- "date": "๐Ÿ“…",
- "deaf_person": "๐Ÿง",
- "deciduous_tree": "๐ŸŒณ",
- "deer": "๐ŸฆŒ",
- "department_store": "๐Ÿฌ",
- "derelict_house": "๐Ÿš",
- "desert": "๐Ÿœ",
- "desert_island": "๐Ÿ",
- "desktop_computer": "๐Ÿ–ฅ",
- "detective": "๐Ÿ•ต",
- "diamond_shape_with_a_dot_inside": "๐Ÿ’ ",
- "diamond_suit": "โ™ฆ๏ธ",
- "diamonds": "โ™ฆ",
- "disappointed": "๐Ÿ˜ž",
- "disappointed_relieved": "๐Ÿ˜ฅ",
- "diving_mask": "๐Ÿคฟ",
- "diya_lamp": "๐Ÿช”",
- "dizzy": "๐Ÿ’ซ",
- "dizzy_face": "๐Ÿ˜ต",
- "dna": "๐Ÿงฌ",
- "do_not_litter": "๐Ÿšฏ",
- "dog": "๐Ÿถ",
- "dog2": "๐Ÿ•",
- "dollar": "๐Ÿ’ต",
- "dolls": "๐ŸŽŽ",
- "dolphin": "๐Ÿฌ",
- "door": "๐Ÿšช",
- "double_exclamation_mark": "โ€ผ",
- "doughnut": "๐Ÿฉ",
- "dove": "๐Ÿ•Š",
- "down_arrow": "โฌ‡",
- "downleft_arrow": "โ†™",
- "downright_arrow": "โ†˜",
- "dragon": "๐Ÿ‰",
- "dragon_face": "๐Ÿฒ",
- "dress": "๐Ÿ‘—",
- "dromedary_camel": "๐Ÿช",
- "drooling_face": "๐Ÿคค",
- "drop_of_blood": "๐Ÿฉธ",
- "droplet": "๐Ÿ’ง",
- "drum": "๐Ÿฅ",
- "duck": "๐Ÿฆ†",
- "dumpling": "๐ŸฅŸ",
- "dvd": "๐Ÿ“€",
- "e-mail": "๐Ÿ“ง",
- "eagle": "๐Ÿฆ…",
- "ear": "๐Ÿ‘‚",
- "ear_of_rice": "๐ŸŒพ",
- "ear_with_hearing_aid": "๐Ÿฆป",
- "earth_africa": "๐ŸŒ",
- "earth_americas": "๐ŸŒŽ",
- "earth_asia": "๐ŸŒ",
- "egg": "๐Ÿฅš",
- "eggplant": "๐Ÿ†",
- "eight": "8โƒฃ",
- "eight_pointed_black_star": "โœด๏ธ",
- "eight_spoked_asterisk": "โœณ๏ธ",
- "eightpointed_star": "โœด",
- "eightspoked_asterisk": "โœณ",
- "eject_button": "โ",
- "electric_plug": "๐Ÿ”Œ",
- "elephant": "๐Ÿ˜",
- "elf": "๐Ÿง",
- "end": "๐Ÿ”š",
- "envelope": "โœ‰",
- "envelope_with_arrow": "๐Ÿ“ฉ",
- "euro": "๐Ÿ’ถ",
- "european_castle": "๐Ÿฐ",
- "european_post_office": "๐Ÿค",
- "evergreen_tree": "๐ŸŒฒ",
- "exclamation": "โ—",
- "exclamation_question_mark": "โ‰",
- "exploding_head": "๐Ÿคฏ",
- "expressionless": "๐Ÿ˜‘",
- "eye": "๐Ÿ‘",
- "eyeglasses": "๐Ÿ‘“",
- "eyes": "๐Ÿ‘€",
- "face_vomiting": "๐Ÿคฎ",
- "face_with_hand_over_mouth": "๐Ÿคญ",
- "face_with_headbandage": "๐Ÿค•",
- "face_with_monocle": "๐Ÿง",
- "face_with_raised_eyebrow": "๐Ÿคจ",
- "face_with_symbols_on_mouth": "๐Ÿคฌ",
- "face_with_symbols_over_mouth": "๐Ÿคฌ",
- "face_with_thermometer": "๐Ÿค’",
- "factory": "๐Ÿญ",
- "fairy": "๐Ÿงš",
- "falafel": "๐Ÿง†",
- "fallen_leaf": "๐Ÿ‚",
- "family": "๐Ÿ‘ช",
- "fast_forward": "โฉ",
- "fax": "๐Ÿ“ ",
- "fearful": "๐Ÿ˜จ",
- "feet": "๐Ÿพ",
- "female_sign": "โ™€",
- "ferris_wheel": "๐ŸŽก",
- "ferry": "โ›ด๏ธ",
- "field_hockey": "๐Ÿ‘",
- "file_cabinet": "๐Ÿ—„",
- "file_folder": "๐Ÿ“",
- "film_frames": "๐ŸŽž",
- "film_projector": "๐Ÿ“ฝ",
- "fingers_crossed": "๐Ÿคž",
- "fire": "๐Ÿ”ฅ",
- "fire_engine": "๐Ÿš’",
- "fire_extinguisher": "๐Ÿงฏ",
- "firecracker": "๐Ÿงจ",
- "fireworks": "๐ŸŽ†",
- "first_place": "๐Ÿฅ‡",
- "first_quarter_moon": "๐ŸŒ“",
- "first_quarter_moon_with_face": "๐ŸŒ›",
- "fish": "๐ŸŸ",
- "fish_cake": "๐Ÿฅ",
- "fishing_pole_and_fish": "๐ŸŽฃ",
- "fist": "โœŠ",
- "five": "5โƒฃ",
- "flag_black": "๐Ÿด",
- "flag_white": "๐Ÿณ",
- "flags": "๐ŸŽ",
- "flamingo": "๐Ÿฆฉ",
- "flashlight": "๐Ÿ”ฆ",
- "flat_shoe": "๐Ÿฅฟ",
- "fleur-de-lis": "โšœ",
- "fleurde-lis": "โšœ๏ธ",
- "floppy_disk": "๐Ÿ’พ",
- "flower_playing_cards": "๐ŸŽด",
- "flushed": "๐Ÿ˜ณ",
- "flying_disc": "๐Ÿฅ",
- "flying_saucer": "๐Ÿ›ธ",
- "fog": "๐ŸŒซ",
- "foggy": "๐ŸŒ",
- "foot": "๐Ÿฆถ",
- "football": "๐Ÿˆ",
- "footprints": "๐Ÿ‘ฃ",
- "fork_and_knife": "๐Ÿด",
- "fork_and_knife_with_plate": "๐Ÿฝ",
- "fortune_cookie": "๐Ÿฅ ",
- "fountain": "โ›ฒ",
- "fountain_pen": "๐Ÿ–‹",
- "four": "4โƒฃ",
- "four_leaf_clover": "๐Ÿ€",
- "fox": "๐ŸฆŠ",
- "framed_picture": "๐Ÿ–ผ",
- "free": "๐Ÿ†“",
- "french_bread": "๐Ÿฅ–",
- "fried_shrimp": "๐Ÿค",
- "fries": "๐ŸŸ",
- "frog": "๐Ÿธ",
- "frowning": "๐Ÿ˜ฆ",
- "frowning_face": "โ˜น๏ธ",
- "fuelpump": "โ›ฝ",
- "full_moon": "๐ŸŒ•",
- "full_moon_with_face": "๐ŸŒ",
- "funeral_urn": "โšฑ๏ธ",
- "game_die": "๐ŸŽฒ",
- "garlic": "๐Ÿง„",
- "gear": "โš™๏ธ",
- "gem": "๐Ÿ’Ž",
- "gemini": "โ™Š",
- "genie": "๐Ÿงž",
- "ghost": "๐Ÿ‘ป",
- "gift": "๐ŸŽ",
- "gift_heart": "๐Ÿ’",
- "giraffe": "๐Ÿฆ’",
- "girl": "๐Ÿ‘ง",
- "glass_of_milk": "๐Ÿฅ›",
- "globe_with_meridians": "๐ŸŒ",
- "gloves": "๐Ÿงค",
- "goal": "๐Ÿฅ…",
- "goal_net": "๐Ÿฅ…",
- "goat": "๐Ÿ",
- "goggles": "๐Ÿฅฝ",
- "golf": "โ›ณ",
- "golfer": "๐ŸŒ",
- "gorilla": "๐Ÿฆ",
- "grapes": "๐Ÿ‡",
- "green_apple": "๐Ÿ",
- "green_book": "๐Ÿ“—",
- "green_circle": "๐ŸŸข",
- "green_heart": "๐Ÿ’š",
- "green_salad": "๐Ÿฅ—",
- "green_square": "๐ŸŸฉ",
- "grey_exclamation": "โ•",
- "grey_question": "โ”",
- "grimacing": "๐Ÿ˜ฌ",
- "grin": "๐Ÿ˜",
- "grinning": "๐Ÿ˜€",
- "guard": "๐Ÿ’‚",
- "guardsman": "๐Ÿ’‚",
- "guide_dog": "๐Ÿฆฎ",
- "guitar": "๐ŸŽธ",
- "gun": "๐Ÿ”ซ",
- "haircut": "๐Ÿ’‡",
- "hamburger": "๐Ÿ”",
- "hammer": "๐Ÿ”จ",
- "hammer_and_pick": "โš’๏ธ",
- "hammer_and_wrench": "๐Ÿ› ",
- "hamster": "๐Ÿน",
- "hand_with_fingers_splayed": "๐Ÿ–",
- "handbag": "๐Ÿ‘œ",
- "handshake": "๐Ÿค",
- "hash": "#โƒฃ",
- "hatched_chick": "๐Ÿฅ",
- "hatching_chick": "๐Ÿฃ",
- "head_bandage": "๐Ÿค•",
- "headphones": "๐ŸŽง",
- "hear_no_evil": "๐Ÿ™‰",
- "heart": "โค๏ธ",
- "heart_decoration": "๐Ÿ’Ÿ",
- "heart_exclamation": "โฃ",
- "heart_eyes": "๐Ÿ˜",
- "heart_eyes_cat": "๐Ÿ˜ป",
- "heart_suit": "โ™ฅ๏ธ",
- "heartbeat": "๐Ÿ’“",
- "heartpulse": "๐Ÿ’—",
- "hearts": "โ™ฅ",
- "heavy_check_mark": "โœ”๏ธ",
- "heavy_division_sign": "โž—",
- "heavy_dollar_sign": "๐Ÿ’ฒ",
- "heavy_minus_sign": "โž–",
- "heavy_multiplication_x": "โœ–๏ธ",
- "heavy_plus_sign": "โž•",
- "hedgehog": "๐Ÿฆ”",
- "helicopter": "๐Ÿš",
- "herb": "๐ŸŒฟ",
- "hibiscus": "๐ŸŒบ",
- "high_brightness": "๐Ÿ”†",
- "high_heel": "๐Ÿ‘ ",
- "hiking_boot": "๐Ÿฅพ",
- "hindu_temple": "๐Ÿ›•",
- "hippopotamus": "๐Ÿฆ›",
- "hockey": "๐Ÿ’",
- "hole": "๐Ÿ•ณ",
- "honey_pot": "๐Ÿฏ",
- "horse": "๐Ÿด",
- "horse_racing": "๐Ÿ‡",
- "hospital": "๐Ÿฅ",
- "hot_face": "๐Ÿฅต",
- "hot_pepper": "๐ŸŒถ",
- "hot_springs": "โ™จ",
- "hotdog": "๐ŸŒญ",
- "hotel": "๐Ÿจ",
- "hotsprings": "โ™จ๏ธ",
- "hourglass": "โŒ›",
- "hourglass_flowing_sand": "โณ",
- "house": "๐Ÿ ",
- "house_with_garden": "๐Ÿก",
- "houses": "๐Ÿ˜",
- "hugging": "๐Ÿค—",
- "hundred_points": "๐Ÿ’ฏ",
- "hushed": "๐Ÿ˜ฏ",
- "ice": "๐ŸงŠ",
- "ice_cream": "๐Ÿจ",
- "ice_hockey": "๐Ÿ’",
- "ice_skate": "โ›ธ๏ธ",
- "icecream": "๐Ÿฆ",
- "id": "๐Ÿ†”",
- "ideograph_advantage": "๐Ÿ‰",
- "imp": "๐Ÿ‘ฟ",
- "inbox_tray": "๐Ÿ“ฅ",
- "incoming_envelope": "๐Ÿ“จ",
- "index_pointing_up": "โ˜",
- "infinity": "โ™พ",
- "information": "โ„น๏ธ",
- "information_desk_person": "๐Ÿ’",
- "information_source": "โ„น",
- "innocent": "๐Ÿ˜‡",
- "input_numbers": "๐Ÿ”ข",
- "interrobang": "โ‰๏ธ",
- "iphone": "๐Ÿ“ฑ",
- "izakaya_lantern": "๐Ÿฎ",
- "jack_o_lantern": "๐ŸŽƒ",
- "japan": "๐Ÿ—พ",
- "japanese_castle": "๐Ÿฏ",
- "japanese_congratulations_button": "ใŠ—๏ธ",
- "japanese_free_of_charge_button": "๐Ÿˆš",
- "japanese_goblin": "๐Ÿ‘บ",
- "japanese_ogre": "๐Ÿ‘น",
- "japanese_reserved_button": "๐Ÿˆฏ",
- "japanese_secret_button": "ใŠ™๏ธ",
- "japanese_service_charge_button": "๐Ÿˆ‚",
- "jeans": "๐Ÿ‘–",
- "joy": "๐Ÿ˜‚",
- "joy_cat": "๐Ÿ˜น",
- "joystick": "๐Ÿ•น",
- "kaaba": "๐Ÿ•‹",
- "kangaroo": "๐Ÿฆ˜",
- "key": "๐Ÿ”‘",
- "keyboard": "โŒจ๏ธ",
- "keycap_ten": "๐Ÿ”Ÿ",
- "kick_scooter": "๐Ÿ›ด",
- "kimono": "๐Ÿ‘˜",
- "kiss": "๐Ÿ’‹",
- "kissing": "๐Ÿ˜—",
- "kissing_cat": "๐Ÿ˜ฝ",
- "kissing_closed_eyes": "๐Ÿ˜š",
- "kissing_heart": "๐Ÿ˜˜",
- "kissing_smiling_eyes": "๐Ÿ˜™",
- "kitchen_knife": "๐Ÿ”ช",
- "kite": "๐Ÿช",
- "kiwi": "๐Ÿฅ",
- "kiwi_fruit": "๐Ÿฅ",
- "knife": "๐Ÿ”ช",
- "koala": "๐Ÿจ",
- "koko": "๐Ÿˆ",
- "lab_coat": "๐Ÿฅผ",
- "label": "๐Ÿท",
- "lacrosse": "๐Ÿฅ",
- "large_blue_diamond": "๐Ÿ”ท",
- "large_orange_diamond": "๐Ÿ”ถ",
- "last_quarter_moon": "๐ŸŒ—",
- "last_quarter_moon_with_face": "๐ŸŒœ",
- "last_track_button": "โฎ๏ธ",
- "latin_cross": "โœ",
- "laughing": "๐Ÿ˜†",
- "leafy_green": "๐Ÿฅฌ",
- "leaves": "๐Ÿƒ",
- "ledger": "๐Ÿ“’",
- "left_arrow": "โฌ…",
- "left_arrow_curving_right": "โ†ช",
- "left_facing_fist": "๐Ÿค›",
- "left_luggage": "๐Ÿ›…",
- "left_right_arrow": "โ†”",
- "leftfacing_fist": "๐Ÿค›",
- "leftright_arrow": "โ†”๏ธ",
- "leftwards_arrow_with_hook": "โ†ฉ๏ธ",
- "leg": "๐Ÿฆต",
- "lemon": "๐Ÿ‹",
- "leo": "โ™Œ",
- "leopard": "๐Ÿ†",
- "level_slider": "๐ŸŽš",
- "libra": "โ™Ž",
- "light_rail": "๐Ÿšˆ",
- "light_skin_tone": "๐Ÿป",
- "link": "๐Ÿ”—",
- "linked_paperclips": "๐Ÿ–‡",
- "lion_face": "๐Ÿฆ",
- "lips": "๐Ÿ‘„",
- "lipstick": "๐Ÿ’„",
- "lizard": "๐ŸฆŽ",
- "llama": "๐Ÿฆ™",
- "lobster": "๐Ÿฆž",
- "lock": "๐Ÿ”’",
- "lock_with_ink_pen": "๐Ÿ”",
- "lollipop": "๐Ÿญ",
- "loop": "โžฟ",
- "lotion_bottle": "๐Ÿงด",
- "loud_sound": "๐Ÿ”Š",
- "loudspeaker": "๐Ÿ“ข",
- "love_hotel": "๐Ÿฉ",
- "love_letter": "๐Ÿ’Œ",
- "love_you_gesture": "๐ŸคŸ",
- "loveyou_gesture": "๐ŸคŸ",
- "low_brightness": "๐Ÿ”…",
- "luggage": "๐Ÿงณ",
- "lying_face": "๐Ÿคฅ",
- "m": "โ“‚๏ธ",
- "mag": "๐Ÿ”",
- "mag_right": "๐Ÿ”Ž",
- "mage": "๐Ÿง™",
- "magnet": "๐Ÿงฒ",
- "mahjong": "๐Ÿ€„",
- "mailbox": "๐Ÿ“ซ",
- "mailbox_closed": "๐Ÿ“ช",
- "mailbox_with_mail": "๐Ÿ“ฌ",
- "mailbox_with_no_mail": "๐Ÿ“ญ",
- "male_sign": "โ™‚",
- "man": "๐Ÿ‘จ",
- "man_dancing": "๐Ÿ•บ",
- "man_in_suit": "๐Ÿ•ด",
- "man_in_tuxedo": "๐Ÿคต",
- "man_with_chinese_cap": "๐Ÿ‘ฒ",
- "man_with_gua_pi_mao": "๐Ÿ‘ฒ",
- "man_with_turban": "๐Ÿ‘ณ",
- "mango": "๐Ÿฅญ",
- "mans_shoe": "๐Ÿ‘ž",
- "mantelpiece_clock": "๐Ÿ•ฐ",
- "manual_wheelchair": "๐Ÿฆฝ",
- "maple_leaf": "๐Ÿ",
- "martial_arts_uniform": "๐Ÿฅ‹",
- "mask": "๐Ÿ˜ท",
- "massage": "๐Ÿ’†",
- "mate": "๐Ÿง‰",
- "meat_on_bone": "๐Ÿ–",
- "mechanical_arm": "๐Ÿฆพ",
- "mechanical_leg": "๐Ÿฆฟ",
- "medal": "๐Ÿ…",
- "medical_symbol": "โš•",
- "medium_skin_tone": "๐Ÿฝ",
- "mediumdark_skin_tone": "๐Ÿพ",
- "mediumlight_skin_tone": "๐Ÿผ",
- "mega": "๐Ÿ“ฃ",
- "melon": "๐Ÿˆ",
- "memo": "๐Ÿ“",
- "menorah": "๐Ÿ•Ž",
- "mens": "๐Ÿšน",
- "merperson": "๐Ÿงœ",
- "metal": "๐Ÿค˜",
- "metro": "๐Ÿš‡",
- "microbe": "๐Ÿฆ ",
- "microphone": "๐ŸŽค",
- "microscope": "๐Ÿ”ฌ",
- "middle_finger": "๐Ÿ–•",
- "military_medal": "๐ŸŽ–",
- "milk": "๐Ÿฅ›",
- "milky_way": "๐ŸŒŒ",
- "minibus": "๐Ÿš",
- "minidisc": "๐Ÿ’ฝ",
- "mobile_phone_off": "๐Ÿ“ด",
- "money_mouth": "๐Ÿค‘",
- "money_with_wings": "๐Ÿ’ธ",
- "moneybag": "๐Ÿ’ฐ",
- "moneymouth_face": "๐Ÿค‘",
- "monkey": "๐Ÿ’",
- "monkey_face": "๐Ÿต",
- "monorail": "๐Ÿš",
- "moon_cake": "๐Ÿฅฎ",
- "mortar_board": "๐ŸŽ“",
- "mosque": "๐Ÿ•Œ",
- "mosquito": "๐ŸฆŸ",
- "motor_boat": "๐Ÿ›ฅ",
- "motor_scooter": "๐Ÿ›ต",
- "motorcycle": "๐Ÿ",
- "motorized_wheelchair": "๐Ÿฆผ",
- "motorway": "๐Ÿ›ฃ",
- "mount_fuji": "๐Ÿ—ป",
- "mountain": "โ›ฐ๏ธ",
- "mountain_bicyclist": "๐Ÿšต",
- "mountain_cableway": "๐Ÿš ",
- "mountain_railway": "๐Ÿšž",
- "mouse": "๐Ÿญ",
- "mouse2": "๐Ÿ",
- "movie_camera": "๐ŸŽฅ",
- "moyai": "๐Ÿ—ฟ",
- "mrs_claus": "๐Ÿคถ",
- "multiplication_sign": "โœ–",
- "muscle": "๐Ÿ’ช",
- "mushroom": "๐Ÿ„",
- "musical_keyboard": "๐ŸŽน",
- "musical_note": "๐ŸŽต",
- "musical_score": "๐ŸŽผ",
- "mute": "๐Ÿ”‡",
- "nail_care": "๐Ÿ’…",
- "name_badge": "๐Ÿ“›",
- "national_park": "๐Ÿž",
- "nauseated_face": "๐Ÿคข",
- "nazar_amulet": "๐Ÿงฟ",
- "necktie": "๐Ÿ‘”",
- "negative_squared_cross_mark": "โŽ",
- "nerd": "๐Ÿค“",
- "neutral_face": "๐Ÿ˜",
- "new": "๐Ÿ†•",
- "new_moon": "๐ŸŒ‘",
- "new_moon_with_face": "๐ŸŒš",
- "newspaper": "๐Ÿ“ฐ",
- "next_track_button": "โญ๏ธ",
- "ng": "๐Ÿ†–",
- "night_with_stars": "๐ŸŒƒ",
- "nine": "9โƒฃ",
- "no_bell": "๐Ÿ”•",
- "no_bicycles": "๐Ÿšณ",
- "no_entry": "โ›”",
- "no_entry_sign": "๐Ÿšซ",
- "no_good": "๐Ÿ™…",
- "no_mobile_phones": "๐Ÿ“ต",
- "no_mouth": "๐Ÿ˜ถ",
- "no_pedestrians": "๐Ÿšท",
- "no_smoking": "๐Ÿšญ",
- "non-potable_water": "๐Ÿšฑ",
- "nose": "๐Ÿ‘ƒ",
- "notebook": "๐Ÿ““",
- "notebook_with_decorative_cover": "๐Ÿ“”",
- "notes": "๐ŸŽถ",
- "nut_and_bolt": "๐Ÿ”ฉ",
- "o": "โญ•",
- "o_button_blood_type": "๐Ÿ…พ",
- "ocean": "๐ŸŒŠ",
- "octagonal_sign": "๐Ÿ›‘",
- "octopus": "๐Ÿ™",
- "oden": "๐Ÿข",
- "office": "๐Ÿข",
- "oil_drum": "๐Ÿ›ข",
- "ok": "๐Ÿ†—",
- "ok_hand": "๐Ÿ‘Œ",
- "ok_woman": "๐Ÿ™†",
- "old_key": "๐Ÿ—",
- "older_adult": "๐Ÿง“",
- "older_man": "๐Ÿ‘ด",
- "older_person": "๐Ÿง“",
- "older_woman": "๐Ÿ‘ต",
- "om_symbol": "๐Ÿ•‰",
- "on": "๐Ÿ”›",
- "oncoming_automobile": "๐Ÿš˜",
- "oncoming_bus": "๐Ÿš",
- "oncoming_fist": "๐Ÿ‘Š",
- "oncoming_police_car": "๐Ÿš”",
- "oncoming_taxi": "๐Ÿš–",
- "one": "1โƒฃ",
- "onepiece_swimsuit": "๐Ÿฉฑ",
- "onion": "๐Ÿง…",
- "open_file_folder": "๐Ÿ“‚",
- "open_hands": "๐Ÿ‘",
- "open_mouth": "๐Ÿ˜ฎ",
- "ophiuchus": "โ›Ž",
- "orange_book": "๐Ÿ“™",
- "orange_circle": "๐ŸŸ ",
- "orange_heart": "๐Ÿงก",
- "orange_square": "๐ŸŸง",
- "orangutan": "๐Ÿฆง",
- "orthodox_cross": "โ˜ฆ๏ธ",
- "otter": "๐Ÿฆฆ",
- "outbox_tray": "๐Ÿ“ค",
- "owl": "๐Ÿฆ‰",
- "ox": "๐Ÿ‚",
- "oyster": "๐Ÿฆช",
- "p_button": "๐Ÿ…ฟ",
- "package": "๐Ÿ“ฆ",
- "page_facing_up": "๐Ÿ“„",
- "page_with_curl": "๐Ÿ“ƒ",
- "pager": "๐Ÿ“Ÿ",
- "paintbrush": "๐Ÿ–Œ",
- "palm_tree": "๐ŸŒด",
- "palms_up_together": "๐Ÿคฒ",
- "pancakes": "๐Ÿฅž",
- "panda_face": "๐Ÿผ",
- "paperclip": "๐Ÿ“Ž",
- "parachute": "๐Ÿช‚",
- "parrot": "๐Ÿฆœ",
- "part_alternation_mark": "ใ€ฝ",
- "partly_sunny": "โ›…",
- "partying_face": "๐Ÿฅณ",
- "passenger_ship": "๐Ÿ›ณ",
- "passport_control": "๐Ÿ›‚",
- "pause_button": "โธ๏ธ",
- "peace": "โ˜ฎ",
- "peace_symbol": "โ˜ฎ๏ธ",
- "peach": "๐Ÿ‘",
- "peacock": "๐Ÿฆš",
- "peanuts": "๐Ÿฅœ",
- "pear": "๐Ÿ",
- "pen": "๐Ÿ–Š",
- "pencil": "๐Ÿ“",
- "pencil2": "โœ",
- "penguin": "๐Ÿง",
- "pensive": "๐Ÿ˜”",
- "people_with_bunny_ears_partying": "๐Ÿ‘ฏ",
- "people_wrestling": "๐Ÿคผ",
- "performing_arts": "๐ŸŽญ",
- "persevere": "๐Ÿ˜ฃ",
- "person": "๐Ÿง‘",
- "person_biking": "๐Ÿšด",
- "person_bouncing_ball": "โ›น๏ธ",
- "person_bowing": "๐Ÿ™‡",
- "person_cartwheeling": "๐Ÿคธ",
- "person_climbing": "๐Ÿง—",
- "person_doing_cartwheel": "๐Ÿคธ",
- "person_facepalming": "๐Ÿคฆ",
- "person_fencing": "๐Ÿคบ",
- "person_frowning": "๐Ÿ™",
- "person_gesturing_no": "๐Ÿ™…",
- "person_gesturing_ok": "๐Ÿ™†",
- "person_getting_haircut": "๐Ÿ’‡",
- "person_getting_massage": "๐Ÿ’†",
- "person_in_lotus_position": "๐Ÿง˜",
- "person_in_steamy_room": "๐Ÿง–",
- "person_juggling": "๐Ÿคน",
- "person_kneeling": "๐ŸงŽ",
- "person_mountain_biking": "๐Ÿšต",
- "person_playing_handball": "๐Ÿคพ",
- "person_playing_water_polo": "๐Ÿคฝ",
- "person_pouting": "๐Ÿ™Ž",
- "person_raising_hand": "๐Ÿ™‹",
- "person_rowing_boat": "๐Ÿšฃ",
- "person_running": "๐Ÿƒ",
- "person_shrugging": "๐Ÿคท",
- "person_standing": "๐Ÿง",
- "person_surfing": "๐Ÿ„",
- "person_swimming": "๐ŸŠ",
- "person_tipping_hand": "๐Ÿ’",
- "person_walking": "๐Ÿšถ",
- "person_wearing_turban": "๐Ÿ‘ณ",
- "person_with_blond_hair": "๐Ÿ‘ฑ",
- "person_with_pouting_face": "๐Ÿ™Ž",
- "petri_dish": "๐Ÿงซ",
- "pick": "โ›๏ธ",
- "pie": "๐Ÿฅง",
- "pig": "๐Ÿท",
- "pig2": "๐Ÿ–",
- "pig_nose": "๐Ÿฝ",
- "pill": "๐Ÿ’Š",
- "pinching_hand": "๐Ÿค",
- "pineapple": "๐Ÿ",
- "ping_pong": "๐Ÿ“",
- "pisces": "โ™“",
- "pizza": "๐Ÿ•",
- "place_of_worship": "๐Ÿ›",
- "play_button": "โ–ถ",
- "play_or_pause_button": "โฏ๏ธ",
- "play_pause": "โฏ",
- "pleading_face": "๐Ÿฅบ",
- "point_down": "๐Ÿ‘‡",
- "point_left": "๐Ÿ‘ˆ",
- "point_right": "๐Ÿ‘‰",
- "point_up": "โ˜๏ธ",
- "point_up_2": "๐Ÿ‘†",
- "police_car": "๐Ÿš“",
- "police_officer": "๐Ÿ‘ฎ",
- "poodle": "๐Ÿฉ",
- "poop": "๐Ÿ’ฉ",
- "popcorn": "๐Ÿฟ",
- "post_office": "๐Ÿฃ",
- "postal_horn": "๐Ÿ“ฏ",
- "postbox": "๐Ÿ“ฎ",
- "potable_water": "๐Ÿšฐ",
- "potato": "๐Ÿฅ”",
- "pouch": "๐Ÿ‘",
- "poultry_leg": "๐Ÿ—",
- "pound": "๐Ÿ’ท",
- "pouting_cat": "๐Ÿ˜พ",
- "pray": "๐Ÿ™",
- "prayer_beads": "๐Ÿ“ฟ",
- "pregnant_woman": "๐Ÿคฐ",
- "pretzel": "๐Ÿฅจ",
- "prince": "๐Ÿคด",
- "princess": "๐Ÿ‘ธ",
- "printer": "๐Ÿ–จ",
- "probing_cane": "๐Ÿฆฏ",
- "punch": "๐Ÿ‘Š",
- "purple_circle": "๐ŸŸฃ",
- "purple_heart": "๐Ÿ’œ",
- "purse": "๐Ÿ‘›",
- "pushpin": "๐Ÿ“Œ",
- "put_litter_in_its_place": "๐Ÿšฎ",
- "puzzle_piece": "๐Ÿงฉ",
- "question": "โ“",
- "rabbit": "๐Ÿฐ",
- "rabbit2": "๐Ÿ‡",
- "raccoon": "๐Ÿฆ",
- "racehorse": "๐ŸŽ",
- "racing_car": "๐ŸŽ",
- "radio": "๐Ÿ“ป",
- "radio_button": "๐Ÿ”˜",
- "radioactive": "โ˜ข๏ธ",
- "rage": "๐Ÿ˜ก",
- "railway_car": "๐Ÿšƒ",
- "railway_track": "๐Ÿ›ค",
- "rainbow": "๐ŸŒˆ",
- "raised_back_of_hand": "๐Ÿคš",
- "raised_hand": "โœ‹",
- "raised_hands": "๐Ÿ™Œ",
- "raising_hand": "๐Ÿ™‹",
- "ram": "๐Ÿ",
- "ramen": "๐Ÿœ",
- "rat": "๐Ÿ€",
- "razor": "๐Ÿช’",
- "receipt": "๐Ÿงพ",
- "record_button": "โบ๏ธ",
- "recycle": "โ™ป",
- "recycling_symbol": "โ™ป๏ธ",
- "red_car": "๐Ÿš—",
- "red_circle": "๐Ÿ”ด",
- "red_envelope": "๐Ÿงง",
- "red_hair": "๐Ÿฆฐ",
- "red_heart": "โค",
- "red_square": "๐ŸŸฅ",
- "regional_indicator_a": "๐Ÿ‡ฆ",
- "regional_indicator_b": "๐Ÿ‡ง",
- "regional_indicator_c": "๐Ÿ‡จ",
- "regional_indicator_d": "๐Ÿ‡ฉ",
- "regional_indicator_e": "๐Ÿ‡ช",
- "regional_indicator_f": "๐Ÿ‡ซ",
- "regional_indicator_g": "๐Ÿ‡ฌ",
- "regional_indicator_h": "๐Ÿ‡ญ",
- "regional_indicator_i": "๐Ÿ‡ฎ",
- "regional_indicator_j": "๐Ÿ‡ฏ",
- "regional_indicator_k": "๐Ÿ‡ฐ",
- "regional_indicator_l": "๐Ÿ‡ฑ",
- "regional_indicator_m": "๐Ÿ‡ฒ",
- "regional_indicator_n": "๐Ÿ‡ณ",
- "regional_indicator_o": "๐Ÿ‡ด",
- "regional_indicator_p": "๐Ÿ‡ต",
- "regional_indicator_q": "๐Ÿ‡ถ",
- "regional_indicator_r": "๐Ÿ‡ท",
- "regional_indicator_s": "๐Ÿ‡ธ",
- "regional_indicator_t": "๐Ÿ‡น",
- "regional_indicator_u": "๐Ÿ‡บ",
- "regional_indicator_v": "๐Ÿ‡ป",
- "regional_indicator_w": "๐Ÿ‡ผ",
- "regional_indicator_x": "๐Ÿ‡ฝ",
- "regional_indicator_y": "๐Ÿ‡พ",
- "regional_indicator_z": "๐Ÿ‡ฟ",
- "registered": "ยฎ",
- "relieved": "๐Ÿ˜Œ",
- "reminder_ribbon": "๐ŸŽ—",
- "repeat": "๐Ÿ”",
- "repeat_one": "๐Ÿ”‚",
- "rescue_workerโ€™s_helmet": "โ›‘๏ธ",
- "restroom": "๐Ÿšป",
- "reverse_button": "โ—€",
- "revolving_hearts": "๐Ÿ’ž",
- "rewind": "โช",
- "rhino": "๐Ÿฆ",
- "rhinoceros": "๐Ÿฆ",
- "ribbon": "๐ŸŽ€",
- "rice": "๐Ÿš",
- "rice_ball": "๐Ÿ™",
- "rice_cracker": "๐Ÿ˜",
- "rice_scene": "๐ŸŽ‘",
- "right_arrow": "โžก๏ธ",
- "right_arrow_curving_down": "โคต",
- "right_arrow_curving_left": "โ†ฉ",
- "right_arrow_curving_up": "โคด",
- "right_facing_fist": "๐Ÿคœ",
- "rightfacing_fist": "๐Ÿคœ",
- "ring": "๐Ÿ’",
- "ringed_planet": "๐Ÿช",
- "robot": "๐Ÿค–",
- "rocket": "๐Ÿš€",
- "rofl": "๐Ÿคฃ",
- "roll_of_paper": "๐Ÿงป",
- "rolledup_newspaper": "๐Ÿ—ž",
- "roller_coaster": "๐ŸŽข",
- "rolling_eyes": "๐Ÿ™„",
- "rolling_on_the_floor_laughing": "๐Ÿคฃ",
- "rooster": "๐Ÿ“",
- "rose": "๐ŸŒน",
- "rosette": "๐Ÿต",
- "rotating_light": "๐Ÿšจ",
- "round_pushpin": "๐Ÿ“",
- "rowboat": "๐Ÿšฃ",
- "rugby_football": "๐Ÿ‰",
- "runner": "๐Ÿƒ",
- "running_shirt_with_sash": "๐ŸŽฝ",
- "safety_pin": "๐Ÿงท",
- "safety_vest": "๐Ÿฆบ",
- "sagittarius": "โ™",
- "sailboat": "โ›ต",
- "sake": "๐Ÿถ",
- "salad": "๐Ÿฅ—",
- "salt": "๐Ÿง‚",
- "sandal": "๐Ÿ‘ก",
- "sandwich": "๐Ÿฅช",
- "santa": "๐ŸŽ…",
- "sari": "๐Ÿฅป",
- "satellite": "๐Ÿ“ก",
- "sauropod": "๐Ÿฆ•",
- "saxophone": "๐ŸŽท",
- "scales": "โš–",
- "scarf": "๐Ÿงฃ",
- "school": "๐Ÿซ",
- "school_satchel": "๐ŸŽ’",
- "scissors": "โœ‚",
- "scooter": "๐Ÿ›ด",
- "scorpion": "๐Ÿฆ‚",
- "scorpius": "โ™",
- "scream": "๐Ÿ˜ฑ",
- "scream_cat": "๐Ÿ™€",
- "scroll": "๐Ÿ“œ",
- "seat": "๐Ÿ’บ",
- "second_place": "๐Ÿฅˆ",
- "secret": "ใŠ™",
- "see_no_evil": "๐Ÿ™ˆ",
- "seedling": "๐ŸŒฑ",
- "selfie": "๐Ÿคณ",
- "seven": "7โƒฃ",
- "shallow_pan_of_food": "๐Ÿฅ˜",
- "shamrock": "โ˜˜๏ธ",
- "shark": "๐Ÿฆˆ",
- "shaved_ice": "๐Ÿง",
- "sheep": "๐Ÿ‘",
- "shell": "๐Ÿš",
- "shield": "๐Ÿ›ก",
- "shinto_shrine": "โ›ฉ๏ธ",
- "ship": "๐Ÿšข",
- "shirt": "๐Ÿ‘•",
- "shopping_bags": "๐Ÿ›",
- "shopping_cart": "๐Ÿ›’",
- "shorts": "๐Ÿฉณ",
- "shower": "๐Ÿšฟ",
- "shrimp": "๐Ÿฆ",
- "shushing_face": "๐Ÿคซ",
- "sign_of_the_horns": "๐Ÿค˜",
- "signal_strength": "๐Ÿ“ถ",
- "six": "6โƒฃ",
- "six_pointed_star": "๐Ÿ”ฏ",
- "skateboard": "๐Ÿ›น",
- "ski": "๐ŸŽฟ",
- "skier": "โ›ท๏ธ",
- "skull": "๐Ÿ’€",
- "skull_and_crossbones": "โ˜ ๏ธ",
- "skull_crossbones": "โ˜ ",
- "skunk": "๐Ÿฆจ",
- "sled": "๐Ÿ›ท",
- "sleeping": "๐Ÿ˜ด",
- "sleeping_accommodation": "๐Ÿ›Œ",
- "sleepy": "๐Ÿ˜ช",
- "slight_frown": "๐Ÿ™",
- "slight_smile": "๐Ÿ™‚",
- "slightly_frowning_face": "๐Ÿ™",
- "slot_machine": "๐ŸŽฐ",
- "sloth": "๐Ÿฆฅ",
- "small_airplane": "๐Ÿ›ฉ",
- "small_blue_diamond": "๐Ÿ”น",
- "small_orange_diamond": "๐Ÿ”ธ",
- "small_red_triangle": "๐Ÿ”บ",
- "small_red_triangle_down": "๐Ÿ”ป",
- "smile": "๐Ÿ˜„",
- "smile_cat": "๐Ÿ˜ธ",
- "smiley": "๐Ÿ˜ƒ",
- "smiley_cat": "๐Ÿ˜บ",
- "smiling": "โ˜บ๏ธ",
- "smiling_face": "โ˜บ",
- "smiling_face_with_hearts": "๐Ÿฅฐ",
- "smiling_imp": "๐Ÿ˜ˆ",
- "smirk": "๐Ÿ˜",
- "smirk_cat": "๐Ÿ˜ผ",
- "smoking": "๐Ÿšฌ",
- "snail": "๐ŸŒ",
- "snake": "๐Ÿ",
- "sneezing_face": "๐Ÿคง",
- "snowboarder": "๐Ÿ‚",
- "snowcapped_mountain": "๐Ÿ”",
- "snowflake": "โ„",
- "snowman": "โ›„",
- "soap": "๐Ÿงผ",
- "sob": "๐Ÿ˜ญ",
- "soccer": "โšฝ",
- "socks": "๐Ÿงฆ",
- "softball": "๐ŸฅŽ",
- "soon": "๐Ÿ”œ",
- "sos": "๐Ÿ†˜",
- "sound": "๐Ÿ”‰",
- "space_invader": "๐Ÿ‘พ",
- "spade_suit": "โ™ ๏ธ",
- "spades": "โ™ ",
- "spaghetti": "๐Ÿ",
- "sparkle": "โ‡",
- "sparkler": "๐ŸŽ‡",
- "sparkles": "โœจ",
- "sparkling_heart": "๐Ÿ’–",
- "speak_no_evil": "๐Ÿ™Š",
- "speaker": "๐Ÿ”ˆ",
- "speaking_head": "๐Ÿ—ฃ",
- "speech_balloon": "๐Ÿ’ฌ",
- "speech_left": "๐Ÿ—จ",
- "speedboat": "๐Ÿšค",
- "spider": "๐Ÿ•ท",
- "spider_web": "๐Ÿ•ธ",
- "spiral_calendar": "๐Ÿ—“",
- "spiral_notepad": "๐Ÿ—’",
- "sponge": "๐Ÿงฝ",
- "spoon": "๐Ÿฅ„",
- "squid": "๐Ÿฆ‘",
- "stadium": "๐ŸŸ",
- "star": "โญ",
- "star2": "๐ŸŒŸ",
- "star_and_crescent": "โ˜ช๏ธ",
- "star_of_david": "โœก",
- "star_struck": "๐Ÿคฉ",
- "stars": "๐ŸŒ ",
- "starstruck": "๐Ÿคฉ",
- "station": "๐Ÿš‰",
- "statue_of_liberty": "๐Ÿ—ฝ",
- "steam_locomotive": "๐Ÿš‚",
- "stethoscope": "๐Ÿฉบ",
- "stew": "๐Ÿฒ",
- "stop_button": "โน๏ธ",
- "stopwatch": "โฑ๏ธ",
- "straight_ruler": "๐Ÿ“",
- "strawberry": "๐Ÿ“",
- "stuck_out_tongue": "๐Ÿ˜›",
- "stuck_out_tongue_closed_eyes": "๐Ÿ˜",
- "stuck_out_tongue_winking_eye": "๐Ÿ˜œ",
- "studio_microphone": "๐ŸŽ™",
- "stuffed_flatbread": "๐Ÿฅ™",
- "sun": "โ˜€",
- "sun_behind_large_cloud": "๐ŸŒฅ",
- "sun_behind_rain_cloud": "๐ŸŒฆ",
- "sun_behind_small_cloud": "๐ŸŒค",
- "sun_with_face": "๐ŸŒž",
- "sunflower": "๐ŸŒป",
- "sunglasses": "๐Ÿ˜Ž",
- "sunny": "โ˜€๏ธ",
- "sunrise": "๐ŸŒ…",
- "sunrise_over_mountains": "๐ŸŒ„",
- "superhero": "๐Ÿฆธ",
- "supervillain": "๐Ÿฆน",
- "surfer": "๐Ÿ„",
- "sushi": "๐Ÿฃ",
- "suspension_railway": "๐ŸšŸ",
- "swan": "๐Ÿฆข",
- "sweat": "๐Ÿ˜“",
- "sweat_drops": "๐Ÿ’ฆ",
- "sweat_smile": "๐Ÿ˜…",
- "sweet_potato": "๐Ÿ ",
- "swimmer": "๐ŸŠ",
- "symbols": "๐Ÿ”ฃ",
- "synagogue": "๐Ÿ•",
- "syringe": "๐Ÿ’‰",
- "t_rex": "๐Ÿฆ–",
- "taco": "๐ŸŒฎ",
- "tada": "๐ŸŽ‰",
- "takeout_box": "๐Ÿฅก",
- "tanabata_tree": "๐ŸŽ‹",
- "tangerine": "๐ŸŠ",
- "taurus": "โ™‰",
- "taxi": "๐Ÿš•",
- "tea": "๐Ÿต",
- "teddy_bear": "๐Ÿงธ",
- "telephone": "โ˜Ž",
- "telephone_receiver": "๐Ÿ“ž",
- "telescope": "๐Ÿ”ญ",
- "tennis": "๐ŸŽพ",
- "tent": "โ›บ",
- "test_tube": "๐Ÿงช",
- "thermometer": "๐ŸŒก",
- "thermometer_face": "๐Ÿค’",
- "thinking": "๐Ÿค”",
- "third_place": "๐Ÿฅ‰",
- "thought_balloon": "๐Ÿ’ญ",
- "thread": "๐Ÿงต",
- "three": "3โƒฃ",
- "thumbsdown": "๐Ÿ‘Ž",
- "thumbsup": "๐Ÿ‘",
- "ticket": "๐ŸŽซ",
- "tiger": "๐Ÿฏ",
- "tiger2": "๐Ÿ…",
- "timer_clock": "โฒ๏ธ",
- "tired_face": "๐Ÿ˜ซ",
- "tm": "โ„ข",
- "toilet": "๐Ÿšฝ",
- "tokyo_tower": "๐Ÿ—ผ",
- "tomato": "๐Ÿ…",
- "tone1": "๐Ÿป",
- "tone2": "๐Ÿผ",
- "tone3": "๐Ÿฝ",
- "tone4": "๐Ÿพ",
- "tone5": "๐Ÿฟ",
- "tongue": "๐Ÿ‘…",
- "toolbox": "๐Ÿงฐ",
- "tooth": "๐Ÿฆท",
- "top": "๐Ÿ”",
- "tophat": "๐ŸŽฉ",
- "tornado": "๐ŸŒช",
- "track_next": "โญ",
- "track_previous": "โฎ",
- "trackball": "๐Ÿ–ฒ",
- "tractor": "๐Ÿšœ",
- "trade_mark": "โ„ข๏ธ",
- "traffic_light": "๐Ÿšฅ",
- "train": "๐Ÿš‹",
- "train2": "๐Ÿš†",
- "tram": "๐ŸšŠ",
- "trex": "๐Ÿฆ–",
- "triangular_flag_on_post": "๐Ÿšฉ",
- "triangular_ruler": "๐Ÿ“",
- "trident": "๐Ÿ”ฑ",
- "triumph": "๐Ÿ˜ค",
- "trolleybus": "๐ŸšŽ",
- "trophy": "๐Ÿ†",
- "tropical_drink": "๐Ÿน",
- "tropical_fish": "๐Ÿ ",
- "truck": "๐Ÿšš",
- "trumpet": "๐ŸŽบ",
- "tulip": "๐ŸŒท",
- "tumbler_glass": "๐Ÿฅƒ",
- "turkey": "๐Ÿฆƒ",
- "turtle": "๐Ÿข",
- "tv": "๐Ÿ“บ",
- "twisted_rightwards_arrows": "๐Ÿ”€",
- "two": "2โƒฃ",
- "two_hearts": "๐Ÿ’•",
- "two_men_holding_hands": "๐Ÿ‘ฌ",
- "two_women_holding_hands": "๐Ÿ‘ญ",
- "u5272": "๐Ÿˆน",
- "u5408": "๐Ÿˆด",
- "u55b6": "๐Ÿˆบ",
- "u6307": "๐Ÿˆฏ",
- "u6708": "๐Ÿˆท",
- "u6709": "๐Ÿˆถ",
- "u6e80": "๐Ÿˆต",
- "u7121": "๐Ÿˆš",
- "u7533": "๐Ÿˆธ",
- "u7981": "๐Ÿˆฒ",
- "u7a7a": "๐Ÿˆณ",
- "umbrella": "โ˜”",
- "umbrella_on_ground": "โ›ฑ๏ธ",
- "unamused": "๐Ÿ˜’",
- "underage": "๐Ÿ”ž",
- "unicorn": "๐Ÿฆ„",
- "unlock": "๐Ÿ”“",
- "up": "๐Ÿ†™",
- "up_arrow": "โฌ†",
- "updown_arrow": "โ†•๏ธ",
- "upleft_arrow": "โ†–๏ธ",
- "upright_arrow": "โ†—",
- "upside_down": "๐Ÿ™ƒ",
- "v": "โœŒ๏ธ",
- "vampire": "๐Ÿง›",
- "vertical_traffic_light": "๐Ÿšฆ",
- "vhs": "๐Ÿ“ผ",
- "vibration_mode": "๐Ÿ“ณ",
- "victory_hand": "โœŒ",
- "video_camera": "๐Ÿ“น",
- "video_game": "๐ŸŽฎ",
- "violin": "๐ŸŽป",
- "virgo": "โ™",
- "volcano": "๐ŸŒ‹",
- "volleyball": "๐Ÿ",
- "vs": "๐Ÿ†š",
- "vulcan": "๐Ÿ––",
- "vulcan_salute": "๐Ÿ––",
- "waffle": "๐Ÿง‡",
- "walking": "๐Ÿšถ",
- "waning_crescent_moon": "๐ŸŒ˜",
- "waning_gibbous_moon": "๐ŸŒ–",
- "warning": "โš ",
- "wastebasket": "๐Ÿ—‘",
- "watch": "โŒš",
- "water_buffalo": "๐Ÿƒ",
- "watermelon": "๐Ÿ‰",
- "wave": "๐Ÿ‘‹",
- "wavy_dash": "ใ€ฐ๏ธ",
- "waxing_crescent_moon": "๐ŸŒ’",
- "waxing_gibbous_moon": "๐ŸŒ”",
- "wc": "๐Ÿšพ",
- "weary": "๐Ÿ˜ฉ",
- "wedding": "๐Ÿ’’",
- "weightlifter": "๐Ÿ‹",
- "whale": "๐Ÿณ",
- "whale2": "๐Ÿ‹",
- "wheel_of_dharma": "โ˜ธ๏ธ",
- "wheelchair": "โ™ฟ",
- "white_check_mark": "โœ…",
- "white_circle": "โšช",
- "white_flower": "๐Ÿ’ฎ",
- "white_hair": "๐Ÿฆณ",
- "white_heart": "๐Ÿค",
- "white_large_square": "โฌœ",
- "white_medium_small_square": "โ—ฝ",
- "white_medium_square": "โ—ป๏ธ",
- "white_small_square": "โ–ซ๏ธ",
- "white_square_button": "๐Ÿ”ณ",
- "wilted_flower": "๐Ÿฅ€",
- "wilted_rose": "๐Ÿฅ€",
- "wind_blowing_face": "๐ŸŒฌ",
- "wind_chime": "๐ŸŽ",
- "wine_glass": "๐Ÿท",
- "wink": "๐Ÿ˜‰",
- "wolf": "๐Ÿบ",
- "woman": "๐Ÿ‘ฉ",
- "woman_with_headscarf": "๐Ÿง•",
- "womans_clothes": "๐Ÿ‘š",
- "womans_hat": "๐Ÿ‘’",
- "womens": "๐Ÿšบ",
- "woozy_face": "๐Ÿฅด",
- "world_map": "๐Ÿ—บ",
- "worried": "๐Ÿ˜Ÿ",
- "wrench": "๐Ÿ”ง",
- "writing_hand": "โœ๏ธ",
- "x": "โŒ",
- "yarn": "๐Ÿงถ",
- "yawning_face": "๐Ÿฅฑ",
- "yellow_circle": "๐ŸŸก",
- "yellow_heart": "๐Ÿ’›",
- "yellow_square": "๐ŸŸจ",
- "yen": "๐Ÿ’ด",
- "yin_yang": "โ˜ฏ๏ธ",
- "yoyo": "๐Ÿช€",
- "yum": "๐Ÿ˜‹",
- "zany_face": "๐Ÿคช",
- "zap": "โšก",
- "zebra": "๐Ÿฆ“",
- "zero": "0โƒฃ",
- "zipper_mouth": "๐Ÿค",
- "zombie": "๐ŸงŸ",
- "zzz": "๐Ÿ’ค"
-} \ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 2fefe76f..a22ab65d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1629,6 +1629,11 @@
dependencies:
pointer-tracker "^2.0.3"
+"@kazvmoe-infra/unicode-emoji-json@^0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@kazvmoe-infra/unicode-emoji-json/-/unicode-emoji-json-0.4.0.tgz#555bab2f8d11db74820ef0a2fbe2805b17c22587"
+ integrity sha512-22OffREdHzD0U6A/W4RaFPV8NR73za6euibtAxNxO/fu5A6TwxRO2lAdbDWKJH9COv/vYs8zqfEiSalXH2nXJA==
+
"@nightwatch/chai@5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@nightwatch/chai/-/chai-5.0.2.tgz#86b20908fc090dffd5c9567c0392bc6a494cc2e6"
@@ -5733,6 +5738,11 @@ lower-case@^2.0.2:
dependencies:
tslib "^2.0.3"
+lozad@^1.16.0:
+ version "1.16.0"
+ resolved "https://registry.yarnpkg.com/lozad/-/lozad-1.16.0.tgz#86ce732c64c69926ccdebb81c8c90bb3735948b4"
+ integrity sha512-JBr9WjvEFeKoyim3svo/gsQPTkgG/mOHJmDctZ/+U9H3ymUuvEkqpn8bdQMFsvTMcyRJrdJkLv0bXqGm0sP72w==
+
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"