chore: bug fix cleanup (#4)
This commit is contained in:
@@ -103,6 +103,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
background-color: var(--background-tertiary);
|
||||
}
|
||||
|
||||
.listWrapper {
|
||||
@@ -118,6 +119,33 @@
|
||||
padding: var(--spacing-3) var(--spacing-2) 0;
|
||||
}
|
||||
|
||||
.emptyState {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.emptyStateInner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--text-primary-muted);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.emptyIcon {
|
||||
font-size: 42px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.emptyLabel {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
import {useLingui} from '@lingui/react/macro';
|
||||
import {SmileySadIcon} from '@phosphor-icons/react';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
import * as EmojiPickerActionCreators from '~/actions/EmojiPickerActionCreators';
|
||||
@@ -37,7 +38,7 @@ import {useSearchInputAutofocus} from '~/hooks/useSearchInputAutofocus';
|
||||
import {ComponentDispatch} from '~/lib/ComponentDispatch';
|
||||
import UnicodeEmojis, {EMOJI_SPRITES} from '~/lib/UnicodeEmojis';
|
||||
import ChannelStore from '~/stores/ChannelStore';
|
||||
import EmojiStore, {type Emoji} from '~/stores/EmojiStore';
|
||||
import EmojiStore, {type Emoji, normalizeEmojiSearchQuery} from '~/stores/EmojiStore';
|
||||
import {checkEmojiAvailability, shouldShowEmojiPremiumUpsell} from '~/utils/ExpressionPermissionUtils';
|
||||
import {shouldShowPremiumFeatures} from '~/utils/PremiumUtils';
|
||||
|
||||
@@ -53,14 +54,16 @@ export const EmojiPicker = observer(
|
||||
const [searchTerm, setSearchTerm] = React.useState('');
|
||||
const [hoveredEmoji, setHoveredEmoji] = React.useState<Emoji | null>(null);
|
||||
const [renderedEmojis, setRenderedEmojis] = React.useState<Array<Emoji>>([]);
|
||||
const [allEmojis, setAllEmojis] = React.useState<Array<Emoji>>([]);
|
||||
const [selectedRow, setSelectedRow] = React.useState(-1);
|
||||
const [selectedColumn, setSelectedColumn] = React.useState(-1);
|
||||
const [shouldScrollOnSelection, setShouldScrollOnSelection] = React.useState(false);
|
||||
const scrollerRef = React.useRef<ScrollerHandle>(null);
|
||||
const searchInputRef = React.useRef<HTMLInputElement>(null);
|
||||
const emojiRefs = React.useRef<Map<string, HTMLButtonElement>>(new Map());
|
||||
const normalizedSearchTerm = React.useMemo(() => normalizeEmojiSearchQuery(searchTerm), [searchTerm]);
|
||||
|
||||
const {i18n} = useLingui();
|
||||
const {i18n, t} = useLingui();
|
||||
const channel = channelId ? (ChannelStore.getChannel(channelId) ?? null) : null;
|
||||
const categoryRefs = React.useRef<Map<string, HTMLDivElement>>(new Map());
|
||||
const forceUpdate = useForceUpdate();
|
||||
@@ -81,7 +84,7 @@ export const EmojiPicker = observer(
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const emojis = EmojiStore.search(channel, searchTerm).slice();
|
||||
const emojis = EmojiStore.search(channel, normalizedSearchTerm).slice();
|
||||
setRenderedEmojis(emojis);
|
||||
if (emojis.length > 0) {
|
||||
setSelectedRow(0);
|
||||
@@ -91,7 +94,12 @@ export const EmojiPicker = observer(
|
||||
setSelectedColumn(-1);
|
||||
setHoveredEmoji(null);
|
||||
}
|
||||
}, [channel, searchTerm]);
|
||||
}, [channel, normalizedSearchTerm]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const emojis = EmojiStore.search(channel, '').slice();
|
||||
setAllEmojis(emojis);
|
||||
}, [channel]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return ComponentDispatch.subscribe('EMOJI_PICKER_RERENDER', forceUpdate);
|
||||
@@ -99,10 +107,13 @@ export const EmojiPicker = observer(
|
||||
|
||||
useSearchInputAutofocus(searchInputRef);
|
||||
|
||||
const {favoriteEmojis, frequentlyUsedEmojis, customEmojisByGuildId, unicodeEmojisByCategory} =
|
||||
useEmojiCategories(renderedEmojis);
|
||||
const {favoriteEmojis, frequentlyUsedEmojis, customEmojisByGuildId, unicodeEmojisByCategory} = useEmojiCategories(
|
||||
allEmojis,
|
||||
renderedEmojis,
|
||||
);
|
||||
const showFrequentlyUsedButton = frequentlyUsedEmojis.length > 0 && !normalizedSearchTerm;
|
||||
const virtualRows = useVirtualRows(
|
||||
searchTerm,
|
||||
normalizedSearchTerm,
|
||||
renderedEmojis,
|
||||
favoriteEmojis,
|
||||
frequentlyUsedEmojis,
|
||||
@@ -110,7 +121,8 @@ export const EmojiPicker = observer(
|
||||
unicodeEmojisByCategory,
|
||||
);
|
||||
|
||||
const showPremiumUpsell = shouldShowPremiumFeatures() && shouldShowEmojiPremiumUpsell(channel);
|
||||
const showPremiumUpsell =
|
||||
shouldShowPremiumFeatures() && shouldShowEmojiPremiumUpsell(channel) && !normalizedSearchTerm;
|
||||
|
||||
const sections = React.useMemo(() => {
|
||||
const result: Array<number> = [];
|
||||
@@ -223,11 +235,12 @@ export const EmojiPicker = observer(
|
||||
className={`${styles.list} ${styles.listWrapper}`}
|
||||
fade={false}
|
||||
key="emoji-picker-scroller"
|
||||
reserveScrollbarTrack={false}
|
||||
reserveScrollbarTrack={true}
|
||||
>
|
||||
{showPremiumUpsell && <PremiumUpsellBanner />}
|
||||
{virtualRows.map((row, index) => {
|
||||
const emojiRowIndex = virtualRows.slice(0, index).filter((r) => r.type === 'emoji-row').length;
|
||||
const needsSpacingAfter = row.type === 'emoji-row' && virtualRows[index + 1]?.type === 'header';
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -241,6 +254,7 @@ export const EmojiPicker = observer(
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
style={row.type === 'emoji-row' && needsSpacingAfter ? {marginBottom: '12px'} : undefined}
|
||||
>
|
||||
<VirtualizedRow
|
||||
row={row}
|
||||
@@ -260,6 +274,16 @@ export const EmojiPicker = observer(
|
||||
);
|
||||
})}
|
||||
</Scroller>
|
||||
{renderedEmojis.length === 0 && (
|
||||
<div className={styles.emptyState}>
|
||||
<div className={styles.emptyStateInner}>
|
||||
<div className={styles.emptyIcon}>
|
||||
<SmileySadIcon weight="duotone" />
|
||||
</div>
|
||||
<div className={styles.emptyLabel}>{t`No emojis match your search`}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<EmojiPickerInspector hoveredEmoji={hoveredEmoji} />
|
||||
@@ -268,6 +292,7 @@ export const EmojiPicker = observer(
|
||||
customEmojisByGuildId={customEmojisByGuildId}
|
||||
unicodeEmojisByCategory={unicodeEmojisByCategory}
|
||||
handleCategoryClick={handleCategoryClick}
|
||||
showFrequentlyUsedButton={showFrequentlyUsedButton}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -280,7 +280,12 @@ export const Messages = observer(function Messages({channel}: {channel: ChannelR
|
||||
const data = payload as {channelId?: string; heightDelta?: number} | undefined;
|
||||
if (data?.channelId && data.channelId !== channel.id) return;
|
||||
|
||||
scrollManager.handleScroll();
|
||||
const heightDelta = data?.heightDelta;
|
||||
const handledLayoutShift = typeof heightDelta === 'number' ? scrollManager.applyLayoutShift(heightDelta) : false;
|
||||
|
||||
if (!handledLayoutShift) {
|
||||
scrollManager.handleScroll();
|
||||
}
|
||||
};
|
||||
|
||||
const onFocusBottommostMessage = (payload?: unknown) => {
|
||||
|
||||
@@ -35,7 +35,7 @@ import {useForceUpdate} from '~/hooks/useForceUpdate';
|
||||
import {ComponentDispatch} from '~/lib/ComponentDispatch';
|
||||
import UnicodeEmojis, {EMOJI_SPRITES} from '~/lib/UnicodeEmojis';
|
||||
import ChannelStore from '~/stores/ChannelStore';
|
||||
import EmojiStore, {type Emoji} from '~/stores/EmojiStore';
|
||||
import EmojiStore, {type Emoji, normalizeEmojiSearchQuery} from '~/stores/EmojiStore';
|
||||
|
||||
export const MobileEmojiPicker = observer(
|
||||
({
|
||||
@@ -57,6 +57,7 @@ export const MobileEmojiPicker = observer(
|
||||
const [internalSearchTerm, setInternalSearchTerm] = React.useState('');
|
||||
const [hoveredEmoji, setHoveredEmoji] = React.useState<Emoji | null>(null);
|
||||
const [renderedEmojis, setRenderedEmojis] = React.useState<Array<Emoji>>([]);
|
||||
const [allEmojis, setAllEmojis] = React.useState<Array<Emoji>>([]);
|
||||
const scrollerRef = React.useRef<ScrollerHandle>(null);
|
||||
const emojiRefs = React.useRef<Map<string, HTMLButtonElement>>(new Map());
|
||||
|
||||
@@ -67,6 +68,7 @@ export const MobileEmojiPicker = observer(
|
||||
|
||||
const searchTerm = externalSearchTerm ?? internalSearchTerm;
|
||||
const setSearchTerm = externalSetSearchTerm ?? setInternalSearchTerm;
|
||||
const normalizedSearchTerm = React.useMemo(() => normalizeEmojiSearchQuery(searchTerm), [searchTerm]);
|
||||
|
||||
const spriteSheetSizes = React.useMemo(() => {
|
||||
const nonDiversitySize = [
|
||||
@@ -83,18 +85,26 @@ export const MobileEmojiPicker = observer(
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const emojis = EmojiStore.search(null, searchTerm).slice();
|
||||
const emojis = EmojiStore.search(channel, normalizedSearchTerm).slice();
|
||||
setRenderedEmojis(emojis);
|
||||
}, [searchTerm]);
|
||||
}, [channel, normalizedSearchTerm]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const emojis = EmojiStore.search(channel, '').slice();
|
||||
setAllEmojis(emojis);
|
||||
}, [channel]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return ComponentDispatch.subscribe('EMOJI_PICKER_RERENDER', forceUpdate);
|
||||
});
|
||||
|
||||
const {customEmojisByGuildId, unicodeEmojisByCategory, favoriteEmojis, frequentlyUsedEmojis} =
|
||||
useEmojiCategories(renderedEmojis);
|
||||
const {customEmojisByGuildId, unicodeEmojisByCategory, favoriteEmojis, frequentlyUsedEmojis} = useEmojiCategories(
|
||||
allEmojis,
|
||||
renderedEmojis,
|
||||
);
|
||||
const showFrequentlyUsedButton = frequentlyUsedEmojis.length > 0 && !normalizedSearchTerm;
|
||||
const virtualRows = useVirtualRows(
|
||||
searchTerm,
|
||||
normalizedSearchTerm,
|
||||
renderedEmojis,
|
||||
favoriteEmojis,
|
||||
frequentlyUsedEmojis,
|
||||
@@ -168,6 +178,7 @@ export const MobileEmojiPicker = observer(
|
||||
unicodeEmojisByCategory={unicodeEmojisByCategory}
|
||||
handleCategoryClick={handleCategoryClick}
|
||||
horizontal={true}
|
||||
showFrequentlyUsedButton={showFrequentlyUsedButton}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,6 +61,9 @@ export const MobileStickersPicker = observer(
|
||||
const [searchTerm, setSearchTerm] = React.useState('');
|
||||
const [hoveredSticker, setHoveredSticker] = React.useState<GuildStickerRecord | null>(null);
|
||||
const [renderedStickers, setRenderedStickers] = React.useState<ReadonlyArray<GuildStickerRecord>>([]);
|
||||
const [allStickersForCategories, setAllStickersForCategories] = React.useState<ReadonlyArray<GuildStickerRecord>>(
|
||||
[],
|
||||
);
|
||||
const scrollerRef = React.useRef<ScrollerHandle>(null);
|
||||
const searchInputRef = React.useRef<HTMLInputElement>(null);
|
||||
const stickerRefs = React.useRef<Map<string, HTMLButtonElement>>(new Map());
|
||||
@@ -74,13 +77,20 @@ export const MobileStickersPicker = observer(
|
||||
setRenderedStickers(stickers);
|
||||
}, [channel, searchTerm]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setAllStickersForCategories(StickerStore.getAllStickers());
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
return ComponentDispatch.subscribe('STICKER_PICKER_RERENDER', forceUpdate);
|
||||
});
|
||||
|
||||
useSearchInputAutofocus(searchInputRef);
|
||||
|
||||
const {favoriteStickers, frequentlyUsedStickers, stickersByGuildId} = useStickerCategories(renderedStickers);
|
||||
const {favoriteStickers, frequentlyUsedStickers, stickersByGuildId} = useStickerCategories(
|
||||
allStickersForCategories,
|
||||
renderedStickers,
|
||||
);
|
||||
const virtualRows = useVirtualRows(
|
||||
searchTerm,
|
||||
renderedStickers,
|
||||
|
||||
@@ -55,6 +55,9 @@ export const StickersPicker = observer(
|
||||
const [searchTerm, setSearchTerm] = React.useState('');
|
||||
const [hoveredSticker, setHoveredSticker] = React.useState<GuildStickerRecord | null>(null);
|
||||
const [renderedStickers, setRenderedStickers] = React.useState<ReadonlyArray<GuildStickerRecord>>([]);
|
||||
const [allStickersForCategories, setAllStickersForCategories] = React.useState<ReadonlyArray<GuildStickerRecord>>(
|
||||
[],
|
||||
);
|
||||
const [selectedRow, setSelectedRow] = React.useState(-1);
|
||||
const [selectedColumn, setSelectedColumn] = React.useState(-1);
|
||||
const [shouldScrollOnSelection, setShouldScrollOnSelection] = React.useState(false);
|
||||
@@ -79,13 +82,20 @@ export const StickersPicker = observer(
|
||||
}
|
||||
}, [channel, searchTerm]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setAllStickersForCategories(StickerStore.getAllStickers());
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
return ComponentDispatch.subscribe('STICKER_PICKER_RERENDER', forceUpdate);
|
||||
});
|
||||
|
||||
useSearchInputAutofocus(searchInputRef);
|
||||
|
||||
const {favoriteStickers, frequentlyUsedStickers, stickersByGuildId} = useStickerCategories(renderedStickers);
|
||||
const {favoriteStickers, frequentlyUsedStickers, stickersByGuildId} = useStickerCategories(
|
||||
allStickersForCategories,
|
||||
renderedStickers,
|
||||
);
|
||||
const virtualRows = useVirtualRows(
|
||||
searchTerm,
|
||||
renderedStickers,
|
||||
@@ -98,7 +108,8 @@ export const StickersPicker = observer(
|
||||
const allStickers = React.useMemo(() => StickerStore.getAllStickers(), []);
|
||||
const hasNoStickersAtAll = allStickers.length === 0;
|
||||
|
||||
const showPremiumUpsell = shouldShowPremiumFeatures() && shouldShowStickerPremiumUpsell(channel);
|
||||
const isSearching = searchTerm.trim().length > 0;
|
||||
const showPremiumUpsell = shouldShowPremiumFeatures() && shouldShowStickerPremiumUpsell(channel) && !isSearching;
|
||||
|
||||
const sections = React.useMemo(() => {
|
||||
const result: Array<number> = [];
|
||||
@@ -216,21 +227,6 @@ export const StickersPicker = observer(
|
||||
/>
|
||||
);
|
||||
|
||||
if (renderedStickers.length === 0 && searchTerm) {
|
||||
return (
|
||||
<div className={gifStyles.gifPickerContainer}>
|
||||
<ExpressionPickerHeaderPortal>{renderSearchBar()}</ExpressionPickerHeaderPortal>
|
||||
<div className={gifStyles.gifPickerMain}>
|
||||
<PickerEmptyState
|
||||
icon={SmileySadIcon}
|
||||
title={t`No Stickers Found`}
|
||||
description={t`Try a different search term`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<ExpressionPickerHeaderPortal>{renderSearchBar()}</ExpressionPickerHeaderPortal>
|
||||
@@ -242,11 +238,12 @@ export const StickersPicker = observer(
|
||||
className={`${styles.list} ${styles.listWrapper}`}
|
||||
fade={false}
|
||||
key="stickers-picker-scroller"
|
||||
reserveScrollbarTrack={false}
|
||||
reserveScrollbarTrack={true}
|
||||
>
|
||||
{showPremiumUpsell && <PremiumUpsellBanner />}
|
||||
{virtualRows.map((row, index) => {
|
||||
const stickerRowIndex = virtualRows.slice(0, index).filter((r) => r.type === 'sticker-row').length;
|
||||
const needsSpacingAfter = row.type === 'sticker-row' && virtualRows[index + 1]?.type === 'header';
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -260,6 +257,7 @@ export const StickersPicker = observer(
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
style={row.type === 'sticker-row' && needsSpacingAfter ? {marginBottom: '12px'} : undefined}
|
||||
>
|
||||
<VirtualRowWrapper
|
||||
row={row}
|
||||
@@ -278,6 +276,16 @@ export const StickersPicker = observer(
|
||||
);
|
||||
})}
|
||||
</Scroller>
|
||||
{renderedStickers.length === 0 && (
|
||||
<div className={styles.emptyState}>
|
||||
<div className={styles.emptyStateInner}>
|
||||
<div className={styles.emptyIcon}>
|
||||
<SmileySadIcon weight="duotone" />
|
||||
</div>
|
||||
<div className={styles.emptyLabel}>{t`No stickers match your search`}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<StickerPickerInspector hoveredSticker={hoveredSticker} />
|
||||
|
||||
@@ -133,6 +133,27 @@ const getOptimizedMediaURL = (
|
||||
});
|
||||
};
|
||||
|
||||
const SuppressEmbedsConfirmModal: FC<{message: MessageRecord}> = ({message}) => {
|
||||
const {t} = useLingui();
|
||||
|
||||
return (
|
||||
<ConfirmModal
|
||||
title={t`Suppress Embeds`}
|
||||
description={
|
||||
<Trans>
|
||||
Are you sure you want to suppress all link embeds on this message? This action will hide all embeds from this
|
||||
message.
|
||||
</Trans>
|
||||
}
|
||||
primaryText={t`Suppress Embeds`}
|
||||
primaryVariant="danger-primary"
|
||||
onPrimary={async () => {
|
||||
await MessageActionCreators.toggleSuppressEmbeds(message.channelId, message.id, message.flags);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const shouldRenderAsInlineThumbnail = (media?: EmbedMedia): boolean => {
|
||||
if (!isValidMedia(media)) return false;
|
||||
|
||||
@@ -697,27 +718,7 @@ export const Embed: FC<EmbedProps> = observer(({embed, message, embedIndex, onDe
|
||||
}, [message, channel]);
|
||||
|
||||
const handleSuppressEmbeds = useCallback(() => {
|
||||
ModalActionCreators.push(
|
||||
modal(() => {
|
||||
const {t} = useLingui();
|
||||
return (
|
||||
<ConfirmModal
|
||||
title={t`Suppress Embeds`}
|
||||
description={
|
||||
<Trans>
|
||||
Are you sure you want to suppress all link embeds on this message? This action will hide all embeds from
|
||||
this message.
|
||||
</Trans>
|
||||
}
|
||||
primaryText={t`Suppress Embeds`}
|
||||
primaryVariant="danger-primary"
|
||||
onPrimary={async () => {
|
||||
await MessageActionCreators.toggleSuppressEmbeds(message.channelId, message.id, message.flags);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
);
|
||||
ModalActionCreators.push(modal(() => <SuppressEmbedsConfirmModal message={message} />));
|
||||
}, [message]);
|
||||
|
||||
const showSuppressButton = !isMobile && canSuppressEmbeds() && AccessibilityStore.showSuppressEmbedsButton;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
import {useLingui} from '@lingui/react/macro';
|
||||
import {ClockIcon} from '@phosphor-icons/react';
|
||||
import {clsx} from 'clsx';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
import styles from '~/components/channel/EmojiPicker.module.css';
|
||||
@@ -31,6 +32,7 @@ interface EmojiPickerCategoryListProps {
|
||||
customEmojisByGuildId: Map<string, Array<Emoji>>;
|
||||
unicodeEmojisByCategory: Map<string, Array<Emoji>>;
|
||||
handleCategoryClick: (category: string) => void;
|
||||
showFrequentlyUsedButton: boolean;
|
||||
horizontal?: boolean;
|
||||
}
|
||||
|
||||
@@ -39,12 +41,23 @@ export const EmojiPickerCategoryList = observer(
|
||||
customEmojisByGuildId,
|
||||
unicodeEmojisByCategory,
|
||||
handleCategoryClick,
|
||||
showFrequentlyUsedButton = false,
|
||||
horizontal = false,
|
||||
}: EmojiPickerCategoryListProps) => {
|
||||
const {i18n} = useLingui();
|
||||
if (horizontal) {
|
||||
return (
|
||||
<div className={styles.horizontalCategories}>
|
||||
{showFrequentlyUsedButton && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleCategoryClick('frequently-used')}
|
||||
className={clsx(styles.categoryListIcon, styles.textPrimaryMuted)}
|
||||
aria-label={i18n._('Frequently Used')}
|
||||
>
|
||||
<ClockIcon className={styles.iconSize} />
|
||||
</button>
|
||||
)}
|
||||
{Array.from(customEmojisByGuildId.keys()).map((guildId) => {
|
||||
const guild = GuildStore.getGuild(guildId)!;
|
||||
return (
|
||||
@@ -81,6 +94,17 @@ export const EmojiPickerCategoryList = observer(
|
||||
<div className={styles.categoryList}>
|
||||
<div className={styles.categoryListScroll}>
|
||||
<div className={styles.listItems}>
|
||||
{showFrequentlyUsedButton && (
|
||||
<Tooltip text={i18n._('Frequently Used')} position="left">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleCategoryClick('frequently-used')}
|
||||
className={clsx(styles.categoryListIcon, styles.textPrimaryMuted)}
|
||||
>
|
||||
<ClockIcon className={styles.iconSize} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{Array.from(customEmojisByGuildId.keys()).map((guildId) => {
|
||||
const guild = GuildStore.getGuild(guildId)!;
|
||||
return (
|
||||
|
||||
@@ -23,15 +23,15 @@ import EmojiPickerStore from '~/stores/EmojiPickerStore';
|
||||
import type {Emoji} from '~/stores/EmojiStore';
|
||||
import GuildListStore from '~/stores/GuildListStore';
|
||||
|
||||
export const useEmojiCategories = (renderedEmojis: Array<Emoji>) => {
|
||||
export const useEmojiCategories = (allEmojis: Array<Emoji>, _renderedEmojis: Array<Emoji>) => {
|
||||
const guilds = GuildListStore.guilds;
|
||||
|
||||
const favoriteEmojis = EmojiPickerStore.getFavoriteEmojis(renderedEmojis);
|
||||
const favoriteEmojis = EmojiPickerStore.getFavoriteEmojis(allEmojis);
|
||||
|
||||
const frequentlyUsedEmojis = EmojiPickerStore.getFrecentEmojis(renderedEmojis, 42);
|
||||
const frequentlyUsedEmojis = EmojiPickerStore.getFrecentEmojis(allEmojis, 42);
|
||||
|
||||
const customEmojisByGuildId = React.useMemo(() => {
|
||||
const guildEmojis = renderedEmojis.filter((emoji) => emoji.guildId != null);
|
||||
const guildEmojis = allEmojis.filter((emoji) => emoji.guildId != null);
|
||||
const guildEmojisByGuildId = new Map<string, Array<Emoji>>();
|
||||
|
||||
for (const guildEmoji of guildEmojis) {
|
||||
@@ -50,10 +50,10 @@ export const useEmojiCategories = (renderedEmojis: Array<Emoji>) => {
|
||||
}
|
||||
|
||||
return sortedGuildEmojisByGuildId;
|
||||
}, [renderedEmojis, guilds]);
|
||||
}, [allEmojis, guilds]);
|
||||
|
||||
const unicodeEmojisByCategory = React.useMemo(() => {
|
||||
const unicodeEmojis = renderedEmojis.filter((emoji) => emoji.guildId == null);
|
||||
const unicodeEmojis = allEmojis.filter((emoji) => emoji.guildId == null);
|
||||
const unicodeEmojisByCategory = new Map<string, Array<Emoji>>();
|
||||
|
||||
for (const emoji of unicodeEmojis) {
|
||||
@@ -76,7 +76,7 @@ export const useEmojiCategories = (renderedEmojis: Array<Emoji>) => {
|
||||
}
|
||||
|
||||
return sortedUnicodeEmojisByCategory;
|
||||
}, [renderedEmojis]);
|
||||
}, [allEmojis]);
|
||||
|
||||
return {
|
||||
favoriteEmojis,
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
} from '~/components/uikit/ContextMenu/ContextMenuIcons';
|
||||
import type {MenuGroupType, MenuItemType} from '~/components/uikit/MenuBottomSheet/MenuBottomSheet';
|
||||
import type {MessageRecord} from '~/records/MessageRecord';
|
||||
import ChannelStore from '~/stores/ChannelStore';
|
||||
import EmojiPickerStore from '~/stores/EmojiPickerStore';
|
||||
import type {Emoji} from '~/stores/EmojiStore';
|
||||
import EmojiStore from '~/stores/EmojiStore';
|
||||
@@ -73,7 +74,8 @@ export const useMessageActionMenuData = (
|
||||
const permissions = useMessagePermissions(message);
|
||||
const handlers = React.useMemo(() => createMessageActionHandlers(message, {onClose}), [message, onClose]);
|
||||
const isSaved = React.useMemo(() => SavedMessagesStore.isSaved(message.id), [message.id]);
|
||||
const allEmojis = React.useMemo(() => EmojiStore.search(null, ''), []);
|
||||
const channel = React.useMemo(() => ChannelStore.getChannel(message.channelId) ?? null, [message.channelId]);
|
||||
const allEmojis = React.useMemo(() => EmojiStore.search(channel, ''), [channel]);
|
||||
const quickReactionEmojis = React.useMemo(
|
||||
() => EmojiPickerStore.getQuickReactionEmojis(allEmojis, quickReactionCount),
|
||||
[allEmojis, quickReactionCount],
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
|
||||
.stickerGrid {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
gap: var(--spacing-2);
|
||||
padding: 0 0 0.5rem;
|
||||
}
|
||||
|
||||
.stickerButton {
|
||||
|
||||
@@ -22,22 +22,25 @@ import type {GuildStickerRecord} from '~/records/GuildStickerRecord';
|
||||
import GuildListStore from '~/stores/GuildListStore';
|
||||
import StickerPickerStore from '~/stores/StickerPickerStore';
|
||||
|
||||
export const useStickerCategories = (renderedStickers: ReadonlyArray<GuildStickerRecord>) => {
|
||||
export const useStickerCategories = (
|
||||
allStickers: ReadonlyArray<GuildStickerRecord>,
|
||||
_renderedStickers: ReadonlyArray<GuildStickerRecord>,
|
||||
) => {
|
||||
const guilds = GuildListStore.guilds;
|
||||
const stickerPickerState = StickerPickerStore;
|
||||
|
||||
const favoriteStickers = React.useMemo(() => {
|
||||
return StickerPickerStore.getFavoriteStickers(renderedStickers);
|
||||
}, [renderedStickers, stickerPickerState.favoriteStickers]);
|
||||
return StickerPickerStore.getFavoriteStickers(allStickers);
|
||||
}, [allStickers, stickerPickerState.favoriteStickers]);
|
||||
|
||||
const frequentlyUsedStickers = React.useMemo(() => {
|
||||
return StickerPickerStore.getFrecentStickers(renderedStickers, 42);
|
||||
}, [renderedStickers, stickerPickerState.stickerUsage]);
|
||||
return StickerPickerStore.getFrecentStickers(allStickers, 42);
|
||||
}, [allStickers, stickerPickerState.stickerUsage]);
|
||||
|
||||
const stickersByGuildId = React.useMemo(() => {
|
||||
const guildStickersMap = new Map<string, Array<GuildStickerRecord>>();
|
||||
|
||||
for (const sticker of renderedStickers) {
|
||||
for (const sticker of allStickers) {
|
||||
if (!guildStickersMap.has(sticker.guildId)) {
|
||||
guildStickersMap.set(sticker.guildId, []);
|
||||
}
|
||||
@@ -53,7 +56,7 @@ export const useStickerCategories = (renderedStickers: ReadonlyArray<GuildSticke
|
||||
}
|
||||
|
||||
return sortedGuildStickersMap;
|
||||
}, [renderedStickers, guilds]);
|
||||
}, [allStickers, guilds]);
|
||||
|
||||
return {
|
||||
favoriteStickers,
|
||||
|
||||
Reference in New Issue
Block a user