From ac6459aca99cb8f599ba7c2d608c625f710fe1c0 Mon Sep 17 00:00:00 2001 From: flisk Date: Fri, 10 Mar 2023 19:10:42 +0000 Subject: [PATCH] add recently used emojis panel to emoji picker (#283) ~~(not intended for merging yet, just submitting this for preliminary review and discussion)~~ this patch adds a tab with recently used emojis to the emoji picker: https://akko.lain.gay/notice/ASoGCtyoiXbYPJjqpk there's a couple of things i'm ~~still trying to work out~~ not totally happy with and i'd appreciate any feedback on them: * the recentEmojis getter is called very frequently and has to do a possibly somewhat expensive lookup of emoji objects by their `displayName` each time, which i'm not sure is ideal * ~~emoji reactions on posts added through the picker are picked up by the recentEmojis module, but clicks on existing emoji reactions are not, because `addReaction` in `react_button.js` only currently receives the replacement and not the full emoji object (if there even is one wherever that method is called from)~~ this works now and does the same stupid full search of all emojis by their name which i guess is less bad because this only happens when you hit a reaction emoji button that already existed Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/283 Co-authored-by: flisk Co-committed-by: flisk --- src/components/emoji_picker/emoji_picker.js | 11 ++++ .../emoji_reactions/emoji_reactions.js | 7 +++ src/i18n/en.json | 3 +- src/lib/persisted_state.js | 3 +- src/main.js | 7 ++- src/modules/recentEmojis.js | 50 +++++++++++++++++++ 6 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 src/modules/recentEmojis.js diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 76934e53..c0391f6c 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -51,6 +51,7 @@ const EmojiPicker = { onEmoji (emoji) { const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen }) + this.$store.commit('emojiUsed', emoji) }, onWheel (e) { e.preventDefault() @@ -96,6 +97,7 @@ const EmojiPicker = { ) }, emojis () { + const recentEmojis = this.$store.getters.recentEmojis const standardEmojis = this.$store.state.instance.emoji || [] const customEmojis = this.sortedEmoji const emojiPacks = [] @@ -108,6 +110,15 @@ const EmojiPicker = { }) }) return [ + { + id: 'recent', + text: this.$t('emoji.recent'), + first: { + imageUrl: '', + replacement: '🕒', + }, + emojis: this.filterByKeyword(recentEmojis) + }, { id: 'standard', text: this.$t('emoji.unicode'), diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js index 06e21f6e..75ab252e 100644 --- a/src/components/emoji_reactions/emoji_reactions.js +++ b/src/components/emoji_reactions/emoji_reactions.js @@ -3,6 +3,11 @@ import UserListPopover from '../user_list_popover/user_list_popover.vue' const EMOJI_REACTION_COUNT_CUTOFF = 12 +const findEmojiByReplacement = (state, replacement) => { + const allEmojis = state.instance.emoji.concat(state.instance.customEmoji) + return allEmojis.find(emoji => emoji.replacement === replacement) +} + const EmojiReactions = { name: 'EmojiReactions', components: { @@ -54,6 +59,8 @@ const EmojiReactions = { }, reactWith (emoji) { this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji }) + const emojiObject = findEmojiByReplacement(this.$store.state, emoji) + this.$store.commit('emojiUsed', emojiObject) }, unreact (emoji) { this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji }) diff --git a/src/i18n/en.json b/src/i18n/en.json index 5739b1b0..cd017950 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -86,7 +86,8 @@ "load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.", "search_emoji": "Search for an emoji", "stickers": "Stickers", - "unicode": "Unicode emoji" + "unicode": "Unicode emoji", + "recent": "Recently used" }, "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/lib/persisted_state.js b/src/lib/persisted_state.js index 735a10de..a9a9d307 100644 --- a/src/lib/persisted_state.js +++ b/src/lib/persisted_state.js @@ -19,7 +19,8 @@ const saveImmedeatelyActions = [ 'setOption', 'setClientData', 'setToken', - 'clearToken' + 'clearToken', + 'emojiUsed', ] const defaultStorage = (() => { diff --git a/src/main.js b/src/main.js index 01a44c96..cad887e5 100644 --- a/src/main.js +++ b/src/main.js @@ -22,6 +22,7 @@ import announcementsModule from './modules/announcements.js' import editStatusModule from './modules/editStatus.js' import statusHistoryModule from './modules/statusHistory.js' import tagModule from './modules/tags.js' +import recentEmojisModule from './modules/recentEmojis.js' import { createI18n } from 'vue-i18n' @@ -47,7 +48,8 @@ const persistedStateOptions = { paths: [ 'config', 'users.lastLoginName', - 'oauth' + 'oauth', + 'recentEmojis.emojis', ] }; @@ -98,7 +100,8 @@ const persistedStateOptions = { announcements: announcementsModule, editStatus: editStatusModule, statusHistory: statusHistoryModule, - tags: tagModule + tags: tagModule, + recentEmojis: recentEmojisModule, }, plugins, strict: false // Socket modifies itself, let's ignore this for now. diff --git a/src/modules/recentEmojis.js b/src/modules/recentEmojis.js new file mode 100644 index 00000000..baab1c52 --- /dev/null +++ b/src/modules/recentEmojis.js @@ -0,0 +1,50 @@ +// each row is 7 emojis, 6 rows chosen arbitrarily. i don't think more than +// that are going to be useful. +const RECENT_MAX = 7 * 6 + +const defaultState = { + emojis: [], +} + +const recentEmojis = { + state: defaultState, + + mutations: { + emojiUsed ({ emojis }, emoji) { + if (emoji.displayText === undefined || emoji.displayText === null) { + console.error('emojiUsed was called with a bad emoji object: ', emoji) + return + } else if (emoji.displayText.includes('@')) { + console.error('emojiUsed was called with a remote emoji: ', emoji) + return + } + + const i = emojis.indexOf(emoji.displayText) + + if (i === -1) { + // not in `emojis` yet, insert and truncate if necessary + const newLength = emojis.unshift(emoji.displayText) + if (newLength > RECENT_MAX) { + emojis.pop() + } + } else if (i !== 0) { + // emoji is already in `emojis` but needs to be bumped to the top + emojis.splice(i, 1) + emojis.unshift(emoji.displayText) + } + }, + }, + + getters: { + recentEmojis: (state, getters, rootState) => state.emojis.reduce((objects, displayText) => { + const allEmojis = rootState.instance.emoji.concat(rootState.instance.customEmoji) + let emojiObject = allEmojis.find(emoji => emoji.displayText === displayText) + if (emojiObject !== undefined) { + objects.push(emojiObject) + } + return objects + }, []), + }, +} + +export default recentEmojis