diff --git a/src/app/organisms/emoji-board/EmojiBoard.jsx b/src/app/organisms/emoji-board/EmojiBoard.jsx index 1e61abd..864a0bf 100644 --- a/src/app/organisms/emoji-board/EmojiBoard.jsx +++ b/src/app/organisms/emoji-board/EmojiBoard.jsx @@ -12,6 +12,7 @@ import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import navigation from '../../../client/state/navigation'; import AsyncSearch from '../../../util/AsyncSearch'; +import { addRecentEmoji, getRecentEmojis } from './recent'; import Text from '../../atoms/text/Text'; import RawIcon from '../../atoms/system-icons/RawIcon'; @@ -20,6 +21,7 @@ import Input from '../../atoms/input/Input'; import ScrollView from '../../atoms/scroll/ScrollView'; import SearchIC from '../../../../public/res/ic/outlined/search.svg'; +import RecentClockIC from '../../../../public/res/ic/outlined/recent-clock.svg'; import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg'; import DogIC from '../../../../public/res/ic/outlined/dog.svg'; import CupIC from '../../../../public/res/ic/outlined/cup.svg'; @@ -29,10 +31,11 @@ import BulbIC from '../../../../public/res/ic/outlined/bulb.svg'; import PeaceIC from '../../../../public/res/ic/outlined/peace.svg'; import FlagIC from '../../../../public/res/ic/outlined/flag.svg'; +const ROW_EMOJIS_COUNT = 7; + const EmojiGroup = React.memo(({ name, groupEmojis }) => { function getEmojiBoard() { const emojiBoard = []; - const ROW_EMOJIS_COUNT = 7; const totalEmojis = groupEmojis.length; for (let r = 0; r < totalEmojis; r += ROW_EMOJIS_COUNT) { @@ -147,8 +150,9 @@ function EmojiBoard({ onSelect, searchRef }) { function selectEmoji(e) { if (isTargetNotEmoji(e.target)) return; - const emoji = e.target; - onSelect(getEmojiDataFromTarget(emoji)); + const emoji = getEmojiDataFromTarget(e.target); + onSelect(emoji); + if (emoji.hexcode) addRecentEmoji(emoji.unicode); } function setEmojiInfo(emoji) { @@ -188,6 +192,9 @@ function EmojiBoard({ onSelect, searchRef }) { } const [availableEmojis, setAvailableEmojis] = useState([]); + const [recentEmojis, setRecentEmojis] = useState([]); + + const recentOffset = recentEmojis.length > 0 ? 1 : 0; useEffect(() => { const updateAvailableEmoji = (selectedRoomId) => { @@ -215,6 +222,9 @@ function EmojiBoard({ onSelect, searchRef }) { const onOpen = () => { searchRef.current.value = ''; handleSearchChange(); + + // only update when board is getting opened to prevent shifting UI + setRecentEmojis(getRecentEmojis(3 * ROW_EMOJIS_COUNT)); }; navigation.on(cons.events.navigation.ROOM_SELECTED, updateAvailableEmoji); @@ -230,7 +240,7 @@ function EmojiBoard({ onSelect, searchRef }) { const $emojiContent = scrollEmojisRef.current.firstElementChild; const groupCount = $emojiContent.childElementCount; if (groupCount > emojiGroups.length) { - tabIndex += groupCount - emojiGroups.length - availableEmojis.length; + tabIndex += groupCount - emojiGroups.length - availableEmojis.length - recentOffset; } $emojiContent.children[tabIndex].scrollIntoView(); } @@ -246,6 +256,7 @@ function EmojiBoard({ onSelect, searchRef }) {
+ {recentEmojis.length > 0 && } { availableEmojis.map((pack) => (
+ {recentEmojis.length > 0 && ( + openGroup(0)} + src={RecentClockIC} + tooltip="Recent" + tooltipPlacement="right" + /> + )}
{ availableEmojis.map((pack) => { const src = initMatrix.matrixClient.mxcUrlToHttp(pack.avatar ?? pack.images[0].mxc); return ( openGroup(pack.packIndex)} + onClick={() => openGroup(recentOffset + pack.packIndex)} src={src} key={pack.packIndex} tooltip={pack.displayName} @@ -301,7 +320,7 @@ function EmojiBoard({ onSelect, searchRef }) { [7, FlagIC, 'Flags'], ].map(([indx, ico, name]) => ( openGroup(availableEmojis.length + indx)} + onClick={() => openGroup(recentOffset + availableEmojis.length + indx)} key={indx} src={ico} tooltip={name} diff --git a/src/app/organisms/emoji-board/recent.js b/src/app/organisms/emoji-board/recent.js new file mode 100644 index 0000000..d175f26 --- /dev/null +++ b/src/app/organisms/emoji-board/recent.js @@ -0,0 +1,36 @@ +import initMatrix from '../../../client/initMatrix'; +import { emojis } from './emoji'; + +const eventType = 'io.element.recent_emoji'; + +function getRecentEmojisRaw() { + return initMatrix.matrixClient.getAccountData(eventType).getContent().recent_emoji ?? []; +} + +export function getRecentEmojis(limit) { + const res = []; + getRecentEmojisRaw() + .sort((a, b) => b[1] - a[1]) + .find(([unicode]) => { + const emoji = emojis.find((e) => e.unicode === unicode); + if (emoji) return res.push(emoji) >= limit; + return false; + }); + return res; +} + +export function addRecentEmoji(unicode) { + const recent = getRecentEmojisRaw(); + const i = recent.findIndex(([u]) => u === unicode); + let entry; + if (i < 0) { + entry = [unicode, 1]; + } else { + [entry] = recent.splice(i, 1); + entry[1] += 1; + } + recent.unshift(entry); + initMatrix.matrixClient.setAccountData(eventType, { + recent_emoji: recent.slice(0, 100), + }); +} diff --git a/src/app/organisms/room/RoomViewCmdBar.jsx b/src/app/organisms/room/RoomViewCmdBar.jsx index 0c72319..4ccb20a 100644 --- a/src/app/organisms/room/RoomViewCmdBar.jsx +++ b/src/app/organisms/room/RoomViewCmdBar.jsx @@ -21,6 +21,7 @@ import AsyncSearch from '../../../util/AsyncSearch'; import Text from '../../atoms/text/Text'; import ScrollView from '../../atoms/scroll/ScrollView'; import FollowingMembers from '../../molecules/following-members/FollowingMembers'; +import { addRecentEmoji } from '../emoji-board/recent'; const commands = [{ name: 'markdown', @@ -237,6 +238,7 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) { viewEvent.emit('cmd_fired'); } if (myCmd.prefix === ':') { + if (!myCmd.result.mxc) addRecentEmoji(myCmd.result.unicode); viewEvent.emit('cmd_fired', { replace: myCmd.result.mxc ? `:${myCmd.result.shortcode}: ` : myCmd.result.unicode, });