Add recently used section to emoji board (#373)
* Add recent section to emoji board * Add section to emoji board sidebar * Add emoji limit like element web has * Ignore custom emojis * Filter out invalid emojis * Update heart icon with clock Co-authored-by: Krishan <33421343+kfiven@users.noreply.github.com>
This commit is contained in:
parent
5a299b21c5
commit
b698982186
3 changed files with 63 additions and 6 deletions
|
@ -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 }) {
|
|||
<ScrollView ref={scrollEmojisRef} autoHide>
|
||||
<div onMouseMove={hoverEmoji} onClick={selectEmoji}>
|
||||
<SearchedEmoji />
|
||||
{recentEmojis.length > 0 && <EmojiGroup name="Recently used" groupEmojis={recentEmojis} />}
|
||||
{
|
||||
availableEmojis.map((pack) => (
|
||||
<EmojiGroup
|
||||
|
@ -271,13 +282,21 @@ function EmojiBoard({ onSelect, searchRef }) {
|
|||
</div>
|
||||
<ScrollView invisible>
|
||||
<div className="emoji-board__nav">
|
||||
{recentEmojis.length > 0 && (
|
||||
<IconButton
|
||||
onClick={() => openGroup(0)}
|
||||
src={RecentClockIC}
|
||||
tooltip="Recent"
|
||||
tooltipPlacement="right"
|
||||
/>
|
||||
)}
|
||||
<div className="emoji-board__nav-custom">
|
||||
{
|
||||
availableEmojis.map((pack) => {
|
||||
const src = initMatrix.matrixClient.mxcUrlToHttp(pack.avatar ?? pack.images[0].mxc);
|
||||
return (
|
||||
<IconButton
|
||||
onClick={() => 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]) => (
|
||||
<IconButton
|
||||
onClick={() => openGroup(availableEmojis.length + indx)}
|
||||
onClick={() => openGroup(recentOffset + availableEmojis.length + indx)}
|
||||
key={indx}
|
||||
src={ico}
|
||||
tooltip={name}
|
||||
|
|
36
src/app/organisms/emoji-board/recent.js
Normal file
36
src/app/organisms/emoji-board/recent.js
Normal file
|
@ -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),
|
||||
});
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue