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 cons from '../../../client/state/cons';
|
||||||
import navigation from '../../../client/state/navigation';
|
import navigation from '../../../client/state/navigation';
|
||||||
import AsyncSearch from '../../../util/AsyncSearch';
|
import AsyncSearch from '../../../util/AsyncSearch';
|
||||||
|
import { addRecentEmoji, getRecentEmojis } from './recent';
|
||||||
|
|
||||||
import Text from '../../atoms/text/Text';
|
import Text from '../../atoms/text/Text';
|
||||||
import RawIcon from '../../atoms/system-icons/RawIcon';
|
import RawIcon from '../../atoms/system-icons/RawIcon';
|
||||||
|
@ -20,6 +21,7 @@ import Input from '../../atoms/input/Input';
|
||||||
import ScrollView from '../../atoms/scroll/ScrollView';
|
import ScrollView from '../../atoms/scroll/ScrollView';
|
||||||
|
|
||||||
import SearchIC from '../../../../public/res/ic/outlined/search.svg';
|
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 EmojiIC from '../../../../public/res/ic/outlined/emoji.svg';
|
||||||
import DogIC from '../../../../public/res/ic/outlined/dog.svg';
|
import DogIC from '../../../../public/res/ic/outlined/dog.svg';
|
||||||
import CupIC from '../../../../public/res/ic/outlined/cup.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 PeaceIC from '../../../../public/res/ic/outlined/peace.svg';
|
||||||
import FlagIC from '../../../../public/res/ic/outlined/flag.svg';
|
import FlagIC from '../../../../public/res/ic/outlined/flag.svg';
|
||||||
|
|
||||||
|
const ROW_EMOJIS_COUNT = 7;
|
||||||
|
|
||||||
const EmojiGroup = React.memo(({ name, groupEmojis }) => {
|
const EmojiGroup = React.memo(({ name, groupEmojis }) => {
|
||||||
function getEmojiBoard() {
|
function getEmojiBoard() {
|
||||||
const emojiBoard = [];
|
const emojiBoard = [];
|
||||||
const ROW_EMOJIS_COUNT = 7;
|
|
||||||
const totalEmojis = groupEmojis.length;
|
const totalEmojis = groupEmojis.length;
|
||||||
|
|
||||||
for (let r = 0; r < totalEmojis; r += ROW_EMOJIS_COUNT) {
|
for (let r = 0; r < totalEmojis; r += ROW_EMOJIS_COUNT) {
|
||||||
|
@ -147,8 +150,9 @@ function EmojiBoard({ onSelect, searchRef }) {
|
||||||
function selectEmoji(e) {
|
function selectEmoji(e) {
|
||||||
if (isTargetNotEmoji(e.target)) return;
|
if (isTargetNotEmoji(e.target)) return;
|
||||||
|
|
||||||
const emoji = e.target;
|
const emoji = getEmojiDataFromTarget(e.target);
|
||||||
onSelect(getEmojiDataFromTarget(emoji));
|
onSelect(emoji);
|
||||||
|
if (emoji.hexcode) addRecentEmoji(emoji.unicode);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setEmojiInfo(emoji) {
|
function setEmojiInfo(emoji) {
|
||||||
|
@ -188,6 +192,9 @@ function EmojiBoard({ onSelect, searchRef }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [availableEmojis, setAvailableEmojis] = useState([]);
|
const [availableEmojis, setAvailableEmojis] = useState([]);
|
||||||
|
const [recentEmojis, setRecentEmojis] = useState([]);
|
||||||
|
|
||||||
|
const recentOffset = recentEmojis.length > 0 ? 1 : 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateAvailableEmoji = (selectedRoomId) => {
|
const updateAvailableEmoji = (selectedRoomId) => {
|
||||||
|
@ -215,6 +222,9 @@ function EmojiBoard({ onSelect, searchRef }) {
|
||||||
const onOpen = () => {
|
const onOpen = () => {
|
||||||
searchRef.current.value = '';
|
searchRef.current.value = '';
|
||||||
handleSearchChange();
|
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);
|
navigation.on(cons.events.navigation.ROOM_SELECTED, updateAvailableEmoji);
|
||||||
|
@ -230,7 +240,7 @@ function EmojiBoard({ onSelect, searchRef }) {
|
||||||
const $emojiContent = scrollEmojisRef.current.firstElementChild;
|
const $emojiContent = scrollEmojisRef.current.firstElementChild;
|
||||||
const groupCount = $emojiContent.childElementCount;
|
const groupCount = $emojiContent.childElementCount;
|
||||||
if (groupCount > emojiGroups.length) {
|
if (groupCount > emojiGroups.length) {
|
||||||
tabIndex += groupCount - emojiGroups.length - availableEmojis.length;
|
tabIndex += groupCount - emojiGroups.length - availableEmojis.length - recentOffset;
|
||||||
}
|
}
|
||||||
$emojiContent.children[tabIndex].scrollIntoView();
|
$emojiContent.children[tabIndex].scrollIntoView();
|
||||||
}
|
}
|
||||||
|
@ -246,6 +256,7 @@ function EmojiBoard({ onSelect, searchRef }) {
|
||||||
<ScrollView ref={scrollEmojisRef} autoHide>
|
<ScrollView ref={scrollEmojisRef} autoHide>
|
||||||
<div onMouseMove={hoverEmoji} onClick={selectEmoji}>
|
<div onMouseMove={hoverEmoji} onClick={selectEmoji}>
|
||||||
<SearchedEmoji />
|
<SearchedEmoji />
|
||||||
|
{recentEmojis.length > 0 && <EmojiGroup name="Recently used" groupEmojis={recentEmojis} />}
|
||||||
{
|
{
|
||||||
availableEmojis.map((pack) => (
|
availableEmojis.map((pack) => (
|
||||||
<EmojiGroup
|
<EmojiGroup
|
||||||
|
@ -271,13 +282,21 @@ function EmojiBoard({ onSelect, searchRef }) {
|
||||||
</div>
|
</div>
|
||||||
<ScrollView invisible>
|
<ScrollView invisible>
|
||||||
<div className="emoji-board__nav">
|
<div className="emoji-board__nav">
|
||||||
|
{recentEmojis.length > 0 && (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => openGroup(0)}
|
||||||
|
src={RecentClockIC}
|
||||||
|
tooltip="Recent"
|
||||||
|
tooltipPlacement="right"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="emoji-board__nav-custom">
|
<div className="emoji-board__nav-custom">
|
||||||
{
|
{
|
||||||
availableEmojis.map((pack) => {
|
availableEmojis.map((pack) => {
|
||||||
const src = initMatrix.matrixClient.mxcUrlToHttp(pack.avatar ?? pack.images[0].mxc);
|
const src = initMatrix.matrixClient.mxcUrlToHttp(pack.avatar ?? pack.images[0].mxc);
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => openGroup(pack.packIndex)}
|
onClick={() => openGroup(recentOffset + pack.packIndex)}
|
||||||
src={src}
|
src={src}
|
||||||
key={pack.packIndex}
|
key={pack.packIndex}
|
||||||
tooltip={pack.displayName}
|
tooltip={pack.displayName}
|
||||||
|
@ -301,7 +320,7 @@ function EmojiBoard({ onSelect, searchRef }) {
|
||||||
[7, FlagIC, 'Flags'],
|
[7, FlagIC, 'Flags'],
|
||||||
].map(([indx, ico, name]) => (
|
].map(([indx, ico, name]) => (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => openGroup(availableEmojis.length + indx)}
|
onClick={() => openGroup(recentOffset + availableEmojis.length + indx)}
|
||||||
key={indx}
|
key={indx}
|
||||||
src={ico}
|
src={ico}
|
||||||
tooltip={name}
|
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 Text from '../../atoms/text/Text';
|
||||||
import ScrollView from '../../atoms/scroll/ScrollView';
|
import ScrollView from '../../atoms/scroll/ScrollView';
|
||||||
import FollowingMembers from '../../molecules/following-members/FollowingMembers';
|
import FollowingMembers from '../../molecules/following-members/FollowingMembers';
|
||||||
|
import { addRecentEmoji } from '../emoji-board/recent';
|
||||||
|
|
||||||
const commands = [{
|
const commands = [{
|
||||||
name: 'markdown',
|
name: 'markdown',
|
||||||
|
@ -237,6 +238,7 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) {
|
||||||
viewEvent.emit('cmd_fired');
|
viewEvent.emit('cmd_fired');
|
||||||
}
|
}
|
||||||
if (myCmd.prefix === ':') {
|
if (myCmd.prefix === ':') {
|
||||||
|
if (!myCmd.result.mxc) addRecentEmoji(myCmd.result.unicode);
|
||||||
viewEvent.emit('cmd_fired', {
|
viewEvent.emit('cmd_fired', {
|
||||||
replace: myCmd.result.mxc ? `:${myCmd.result.shortcode}: ` : myCmd.result.unicode,
|
replace: myCmd.result.mxc ? `:${myCmd.result.shortcode}: ` : myCmd.result.unicode,
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue