mirror of
https://github.com/mastodon/mastodon.git
synced 2026-09-27 17:07:48 -05:00
Status redesign: Replace icons for status actions (#40655)
This commit is contained in:
@@ -1,18 +1,10 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import {
|
||||
ArrowsClockwiseIcon,
|
||||
BookmarkSimpleIcon,
|
||||
ChatCircleIcon,
|
||||
DotsThreeIcon,
|
||||
HeartIcon,
|
||||
QuotesIcon,
|
||||
ShareFatIcon,
|
||||
} from '@phosphor-icons/react';
|
||||
import { DotsThreeIcon, ShareFatIcon } from '@phosphor-icons/react';
|
||||
|
||||
import { statusInteraction } from '@/mastodon/actions/interactions';
|
||||
import { fetchStatus } from '@/mastodon/actions/statuses';
|
||||
@@ -20,7 +12,6 @@ import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
||||
import { useAccountStatus } from '@/mastodon/hooks/useStatus';
|
||||
import { quickBoosting } from '@/mastodon/initial_state';
|
||||
import type { AccountStatusShape } from '@/mastodon/models/status';
|
||||
import { selectStatusConditions } from '@/mastodon/selectors/statuses';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
|
||||
import {
|
||||
@@ -29,7 +20,6 @@ import {
|
||||
ToggleButton,
|
||||
ToggleIconButton,
|
||||
} from '../button/redesign';
|
||||
import { iconWeight, useIconWeight } from '../icon';
|
||||
import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
@@ -38,8 +28,11 @@ import {
|
||||
LegacyDropdownMenuItems,
|
||||
} from '../menu';
|
||||
|
||||
import { boostItemState, quoteItemState } from './boost_button_utils';
|
||||
import { useStatusContext, useStatusMenuActions } from './hooks';
|
||||
import {
|
||||
useStatusContext,
|
||||
useStatusIcons,
|
||||
useStatusMenuActions,
|
||||
} from './hooks';
|
||||
import { RemoveQuoteHint } from './legacy/action_bar/remove_quote_hint';
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
@@ -51,15 +44,6 @@ interface StatusActionBarProps {
|
||||
onlyResponses?: boolean;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
|
||||
favourite: { id: 'status.like', defaultMessage: 'Like' },
|
||||
removeFavourite: {
|
||||
id: 'status.unlike',
|
||||
defaultMessage: 'Unlike',
|
||||
},
|
||||
});
|
||||
|
||||
export const StatusActionBar: React.FC<StatusActionBarProps> = ({
|
||||
statusId,
|
||||
withDismiss,
|
||||
@@ -77,12 +61,6 @@ export const StatusActionBar: React.FC<StatusActionBarProps> = ({
|
||||
|
||||
// Actions
|
||||
const dispatch = useAppDispatch();
|
||||
const handleReplyClick = useCallback(() => {
|
||||
dispatch(statusInteraction({ statusId, intent: 'reply', contextType }));
|
||||
}, [contextType, dispatch, statusId]);
|
||||
const handleFavouriteClick = useCallback(() => {
|
||||
dispatch(statusInteraction({ statusId, intent: 'favourite', contextType }));
|
||||
}, [contextType, dispatch, statusId]);
|
||||
const handleShareClick = useCallback(() => {
|
||||
if (!statusUrl) {
|
||||
return;
|
||||
@@ -99,17 +77,8 @@ export const StatusActionBar: React.FC<StatusActionBarProps> = ({
|
||||
dispatch(statusInteraction({ statusId, intent: 'copy', contextType }));
|
||||
}
|
||||
}, [contextType, dispatch, statusId, statusUrl]);
|
||||
const handleBookmarkClick = useCallback(() => {
|
||||
dispatch(statusInteraction({ statusId, intent: 'bookmark', contextType }));
|
||||
}, [contextType, dispatch, statusId]);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const favouriteIcon = useIconWeight(HeartIcon, status?.favourited && 'fill');
|
||||
const bookmarkIcon = useIconWeight(
|
||||
BookmarkSimpleIcon,
|
||||
status?.bookmarked && 'fill',
|
||||
);
|
||||
const { reply, like, bookmark } = useStatusIcons(statusId);
|
||||
|
||||
if (!status) {
|
||||
return null;
|
||||
@@ -118,10 +87,6 @@ export const StatusActionBar: React.FC<StatusActionBarProps> = ({
|
||||
const isPublic =
|
||||
status.visibility === 'public' || status.visibility === 'unlisted';
|
||||
|
||||
const favouriteTitle = intl.formatMessage(
|
||||
status.favourited ? messages.removeFavourite : messages.favourite,
|
||||
);
|
||||
|
||||
const isQuotingMe = quotedAccountId === currentAccountId;
|
||||
const shouldShowQuoteRemovalHint =
|
||||
isQuotingMe && contextType === 'notifications';
|
||||
@@ -132,11 +97,11 @@ export const StatusActionBar: React.FC<StatusActionBarProps> = ({
|
||||
size='sm'
|
||||
clipPadding
|
||||
variant='ghost'
|
||||
title={intl.formatMessage(messages.replyAll)}
|
||||
leadingIcon={ChatCircleIcon}
|
||||
onClick={handleReplyClick}
|
||||
title={reply.title}
|
||||
leadingIcon={reply.icon}
|
||||
onClick={reply.action}
|
||||
>
|
||||
{withCounters && status.replies_count}
|
||||
{withCounters && reply.counter}
|
||||
</Button>
|
||||
|
||||
<StatusReblogButton statusId={statusId}>
|
||||
@@ -146,13 +111,13 @@ export const StatusActionBar: React.FC<StatusActionBarProps> = ({
|
||||
<ToggleButton
|
||||
size='sm'
|
||||
variant='ghost'
|
||||
active={status.favourited}
|
||||
title={favouriteTitle}
|
||||
leadingIcon={favouriteIcon}
|
||||
onClick={handleFavouriteClick}
|
||||
active={like.active}
|
||||
title={like.title}
|
||||
leadingIcon={like.icon}
|
||||
onClick={like.action}
|
||||
className={classNames(!onlyResponses && classes.actionsButtonGap)}
|
||||
>
|
||||
{withCounters && status.favourites_count}
|
||||
{withCounters && like.counter}
|
||||
</ToggleButton>
|
||||
</>
|
||||
);
|
||||
@@ -179,18 +144,12 @@ export const StatusActionBar: React.FC<StatusActionBarProps> = ({
|
||||
<ToggleIconButton
|
||||
size='sm'
|
||||
variant='ghost'
|
||||
active={status.bookmarked}
|
||||
icon={bookmarkIcon}
|
||||
onClick={handleBookmarkClick}
|
||||
active={bookmark.active}
|
||||
title={bookmark.title}
|
||||
icon={bookmark.icon}
|
||||
onClick={bookmark.action}
|
||||
>
|
||||
{!status.bookmarked ? (
|
||||
<FormattedMessage id='status.save' defaultMessage='Save' />
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='status.remove_from_saved'
|
||||
defaultMessage='Remove from Saved'
|
||||
/>
|
||||
)}
|
||||
{bookmark.title}
|
||||
</ToggleIconButton>
|
||||
|
||||
<RemoveQuoteHint
|
||||
@@ -213,32 +172,18 @@ const StatusReblogButton: React.FC<{
|
||||
statusId: string;
|
||||
children: React.ReactNode;
|
||||
}> = ({ statusId, children }) => {
|
||||
const conditions = useAppSelector((state) =>
|
||||
selectStatusConditions(state, statusId),
|
||||
);
|
||||
const { isBoosted } = conditions;
|
||||
|
||||
const boostState = boostItemState(conditions);
|
||||
const quoteState = quoteItemState(conditions);
|
||||
const intl = useIntl();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const onReblog = useCallback(() => {
|
||||
dispatch(statusInteraction({ statusId, intent: 'reblog' }));
|
||||
}, [dispatch, statusId]);
|
||||
const onQuote = useCallback(() => {
|
||||
dispatch(statusInteraction({ statusId, intent: 'quote' }));
|
||||
}, [dispatch, statusId]);
|
||||
const { boost, quote } = useStatusIcons(statusId);
|
||||
|
||||
if (quickBoosting) {
|
||||
return (
|
||||
<ToggleButton
|
||||
size='sm'
|
||||
variant='ghost'
|
||||
active={isBoosted}
|
||||
leadingIcon={ArrowsClockwiseIcon}
|
||||
onClick={onReblog}
|
||||
disabled={boostState.disabled}
|
||||
active={boost.active}
|
||||
title={boost.title}
|
||||
leadingIcon={boost.icon}
|
||||
disabled={boost.disabled}
|
||||
onClick={boost.action}
|
||||
>
|
||||
{children}
|
||||
</ToggleButton>
|
||||
@@ -251,36 +196,35 @@ const StatusReblogButton: React.FC<{
|
||||
as={ToggleButton}
|
||||
size='sm'
|
||||
variant='ghost'
|
||||
active={isBoosted}
|
||||
leadingIcon={ArrowsClockwiseIcon}
|
||||
active={boost.active}
|
||||
title={boost.title}
|
||||
leadingIcon={boost.icon}
|
||||
>
|
||||
{children}
|
||||
</MenuTrigger>
|
||||
|
||||
<MenuList placement='bottom' maxWidth={180}>
|
||||
<MenuItem
|
||||
onClick={onReblog}
|
||||
icon={ArrowsClockwiseIcon}
|
||||
disabled={boostState.disabled}
|
||||
description={boostState.meta && intl.formatMessage(boostState.meta)}
|
||||
onClick={boost.action}
|
||||
icon={boost.icon}
|
||||
disabled={boost.disabled}
|
||||
description={boost.meta}
|
||||
>
|
||||
{intl.formatMessage(boostState.title)}
|
||||
{boost.title}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={onQuote}
|
||||
icon={QuotesFilledIcon}
|
||||
disabled={quoteState.disabled}
|
||||
description={quoteState.meta && intl.formatMessage(quoteState.meta)}
|
||||
onClick={quote.action}
|
||||
icon={quote.icon}
|
||||
disabled={quote.disabled}
|
||||
description={quote.meta}
|
||||
>
|
||||
{intl.formatMessage(quoteState.title)}
|
||||
{quote.title}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
const QuotesFilledIcon = iconWeight(QuotesIcon, 'fill');
|
||||
|
||||
const StatusActionMenu: React.FC<{
|
||||
dismissQuoteHint: () => void;
|
||||
status: AccountStatusShape;
|
||||
|
||||
@@ -2,16 +2,20 @@ import { defineMessages } from 'react-intl';
|
||||
import type { MessageDescriptor } from 'react-intl';
|
||||
|
||||
import type { StatusConditions } from '@/mastodon/selectors/statuses';
|
||||
import FormatQuote from '@/material-icons/400-24px/format_quote-fill.svg?react';
|
||||
import { isRedesignEnabled } from '@/mastodon/utils/environment';
|
||||
import FormatQuoteOff from '@/material-icons/400-24px/format_quote_off-fill.svg?react';
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
|
||||
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
|
||||
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
|
||||
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
|
||||
|
||||
import type { IconProp } from '../icon';
|
||||
|
||||
import {
|
||||
StatusBoostActiveIcon,
|
||||
StatusBoostIcon,
|
||||
StatusQuoteIcon,
|
||||
} from './icons';
|
||||
|
||||
export const messages = defineMessages({
|
||||
all_disabled: {
|
||||
id: 'status.all_disabled',
|
||||
@@ -76,20 +80,27 @@ export function boostItemState({
|
||||
if (isBoosted) {
|
||||
return {
|
||||
title: messages.reblog_cancel,
|
||||
iconComponent: isPublic ? RepeatActiveIcon : RepeatPrivateActiveIcon,
|
||||
iconComponent:
|
||||
isPublic || isRedesignEnabled()
|
||||
? StatusBoostActiveIcon
|
||||
: RepeatPrivateActiveIcon,
|
||||
};
|
||||
}
|
||||
const iconText: MenuItemState = {
|
||||
title: messages.reblog,
|
||||
iconComponent: RepeatIcon,
|
||||
iconComponent: StatusBoostIcon,
|
||||
};
|
||||
|
||||
if (isPrivateReblog) {
|
||||
iconText.meta = messages.reblog_private;
|
||||
iconText.iconComponent = RepeatPrivateIcon;
|
||||
if (!isRedesignEnabled()) {
|
||||
iconText.iconComponent = RepeatPrivateIcon;
|
||||
}
|
||||
} else if (!isPublic) {
|
||||
iconText.meta = messages.reblog_cannot;
|
||||
iconText.iconComponent = RepeatDisabledIcon;
|
||||
if (!isRedesignEnabled()) {
|
||||
iconText.iconComponent = RepeatDisabledIcon;
|
||||
}
|
||||
iconText.disabled = true;
|
||||
}
|
||||
return iconText;
|
||||
@@ -105,12 +116,14 @@ export function quoteItemState({
|
||||
}: StatusConditions): MenuItemState {
|
||||
const iconText: MenuItemState = {
|
||||
title: messages.quote,
|
||||
iconComponent: FormatQuote,
|
||||
iconComponent: StatusQuoteIcon,
|
||||
};
|
||||
|
||||
if (!isPublic && !isMine) {
|
||||
iconText.disabled = true;
|
||||
iconText.iconComponent = FormatQuoteOff;
|
||||
if (!isRedesignEnabled()) {
|
||||
iconText.iconComponent = FormatQuoteOff;
|
||||
}
|
||||
iconText.meta = messages.quote_private;
|
||||
} else if (isQuoteAutomaticallyAccepted) {
|
||||
iconText.title = messages.quote;
|
||||
@@ -120,7 +133,9 @@ export function quoteItemState({
|
||||
// We don't show the disabled state when logged out
|
||||
} else if (isLoggedIn) {
|
||||
iconText.disabled = true;
|
||||
iconText.iconComponent = FormatQuoteOff;
|
||||
if (!isRedesignEnabled()) {
|
||||
iconText.iconComponent = FormatQuoteOff;
|
||||
}
|
||||
iconText.meta = isQuoteFollowersOnly
|
||||
? messages.quote_followers_only
|
||||
: messages.quote_cannot;
|
||||
|
||||
@@ -25,7 +25,7 @@ import { statusInteraction } from '@/mastodon/actions/interactions_typed';
|
||||
import { openModal } from '@/mastodon/actions/modal';
|
||||
import { toggleStatusSpoilers } from '@/mastodon/actions/statuses';
|
||||
import { useRelationship } from '@/mastodon/hooks/useRelationship';
|
||||
import { useExpandedStatus } from '@/mastodon/hooks/useStatus';
|
||||
import { useExpandedStatus, useStatus } from '@/mastodon/hooks/useStatus';
|
||||
import { useToggle } from '@/mastodon/hooks/useToggle';
|
||||
import { useIdentity } from '@/mastodon/identity_context';
|
||||
import { quickBoosting } from '@/mastodon/initial_state';
|
||||
@@ -56,8 +56,18 @@ import type { OnElementHandler } from '@/mastodon/utils/html';
|
||||
|
||||
import { FOCUS_TARGET } from '../navigation_focus_target';
|
||||
|
||||
import { quoteItemState } from './boost_button_utils';
|
||||
import { boostItemState, quoteItemState } from './boost_button_utils';
|
||||
import { useElementHandledLink } from './handled_link';
|
||||
import {
|
||||
StatusBookmarkActiveIcon,
|
||||
StatusBookmarkIcon,
|
||||
StatusBoostActiveIcon,
|
||||
StatusBoostIcon,
|
||||
StatusLikeActiveIcon,
|
||||
StatusLikeIcon,
|
||||
StatusReplyAllIcon,
|
||||
StatusReplyIcon,
|
||||
} from './icons';
|
||||
import type { StatusContextType } from './types';
|
||||
|
||||
export const StatusContext = createContext<{
|
||||
@@ -69,6 +79,23 @@ export function useStatusContext() {
|
||||
return use(StatusContext);
|
||||
}
|
||||
|
||||
export function useStatusInteractionFactory(
|
||||
statusId?: string,
|
||||
contextTypeArg?: StatusContextType,
|
||||
) {
|
||||
const statusContext = useStatusContext();
|
||||
const contextType = contextTypeArg ?? statusContext.contextType;
|
||||
const dispatch = useAppDispatch();
|
||||
return useCallback(
|
||||
(intent: StatusInteractionIntent) => {
|
||||
return () => {
|
||||
dispatch(statusInteraction({ statusId, intent, contextType }));
|
||||
};
|
||||
},
|
||||
[contextType, dispatch, statusId],
|
||||
);
|
||||
}
|
||||
|
||||
export function useStatusHandlers({
|
||||
status,
|
||||
contextType,
|
||||
@@ -101,14 +128,7 @@ export function useStatusHandlers({
|
||||
}, [dispatch, filterAction, onFilterToggle, showDespiteFilter, status]);
|
||||
|
||||
// Interaction handlers
|
||||
const handlerFactory = useCallback(
|
||||
(intent: StatusInteractionIntent) => {
|
||||
return () => {
|
||||
dispatch(statusInteraction({ statusId, intent, contextType }));
|
||||
};
|
||||
},
|
||||
[contextType, dispatch, statusId],
|
||||
);
|
||||
const handlerFactory = useStatusInteractionFactory(statusId, contextType);
|
||||
|
||||
const accountId = status?.account.id;
|
||||
const onMention = useCallback(() => {
|
||||
@@ -238,6 +258,120 @@ export function useStatusHandlers({
|
||||
}
|
||||
export type StatusHandlers = ReturnType<typeof useStatusHandlers>;
|
||||
|
||||
interface StatusIcon {
|
||||
icon: React.FC<React.SVGProps<SVGSVGElement>>;
|
||||
title: string;
|
||||
meta?: string;
|
||||
counter?: number;
|
||||
active?: boolean;
|
||||
action: () => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const iconMessages = defineMessages({
|
||||
reply: { id: 'status.reply', defaultMessage: 'Reply' },
|
||||
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
|
||||
removeFavourite: {
|
||||
id: 'status.remove_favourite',
|
||||
defaultMessage: 'Remove from favorites',
|
||||
},
|
||||
like: { id: 'status.like', defaultMessage: 'Like' },
|
||||
removeLike: {
|
||||
id: 'status.unlike',
|
||||
defaultMessage: 'Unlike',
|
||||
},
|
||||
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
||||
removeBookmark: {
|
||||
id: 'status.remove_bookmark',
|
||||
defaultMessage: 'Remove bookmark',
|
||||
},
|
||||
});
|
||||
|
||||
export function useStatusIcons(statusId: string) {
|
||||
const intl = useIntl();
|
||||
const conditions = useAppSelector((state) =>
|
||||
selectStatusConditions(state, statusId),
|
||||
);
|
||||
const status = useStatus(statusId);
|
||||
const interactions = useAppSelector((state) =>
|
||||
selectStatusInteractionsAllowed(state, statusId),
|
||||
);
|
||||
const interactionFactory = useStatusInteractionFactory(statusId);
|
||||
const isRedesign = isRedesignEnabled();
|
||||
|
||||
const isReplyAll = !!status?.in_reply_to_id;
|
||||
const reply: StatusIcon = {
|
||||
icon: isReplyAll ? StatusReplyAllIcon : StatusReplyIcon,
|
||||
title: intl.formatMessage(
|
||||
isReplyAll ? iconMessages.replyAll : iconMessages.reply,
|
||||
),
|
||||
counter: status?.replies_count,
|
||||
action: interactionFactory('reply'),
|
||||
disabled: interactions.reply,
|
||||
};
|
||||
|
||||
const boostState = boostItemState(conditions);
|
||||
const boost: StatusIcon = {
|
||||
icon: boostState.iconComponent,
|
||||
title: intl.formatMessage(boostState.title),
|
||||
meta: boostState.meta ? intl.formatMessage(boostState.meta) : undefined,
|
||||
counter: status?.reblogs_count,
|
||||
active: status?.reblogged ?? false,
|
||||
action: interactionFactory('reblog'),
|
||||
disabled: boostState.disabled ?? false,
|
||||
};
|
||||
if (isRedesign) {
|
||||
boost.icon = status?.reblogged ? StatusBoostActiveIcon : StatusBoostIcon;
|
||||
}
|
||||
|
||||
const quoteState = quoteItemState(conditions);
|
||||
const quote: StatusIcon = {
|
||||
icon: quoteState.iconComponent,
|
||||
title: intl.formatMessage(quoteState.title),
|
||||
meta: quoteState.meta ? intl.formatMessage(quoteState.meta) : undefined,
|
||||
counter: status?.quotes_count,
|
||||
action: interactionFactory('quote'),
|
||||
disabled: quoteState.disabled ?? false,
|
||||
};
|
||||
|
||||
const isLiked = !!status?.favourited;
|
||||
const like: StatusIcon = {
|
||||
icon: isLiked ? StatusLikeActiveIcon : StatusLikeIcon,
|
||||
title: intl.formatMessage(
|
||||
isLiked ? iconMessages.removeFavourite : iconMessages.favourite,
|
||||
),
|
||||
counter: status?.favourites_count ?? 0,
|
||||
active: isLiked,
|
||||
action: interactionFactory('favourite'),
|
||||
disabled: interactions.favourite,
|
||||
};
|
||||
if (isRedesign) {
|
||||
like.title = intl.formatMessage(
|
||||
isLiked ? iconMessages.removeLike : iconMessages.like,
|
||||
);
|
||||
}
|
||||
|
||||
const isBookmarked = !!status?.bookmarked;
|
||||
const bookmark: StatusIcon = {
|
||||
icon: isBookmarked ? StatusBookmarkActiveIcon : StatusBookmarkIcon,
|
||||
title: intl.formatMessage(
|
||||
isBookmarked ? iconMessages.removeBookmark : iconMessages.bookmark,
|
||||
),
|
||||
active: isBookmarked,
|
||||
action: interactionFactory('bookmark'),
|
||||
disabled: interactions.bookmark,
|
||||
};
|
||||
|
||||
return {
|
||||
reply,
|
||||
boost,
|
||||
quote,
|
||||
like,
|
||||
bookmark,
|
||||
} as const;
|
||||
}
|
||||
|
||||
const screenReaderMessages = defineMessages({
|
||||
quote_noun: {
|
||||
id: 'status.quote_noun',
|
||||
@@ -436,16 +570,7 @@ export function useStatusMenuActions({
|
||||
const interactions = useAppSelector((state) =>
|
||||
selectStatusInteractionsAllowed(state, status.id),
|
||||
);
|
||||
const statusInteractionFactory = useCallback(
|
||||
(intent: StatusInteractionIntent) => {
|
||||
return () => {
|
||||
dispatch(
|
||||
statusInteraction({ statusId: status.id, contextType, intent }),
|
||||
);
|
||||
};
|
||||
},
|
||||
[contextType, dispatch, status.id],
|
||||
);
|
||||
const statusInteractionFactory = useStatusInteractionFactory(status.id);
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
|
||||
49
app/javascript/mastodon/components/status/icons.tsx
Normal file
49
app/javascript/mastodon/components/status/icons.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
ArrowsClockwiseIcon,
|
||||
BookmarkSimpleIcon,
|
||||
ChatCircleIcon,
|
||||
HeartIcon,
|
||||
QuotesIcon,
|
||||
} from '@phosphor-icons/react';
|
||||
|
||||
import { isRedesignEnabled } from '@/mastodon/utils/environment';
|
||||
import BookmarkLegacyIcon from '@/material-icons/400-24px/bookmark-fill.svg?react';
|
||||
import BookmarkBorderLegacyIcon from '@/material-icons/400-24px/bookmark.svg?react';
|
||||
import QuoteLegacyIcon from '@/material-icons/400-24px/format_quote-fill.svg?react';
|
||||
import BoostLegacyIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import ReplyLegacyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import ReplyAllLegacyIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
||||
import StarLegacyIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||
import StarBorderLegacyIcon from '@/material-icons/400-24px/star.svg?react';
|
||||
import BoostActiveIcon from '@/svg-icons/boost_active.svg?react';
|
||||
import BoostActiveLegacyIcon from '@/svg-icons/repeat_active.svg?react';
|
||||
|
||||
import { iconWeight } from '../icon';
|
||||
|
||||
export const StatusReplyIcon = isRedesignEnabled()
|
||||
? ChatCircleIcon
|
||||
: ReplyLegacyIcon;
|
||||
export const StatusReplyAllIcon = isRedesignEnabled()
|
||||
? StatusReplyIcon
|
||||
: ReplyAllLegacyIcon;
|
||||
export const StatusQuoteIcon = isRedesignEnabled()
|
||||
? QuoteLegacyIcon
|
||||
: QuotesIcon;
|
||||
export const StatusBoostIcon = isRedesignEnabled()
|
||||
? ArrowsClockwiseIcon
|
||||
: BoostLegacyIcon;
|
||||
export const StatusBoostActiveIcon = isRedesignEnabled()
|
||||
? BoostActiveIcon
|
||||
: BoostActiveLegacyIcon;
|
||||
export const StatusLikeIcon = isRedesignEnabled()
|
||||
? HeartIcon
|
||||
: StarBorderLegacyIcon;
|
||||
export const StatusLikeActiveIcon = isRedesignEnabled()
|
||||
? iconWeight(HeartIcon, 'fill')
|
||||
: StarLegacyIcon;
|
||||
export const StatusBookmarkIcon = isRedesignEnabled()
|
||||
? BookmarkSimpleIcon
|
||||
: BookmarkBorderLegacyIcon;
|
||||
export const StatusBookmarkActiveIcon = isRedesignEnabled()
|
||||
? iconWeight(BookmarkSimpleIcon, 'fill')
|
||||
: BookmarkLegacyIcon;
|
||||
@@ -8,13 +8,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { ImmutablePureComponent } from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react';
|
||||
import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react';
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
||||
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||
import StarBorderIcon from '@/material-icons/400-24px/star.svg?react';
|
||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
@@ -29,6 +23,7 @@ import { RemoveQuoteHint } from './remove_quote_hint';
|
||||
import { quoteItemState } from '../../boost_button_utils';
|
||||
import { selectStatusConditions } from '@/mastodon/selectors/statuses';
|
||||
import { isRedesignEnabled } from '@/mastodon/utils/environment';
|
||||
import { StatusBookmarkActiveIcon, StatusBookmarkIcon, StatusLikeActiveIcon, StatusLikeIcon, StatusReplyAllIcon, StatusReplyIcon } from '../../icons';
|
||||
|
||||
|
||||
const baseMessages = defineMessages({
|
||||
@@ -386,11 +381,11 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
|
||||
if (status.get('in_reply_to_id', null) === null) {
|
||||
replyIcon = 'reply';
|
||||
replyIconComponent = ReplyIcon;
|
||||
replyIconComponent = StatusReplyIcon;
|
||||
replyTitle = intl.formatMessage(messages.reply);
|
||||
} else {
|
||||
replyIcon = 'reply-all';
|
||||
replyIconComponent = ReplyAllIcon;
|
||||
replyIconComponent = StatusReplyAllIcon;
|
||||
replyTitle = intl.formatMessage(messages.replyAll);
|
||||
}
|
||||
|
||||
@@ -403,16 +398,16 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
return (
|
||||
<div className='status__action-bar'>
|
||||
<div className='status__action-bar__button-wrapper'>
|
||||
<IconButton className='status__action-bar__button' title={replyTitle} icon={isReply ? 'reply' : replyIcon} iconComponent={isReply ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
|
||||
<IconButton className='status__action-bar__button' title={replyTitle} icon={isReply ? 'reply' : replyIcon} iconComponent={isReply ? StatusReplyIcon : replyIconComponent} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
|
||||
</div>
|
||||
<div className='status__action-bar__button-wrapper'>
|
||||
<BoostButton statusId={status.get('id')} counters={withCounters} />
|
||||
</div>
|
||||
<div className='status__action-bar__button-wrapper'>
|
||||
<IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={favouriteTitle} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
||||
<IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={favouriteTitle} icon='star' iconComponent={status.get('favourited') ? StatusLikeActiveIcon : StatusLikeIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
||||
</div>
|
||||
<div className='status__action-bar__button-wrapper'>
|
||||
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={bookmarkTitle} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} />
|
||||
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={bookmarkTitle} icon='bookmark' iconComponent={status.get('bookmarked') ? StatusBookmarkActiveIcon : StatusBookmarkIcon} onClick={this.handleBookmarkClick} />
|
||||
</div>
|
||||
<RemoveQuoteHint className='status__action-bar__button-wrapper' canShowHint={shouldShowQuoteRemovalHint}>
|
||||
{(dismissQuoteHint) => (
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
messages,
|
||||
quoteItemState,
|
||||
} from '../boost_button_utils';
|
||||
import { useStatusIcons } from '../hooks';
|
||||
|
||||
const StandaloneBoostButton: FC<ReblogButtonProps> = ({
|
||||
statusId,
|
||||
@@ -135,6 +136,8 @@ const BoostOrQuoteMenu: FC<ReblogButtonProps> = ({ statusId, counters }) => {
|
||||
);
|
||||
}, [dispatch, status]);
|
||||
|
||||
const { boost, quote } = useStatusIcons(statusId);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const boostItem = boostItemState(statusState);
|
||||
const quoteItem = quoteItemState(statusState);
|
||||
@@ -144,7 +147,7 @@ const BoostOrQuoteMenu: FC<ReblogButtonProps> = ({ statusId, counters }) => {
|
||||
description: boostItem.meta
|
||||
? intl.formatMessage(boostItem.meta)
|
||||
: undefined,
|
||||
icon: boostItem.iconComponent,
|
||||
icon: boost.icon,
|
||||
highlighted: wasBoosted,
|
||||
disabled: boostItem.disabled,
|
||||
action: (event) => {
|
||||
@@ -156,14 +159,22 @@ const BoostOrQuoteMenu: FC<ReblogButtonProps> = ({ statusId, counters }) => {
|
||||
description: quoteItem.meta
|
||||
? intl.formatMessage(quoteItem.meta)
|
||||
: undefined,
|
||||
icon: quoteItem.iconComponent,
|
||||
icon: quote.icon,
|
||||
disabled: quoteItem.disabled,
|
||||
action: () => {
|
||||
dispatch(quoteComposeById(statusId));
|
||||
},
|
||||
},
|
||||
] satisfies [ActionMenuItemWithIcon, ActionMenuItemWithIcon];
|
||||
}, [dispatch, intl, statusId, statusState, wasBoosted]);
|
||||
}, [
|
||||
boost.icon,
|
||||
dispatch,
|
||||
intl,
|
||||
quote.icon,
|
||||
statusId,
|
||||
statusState,
|
||||
wasBoosted,
|
||||
]);
|
||||
|
||||
const boostIcon = items[0].icon;
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { ImmutablePureComponent } from 'react-immutable-pure-component';
|
||||
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import { Hotkeys } from '@/mastodon/components/hotkeys';
|
||||
import { Icon } from '@/mastodon/components/icon';
|
||||
import { PictureInPicturePlaceholder } from './picture_in_picture_placeholder';
|
||||
@@ -34,6 +33,7 @@ import { getHashtagBarForStatus } from './hashtag_bar';
|
||||
import StatusActionBar from './action_bar';
|
||||
import StatusContent from './content';
|
||||
import { StatusThreadLabel } from './thread_label';
|
||||
import { StatusBoostIcon } from '../icons';
|
||||
|
||||
const domParser = new DOMParser();
|
||||
|
||||
@@ -438,7 +438,7 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend__icon'><Icon id='retweet' icon={RepeatIcon} /></div>
|
||||
<div className='status__prepend__icon'><Icon id='retweet' icon={StatusBoostIcon} /></div>
|
||||
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name }} tagName='span' />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { LinkedDisplayName } from '../../display_name';
|
||||
import { StatusReplyIcon } from '../icons';
|
||||
|
||||
export const StatusThreadLabel: React.FC<{
|
||||
accountId: string;
|
||||
@@ -49,7 +49,7 @@ export const StatusThreadLabel: React.FC<{
|
||||
return (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend__icon'>
|
||||
<Icon id='reply' icon={ReplyIcon} />
|
||||
<Icon id='reply' icon={StatusReplyIcon} />
|
||||
</div>
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
|
||||
import { LinkedDisplayName } from '@/mastodon/components/display_name';
|
||||
import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context';
|
||||
import { StatusReplyIcon } from '@/mastodon/components/status/icons';
|
||||
import StatusContent from '@/mastodon/components/status/legacy/content';
|
||||
import type { Account } from '@/mastodon/models/account';
|
||||
import type { StatusShape } from '@/mastodon/models/status';
|
||||
@@ -22,7 +23,6 @@ import {
|
||||
useAppSelector,
|
||||
} from '@/mastodon/store';
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import { replyCompose } from 'mastodon/actions/compose';
|
||||
import {
|
||||
markConversationRead,
|
||||
@@ -256,7 +256,7 @@ export const Conversation: React.FC<{
|
||||
className='status__action-bar-button'
|
||||
title={intl.formatMessage(messages.reply)}
|
||||
icon='reply'
|
||||
iconComponent={ReplyIcon}
|
||||
iconComponent={StatusReplyIcon}
|
||||
onClick={handleReply}
|
||||
/>
|
||||
|
||||
|
||||
@@ -10,11 +10,15 @@ import type { Map as ImmutableMap } from 'immutable';
|
||||
import { animated, useSpring } from '@react-spring/web';
|
||||
import { useDrag } from '@use-gesture/react';
|
||||
|
||||
import {
|
||||
StatusBookmarkActiveIcon,
|
||||
StatusBookmarkIcon,
|
||||
StatusLikeActiveIcon,
|
||||
StatusLikeIcon,
|
||||
} from '@/mastodon/components/status/icons';
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import BookmarksActiveIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
|
||||
import BookmarksIcon from '@/material-icons/400-24px/bookmarks.svg?react';
|
||||
import CollectionsActiveIcon from '@/material-icons/400-24px/category-fill.svg?react';
|
||||
import CollectionsIcon from '@/material-icons/400-24px/category.svg?react';
|
||||
import HomeActiveIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
@@ -26,8 +30,6 @@ import PersonAddActiveIcon from '@/material-icons/400-24px/person_add-fill.svg?r
|
||||
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
|
||||
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
||||
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
|
||||
import StarActiveIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
||||
import TrendingUpIcon from '@/material-icons/400-24px/trending_up.svg?react';
|
||||
import { fetchFollowRequests } from 'mastodon/actions/accounts';
|
||||
import { openNavigation, closeNavigation } from 'mastodon/actions/navigation';
|
||||
@@ -345,8 +347,8 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({
|
||||
transparent
|
||||
to='/favourites'
|
||||
icon='star'
|
||||
iconComponent={StarIcon}
|
||||
activeIconComponent={StarActiveIcon}
|
||||
iconComponent={StatusLikeIcon}
|
||||
activeIconComponent={StatusLikeActiveIcon}
|
||||
text={intl.formatMessage(messages.favourites)}
|
||||
/>
|
||||
</li>
|
||||
@@ -355,8 +357,8 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({
|
||||
transparent
|
||||
to='/bookmarks'
|
||||
icon='bookmarks'
|
||||
iconComponent={BookmarksIcon}
|
||||
activeIconComponent={BookmarksActiveIcon}
|
||||
iconComponent={StatusBookmarkIcon}
|
||||
activeIconComponent={StatusBookmarkActiveIcon}
|
||||
text={intl.formatMessage(messages.bookmarks)}
|
||||
/>
|
||||
</li>
|
||||
|
||||
@@ -3,20 +3,17 @@ import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, defineMessages } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { ImmutablePureComponent } from 'react-immutable-pure-component';
|
||||
|
||||
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
|
||||
import FlagIcon from '@/material-icons/400-24px/flag-fill.svg?react';
|
||||
import FormatQuoteIcon from '@/material-icons/400-24px/format_quote-fill.svg?react';
|
||||
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react';
|
||||
import PersonIcon from '@/material-icons/400-24px/person-fill.svg?react';
|
||||
import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { LinkedDisplayName } from '@/mastodon/components/display_name';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
@@ -32,6 +29,7 @@ import { ModerationWarning } from './moderation_warning';
|
||||
import { RelationshipsSeveranceEvent } from './relationships_severance_event';
|
||||
import Report from './report';
|
||||
import { isRedesignEnabled } from '@/mastodon/utils/environment';
|
||||
import { StatusBoostIcon, StatusLikeIcon, StatusQuoteIcon } from '@/mastodon/components/status/icons';
|
||||
|
||||
const messages = defineMessages({
|
||||
favourite: { id: 'notification.favourite', defaultMessage: '{name} favorited your post' },
|
||||
@@ -190,7 +188,7 @@ class Notification extends ImmutablePureComponent {
|
||||
<Hotkeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-favourite focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(likeMessage, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<Icon id='star' icon={StarIcon} className='star-icon' />
|
||||
<Icon id='star' icon={StatusLikeIcon} className='star-icon' />
|
||||
|
||||
<span title={notification.get('created_at')}>
|
||||
<FormattedMessage {...likeMessage} values={{ name: link }} />
|
||||
@@ -220,7 +218,7 @@ class Notification extends ImmutablePureComponent {
|
||||
<Hotkeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-reblog focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.reblog, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<Icon id='retweet' icon={RepeatIcon} />
|
||||
<Icon icon={StatusBoostIcon} />
|
||||
|
||||
<span title={notification.get('created_at')}>
|
||||
<FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your post' values={{ name: link }} />
|
||||
@@ -250,7 +248,7 @@ class Notification extends ImmutablePureComponent {
|
||||
<Hotkeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-quote focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.quote, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<Icon id='quote' icon={FormatQuoteIcon} />
|
||||
<Icon id='quote' icon={StatusQuoteIcon} />
|
||||
|
||||
<span title={notification.get('created_at')}>
|
||||
<FormattedMessage id='notification.label.quote' defaultMessage='{name} quoted your post' values={{ name: link }} />
|
||||
|
||||
@@ -2,8 +2,8 @@ import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { StatusLikeActiveIcon } from '@/mastodon/components/status/icons';
|
||||
import { isRedesignEnabled } from '@/mastodon/utils/environment';
|
||||
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||
import type { NotificationGroupFavourite } from 'mastodon/models/notification_group';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
@@ -117,7 +117,7 @@ export const NotificationFavourite: React.FC<{
|
||||
return (
|
||||
<NotificationGroupWithStatus
|
||||
type='favourite'
|
||||
icon={StarIcon}
|
||||
icon={StatusLikeActiveIcon}
|
||||
iconId='star'
|
||||
accountIds={notification.sampleAccountIds}
|
||||
statusId={notification.statusId}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { StatusReplyIcon } from '@/mastodon/components/status/icons';
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import type { NotificationGroupMention } from 'mastodon/models/notification_group';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
@@ -59,7 +59,7 @@ export const NotificationMention: React.FC<{
|
||||
return (
|
||||
<NotificationWithStatus
|
||||
type='mention'
|
||||
icon={isReply ? ReplyIcon : AlternateEmailIcon}
|
||||
icon={isReply ? StatusReplyIcon : AlternateEmailIcon}
|
||||
iconId='reply'
|
||||
accountIds={notification.sampleAccountIds}
|
||||
count={notification.notifications_count}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import FormatQuoteIcon from '@/material-icons/400-24px/format_quote-fill.svg?react';
|
||||
import { StatusQuoteIcon } from '@/mastodon/components/status/icons';
|
||||
import type { NotificationGroupQuote } from 'mastodon/models/notification_group';
|
||||
|
||||
import type { LabelRenderer } from './notification_group_with_status';
|
||||
@@ -21,7 +21,7 @@ export const NotificationQuote: React.FC<{
|
||||
return (
|
||||
<NotificationWithStatus
|
||||
type='quote'
|
||||
icon={FormatQuoteIcon}
|
||||
icon={StatusQuoteIcon}
|
||||
iconId='quote'
|
||||
accountIds={notification.sampleAccountIds}
|
||||
count={notification.notifications_count}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import { StatusBoostIcon } from '@/mastodon/components/status/icons';
|
||||
import type { NotificationGroupReblog } from 'mastodon/models/notification_group';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
@@ -47,7 +47,7 @@ export const NotificationReblog: React.FC<{
|
||||
return (
|
||||
<NotificationGroupWithStatus
|
||||
type='reblog'
|
||||
icon={RepeatIcon}
|
||||
icon={StatusBoostIcon}
|
||||
iconId='repeat'
|
||||
accountIds={notification.sampleAccountIds}
|
||||
statusId={notification.statusId}
|
||||
|
||||
@@ -3,13 +3,15 @@ import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import {
|
||||
StatusBoostIcon,
|
||||
StatusLikeIcon,
|
||||
StatusReplyAllIcon,
|
||||
} from '@/mastodon/components/status/icons';
|
||||
import CollectionsIcon from '@/material-icons/400-24px/category.svg?react';
|
||||
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react';
|
||||
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
||||
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
||||
import { setNotificationsFilter } from 'mastodon/actions/notification_groups';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import {
|
||||
@@ -87,7 +89,7 @@ export const FilterBar: React.FC = () => {
|
||||
key='mention'
|
||||
title={intl.formatMessage(tooltips.mentions)}
|
||||
>
|
||||
<Icon id='reply-all' icon={ReplyAllIcon} />
|
||||
<Icon id='reply-all' icon={StatusReplyAllIcon} />
|
||||
</BarButton>
|
||||
<BarButton
|
||||
selectedFilter={selectedFilter}
|
||||
@@ -95,7 +97,7 @@ export const FilterBar: React.FC = () => {
|
||||
key='favourite'
|
||||
title={intl.formatMessage(tooltips.favourites)}
|
||||
>
|
||||
<Icon id='star' icon={StarIcon} />
|
||||
<Icon id='star' icon={StatusLikeIcon} />
|
||||
</BarButton>
|
||||
<BarButton
|
||||
selectedFilter={selectedFilter}
|
||||
@@ -103,7 +105,7 @@ export const FilterBar: React.FC = () => {
|
||||
key='reblog'
|
||||
title={intl.formatMessage(tooltips.boosts)}
|
||||
>
|
||||
<Icon id='retweet' icon={RepeatIcon} />
|
||||
<Icon id='retweet' icon={StatusBoostIcon} />
|
||||
</BarButton>
|
||||
<BarButton
|
||||
selectedFilter={selectedFilter}
|
||||
|
||||
@@ -4,15 +4,10 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { useStatusIcons } from '@/mastodon/components/status/hooks';
|
||||
import { BoostButton } from '@/mastodon/components/status/legacy/boost_button';
|
||||
import { isRedesignEnabled } from '@/mastodon/utils/environment';
|
||||
import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
||||
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||
import StarBorderIcon from '@/material-icons/400-24px/star.svg?react';
|
||||
import { replyCompose } from 'mastodon/actions/compose';
|
||||
import { toggleFavourite } from 'mastodon/actions/interactions';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { useIdentity } from 'mastodon/identity_context';
|
||||
@@ -25,26 +20,6 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
const messages = defineMessages({
|
||||
reply: { id: 'status.reply', defaultMessage: 'Reply' },
|
||||
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
|
||||
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
|
||||
reblog_private: {
|
||||
id: 'status.reblog_private',
|
||||
defaultMessage: 'Share again with your followers',
|
||||
},
|
||||
cancel_reblog_private: {
|
||||
id: 'status.cancel_reblog_private',
|
||||
defaultMessage: 'Unboost',
|
||||
},
|
||||
cannot_reblog: {
|
||||
id: 'status.cannot_reblog',
|
||||
defaultMessage: 'This post cannot be boosted',
|
||||
},
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
|
||||
removeFavourite: {
|
||||
id: 'status.remove_favourite',
|
||||
defaultMessage: 'Remove from favorites',
|
||||
},
|
||||
like: { id: 'status.like', defaultMessage: 'Like' },
|
||||
unlike: { id: 'status.unlike', defaultMessage: 'Unlike' },
|
||||
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
||||
});
|
||||
|
||||
@@ -98,27 +73,6 @@ export const Footer: React.FC<{
|
||||
}
|
||||
}, [dispatch, status, signedIn, askReplyConfirmation, onClose]);
|
||||
|
||||
const handleFavouriteClick = useCallback(() => {
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (signedIn) {
|
||||
dispatch(toggleFavourite(status.get('id')));
|
||||
} else {
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'INTERACTION',
|
||||
modalProps: {
|
||||
intent: 'favourite',
|
||||
accountId: status.getIn(['account', 'id']),
|
||||
url: status.get('uri'),
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [dispatch, status, signedIn]);
|
||||
|
||||
const handleOpenClick = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
if (e.button !== 0 || !status) {
|
||||
@@ -132,50 +86,22 @@ export const Footer: React.FC<{
|
||||
[history, status, account, onClose],
|
||||
);
|
||||
|
||||
const { reply, like } = useStatusIcons(statusId);
|
||||
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let replyIcon, replyIconComponent, replyTitle;
|
||||
|
||||
if (status.get('in_reply_to_id', null) === null) {
|
||||
replyIcon = 'reply';
|
||||
replyIconComponent = ReplyIcon;
|
||||
replyTitle = intl.formatMessage(messages.reply);
|
||||
} else {
|
||||
replyIcon = 'reply-all';
|
||||
replyIconComponent = ReplyAllIcon;
|
||||
replyTitle = intl.formatMessage(messages.replyAll);
|
||||
}
|
||||
|
||||
let favouriteTitle = intl.formatMessage(
|
||||
status.get('favourited') ? messages.removeFavourite : messages.favourite,
|
||||
);
|
||||
if (isRedesignEnabled()) {
|
||||
favouriteTitle = intl.formatMessage(
|
||||
status.get('favourited') ? messages.unlike : messages.like,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='picture-in-picture__footer'>
|
||||
<IconButton
|
||||
className='status__action-bar-button'
|
||||
title={replyTitle}
|
||||
icon={
|
||||
status.get('in_reply_to_account_id') ===
|
||||
status.getIn(['account', 'id'])
|
||||
? 'reply'
|
||||
: replyIcon
|
||||
}
|
||||
iconComponent={
|
||||
status.get('in_reply_to_account_id') ===
|
||||
status.getIn(['account', 'id'])
|
||||
? ReplyIcon
|
||||
: replyIconComponent
|
||||
}
|
||||
title={reply.title}
|
||||
active={reply.active}
|
||||
icon='reply'
|
||||
iconComponent={reply.icon}
|
||||
onClick={handleReplyClick}
|
||||
counter={status.get('replies_count') as number}
|
||||
counter={reply.counter}
|
||||
/>
|
||||
|
||||
<BoostButton counters statusId={statusId} />
|
||||
@@ -183,12 +109,12 @@ export const Footer: React.FC<{
|
||||
<IconButton
|
||||
className='status__action-bar-button star-icon'
|
||||
animate
|
||||
active={status.get('favourited') as boolean}
|
||||
title={favouriteTitle}
|
||||
active={like.active}
|
||||
title={like.title}
|
||||
icon='star'
|
||||
iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon}
|
||||
onClick={handleFavouriteClick}
|
||||
counter={status.get('favourites_count') as number}
|
||||
iconComponent={like.icon}
|
||||
onClick={like.action}
|
||||
counter={like.counter}
|
||||
/>
|
||||
|
||||
{withOpenButton && (
|
||||
|
||||
@@ -6,13 +6,7 @@ import { defineMessages } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react';
|
||||
import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react';
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
||||
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||
import StarBorderIcon from '@/material-icons/400-24px/star.svg?react';
|
||||
import { injectIntl } from '@/mastodon/components/intl';
|
||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||
@@ -24,6 +18,14 @@ import { BoostButton } from '@/mastodon/components/status/legacy/boost_button';
|
||||
import { quoteItemState } from '@/mastodon/components/status/boost_button_utils';
|
||||
import { selectStatusConditions } from '@/mastodon/selectors/statuses';
|
||||
import { isRedesignEnabled } from '@/mastodon/utils/environment';
|
||||
import {
|
||||
StatusBookmarkActiveIcon,
|
||||
StatusBookmarkIcon,
|
||||
StatusLikeActiveIcon,
|
||||
StatusLikeIcon,
|
||||
StatusReplyAllIcon,
|
||||
StatusReplyIcon,
|
||||
} from '@/mastodon/components/status/icons';
|
||||
|
||||
const baseMessages = defineMessages({
|
||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||
@@ -331,10 +333,10 @@ class ActionBar extends PureComponent {
|
||||
|
||||
if (status.get('in_reply_to_id', null) === null) {
|
||||
replyIcon = 'reply';
|
||||
replyIconComponent = ReplyIcon;
|
||||
replyIconComponent = StatusReplyIcon;
|
||||
} else {
|
||||
replyIcon = 'reply-all';
|
||||
replyIconComponent = ReplyAllIcon;
|
||||
replyIconComponent = StatusReplyAllIcon;
|
||||
}
|
||||
|
||||
const bookmarkTitle = intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark);
|
||||
@@ -342,12 +344,18 @@ class ActionBar extends PureComponent {
|
||||
|
||||
return (
|
||||
<div className='detailed-status__action-bar'>
|
||||
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} /></div>
|
||||
<div className='detailed-status__button'>
|
||||
<IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? StatusReplyIcon : replyIconComponent} onClick={this.handleReplyClick} />
|
||||
</div>
|
||||
<div className='detailed-status__button'>
|
||||
<BoostButton statusId={status.get('id')} />
|
||||
</div>
|
||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={favouriteTitle} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={bookmarkTitle} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} /></div>
|
||||
<div className='detailed-status__button'>
|
||||
<IconButton className='star-icon' animate active={status.get('favourited')} title={favouriteTitle} icon='star' iconComponent={status.get('favourited') ? StatusLikeActiveIcon : StatusLikeIcon} onClick={this.handleFavouriteClick} />
|
||||
</div>
|
||||
<div className='detailed-status__button'>
|
||||
<IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={bookmarkTitle} icon='bookmark' iconComponent={status.get('bookmarked') ? StatusBookmarkActiveIcon : StatusBookmarkIcon} onClick={this.handleBookmarkClick} />
|
||||
</div>
|
||||
|
||||
<div className='detailed-status__action-bar-dropdown'>
|
||||
<Dropdown icon='ellipsis-h' iconComponent={MoreHorizIcon} status={status} items={menu} direction='left' title={intl.formatMessage(messages.more)} />
|
||||
|
||||
@@ -11,13 +11,13 @@ import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?re
|
||||
import BlockIcon from '@/material-icons/400-24px/block.svg?react';
|
||||
import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react';
|
||||
import CollectionsIcon from '@/material-icons/400-24px/category.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
|
||||
import { blockAccount } from 'mastodon/actions/accounts';
|
||||
import { closeModal } from 'mastodon/actions/modal';
|
||||
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { StatusReplyIcon } from '@/mastodon/components/status/icons';
|
||||
|
||||
export const BlockModal = ({ accountId, acct }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -71,7 +71,7 @@ export const BlockModal = ({ accountId, acct }) => {
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div className='safety-action-modal__bullet-points__icon'><Icon icon={ReplyIcon} /></div>
|
||||
<div className='safety-action-modal__bullet-points__icon'><Icon icon={StatusReplyIcon} /></div>
|
||||
<div><FormattedMessage id='block_modal.they_cant_mention' defaultMessage="You can't mention, follow, or quote each other." /></div>
|
||||
</li>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import { StatusBoostIcon } from '@/mastodon/components/status/icons';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||
@@ -57,7 +57,7 @@ export const BoostModal: React.FC<{
|
||||
<div className='safety-action-modal__top'>
|
||||
<div className='safety-action-modal__header'>
|
||||
<div className='safety-action-modal__header__icon'>
|
||||
<Icon icon={RepeatIcon} id='retweet' />
|
||||
<Icon icon={StatusBoostIcon} id='retweet' />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -81,7 +81,8 @@ export const BoostModal: React.FC<{
|
||||
values={{
|
||||
combo: (
|
||||
<span className='hotkey-combination'>
|
||||
<kbd>Shift</kbd>+<Icon id='retweet' icon={RepeatIcon} />
|
||||
<kbd>Shift</kbd>+
|
||||
<Icon id='retweet' icon={StatusBoostIcon} />
|
||||
</span>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -2,11 +2,11 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { StatusReplyIcon } from '@/mastodon/components/status/icons';
|
||||
import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react';
|
||||
import DomainDisabledIcon from '@/material-icons/400-24px/domain_disabled.svg?react';
|
||||
import HistoryIcon from '@/material-icons/400-24px/history.svg?react';
|
||||
import PersonRemoveIcon from '@/material-icons/400-24px/person_remove.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
|
||||
import { blockAccount } from 'mastodon/actions/accounts';
|
||||
import { blockDomain } from 'mastodon/actions/domain_blocks';
|
||||
@@ -159,7 +159,7 @@ export const DomainBlockModal: React.FC<{
|
||||
|
||||
<li className='safety-action-modal__bullet-points--deemphasized'>
|
||||
<div className='safety-action-modal__bullet-points__icon'>
|
||||
<Icon id='' icon={ReplyIcon} />
|
||||
<Icon icon={StatusReplyIcon} />
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
@@ -171,7 +171,7 @@ export const DomainBlockModal: React.FC<{
|
||||
|
||||
<li className='safety-action-modal__bullet-points--deemphasized'>
|
||||
<div className='safety-action-modal__bullet-points__icon'>
|
||||
<Icon id='' icon={HistoryIcon} />
|
||||
<Icon icon={HistoryIcon} />
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
|
||||
@@ -10,7 +10,6 @@ import { useDispatch } from 'react-redux';
|
||||
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
|
||||
import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react';
|
||||
import { muteAccount } from 'mastodon/actions/accounts';
|
||||
@@ -20,6 +19,7 @@ import { CheckBox } from 'mastodon/components/check_box';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||
import { RadioButton } from 'mastodon/components/radio_button';
|
||||
import { StatusReplyIcon } from '@/mastodon/components/status/icons';
|
||||
|
||||
const messages = defineMessages({
|
||||
minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
|
||||
@@ -109,7 +109,7 @@ export const MuteModal = ({ accountId, acct }) => {
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div className='safety-action-modal__bullet-points__icon'><Icon icon={ReplyIcon} /></div>
|
||||
<div className='safety-action-modal__bullet-points__icon'><Icon icon={StatusReplyIcon} /></div>
|
||||
<div><FormattedMessage id='mute_modal.they_can_mention_and_follow' defaultMessage="They can mention and follow you, but you won't see them." /></div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
1
app/javascript/svg-icons/boost_active.svg
Normal file
1
app/javascript/svg-icons/boost_active.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20"><path fill="currentColor" d="M6.875 11.725a.626.626 0 1 1 0 1.25H4.634l1.142 1.142a6.25 6.25 0 0 0 8.788.052.624.624 0 0 1 .874.893 7.45 7.45 0 0 1-5.24 2.136h-.042a7.46 7.46 0 0 1-5.263-2.197L3.75 13.858V16.1a.626.626 0 1 1-1.25 0v-3.75a.626.626 0 0 1 .625-.625zM10 5c1.072 0 2.063.34 2.877.913a1.46 1.46 0 0 0-.677.309l-.106.096a1.46 1.46 0 0 0 1.031 2.49h1.73q.143.574.145 1.192a5 5 0 0 1-8.24 3.808h.115a1.459 1.459 0 1 0 0-2.916H5.081A5 5 0 0 1 10 5m-.15-2.5a7.5 7.5 0 0 1 5.26 2.198l1.14 1.143V3.6a.626.626 0 1 1 1.25 0v3.75a.626.626 0 0 1-.625.625h-3.75a.626.626 0 1 1 0-1.25h2.241l-1.142-1.143a6.22 6.22 0 0 0-4.386-1.831h-.035a6.21 6.21 0 0 0-4.366 1.78.625.625 0 0 1-.874-.894A7.5 7.5 0 0 1 9.85 2.5"/></svg>
|
||||
|
After Width: | Height: | Size: 791 B |
Reference in New Issue
Block a user