pl-fe: Allow to configure per-post interaction policies
Signed-off-by: mkljczk <git@mkljczk.pl>
This commit is contained in:
@ -19,9 +19,10 @@ import { saveSettings } from './settings';
|
||||
import { createStatus } from './statuses';
|
||||
|
||||
import type { EditorState } from 'lexical';
|
||||
import type { Account as BaseAccount, CreateStatusParams, CustomEmoji, Group, MediaAttachment, Status as BaseStatus, Tag, Poll, ScheduledStatus } from 'pl-api';
|
||||
import type { Account as BaseAccount, CreateStatusParams, CustomEmoji, Group, MediaAttachment, Status as BaseStatus, Tag, Poll, ScheduledStatus, InteractionPolicy } from 'pl-api';
|
||||
import type { AutoSuggestion } from 'pl-fe/components/autosuggest-input';
|
||||
import type { Emoji } from 'pl-fe/features/emoji';
|
||||
import type { Policy, Rule, Scope } from 'pl-fe/features/interaction-policies';
|
||||
import type { Account } from 'pl-fe/normalizers/account';
|
||||
import type { Status } from 'pl-fe/normalizers/status';
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
@ -92,6 +93,8 @@ const COMPOSE_CHANGE_MEDIA_ORDER = 'COMPOSE_CHANGE_MEDIA_ORDER' as const;
|
||||
const COMPOSE_ADD_SUGGESTED_QUOTE = 'COMPOSE_ADD_SUGGESTED_QUOTE' as const;
|
||||
const COMPOSE_ADD_SUGGESTED_LANGUAGE = 'COMPOSE_ADD_SUGGESTED_LANGUAGE' as const;
|
||||
|
||||
const COMPOSE_INTERACTION_POLICY_OPTION_CHANGE = 'COMPOSE_INTERACTION_POLICY_OPTION_CHANGE' as const;
|
||||
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
const messages = defineMessages({
|
||||
@ -400,6 +403,7 @@ const submitCompose = (composeId: string, opts: SubmitComposeOpts = {}) =>
|
||||
language: compose.language || compose.suggested_language || undefined,
|
||||
to: explicitAddressing && to.length ? to : undefined,
|
||||
local_only: !compose.federated,
|
||||
interaction_policy: ['public', 'unlisted', 'private'].includes(compose.privacy) && compose.interactionPolicy || undefined,
|
||||
};
|
||||
|
||||
if (compose.poll) {
|
||||
@ -929,6 +933,15 @@ const changeComposeFederated = (composeId: string) => ({
|
||||
composeId,
|
||||
});
|
||||
|
||||
const changeComposeInteractionPolicyOption = (composeId: string, policy: Policy, rule: Rule, value: Scope[], initial: InteractionPolicy) => ({
|
||||
type: COMPOSE_INTERACTION_POLICY_OPTION_CHANGE,
|
||||
composeId,
|
||||
policy,
|
||||
rule,
|
||||
value,
|
||||
initial,
|
||||
});
|
||||
|
||||
type ComposeAction =
|
||||
ComposeSetStatusAction
|
||||
| ReturnType<typeof changeCompose>
|
||||
@ -980,7 +993,8 @@ type ComposeAction =
|
||||
| ReturnType<typeof changeMediaOrder>
|
||||
| ReturnType<typeof addSuggestedQuote>
|
||||
| ReturnType<typeof addSuggestedLanguage>
|
||||
| ReturnType<typeof changeComposeFederated>;
|
||||
| ReturnType<typeof changeComposeFederated>
|
||||
| ReturnType<typeof changeComposeInteractionPolicyOption>;
|
||||
|
||||
export {
|
||||
COMPOSE_CHANGE,
|
||||
@ -1034,6 +1048,7 @@ export {
|
||||
COMPOSE_ADD_SUGGESTED_QUOTE,
|
||||
COMPOSE_ADD_SUGGESTED_LANGUAGE,
|
||||
COMPOSE_FEDERATED_CHANGE,
|
||||
COMPOSE_INTERACTION_POLICY_OPTION_CHANGE,
|
||||
setComposeToStatus,
|
||||
replyCompose,
|
||||
cancelReplyCompose,
|
||||
@ -1080,6 +1095,7 @@ export {
|
||||
addSuggestedQuote,
|
||||
addSuggestedLanguage,
|
||||
changeComposeFederated,
|
||||
changeComposeInteractionPolicyOption,
|
||||
type ComposeReplyAction,
|
||||
type ComposeSuggestionSelectAction,
|
||||
type ComposeAction,
|
||||
|
||||
@ -31,6 +31,7 @@ import { $createEmojiNode } from '../editor/nodes/emoji-node';
|
||||
import { countableText } from '../util/counter';
|
||||
|
||||
import ContentTypeButton from './content-type-button';
|
||||
import InteractionPolicyButton from './interaction-policy-button';
|
||||
import LanguageDropdown from './language-dropdown';
|
||||
import PollButton from './poll-button';
|
||||
import PollForm from './polls/poll-form';
|
||||
@ -186,6 +187,7 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
|
||||
{features.polls && <PollButton composeId={id} />}
|
||||
{features.scheduledStatuses && <ScheduleButton composeId={id} />}
|
||||
{anyMedia && features.spoilers && <SensitiveMediaButton composeId={id} />}
|
||||
{features.interactionRequests && <InteractionPolicyButton composeId={id} />}
|
||||
</HStack>
|
||||
), [features, id, anyMedia]);
|
||||
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { useCompose } from 'pl-fe/hooks/use-compose';
|
||||
import { useModalsStore } from 'pl-fe/stores/modals';
|
||||
|
||||
import ComposeFormButton from './compose-form-button';
|
||||
|
||||
const messages = defineMessages({
|
||||
label: { id: 'compose_form.interaction_policy.label', defaultMessage: 'Manage interaction policy' },
|
||||
});
|
||||
|
||||
interface IInteractionPolicyButton {
|
||||
composeId: string;
|
||||
}
|
||||
|
||||
const InteractionPolicyButton: React.FC<IInteractionPolicyButton> = ({ composeId }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { openModal } = useModalsStore();
|
||||
|
||||
const handleClick = () => {
|
||||
openModal('COMPOSE_INTERACTION_POLICY', {
|
||||
composeId,
|
||||
});
|
||||
};
|
||||
|
||||
const { privacy, interactionPolicy } = useCompose(composeId);
|
||||
|
||||
if (!['public', 'unlisted', 'private'].includes(privacy)) return null;
|
||||
|
||||
return (
|
||||
<ComposeFormButton
|
||||
icon={require('@tabler/icons/outline/adjustments-star.svg')}
|
||||
title={intl.formatMessage(messages.label)}
|
||||
onClick={handleClick}
|
||||
active={!!interactionPolicy}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { InteractionPolicyButton as default };
|
||||
@ -207,4 +207,10 @@ const InteractionPolicies = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export { InteractionPolicies as default };
|
||||
export {
|
||||
InteractionPolicies as default,
|
||||
InteractionPolicyConfig,
|
||||
type Policy,
|
||||
type Rule,
|
||||
type Scope,
|
||||
};
|
||||
|
||||
@ -15,6 +15,7 @@ const MODAL_COMPONENTS = {
|
||||
COMPARE_HISTORY: lazy(() => import('pl-fe/features/ui/components/modals/compare-history-modal')),
|
||||
COMPONENT: lazy(() => import('pl-fe/features/ui/components/modals/component-modal')),
|
||||
COMPOSE: lazy(() => import('pl-fe/features/ui/components/modals/compose-modal')),
|
||||
COMPOSE_INTERACTION_POLICY: lazy(() => import('pl-fe/features/ui/components/modals/compose-interaction-policy-modal')),
|
||||
CONFIRM: lazy(() => import('pl-fe/features/ui/components/modals/confirmation-modal')),
|
||||
CREATE_GROUP: lazy(() => import('pl-fe/features/ui/components/modals/manage-group-modal')),
|
||||
CRYPTO_DONATE: lazy(() => import('pl-fe/features/ui/components/modals/crypto-donate-modal')),
|
||||
|
||||
@ -0,0 +1,65 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { changeComposeInteractionPolicyOption } from 'pl-fe/actions/compose';
|
||||
import Modal from 'pl-fe/components/ui/modal';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import { InteractionPolicyConfig, type Policy, type Rule, type Scope } from 'pl-fe/features/interaction-policies';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useCompose } from 'pl-fe/hooks/use-compose';
|
||||
import { useInteractionPolicies } from 'pl-fe/queries/settings/use-interaction-policies';
|
||||
|
||||
import type { BaseModalProps } from '../modal-root';
|
||||
|
||||
interface ComposeInteractionPolicyModalProps {
|
||||
composeId: string;
|
||||
}
|
||||
|
||||
const ComposeInteractionPolicyModal: React.FC<BaseModalProps & ComposeInteractionPolicyModalProps> = ({ composeId, onClose }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { interactionPolicies: initial } = useInteractionPolicies();
|
||||
const compose = useCompose(composeId);
|
||||
|
||||
const canManageInteractionPolicies = compose.privacy === 'public' || compose.privacy === 'unlisted' || compose.privacy === 'private';
|
||||
|
||||
useEffect(() => {
|
||||
if (!canManageInteractionPolicies) {
|
||||
onClose('COMPOSE_INTERACTION_POLICY');
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!canManageInteractionPolicies) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const interactionPolicy = (compose.interactionPolicy || initial[compose.privacy as 'public']);
|
||||
|
||||
const onClickClose = () => {
|
||||
onClose('COMPOSE_INTERACTION_POLICY');
|
||||
};
|
||||
|
||||
const onChange = (policy: Policy, rule: Rule, value: Scope[]) => {
|
||||
dispatch(changeComposeInteractionPolicyOption(composeId, policy, rule, value, interactionPolicy));
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={<FormattedMessage id='navigation_bar.interaction_policy' defaultMessage='Status interaction policy' />}
|
||||
onClose={onClickClose}
|
||||
closeIcon={composeId === 'compose-modal' ? require('@tabler/icons/outline/arrow-left.svg') : undefined}
|
||||
closePosition={composeId === 'compose-modal' ? 'left' : undefined}
|
||||
>
|
||||
<Stack space={4}>
|
||||
<InteractionPolicyConfig
|
||||
interactionPolicy={interactionPolicy}
|
||||
visibility={compose.privacy as 'public'}
|
||||
onChange={onChange}
|
||||
singlePost
|
||||
/>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export { ComposeInteractionPolicyModal as default, type ComposeInteractionPolicyModalProps };
|
||||
@ -1,5 +1,4 @@
|
||||
import { create } from 'mutative';
|
||||
import { type CredentialAccount, type Instance, type MediaAttachment, type Tag } from 'pl-api';
|
||||
|
||||
import { INSTANCE_FETCH_SUCCESS, type InstanceAction } from 'pl-fe/actions/instance';
|
||||
import { tagHistory } from 'pl-fe/settings';
|
||||
@ -58,6 +57,7 @@ import {
|
||||
COMPOSE_FEDERATED_CHANGE,
|
||||
type ComposeAction,
|
||||
type ComposeSuggestionSelectAction,
|
||||
COMPOSE_INTERACTION_POLICY_OPTION_CHANGE,
|
||||
} from '../actions/compose';
|
||||
import { EVENT_COMPOSE_CANCEL, EVENT_FORM_SET, type EventsAction } from '../actions/events';
|
||||
import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS, type MeAction } from '../actions/me';
|
||||
@ -65,6 +65,7 @@ import { FE_NAME } from '../actions/settings';
|
||||
import { TIMELINE_DELETE, type TimelineAction } from '../actions/timelines';
|
||||
import { unescapeHTML } from '../utils/html';
|
||||
|
||||
import type { InteractionPolicy, CredentialAccount, Instance, MediaAttachment, Tag } from 'pl-api';
|
||||
import type { Emoji } from 'pl-fe/features/emoji';
|
||||
import type { Language } from 'pl-fe/features/preferences';
|
||||
import type { Account } from 'pl-fe/normalizers/account';
|
||||
@ -127,6 +128,7 @@ interface Compose {
|
||||
suggested_language: string | null;
|
||||
federated: boolean;
|
||||
approvalRequired: boolean;
|
||||
interactionPolicy: InteractionPolicy | null;
|
||||
}
|
||||
|
||||
const newCompose = (params: Partial<Compose> = {}): Compose => ({
|
||||
@ -167,6 +169,7 @@ const newCompose = (params: Partial<Compose> = {}): Compose => ({
|
||||
suggested_language: null,
|
||||
federated: true,
|
||||
approvalRequired: false,
|
||||
interactionPolicy: null,
|
||||
...params,
|
||||
});
|
||||
|
||||
@ -676,6 +679,15 @@ const compose = (state = initialState, action: ComposeAction | EventsAction | In
|
||||
return updateCompose(state, action.composeId, compose => {
|
||||
compose.federated = !compose.federated;
|
||||
});
|
||||
case COMPOSE_INTERACTION_POLICY_OPTION_CHANGE:
|
||||
return updateCompose(state, action.composeId, compose => {
|
||||
if (compose.interactionPolicy === null) compose.interactionPolicy = JSON.parse(JSON.stringify(action.initial))!;
|
||||
|
||||
compose.interactionPolicy = create(compose.interactionPolicy || action.initial, (interactionPolicy) => {
|
||||
interactionPolicy[action.policy][action.rule] = action.value;
|
||||
interactionPolicy[action.policy][action.rule === 'always' ? 'with_approval' : 'always'] = interactionPolicy[action.policy][action.rule === 'always' ? 'with_approval' : 'always'].filter(rule => !action.value.includes(rule as any));
|
||||
});
|
||||
});
|
||||
case INSTANCE_FETCH_SUCCESS:
|
||||
return updateCompose(state, 'default', (compose) => updateDefaultContentType(compose, action.instance));
|
||||
default:
|
||||
|
||||
@ -9,6 +9,7 @@ import type { AccountModerationModalProps } from 'pl-fe/features/ui/components/m
|
||||
import type { BoostModalProps } from 'pl-fe/features/ui/components/modals/boost-modal';
|
||||
import type { CompareHistoryModalProps } from 'pl-fe/features/ui/components/modals/compare-history-modal';
|
||||
import type { ComponentModalProps } from 'pl-fe/features/ui/components/modals/component-modal';
|
||||
import type { ComposeInteractionPolicyModalProps } from 'pl-fe/features/ui/components/modals/compose-interaction-policy-modal';
|
||||
import type { ComposeModalProps } from 'pl-fe/features/ui/components/modals/compose-modal';
|
||||
import type { ConfirmationModalProps } from 'pl-fe/features/ui/components/modals/confirmation-modal';
|
||||
import type { DislikesModalProps } from 'pl-fe/features/ui/components/modals/dislikes-modal';
|
||||
@ -45,6 +46,7 @@ type OpenModalProps =
|
||||
| [type: 'COMPARE_HISTORY', props: CompareHistoryModalProps]
|
||||
| [type: 'COMPONENT', props: ComponentModalProps]
|
||||
| [type: 'COMPOSE', props?: ComposeModalProps]
|
||||
| [type: 'COMPOSE_INTERACTION_POLICY', props?: ComposeInteractionPolicyModalProps]
|
||||
| [type: 'CONFIRM', props: ConfirmationModalProps]
|
||||
| [type: 'CRYPTO_DONATE', props: ICryptoAddress]
|
||||
| [type: 'DISLIKES', props: DislikesModalProps]
|
||||
|
||||
Reference in New Issue
Block a user