pl-fe: Support GoToSocial interaction policies
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
bff924891c
commit
0c84346c3a
5 changed files with 121 additions and 7 deletions
|
@ -106,10 +106,9 @@ const GroupPopover = (props: IGroupPopoverContainer) => {
|
||||||
</Stack>
|
</Stack>
|
||||||
}
|
}
|
||||||
isFlush
|
isFlush
|
||||||
children={
|
>
|
||||||
<div className='inline-block'>{children}</div>
|
<div className='inline-block'>{children}</div>
|
||||||
}
|
</Popover>
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import EmojiPickerDropdown from 'pl-fe/features/emoji/containers/emoji-picker-dr
|
||||||
import { languages } from 'pl-fe/features/preferences';
|
import { languages } from 'pl-fe/features/preferences';
|
||||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||||
|
import { useCanInteract } from 'pl-fe/hooks/use-can-interact';
|
||||||
import { useFeatures } from 'pl-fe/hooks/use-features';
|
import { useFeatures } from 'pl-fe/hooks/use-features';
|
||||||
import { useInstance } from 'pl-fe/hooks/use-instance';
|
import { useInstance } from 'pl-fe/hooks/use-instance';
|
||||||
import { useOwnAccount } from 'pl-fe/hooks/use-own-account';
|
import { useOwnAccount } from 'pl-fe/hooks/use-own-account';
|
||||||
|
@ -38,6 +39,9 @@ import toast from 'pl-fe/toast';
|
||||||
import copy from 'pl-fe/utils/copy';
|
import copy from 'pl-fe/utils/copy';
|
||||||
|
|
||||||
import GroupPopover from './groups/popover/group-popover';
|
import GroupPopover from './groups/popover/group-popover';
|
||||||
|
import Popover from './ui/popover';
|
||||||
|
import Stack from './ui/stack';
|
||||||
|
import Text from './ui/text';
|
||||||
|
|
||||||
import type { Menu } from 'pl-fe/components/dropdown-menu';
|
import type { Menu } from 'pl-fe/components/dropdown-menu';
|
||||||
import type { Emoji as EmojiType } from 'pl-fe/features/emoji';
|
import type { Emoji as EmojiType } from 'pl-fe/features/emoji';
|
||||||
|
@ -111,8 +115,76 @@ const messages = defineMessages({
|
||||||
addKnownLanguage: { id: 'status.add_known_language', defaultMessage: 'Do not auto-translate posts in {language}.' },
|
addKnownLanguage: { id: 'status.add_known_language', defaultMessage: 'Do not auto-translate posts in {language}.' },
|
||||||
translate: { id: 'status.translate', defaultMessage: 'Translate' },
|
translate: { id: 'status.translate', defaultMessage: 'Translate' },
|
||||||
hideTranslation: { id: 'status.hide_translation', defaultMessage: 'Hide translation' },
|
hideTranslation: { id: 'status.hide_translation', defaultMessage: 'Hide translation' },
|
||||||
|
|
||||||
|
favouriteInteractionPolicyHeader: { id: 'status.interaction_policy.favourite.header', defaultMessage: 'The author limits who can like this post.' },
|
||||||
|
reblogInteractionPolicyHeader: { id: 'status.interaction_policy.reblog.header', defaultMessage: 'The author limits who can repost this post.' },
|
||||||
|
replyInteractionPolicyHeader: { id: 'status.interaction_policy.reply.header', defaultMessage: 'The author limits who can reply to this post.' },
|
||||||
|
|
||||||
|
favouriteInteractionPolicyFollowers: { id: 'status.interaction_policy.favourite.followers_only', defaultMessage: 'Only users following the author can like.' },
|
||||||
|
favouriteInteractionPolicyFollowing: { id: 'status.interaction_policy.favourite.following_only', defaultMessage: 'Only users followed by the author can like.' },
|
||||||
|
favouriteInteractionPolicyMutuals: { id: 'status.interaction_policy.favourite.mutuals_only', defaultMessage: 'Only users mutually following the author can like.' },
|
||||||
|
favouriteInteractionPolicyMentioned: { id: 'status.interaction_policy.favourite.mentioned_only', defaultMessage: 'Only users mentioned by the author can like.' },
|
||||||
|
|
||||||
|
reblogInteractionPolicyFollowers: { id: 'status.interaction_policy.reblog.followers_only', defaultMessage: 'Only users following the author can repost.' },
|
||||||
|
reblogInteractionPolicyFollowing: { id: 'status.interaction_policy.reblog.following_only', defaultMessage: 'Only users followed by the author can repost.' },
|
||||||
|
reblogInteractionPolicyMutuals: { id: 'status.interaction_policy.reblog.mutuals_only', defaultMessage: 'Only users mutually following the author can repost.' },
|
||||||
|
reblogInteractionPolicyMentioned: { id: 'status.interaction_policy.reblog.mentioned_only', defaultMessage: 'Only users mentioned by the author can repost.' },
|
||||||
|
|
||||||
|
replyInteractionPolicyFollowers: { id: 'status.interaction_policy.reply.followers_only', defaultMessage: 'Only users following the author can reply.' },
|
||||||
|
replyInteractionPolicyFollowing: { id: 'status.interaction_policy.reply.following_only', defaultMessage: 'Only users followed by the author can reply.' },
|
||||||
|
replyInteractionPolicyMutuals: { id: 'status.interaction_policy.reply.mutuals_only', defaultMessage: 'Only users mutually following the author can reply.' },
|
||||||
|
replyInteractionPolicyMentioned: { id: 'status.interaction_policy.reply.mentioned_only', defaultMessage: 'Only users mentioned by the author can reply.' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
interface IInteractionPopover {
|
||||||
|
type: 'favourite' | 'reblog' | 'reply';
|
||||||
|
allowed: ReturnType<typeof useCanInteract>['allowed'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const INTERACTION_POLICY_HEADERS = {
|
||||||
|
favourite: messages.favouriteInteractionPolicyHeader,
|
||||||
|
reblog: messages.reblogInteractionPolicyHeader,
|
||||||
|
reply: messages.replyInteractionPolicyHeader,
|
||||||
|
};
|
||||||
|
|
||||||
|
const INTERACTION_POLICY_DESCRIPTIONS = {
|
||||||
|
favourite: {
|
||||||
|
followers: messages.favouriteInteractionPolicyFollowers,
|
||||||
|
following: messages.favouriteInteractionPolicyFollowing,
|
||||||
|
mutuals: messages.favouriteInteractionPolicyMutuals,
|
||||||
|
mentioned: messages.favouriteInteractionPolicyMentioned,
|
||||||
|
},
|
||||||
|
reblog: {
|
||||||
|
followers: messages.reblogInteractionPolicyFollowers,
|
||||||
|
following: messages.reblogInteractionPolicyFollowing,
|
||||||
|
mutuals: messages.reblogInteractionPolicyMutuals,
|
||||||
|
mentioned: messages.reblogInteractionPolicyMentioned,
|
||||||
|
},
|
||||||
|
reply: {
|
||||||
|
followers: messages.replyInteractionPolicyFollowers,
|
||||||
|
following: messages.replyInteractionPolicyFollowing,
|
||||||
|
mutuals: messages.replyInteractionPolicyMutuals,
|
||||||
|
mentioned: messages.replyInteractionPolicyMentioned,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const InteractionPopover: React.FC<IInteractionPopover> = ({ type, allowed }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const allowedType = allowed?.includes('followers') ? 'followers' : allowed?.includes('following') ? 'following' : allowed?.includes('mutuals') ? 'mutuals' : 'mentioned';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack space={1} className='max-w-96'>
|
||||||
|
<Text weight='semibold' align='center'>
|
||||||
|
{intl.formatMessage(INTERACTION_POLICY_HEADERS[type])}
|
||||||
|
</Text>
|
||||||
|
<Text theme='muted' align='center'>
|
||||||
|
{intl.formatMessage(INTERACTION_POLICY_DESCRIPTIONS[type][allowedType])}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface IActionButton extends Pick<IStatusActionBar, 'status' | 'statusActionButtonTheme' | 'withLabels'> {
|
interface IActionButton extends Pick<IStatusActionBar, 'status' | 'statusActionButtonTheme' | 'withLabels'> {
|
||||||
me: Me;
|
me: Me;
|
||||||
onOpenUnauthorizedModal: (action?: UnauthorizedModalAction) => void;
|
onOpenUnauthorizedModal: (action?: UnauthorizedModalAction) => void;
|
||||||
|
@ -133,6 +205,7 @@ const ReplyButton: React.FC<IReplyButton> = ({
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const canReply = useCanInteract(status, 'can_reply');
|
||||||
const { groupRelationship } = useGroupRelationship(status.group_id || undefined);
|
const { groupRelationship } = useGroupRelationship(status.group_id || undefined);
|
||||||
|
|
||||||
let replyTitle;
|
let replyTitle;
|
||||||
|
@ -170,6 +243,15 @@ const ReplyButton: React.FC<IReplyButton> = ({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (me && !canReply.canInteract) return (
|
||||||
|
<Popover
|
||||||
|
interaction='click'
|
||||||
|
content={<InteractionPopover allowed={canReply.allowed} type='reply' />}
|
||||||
|
>
|
||||||
|
{replyButton}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
|
||||||
return status.group ? (
|
return status.group ? (
|
||||||
<GroupPopover
|
<GroupPopover
|
||||||
group={status.group}
|
group={status.group}
|
||||||
|
@ -198,6 +280,7 @@ const ReblogButton: React.FC<IReblogButton> = ({
|
||||||
|
|
||||||
const { boostModal } = useSettings();
|
const { boostModal } = useSettings();
|
||||||
const { openModal } = useModalsStore();
|
const { openModal } = useModalsStore();
|
||||||
|
const canReblog = useCanInteract(status, 'can_reblog');
|
||||||
|
|
||||||
let reblogIcon = require('@tabler/icons/outline/repeat.svg');
|
let reblogIcon = require('@tabler/icons/outline/repeat.svg');
|
||||||
|
|
||||||
|
@ -239,6 +322,15 @@ const ReblogButton: React.FC<IReblogButton> = ({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (me && !canReblog.canInteract) return (
|
||||||
|
<Popover
|
||||||
|
interaction='click'
|
||||||
|
content={<InteractionPopover allowed={canReblog.allowed} type='reblog' />}
|
||||||
|
>
|
||||||
|
{reblogButton}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
|
||||||
if (!features.quotePosts || !me) return reblogButton;
|
if (!features.quotePosts || !me) return reblogButton;
|
||||||
|
|
||||||
const handleQuoteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleQuoteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
|
@ -282,6 +374,7 @@ const FavouriteButton: React.FC<IActionButton> = ({
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { openModal } = useModalsStore();
|
const { openModal } = useModalsStore();
|
||||||
|
const canFavourite = useCanInteract(status, 'can_favourite');
|
||||||
|
|
||||||
const handleFavouriteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleFavouriteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
if (me) {
|
if (me) {
|
||||||
|
@ -295,7 +388,7 @@ const FavouriteButton: React.FC<IActionButton> = ({
|
||||||
openModal('FAVOURITES', { statusId: status.id });
|
openModal('FAVOURITES', { statusId: status.id });
|
||||||
} : undefined;
|
} : undefined;
|
||||||
|
|
||||||
return (
|
const favouriteButton = (
|
||||||
<StatusActionButton
|
<StatusActionButton
|
||||||
title={intl.formatMessage(messages.favourite)}
|
title={intl.formatMessage(messages.favourite)}
|
||||||
icon={features.statusDislikes ? require('@tabler/icons/outline/thumb-up.svg') : require('@tabler/icons/outline/heart.svg')}
|
icon={features.statusDislikes ? require('@tabler/icons/outline/thumb-up.svg') : require('@tabler/icons/outline/heart.svg')}
|
||||||
|
@ -309,6 +402,15 @@ const FavouriteButton: React.FC<IActionButton> = ({
|
||||||
theme={statusActionButtonTheme}
|
theme={statusActionButtonTheme}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (me && !canFavourite.canInteract) return (
|
||||||
|
<Popover
|
||||||
|
interaction='click'
|
||||||
|
content={<InteractionPopover allowed={canFavourite.allowed} type='favourite' />}
|
||||||
|
>
|
||||||
|
{favouriteButton}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DislikeButton: React.FC<IActionButton> = ({
|
const DislikeButton: React.FC<IActionButton> = ({
|
||||||
|
|
|
@ -683,7 +683,6 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
||||||
theme='outlined'
|
theme='outlined'
|
||||||
className='px-2'
|
className='px-2'
|
||||||
iconClassName='h-4 w-4'
|
iconClassName='h-4 w-4'
|
||||||
children={null}
|
|
||||||
/>
|
/>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -389,7 +389,6 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
||||||
theme='outlined'
|
theme='outlined'
|
||||||
className='h-[30px] px-2'
|
className='h-[30px] px-2'
|
||||||
iconClassName='h-4 w-4'
|
iconClassName='h-4 w-4'
|
||||||
children={null}
|
|
||||||
/>
|
/>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
||||||
|
|
|
@ -1459,6 +1459,21 @@
|
||||||
"status.group": "Posted in {group}",
|
"status.group": "Posted in {group}",
|
||||||
"status.group_mod_delete": "Delete post from group",
|
"status.group_mod_delete": "Delete post from group",
|
||||||
"status.hide_translation": "Hide translation",
|
"status.hide_translation": "Hide translation",
|
||||||
|
"status.interaction_policy.favourite.followers_only": "Only users following the author can like.",
|
||||||
|
"status.interaction_policy.favourite.following_only": "Only users followed by the author can like.",
|
||||||
|
"status.interaction_policy.favourite.header": "The author limits who can like this post.",
|
||||||
|
"status.interaction_policy.favourite.mentioned_only": "Only users mentioned by the author can like.",
|
||||||
|
"status.interaction_policy.favourite.mutuals_only": "Only users mutually following the author can like.",
|
||||||
|
"status.interaction_policy.reblog.followers_only": "Only users following the author can repost.",
|
||||||
|
"status.interaction_policy.reblog.following_only": "Only users followed by the author can repost.",
|
||||||
|
"status.interaction_policy.reblog.header": "The author limits who can repost this post.",
|
||||||
|
"status.interaction_policy.reblog.mentioned_only": "Only users mentioned by the author can repost.",
|
||||||
|
"status.interaction_policy.reblog.mutuals_only": "Only users mutually following the author can repost.",
|
||||||
|
"status.interaction_policy.reply.followers_only": "Only users following the author can reply.",
|
||||||
|
"status.interaction_policy.reply.following_only": "Only users followed by the author can reply.",
|
||||||
|
"status.interaction_policy.reply.header": "The author limits who can reply to this post.",
|
||||||
|
"status.interaction_policy.reply.mentioned_only": "Only users mentioned by the author can reply.",
|
||||||
|
"status.interaction_policy.reply.mutuals_only": "Only users mutually following the author can reply.",
|
||||||
"status.interactions.dislikes": "{count, plural, one {Dislike} other {Dislikes}}",
|
"status.interactions.dislikes": "{count, plural, one {Dislike} other {Dislikes}}",
|
||||||
"status.interactions.favourites": "{count, plural, one {Like} other {Likes}}",
|
"status.interactions.favourites": "{count, plural, one {Like} other {Likes}}",
|
||||||
"status.interactions.quotes": "{count, plural, one {Quote} other {Quotes}}",
|
"status.interactions.quotes": "{count, plural, one {Quote} other {Quotes}}",
|
||||||
|
|
Loading…
Reference in a new issue