pl-fe: more attempts on optimization
Signed-off-by: mkljczk <git@mkljczk.pl>
This commit is contained in:
@ -4,7 +4,6 @@ import { getClient } from '../api';
|
||||
|
||||
import { importEntities } from './importer';
|
||||
|
||||
import type { Status } from 'pl-api';
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
|
||||
const EMOJI_REACT_REQUEST = 'EMOJI_REACT_REQUEST' as const;
|
||||
@ -14,26 +13,26 @@ const UNEMOJI_REACT_REQUEST = 'UNEMOJI_REACT_REQUEST' as const;
|
||||
|
||||
const noOp = () => () => new Promise(f => f(undefined));
|
||||
|
||||
const emojiReact = (status: Pick<Status, 'id'>, emoji: string, custom?: string) =>
|
||||
const emojiReact = (statusId: string, emoji: string, custom?: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return dispatch(noOp());
|
||||
|
||||
dispatch(emojiReactRequest(status.id, emoji, custom));
|
||||
dispatch(emojiReactRequest(statusId, emoji, custom));
|
||||
|
||||
return getClient(getState).statuses.createStatusReaction(status.id, emoji).then((response) => {
|
||||
return getClient(getState).statuses.createStatusReaction(statusId, emoji).then((response) => {
|
||||
dispatch(importEntities({ statuses: [response] }));
|
||||
}).catch((error) => {
|
||||
dispatch(emojiReactFail(status.id, emoji, error));
|
||||
dispatch(emojiReactFail(statusId, emoji, error));
|
||||
});
|
||||
};
|
||||
|
||||
const unEmojiReact = (status: Pick<Status, 'id'>, emoji: string) =>
|
||||
const unEmojiReact = (statusId: string, emoji: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return dispatch(noOp());
|
||||
|
||||
dispatch(unEmojiReactRequest(status.id, emoji));
|
||||
dispatch(unEmojiReactRequest(statusId, emoji));
|
||||
|
||||
return getClient(getState).statuses.deleteStatusReaction(status.id, emoji).then(response => {
|
||||
return getClient(getState).statuses.deleteStatusReaction(statusId, emoji).then(response => {
|
||||
dispatch(importEntities({ statuses: [response] }));
|
||||
});
|
||||
};
|
||||
|
||||
@ -19,7 +19,7 @@ interface IHoverAccountWrapper {
|
||||
}
|
||||
|
||||
/** Makes a profile hover card appear when the wrapped element is hovered. */
|
||||
const HoverAccountWrapper: React.FC<IHoverAccountWrapper> = ({ accountId, children, element: Elem = 'div', className }) => {
|
||||
const HoverAccountWrapper: React.FC<IHoverAccountWrapper> = React.memo(({ accountId, children, element: Elem = 'div', className }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { openAccountHoverCard, closeAccountHoverCard } = useAccountHoverCardStore();
|
||||
@ -54,6 +54,6 @@ const HoverAccountWrapper: React.FC<IHoverAccountWrapper> = ({ accountId, childr
|
||||
{children}
|
||||
</Elem>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export { HoverAccountWrapper as default, showAccountHoverCard };
|
||||
|
||||
@ -25,7 +25,7 @@ interface ISidebarNavigationLink {
|
||||
}
|
||||
|
||||
/** Desktop sidebar navigation link. */
|
||||
const SidebarNavigationLink = React.forwardRef((props: ISidebarNavigationLink, ref: React.ForwardedRef<HTMLAnchorElement>): JSX.Element => {
|
||||
const SidebarNavigationLink = React.memo(React.forwardRef((props: ISidebarNavigationLink, ref: React.ForwardedRef<HTMLAnchorElement>): JSX.Element => {
|
||||
const { icon, activeIcon, text, to = '', count, countMax, onClick } = props;
|
||||
const isActive = location.pathname === to;
|
||||
|
||||
@ -72,6 +72,6 @@ const SidebarNavigationLink = React.forwardRef((props: ISidebarNavigationLink, r
|
||||
<Text weight='semibold' theme='inherit'>{text}</Text>
|
||||
</NavLink>
|
||||
);
|
||||
});
|
||||
}), (prevProps, nextProps) => prevProps.count === nextProps.count);
|
||||
|
||||
export { SidebarNavigationLink as default };
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import Icon from 'pl-fe/components/ui/icon';
|
||||
@ -57,7 +57,7 @@ const SidebarNavigation = React.memo(() => {
|
||||
|
||||
const restrictUnauth = instance.pleroma.metadata.restrict_unauthenticated;
|
||||
|
||||
const makeMenu = (): Menu => {
|
||||
const menu = useMemo((): Menu => {
|
||||
const menu: Menu = [];
|
||||
|
||||
if (account) {
|
||||
@ -155,9 +155,7 @@ const SidebarNavigation = React.memo(() => {
|
||||
}
|
||||
|
||||
return menu;
|
||||
};
|
||||
|
||||
const menu = makeMenu();
|
||||
}, [!!account, features, isDeveloper, followRequestsCount, interactionRequestsCount, scheduledStatusCount, draftCount]);
|
||||
|
||||
return (
|
||||
<Stack space={4}>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { type CustomEmoji, GroupRoles } from 'pl-api';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { useHistory, useRouteMatch } from 'react-router-dom';
|
||||
|
||||
@ -484,15 +484,15 @@ const WrenchButton: React.FC<IActionButton> = ({
|
||||
|
||||
const handleWrenchClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
if (wrenches?.me) {
|
||||
dispatch(unEmojiReact(status, '🔧'));
|
||||
dispatch(unEmojiReact(status.id, '🔧'));
|
||||
} else {
|
||||
dispatch(emojiReact(status, '🔧'));
|
||||
dispatch(emojiReact(status.id, '🔧'));
|
||||
}
|
||||
};
|
||||
|
||||
const handleWrenchLongPress = () => {
|
||||
if (features.customEmojiReacts && hasLongerWrench) {
|
||||
dispatch(emojiReact(status, hasLongerWrench.shortcode, hasLongerWrench.url));
|
||||
dispatch(emojiReact(status.id, hasLongerWrench.shortcode, hasLongerWrench.url));
|
||||
} else if (wrenches?.count) {
|
||||
openModal('REACTIONS', { statusId: status.id, reaction: wrenches.name });
|
||||
}
|
||||
@ -524,7 +524,7 @@ const EmojiPickerButton: React.FC<Omit<IActionButton, 'onOpenUnauthorizedModal'>
|
||||
const features = useFeatures();
|
||||
|
||||
const handlePickEmoji = (emoji: EmojiType) => {
|
||||
dispatch(emojiReact(status, emoji.custom ? emoji.id : emoji.native, emoji.custom ? emoji.imageUrl : undefined));
|
||||
dispatch(emojiReact(status.id, emoji.custom ? emoji.id : emoji.native, emoji.custom ? emoji.imageUrl : undefined));
|
||||
};
|
||||
|
||||
return me && !withLabels && features.emojiReacts && (
|
||||
@ -613,178 +613,177 @@ const MenuButton: React.FC<IMenuButton> = ({
|
||||
const isStaff = account ? account.is_admin || account.is_moderator : false;
|
||||
const isAdmin = account ? account.is_admin : false;
|
||||
|
||||
const handleBookmarkClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(toggleBookmark(status));
|
||||
};
|
||||
|
||||
const handleBookmarkFolderClick = () => {
|
||||
openModal('SELECT_BOOKMARK_FOLDER', {
|
||||
statusId: status.id,
|
||||
});
|
||||
};
|
||||
|
||||
const doDeleteStatus = (withRedraft = false) => {
|
||||
if (!deleteModal) {
|
||||
dispatch(deleteStatus(status.id, withRedraft));
|
||||
} else {
|
||||
openModal('CONFIRM', {
|
||||
heading: intl.formatMessage(withRedraft ? messages.redraftHeading : messages.deleteHeading),
|
||||
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
|
||||
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
|
||||
onConfirm: () => dispatch(deleteStatus(status.id, withRedraft)),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
doDeleteStatus();
|
||||
};
|
||||
|
||||
const handleRedraftClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
doDeleteStatus(true);
|
||||
};
|
||||
|
||||
const handleEditClick: React.EventHandler<React.MouseEvent> = () => {
|
||||
if (status.event) history.push(`/@${status.account.acct}/events/${status.id}/edit`);
|
||||
else dispatch(editStatus(status.id));
|
||||
};
|
||||
|
||||
const handlePinClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(togglePin(status));
|
||||
};
|
||||
|
||||
const handleReblogClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
const modalReblog = () => dispatch(toggleReblog(status));
|
||||
if ((e && e.shiftKey) || !boostModal) {
|
||||
modalReblog();
|
||||
} else {
|
||||
openModal('BOOST', { statusId: status.id, onReblog: modalReblog });
|
||||
}
|
||||
};
|
||||
|
||||
const handleMentionClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(mentionCompose(status.account));
|
||||
};
|
||||
|
||||
const handleDirectClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(directCompose(status.account));
|
||||
};
|
||||
|
||||
const handleChatClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
const account = status.account;
|
||||
|
||||
getOrCreateChatByAccountId(account.id)
|
||||
.then((chat) => history.push(`/chats/${chat.id}`))
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const handleMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
openModal('MUTE', { accountId: status.account.id });
|
||||
};
|
||||
|
||||
const handleBlockClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
const account = status.account;
|
||||
|
||||
openModal('CONFIRM', {
|
||||
heading: <FormattedMessage id='confirmations.block.heading' defaultMessage='Block @{name}' values={{ name: account.acct }} />,
|
||||
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong className='break-words'>@{account.acct}</strong> }} />,
|
||||
confirm: intl.formatMessage(messages.blockConfirm),
|
||||
onConfirm: () => dispatch(blockAccount(account.id)),
|
||||
secondary: intl.formatMessage(messages.blockAndReport),
|
||||
onSecondary: () => {
|
||||
dispatch(blockAccount(account.id));
|
||||
dispatch(initReport(ReportableEntities.STATUS, account, { status }));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleEmbed = () => {
|
||||
openModal('EMBED', {
|
||||
url: status.url,
|
||||
onError: (error: any) => toast.showAlertForError(error),
|
||||
});
|
||||
};
|
||||
|
||||
const handleOpenReactionsModal = () => {
|
||||
openModal('REACTIONS', { statusId: status.id });
|
||||
};
|
||||
|
||||
const handleReport: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(initReport(ReportableEntities.STATUS, status.account, { status }));
|
||||
};
|
||||
|
||||
const handleConversationMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(toggleMuteStatus(status));
|
||||
};
|
||||
|
||||
const handleCopy: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
const { uri } = status;
|
||||
|
||||
copy(uri);
|
||||
};
|
||||
|
||||
const onModerate: React.MouseEventHandler = (e) => {
|
||||
const account = status.account;
|
||||
openModal('ACCOUNT_MODERATION', { accountId: account.id });
|
||||
};
|
||||
|
||||
const handleDeleteStatus: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(deleteStatusModal(intl, status.id));
|
||||
};
|
||||
|
||||
const handleToggleStatusSensitivity: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(toggleStatusSensitivityModal(intl, status.id, status.sensitive));
|
||||
};
|
||||
|
||||
const handleDeleteFromGroup: React.EventHandler<React.MouseEvent> = () => {
|
||||
const account = status.account;
|
||||
|
||||
openModal('CONFIRM', {
|
||||
heading: intl.formatMessage(messages.deleteHeading),
|
||||
message: intl.formatMessage(messages.deleteFromGroupMessage, { name: <strong className='break-words'>{account.username}</strong> }),
|
||||
confirm: intl.formatMessage(messages.deleteConfirm),
|
||||
onConfirm: () => {
|
||||
deleteGroupStatus.mutate(status.id, {
|
||||
onSuccess() {
|
||||
dispatch(deleteFromTimelines(status.id));
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleBlockFromGroup = () => {
|
||||
openModal('CONFIRM', {
|
||||
heading: intl.formatMessage(messages.groupBlockFromGroupHeading),
|
||||
message: intl.formatMessage(messages.groupBlockFromGroupMessage, { name: status.account.username }),
|
||||
confirm: intl.formatMessage(messages.groupBlockConfirm),
|
||||
onConfirm: () => {
|
||||
blockGroupMember(undefined, {
|
||||
onSuccess: () => {
|
||||
toast.success(intl.formatMessage(messages.blocked, { name: account?.acct }));
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleIgnoreLanguage = () => {
|
||||
dispatch(changeSetting(['autoTranslate'], [...knownLanguages, status.language], { showAlert: true }));
|
||||
};
|
||||
|
||||
const handleTranslate = () => {
|
||||
if (targetLanguage) {
|
||||
hideTranslation(status.id);
|
||||
} else {
|
||||
fetchTranslation(status.id, intl.locale);
|
||||
}
|
||||
};
|
||||
|
||||
const _makeMenu = (publicStatus: boolean) => {
|
||||
const menu = useMemo(() => {
|
||||
const mutingConversation = status.muted;
|
||||
const ownAccount = status.account_id === me;
|
||||
const username = status.account.username;
|
||||
const account = status.account;
|
||||
const { username, local: localAccount } = status.account;
|
||||
|
||||
const handleBookmarkClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(toggleBookmark(status));
|
||||
};
|
||||
|
||||
const handleBookmarkFolderClick = () => {
|
||||
openModal('SELECT_BOOKMARK_FOLDER', {
|
||||
statusId: status.id,
|
||||
});
|
||||
};
|
||||
|
||||
const doDeleteStatus = (withRedraft = false) => {
|
||||
if (!deleteModal) {
|
||||
dispatch(deleteStatus(status.id, withRedraft));
|
||||
} else {
|
||||
openModal('CONFIRM', {
|
||||
heading: intl.formatMessage(withRedraft ? messages.redraftHeading : messages.deleteHeading),
|
||||
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
|
||||
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
|
||||
onConfirm: () => dispatch(deleteStatus(status.id, withRedraft)),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
doDeleteStatus();
|
||||
};
|
||||
|
||||
const handleRedraftClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
doDeleteStatus(true);
|
||||
};
|
||||
|
||||
const handleEditClick: React.EventHandler<React.MouseEvent> = () => {
|
||||
if (status.event) history.push(`/@${status.account.acct}/events/${status.id}/edit`);
|
||||
else dispatch(editStatus(status.id));
|
||||
};
|
||||
|
||||
const handlePinClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(togglePin(status));
|
||||
};
|
||||
|
||||
const handleReblogClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
const modalReblog = () => dispatch(toggleReblog(status));
|
||||
if ((e && e.shiftKey) || !boostModal) {
|
||||
modalReblog();
|
||||
} else {
|
||||
openModal('BOOST', { statusId: status.id, onReblog: modalReblog });
|
||||
}
|
||||
};
|
||||
|
||||
const handleMentionClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(mentionCompose(status.account));
|
||||
};
|
||||
|
||||
const handleDirectClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(directCompose(status.account));
|
||||
};
|
||||
|
||||
const handleChatClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
const account = status.account;
|
||||
|
||||
getOrCreateChatByAccountId(account.id)
|
||||
.then((chat) => history.push(`/chats/${chat.id}`))
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const handleMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
openModal('MUTE', { accountId: status.account.id });
|
||||
};
|
||||
|
||||
const handleBlockClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
const account = status.account;
|
||||
|
||||
openModal('CONFIRM', {
|
||||
heading: <FormattedMessage id='confirmations.block.heading' defaultMessage='Block @{name}' values={{ name: account.acct }} />,
|
||||
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong className='break-words'>@{account.acct}</strong> }} />,
|
||||
confirm: intl.formatMessage(messages.blockConfirm),
|
||||
onConfirm: () => dispatch(blockAccount(account.id)),
|
||||
secondary: intl.formatMessage(messages.blockAndReport),
|
||||
onSecondary: () => {
|
||||
dispatch(blockAccount(account.id));
|
||||
dispatch(initReport(ReportableEntities.STATUS, account, { status }));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleEmbed = () => {
|
||||
openModal('EMBED', {
|
||||
url: status.url,
|
||||
onError: (error: any) => toast.showAlertForError(error),
|
||||
});
|
||||
};
|
||||
|
||||
const handleOpenReactionsModal = () => {
|
||||
openModal('REACTIONS', { statusId: status.id });
|
||||
};
|
||||
|
||||
const handleReport: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(initReport(ReportableEntities.STATUS, status.account, { status }));
|
||||
};
|
||||
|
||||
const handleConversationMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(toggleMuteStatus(status));
|
||||
};
|
||||
|
||||
const handleCopy: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
const { uri } = status;
|
||||
|
||||
copy(uri);
|
||||
};
|
||||
|
||||
const onModerate: React.MouseEventHandler = (e) => {
|
||||
const account = status.account;
|
||||
openModal('ACCOUNT_MODERATION', { accountId: account.id });
|
||||
};
|
||||
|
||||
const handleDeleteStatus: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(deleteStatusModal(intl, status.id));
|
||||
};
|
||||
|
||||
const handleToggleStatusSensitivity: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(toggleStatusSensitivityModal(intl, status.id, status.sensitive));
|
||||
};
|
||||
|
||||
const handleDeleteFromGroup: React.EventHandler<React.MouseEvent> = () => {
|
||||
const account = status.account;
|
||||
|
||||
openModal('CONFIRM', {
|
||||
heading: intl.formatMessage(messages.deleteHeading),
|
||||
message: intl.formatMessage(messages.deleteFromGroupMessage, { name: <strong className='break-words'>{account.username}</strong> }),
|
||||
confirm: intl.formatMessage(messages.deleteConfirm),
|
||||
onConfirm: () => {
|
||||
deleteGroupStatus.mutate(status.id, {
|
||||
onSuccess() {
|
||||
dispatch(deleteFromTimelines(status.id));
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleBlockFromGroup = () => {
|
||||
openModal('CONFIRM', {
|
||||
heading: intl.formatMessage(messages.groupBlockFromGroupHeading),
|
||||
message: intl.formatMessage(messages.groupBlockFromGroupMessage, { name: status.account.username }),
|
||||
confirm: intl.formatMessage(messages.groupBlockConfirm),
|
||||
onConfirm: () => {
|
||||
blockGroupMember(undefined, {
|
||||
onSuccess: () => {
|
||||
toast.success(intl.formatMessage(messages.blocked, { name: account?.acct }));
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleIgnoreLanguage = () => {
|
||||
dispatch(changeSetting(['autoTranslate'], [...knownLanguages, status.language], { showAlert: true }));
|
||||
};
|
||||
|
||||
const handleTranslate = () => {
|
||||
if (targetLanguage) {
|
||||
hideTranslation(status.id);
|
||||
} else {
|
||||
fetchTranslation(status.id, intl.locale);
|
||||
}
|
||||
};
|
||||
|
||||
const menu: Menu = [];
|
||||
|
||||
@ -803,7 +802,7 @@ const MenuButton: React.FC<IMenuButton> = ({
|
||||
icon: require('@tabler/icons/outline/clipboard-copy.svg'),
|
||||
});
|
||||
|
||||
if (features.embeds && account.local) {
|
||||
if (features.embeds && localAccount) {
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.embed),
|
||||
action: handleEmbed,
|
||||
@ -842,7 +841,7 @@ const MenuButton: React.FC<IMenuButton> = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (features.federating && !account.local) {
|
||||
if (features.federating && !localAccount) {
|
||||
const { hostname: domain } = new URL(status.uri);
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.external, { domain }),
|
||||
@ -1026,11 +1025,9 @@ const MenuButton: React.FC<IMenuButton> = ({
|
||||
}
|
||||
|
||||
return menu;
|
||||
};
|
||||
}, [me, targetLanguage, status.muted, status.emoji_reactions.length > 0, status.pinned, status.reblogged]);
|
||||
|
||||
const menu = _makeMenu(publicStatus);
|
||||
|
||||
return (
|
||||
return useMemo(() => (
|
||||
<DropdownMenu items={menu}>
|
||||
<StatusActionButton
|
||||
title={intl.formatMessage(messages.more)}
|
||||
@ -1038,7 +1035,7 @@ const MenuButton: React.FC<IMenuButton> = ({
|
||||
theme={statusActionButtonTheme}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
);
|
||||
), [menu, statusActionButtonTheme]);
|
||||
};
|
||||
|
||||
interface IStatusActionBar {
|
||||
@ -1065,18 +1062,20 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||
|
||||
const me = useAppSelector(state => state.me);
|
||||
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
const publicStatus = useMemo(() => status ? ['public', 'unlisted', 'group'].includes(status.visibility) : false, [status.visibility]);
|
||||
|
||||
const onOpenUnauthorizedModal = (action?: UnauthorizedModalAction) => {
|
||||
const onContainerClick: React.MouseEventHandler<HTMLDivElement> = useCallback((e) => e.stopPropagation(), []);
|
||||
|
||||
const onOpenUnauthorizedModal = useCallback((action?: UnauthorizedModalAction) => {
|
||||
openModal('UNAUTHORIZED', {
|
||||
action,
|
||||
ap_id: status.url,
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const publicStatus = useMemo(() => ['public', 'unlisted', 'group'].includes(status.visibility), [status.visibility]);
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const spacing: {
|
||||
[key: string]: React.ComponentProps<typeof HStack>['space'];
|
||||
@ -1087,79 +1086,77 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<HStack data-testid='status-action-bar'>
|
||||
<HStack
|
||||
justifyContent={space === 'lg' ? 'between' : undefined}
|
||||
space={spacing[space]}
|
||||
grow={space === 'lg'}
|
||||
onClick={e => e.stopPropagation()}
|
||||
alignItems='center'
|
||||
>
|
||||
<ReplyButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
withLabels={withLabels}
|
||||
me={me}
|
||||
onOpenUnauthorizedModal={onOpenUnauthorizedModal}
|
||||
rebloggedBy={rebloggedBy}
|
||||
/>
|
||||
<HStack
|
||||
justifyContent={space === 'lg' ? 'between' : undefined}
|
||||
space={spacing[space]}
|
||||
grow={space === 'lg'}
|
||||
onClick={onContainerClick}
|
||||
alignItems='center'
|
||||
>
|
||||
<ReplyButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
withLabels={withLabels}
|
||||
me={me}
|
||||
onOpenUnauthorizedModal={onOpenUnauthorizedModal}
|
||||
rebloggedBy={rebloggedBy}
|
||||
/>
|
||||
|
||||
<ReblogButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
withLabels={withLabels}
|
||||
me={me}
|
||||
onOpenUnauthorizedModal={onOpenUnauthorizedModal}
|
||||
publicStatus={publicStatus}
|
||||
/>
|
||||
<ReblogButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
withLabels={withLabels}
|
||||
me={me}
|
||||
onOpenUnauthorizedModal={onOpenUnauthorizedModal}
|
||||
publicStatus={publicStatus}
|
||||
/>
|
||||
|
||||
<FavouriteButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
withLabels={withLabels}
|
||||
me={me}
|
||||
onOpenUnauthorizedModal={onOpenUnauthorizedModal}
|
||||
/>
|
||||
<FavouriteButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
withLabels={withLabels}
|
||||
me={me}
|
||||
onOpenUnauthorizedModal={onOpenUnauthorizedModal}
|
||||
/>
|
||||
|
||||
<DislikeButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
withLabels={withLabels}
|
||||
me={me}
|
||||
onOpenUnauthorizedModal={onOpenUnauthorizedModal}
|
||||
/>
|
||||
<DislikeButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
withLabels={withLabels}
|
||||
me={me}
|
||||
onOpenUnauthorizedModal={onOpenUnauthorizedModal}
|
||||
/>
|
||||
|
||||
<WrenchButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
withLabels={withLabels}
|
||||
me={me}
|
||||
onOpenUnauthorizedModal={onOpenUnauthorizedModal}
|
||||
/>
|
||||
<WrenchButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
withLabels={withLabels}
|
||||
me={me}
|
||||
onOpenUnauthorizedModal={onOpenUnauthorizedModal}
|
||||
/>
|
||||
|
||||
<EmojiPickerButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
withLabels={withLabels}
|
||||
me={me}
|
||||
/>
|
||||
<EmojiPickerButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
withLabels={withLabels}
|
||||
me={me}
|
||||
/>
|
||||
|
||||
<ShareButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
/>
|
||||
<ShareButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
/>
|
||||
|
||||
<MenuButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
withLabels={withLabels}
|
||||
me={me}
|
||||
onOpenUnauthorizedModal={onOpenUnauthorizedModal}
|
||||
expandable={expandable}
|
||||
fromBookmarks={fromBookmarks}
|
||||
publicStatus={publicStatus}
|
||||
/>
|
||||
</HStack>
|
||||
<MenuButton
|
||||
status={status}
|
||||
statusActionButtonTheme={statusActionButtonTheme}
|
||||
withLabels={withLabels}
|
||||
me={me}
|
||||
onOpenUnauthorizedModal={onOpenUnauthorizedModal}
|
||||
expandable={expandable}
|
||||
fromBookmarks={fromBookmarks}
|
||||
publicStatus={publicStatus}
|
||||
/>
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
@ -23,7 +23,7 @@ interface IStatusActionCounter {
|
||||
}
|
||||
|
||||
/** Action button numerical counter, eg "5" likes. */
|
||||
const StatusActionCounter: React.FC<IStatusActionCounter> = ({ count = 0 }): JSX.Element => {
|
||||
const StatusActionCounter: React.FC<IStatusActionCounter> = React.memo(({ count = 0 }): JSX.Element => {
|
||||
const { demetricator } = useSettings();
|
||||
|
||||
return (
|
||||
@ -31,7 +31,7 @@ const StatusActionCounter: React.FC<IStatusActionCounter> = ({ count = 0 }): JSX
|
||||
<AnimatedNumber value={count} obfuscate={demetricator} short />
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
interface IStatusActionButton extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
iconClassName?: string;
|
||||
|
||||
@ -20,7 +20,7 @@ interface IStatusLanguagePicker {
|
||||
showLabel?: boolean;
|
||||
}
|
||||
|
||||
const StatusLanguagePicker: React.FC<IStatusLanguagePicker> = ({ status, showLabel }) => {
|
||||
const StatusLanguagePicker: React.FC<IStatusLanguagePicker> = React.memo(({ status, showLabel }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { statuses, setStatusLanguage } = useStatusMetaStore();
|
||||
@ -55,7 +55,7 @@ const StatusLanguagePicker: React.FC<IStatusLanguagePicker> = ({ status, showLab
|
||||
</DropdownMenu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export {
|
||||
StatusLanguagePicker as default,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import clsx from 'clsx';
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import LoadGap from 'pl-fe/components/load-gap';
|
||||
@ -128,45 +128,45 @@ const StatusList: React.FC<IStatusList> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const renderFeaturedStatuses = (): React.ReactNode[] => {
|
||||
if (!featuredStatusIds) return [];
|
||||
|
||||
return featuredStatusIds.map(statusId => (
|
||||
<StatusContainer
|
||||
key={`f-${statusId}`}
|
||||
id={statusId}
|
||||
featured
|
||||
onMoveUp={handleMoveUp}
|
||||
onMoveDown={handleMoveDown}
|
||||
contextType={timelineId}
|
||||
showGroup={showGroup}
|
||||
variant={divideType === 'border' ? 'slim' : 'default'}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
const renderStatuses = (): React.ReactNode[] => {
|
||||
if (isLoading || statusIds.length > 0) {
|
||||
return statusIds.reduce((acc, statusId, index) => {
|
||||
if (statusId === null) {
|
||||
const gap = renderLoadGap(index);
|
||||
if (gap) {
|
||||
acc.push(gap);
|
||||
const scrollableContent = useMemo(() => {
|
||||
const renderFeaturedStatuses = (): React.ReactNode[] => {
|
||||
if (!featuredStatusIds) return [];
|
||||
|
||||
return featuredStatusIds.map(statusId => (
|
||||
<StatusContainer
|
||||
key={`f-${statusId}`}
|
||||
id={statusId}
|
||||
featured
|
||||
onMoveUp={handleMoveUp}
|
||||
onMoveDown={handleMoveDown}
|
||||
contextType={timelineId}
|
||||
showGroup={showGroup}
|
||||
variant={divideType === 'border' ? 'slim' : 'default'}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
const renderStatuses = (): React.ReactNode[] => {
|
||||
if (isLoading || statusIds.length > 0) {
|
||||
return statusIds.reduce((acc, statusId, index) => {
|
||||
if (statusId === null) {
|
||||
const gap = renderLoadGap(index);
|
||||
if (gap) {
|
||||
acc.push(gap);
|
||||
}
|
||||
} else if (statusId.startsWith('末pending-')) {
|
||||
acc.push(renderPendingStatus(statusId));
|
||||
} else {
|
||||
acc.push(renderStatus(statusId));
|
||||
}
|
||||
} else if (statusId.startsWith('末pending-')) {
|
||||
acc.push(renderPendingStatus(statusId));
|
||||
} else {
|
||||
acc.push(renderStatus(statusId));
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, [] as React.ReactNode[]);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, [] as React.ReactNode[]);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const renderScrollableContent = () => {
|
||||
const featuredStatuses = renderFeaturedStatuses();
|
||||
const statuses = renderStatuses();
|
||||
|
||||
@ -175,7 +175,7 @@ const StatusList: React.FC<IStatusList> = ({
|
||||
} else {
|
||||
return statuses;
|
||||
}
|
||||
};
|
||||
}, [featuredStatusIds, statusIds, isLoading, timelineId, showGroup, divideType]);
|
||||
|
||||
if (isPartial) {
|
||||
return (
|
||||
@ -209,7 +209,7 @@ const StatusList: React.FC<IStatusList> = ({
|
||||
})}
|
||||
{...other}
|
||||
>
|
||||
{renderScrollableContent()}
|
||||
{scrollableContent}
|
||||
</ScrollableList>
|
||||
);
|
||||
};
|
||||
|
||||
@ -33,13 +33,13 @@ interface IStatusReactionsBar {
|
||||
}
|
||||
|
||||
interface IStatusReaction {
|
||||
status: Pick<SelectedStatus, 'id'>;
|
||||
statusId: string;
|
||||
reaction: EmojiReaction;
|
||||
obfuscate?: boolean;
|
||||
unauthenticated?: boolean;
|
||||
}
|
||||
|
||||
const StatusReaction: React.FC<IStatusReaction> = ({ reaction, status, obfuscate, unauthenticated }) => {
|
||||
const StatusReaction: React.FC<IStatusReaction> = ({ reaction, statusId, obfuscate, unauthenticated }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const features = useFeatures();
|
||||
@ -51,7 +51,7 @@ const StatusReaction: React.FC<IStatusReaction> = ({ reaction, status, obfuscate
|
||||
e.stopPropagation();
|
||||
|
||||
if ('vibrate' in navigator) navigator.vibrate(1);
|
||||
openModal('REACTIONS', { statusId: status.id, reaction: reaction.name });
|
||||
openModal('REACTIONS', { statusId: statusId, reaction: reaction.name });
|
||||
});
|
||||
|
||||
if (!reaction.count) return null;
|
||||
@ -61,11 +61,11 @@ const StatusReaction: React.FC<IStatusReaction> = ({ reaction, status, obfuscate
|
||||
|
||||
if (unauthenticated) {
|
||||
if (!features.emojiReactsList) return;
|
||||
openModal('REACTIONS', { statusId: status.id, reaction: reaction.name });
|
||||
openModal('REACTIONS', { statusId, reaction: reaction.name });
|
||||
} else if (reaction.me) {
|
||||
dispatch(unEmojiReact(status, reaction.name));
|
||||
dispatch(unEmojiReact(statusId, reaction.name));
|
||||
} else {
|
||||
dispatch(emojiReact(status, reaction.name, reaction.url));
|
||||
dispatch(emojiReact(statusId, reaction.name, reaction.url));
|
||||
}
|
||||
};
|
||||
|
||||
@ -112,7 +112,7 @@ const StatusReactionsBar: React.FC<IStatusReactionsBar> = ({ status, collapsed }
|
||||
const features = useFeatures();
|
||||
|
||||
const handlePickEmoji = (emoji: EmojiType) => {
|
||||
dispatch(emojiReact(status, emoji.custom ? emoji.id : emoji.native, emoji.custom ? emoji.imageUrl : undefined));
|
||||
dispatch(emojiReact(status.id, emoji.custom ? emoji.id : emoji.native, emoji.custom ? emoji.imageUrl : undefined));
|
||||
};
|
||||
|
||||
if ((demetricator || status.emoji_reactions.length === 0) && collapsed) return null;
|
||||
@ -125,7 +125,7 @@ const StatusReactionsBar: React.FC<IStatusReactionsBar> = ({ status, collapsed }
|
||||
{sortedReactions.map((reaction) => reaction.count ? (
|
||||
<StatusReaction
|
||||
key={reaction.name}
|
||||
status={status}
|
||||
statusId={status.id}
|
||||
reaction={reaction}
|
||||
obfuscate={demetricator}
|
||||
unauthenticated={!me}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import { defineMessages, useIntl, FormattedList, FormattedMessage } from 'react-intl';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
|
||||
@ -83,7 +83,7 @@ const Status: React.FC<IStatus> = (props) => {
|
||||
const didShowCard = useRef(false);
|
||||
const node = useRef<HTMLDivElement>(null);
|
||||
|
||||
const getStatus = useCallback(makeGetStatus(), []);
|
||||
const getStatus = useMemo(makeGetStatus, []);
|
||||
const actualStatus = useAppSelector(state => status.reblog_id && getStatus(state, { id: status.reblog_id }) || status)!;
|
||||
|
||||
const isReblog = status.reblog_id;
|
||||
@ -185,7 +185,7 @@ const Status: React.FC<IStatus> = (props) => {
|
||||
|
||||
const handleUnfilter = () => dispatch(unfilterStatus(status.filtered.length ? status.id : actualStatus.id));
|
||||
|
||||
const renderStatusInfo = () => {
|
||||
const statusInfo = useMemo(() => {
|
||||
if (isReblog && showGroup && group) {
|
||||
return (
|
||||
<StatusInfo
|
||||
@ -294,7 +294,7 @@ const Status: React.FC<IStatus> = (props) => {
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [status.accounts, group?.id]);
|
||||
|
||||
if (!status) return null;
|
||||
|
||||
@ -366,7 +366,7 @@ const Status: React.FC<IStatus> = (props) => {
|
||||
})}
|
||||
data-id={status.id}
|
||||
>
|
||||
{renderStatusInfo()}
|
||||
{statusInfo}
|
||||
|
||||
<AccountContainer
|
||||
key={actualStatus.account_id}
|
||||
@ -382,7 +382,7 @@ const Status: React.FC<IStatus> = (props) => {
|
||||
avatarSize={avatarSize}
|
||||
items={(
|
||||
<>
|
||||
<StatusTypeIcon status={actualStatus} />
|
||||
<StatusTypeIcon visibility={actualStatus.visibility} />
|
||||
<StatusLanguagePicker status={actualStatus} />
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -24,7 +24,7 @@ const messages = defineMessages({
|
||||
sidebar: { id: 'navigation.sidebar', defaultMessage: 'Open sidebar' },
|
||||
});
|
||||
|
||||
const ThumbNavigation: React.FC = (): JSX.Element => {
|
||||
const ThumbNavigation: React.FC = React.memo((): JSX.Element => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const { account } = useOwnAccount();
|
||||
@ -132,6 +132,6 @@ const ThumbNavigation: React.FC = (): JSX.Element => {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export { ThumbNavigation as default };
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import Status, { IStatus } from 'pl-fe/components/status';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
@ -16,13 +16,13 @@ interface IStatusContainer extends Omit<IStatus, 'status'> {
|
||||
* @deprecated Use the Status component directly.
|
||||
*/
|
||||
const StatusContainer: React.FC<IStatusContainer> = (props) => {
|
||||
const { id, contextType, ...rest } = props;
|
||||
const { id, contextType } = props;
|
||||
|
||||
const getStatus = useCallback(makeGetStatus(), []);
|
||||
const getStatus = useMemo(makeGetStatus, []);
|
||||
const status = useAppSelector(state => getStatus(state, { id, contextType }));
|
||||
|
||||
if (status) {
|
||||
return <Status status={status} {...rest} />;
|
||||
return <Status {...props} status={status} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
@ -31,38 +31,33 @@ const HomeTimeline: React.FC = () => {
|
||||
|
||||
const isPartial = useAppSelector(state => state.timelines.home?.isPartial === true);
|
||||
|
||||
const handleLoadMore = (maxId: string) => {
|
||||
dispatch(fetchHomeTimeline(true));
|
||||
};
|
||||
|
||||
// Mastodon generates the feed in Redis, and can return a partial timeline
|
||||
// (HTTP 206) for new users. Poll until we get a full page of results.
|
||||
const checkIfReloadNeeded = () => {
|
||||
const checkIfReloadNeeded = useCallback((isPartial: boolean) => {
|
||||
if (isPartial) {
|
||||
polling.current = setInterval(() => {
|
||||
dispatch(fetchHomeTimeline());
|
||||
}, 3000);
|
||||
} else {
|
||||
stopPolling();
|
||||
if (polling.current) {
|
||||
clearInterval(polling.current);
|
||||
polling.current = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const stopPolling = () => {
|
||||
if (polling.current) {
|
||||
clearInterval(polling.current);
|
||||
polling.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRefresh = () => dispatch(fetchHomeTimeline(true));
|
||||
|
||||
useEffect(() => {
|
||||
checkIfReloadNeeded();
|
||||
|
||||
return () => {
|
||||
stopPolling();
|
||||
if (polling.current) {
|
||||
clearInterval(polling.current);
|
||||
polling.current = null;
|
||||
}
|
||||
};
|
||||
}, [isPartial]);
|
||||
}, []);
|
||||
|
||||
const handleLoadMore = useCallback(() => dispatch(fetchHomeTimeline(true)), []);
|
||||
|
||||
const handleRefresh = useCallback(() => dispatch(fetchHomeTimeline(false)), []);
|
||||
|
||||
useEffect(() => checkIfReloadNeeded(isPartial), [isPartial]);
|
||||
|
||||
return (
|
||||
<Column className='py-0' label={intl.formatMessage(messages.title)} transparent={!isMobile} withHeader={false}>
|
||||
|
||||
@ -151,7 +151,7 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
|
||||
</Text>
|
||||
</span>
|
||||
|
||||
<StatusTypeIcon status={actualStatus} />
|
||||
<StatusTypeIcon visibility={actualStatus.visibility} />
|
||||
|
||||
<StatusLanguagePicker status={actualStatus} showLabel />
|
||||
</HStack>
|
||||
|
||||
@ -7,7 +7,7 @@ import Text from 'pl-fe/components/ui/text';
|
||||
import type { Status } from 'pl-fe/normalizers/status';
|
||||
|
||||
interface IStatusTypeIcon {
|
||||
status: Pick<Status, 'visibility'>;
|
||||
visibility: Status['visibility'];
|
||||
}
|
||||
|
||||
const messages: Record<string, MessageDescriptor> = defineMessages({
|
||||
@ -29,11 +29,11 @@ const STATUS_TYPE_ICONS: Record<string, string> = {
|
||||
subscribers: require('@tabler/icons/outline/coin.svg'),
|
||||
};
|
||||
|
||||
const StatusTypeIcon: React.FC<IStatusTypeIcon> = ({ status }) => {
|
||||
const StatusTypeIcon: React.FC<IStatusTypeIcon> = React.memo(({ visibility }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const icon = STATUS_TYPE_ICONS[status.visibility];
|
||||
const message = messages[status.visibility];
|
||||
const icon = STATUS_TYPE_ICONS[visibility];
|
||||
const message = messages[visibility];
|
||||
|
||||
if (!icon) return null;
|
||||
|
||||
@ -44,6 +44,6 @@ const StatusTypeIcon: React.FC<IStatusTypeIcon> = ({ status }) => {
|
||||
<Icon title={message ? intl.formatMessage(message) : undefined} className='size-4 text-gray-700 dark:text-gray-600' src={icon} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export { StatusTypeIcon as default };
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import { dequeueTimeline, scrollTopTimeline } from 'pl-fe/actions/timelines';
|
||||
@ -31,7 +31,7 @@ const Timeline: React.FC<ITimeline> = ({
|
||||
...rest
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const getStatusIds = useCallback(makeGetStatusIds(), []);
|
||||
const getStatusIds = useMemo(makeGetStatusIds, []);
|
||||
|
||||
const statusIds = useAppSelector(state => getStatusIds(state, { type: timelineId, prefix }));
|
||||
const lastStatusId = statusIds.at(-1);
|
||||
|
||||
@ -152,7 +152,7 @@ interface ISwitchingColumnsArea {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) => {
|
||||
const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = React.memo(({ children }) => {
|
||||
const instance = useInstance();
|
||||
const features = useFeatures();
|
||||
const { search } = useLocation();
|
||||
@ -349,13 +349,13 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
|
||||
<WrappedRoute layout={EmptyLayout} component={GenericNotFound} content={children} />
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
interface IUI {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const UI: React.FC<IUI> = ({ children }) => {
|
||||
const UI: React.FC<IUI> = React.memo(({ children }) => {
|
||||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
const node = useRef<HTMLDivElement | null>(null);
|
||||
@ -507,6 +507,6 @@ const UI: React.FC<IUI> = ({ children }) => {
|
||||
</div>
|
||||
</GlobalHotkeys>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export { UI as default };
|
||||
|
||||
@ -121,49 +121,52 @@ const checkFiltered = (index: string, filters: Array<Filter>) =>
|
||||
|
||||
type APIStatus = { id: string; username?: string };
|
||||
|
||||
const makeGetStatus = () => createSelector(
|
||||
[
|
||||
(state: RootState, { id }: APIStatus) => state.statuses[id],
|
||||
(state: RootState, { id }: APIStatus) => state.statuses[state.statuses[id]?.reblog_id || ''] || null,
|
||||
(state: RootState, { id }: APIStatus) => state.statuses[state.statuses[id]?.quote_id || ''] || null,
|
||||
(state: RootState, { id }: APIStatus) => {
|
||||
const group = state.statuses[id]?.group_id;
|
||||
if (group) return state.entities[Entities.GROUPS]?.store[group] as Group;
|
||||
return undefined;
|
||||
},
|
||||
(state: RootState, { id }: APIStatus) => state.polls[id] || null,
|
||||
(_state: RootState, { username }: APIStatus) => username,
|
||||
getFilters,
|
||||
(state: RootState) => state.me,
|
||||
(state: RootState) => state.auth.client.features,
|
||||
],
|
||||
const makeGetStatus = () => {
|
||||
console.log('making get status');
|
||||
return createSelector(
|
||||
[
|
||||
(state: RootState, { id }: APIStatus) => state.statuses[id],
|
||||
(state: RootState, { id }: APIStatus) => state.statuses[state.statuses[id]?.reblog_id || ''] || null,
|
||||
(state: RootState, { id }: APIStatus) => state.statuses[state.statuses[id]?.quote_id || ''] || null,
|
||||
(state: RootState, { id }: APIStatus) => {
|
||||
const group = state.statuses[id]?.group_id;
|
||||
if (group) return state.entities[Entities.GROUPS]?.store[group] as Group;
|
||||
return undefined;
|
||||
},
|
||||
(state: RootState, { id }: APIStatus) => state.polls[id] || null,
|
||||
(_state: RootState, { username }: APIStatus) => username,
|
||||
getFilters,
|
||||
(state: RootState) => state.me,
|
||||
(state: RootState) => state.auth.client.features,
|
||||
],
|
||||
|
||||
(statusBase, statusReblog, statusQuote, statusGroup, poll, username, filters, me, features) => {
|
||||
(statusBase, statusReblog, statusQuote, statusGroup, poll, username, filters, me, features) => {
|
||||
// const locale = getLocale('en');
|
||||
|
||||
if (!statusBase) return null;
|
||||
const { account } = statusBase;
|
||||
const accountUsername = account.acct;
|
||||
if (!statusBase) return null;
|
||||
const { account } = statusBase;
|
||||
const accountUsername = account.acct;
|
||||
|
||||
// Must be owner of status if username exists.
|
||||
if (accountUsername !== username && username !== undefined) {
|
||||
return null;
|
||||
}
|
||||
// Must be owner of status if username exists.
|
||||
if (accountUsername !== username && username !== undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filtered = features.filtersV2
|
||||
? statusBase.filtered
|
||||
: features.filters && account.id !== me && checkFiltered(statusReblog?.search_index || statusBase.search_index || '', filters) || [];
|
||||
const filtered = features.filtersV2
|
||||
? statusBase.filtered
|
||||
: features.filters && account.id !== me && checkFiltered(statusReblog?.search_index || statusBase.search_index || '', filters) || [];
|
||||
|
||||
return {
|
||||
...statusBase,
|
||||
reblog: statusReblog || null,
|
||||
quote: statusQuote || null,
|
||||
group: statusGroup || null,
|
||||
poll,
|
||||
filtered,
|
||||
};
|
||||
},
|
||||
);
|
||||
return {
|
||||
...statusBase,
|
||||
reblog: statusReblog || null,
|
||||
quote: statusQuote || null,
|
||||
group: statusGroup || null,
|
||||
poll,
|
||||
filtered,
|
||||
};
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
type SelectedStatus = Exclude<ReturnType<ReturnType<typeof makeGetStatus>>, null>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user