mirror of
https://github.com/mastodon/mastodon.git
synced 2025-09-05 17:31:12 +00:00
Composer quote improvements (#35835)
Co-authored-by: Claire <claire.github-309c@sitedethib.com> Co-authored-by: diondiondion <mail@diondiondion.com>
This commit is contained in:
parent
e770303968
commit
f85f0eee1b
|
@ -84,6 +84,7 @@ export const COMPOSE_FOCUS = 'COMPOSE_FOCUS';
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
||||||
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
|
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
|
||||||
|
uploadQuote: { id: 'upload_error.quote', defaultMessage: 'File upload not allowed with quotes.' },
|
||||||
open: { id: 'compose.published.open', defaultMessage: 'Open' },
|
open: { id: 'compose.published.open', defaultMessage: 'Open' },
|
||||||
published: { id: 'compose.published.body', defaultMessage: 'Post published.' },
|
published: { id: 'compose.published.body', defaultMessage: 'Post published.' },
|
||||||
saved: { id: 'compose.saved.body', defaultMessage: 'Post saved.' },
|
saved: { id: 'compose.saved.body', defaultMessage: 'Post saved.' },
|
||||||
|
@ -146,7 +147,7 @@ export function resetCompose() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const focusCompose = (defaultText) => (dispatch, getState) => {
|
export const focusCompose = (defaultText = '') => (dispatch, getState) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: COMPOSE_FOCUS,
|
type: COMPOSE_FOCUS,
|
||||||
defaultText,
|
defaultText,
|
||||||
|
@ -303,6 +304,11 @@ export function submitComposeFail(error) {
|
||||||
|
|
||||||
export function uploadCompose(files) {
|
export function uploadCompose(files) {
|
||||||
return function (dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
|
// Exit if there's a quote.
|
||||||
|
if (getState().compose.get('quoted_status_id')) {
|
||||||
|
dispatch(showAlert({ message: messages.uploadQuote }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
const uploadLimit = getState().getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments']);
|
const uploadLimit = getState().getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments']);
|
||||||
const media = getState().getIn(['compose', 'media_attachments']);
|
const media = getState().getIn(['compose', 'media_attachments']);
|
||||||
const pending = getState().getIn(['compose', 'pending_media_attachments']);
|
const pending = getState().getIn(['compose', 'pending_media_attachments']);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { defineMessages } from 'react-intl';
|
||||||
|
|
||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import type { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
import type { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
||||||
|
|
||||||
|
@ -12,7 +14,27 @@ import {
|
||||||
import type { ApiQuotePolicy } from '../api_types/quotes';
|
import type { ApiQuotePolicy } from '../api_types/quotes';
|
||||||
import type { Status } from '../models/status';
|
import type { Status } from '../models/status';
|
||||||
|
|
||||||
import { ensureComposeIsVisible } from './compose';
|
import { showAlert } from './alerts';
|
||||||
|
import { focusCompose } from './compose';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
quoteErrorUpload: {
|
||||||
|
id: 'quote_error.upload',
|
||||||
|
defaultMessage: 'Quoting is not allowed with media attachments.',
|
||||||
|
},
|
||||||
|
quoteErrorPoll: {
|
||||||
|
id: 'quote_error.poll',
|
||||||
|
defaultMessage: 'Quoting is not allowed with polls.',
|
||||||
|
},
|
||||||
|
quoteErrorQuote: {
|
||||||
|
id: 'quote_error.quote',
|
||||||
|
defaultMessage: 'Only one quote at a time is allowed.',
|
||||||
|
},
|
||||||
|
quoteErrorUnauthorized: {
|
||||||
|
id: 'quote_error.unauthorized',
|
||||||
|
defaultMessage: 'You are not authorized to quote this post.',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & {
|
type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & {
|
||||||
unattached?: boolean;
|
unattached?: boolean;
|
||||||
|
@ -78,14 +100,43 @@ export const changeUploadCompose = createDataLoadingThunk(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const quoteComposeByStatus = createAppThunk(
|
export const quoteCompose = createAppThunk(
|
||||||
'compose/quoteComposeStatus',
|
'compose/quoteComposeStatus',
|
||||||
(status: Status, { getState }) => {
|
(status: Status, { dispatch }) => {
|
||||||
ensureComposeIsVisible(getState);
|
dispatch(focusCompose());
|
||||||
return status;
|
return status;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const quoteComposeByStatus = createAppThunk(
|
||||||
|
(status: Status, { dispatch, getState }) => {
|
||||||
|
const composeState = getState().compose;
|
||||||
|
const mediaAttachments = composeState.get('media_attachments');
|
||||||
|
|
||||||
|
if (composeState.get('poll')) {
|
||||||
|
dispatch(showAlert({ message: messages.quoteErrorPoll }));
|
||||||
|
} else if (
|
||||||
|
composeState.get('is_uploading') ||
|
||||||
|
(mediaAttachments &&
|
||||||
|
typeof mediaAttachments !== 'string' &&
|
||||||
|
typeof mediaAttachments !== 'number' &&
|
||||||
|
typeof mediaAttachments !== 'boolean' &&
|
||||||
|
mediaAttachments.size !== 0)
|
||||||
|
) {
|
||||||
|
dispatch(showAlert({ message: messages.quoteErrorUpload }));
|
||||||
|
} else if (composeState.get('quoted_status_id')) {
|
||||||
|
dispatch(showAlert({ message: messages.quoteErrorQuote }));
|
||||||
|
} else if (
|
||||||
|
status.getIn(['quote_approval', 'current_user']) !== 'automatic' &&
|
||||||
|
status.getIn(['quote_approval', 'current_user']) !== 'manual'
|
||||||
|
) {
|
||||||
|
dispatch(showAlert({ message: messages.quoteErrorUnauthorized }));
|
||||||
|
} else {
|
||||||
|
dispatch(quoteCompose(status));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const quoteComposeById = createAppThunk(
|
export const quoteComposeById = createAppThunk(
|
||||||
(statusId: string, { dispatch, getState }) => {
|
(statusId: string, { dispatch, getState }) => {
|
||||||
const status = getState().statuses.get(statusId);
|
const status = getState().statuses.get(statusId);
|
||||||
|
@ -97,6 +148,6 @@ export const quoteComposeById = createAppThunk(
|
||||||
|
|
||||||
export const quoteComposeCancel = createAction('compose/quoteComposeCancel');
|
export const quoteComposeCancel = createAction('compose/quoteComposeCancel');
|
||||||
|
|
||||||
export const setQuotePolicy = createAction<ApiQuotePolicy>(
|
export const setComposeQuotePolicy = createAction<ApiQuotePolicy>(
|
||||||
'compose/setQuotePolicy',
|
'compose/setQuotePolicy',
|
||||||
);
|
);
|
||||||
|
|
|
@ -133,3 +133,9 @@ export interface ApiStatusSourceJSON {
|
||||||
text: string;
|
text: string;
|
||||||
spoiler_text: string;
|
spoiler_text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isStatusVisibility(
|
||||||
|
visibility: string,
|
||||||
|
): visibility is StatusVisibility {
|
||||||
|
return ['public', 'unlisted', 'private', 'direct'].includes(visibility);
|
||||||
|
}
|
||||||
|
|
|
@ -110,6 +110,7 @@ class Status extends ImmutablePureComponent {
|
||||||
onToggleCollapsed: PropTypes.func,
|
onToggleCollapsed: PropTypes.func,
|
||||||
onTranslate: PropTypes.func,
|
onTranslate: PropTypes.func,
|
||||||
onInteractionModal: PropTypes.func,
|
onInteractionModal: PropTypes.func,
|
||||||
|
onQuoteCancel: PropTypes.func,
|
||||||
muted: PropTypes.bool,
|
muted: PropTypes.bool,
|
||||||
hidden: PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
unread: PropTypes.bool,
|
unread: PropTypes.bool,
|
||||||
|
@ -583,7 +584,7 @@ class Status extends ImmutablePureComponent {
|
||||||
<DisplayName account={status.get('account')} />
|
<DisplayName account={status.get('account')} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{this.props.contextType === 'compose' && isQuotedPost && (
|
{isQuotedPost && !!this.props.onQuoteCancel && (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={this.handleQuoteCancel}
|
onClick={this.handleQuoteCancel}
|
||||||
className='status__quote-cancel'
|
className='status__quote-cancel'
|
||||||
|
|
|
@ -63,12 +63,21 @@ type GetStatusSelector = (
|
||||||
props: { id?: string | null; contextType?: string },
|
props: { id?: string | null; contextType?: string },
|
||||||
) => Status | null;
|
) => Status | null;
|
||||||
|
|
||||||
export const QuotedStatus: React.FC<{
|
interface QuotedStatusProps {
|
||||||
quote: QuoteMap;
|
quote: QuoteMap;
|
||||||
contextType?: string;
|
contextType?: string;
|
||||||
variant?: 'full' | 'link';
|
variant?: 'full' | 'link';
|
||||||
nestingLevel?: number;
|
nestingLevel?: number;
|
||||||
}> = ({ quote, contextType, nestingLevel = 1, variant = 'full' }) => {
|
onQuoteCancel?: () => void; // Used for composer.
|
||||||
|
}
|
||||||
|
|
||||||
|
export const QuotedStatus: React.FC<QuotedStatusProps> = ({
|
||||||
|
quote,
|
||||||
|
contextType,
|
||||||
|
nestingLevel = 1,
|
||||||
|
variant = 'full',
|
||||||
|
onQuoteCancel,
|
||||||
|
}) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const quotedStatusId = quote.get('quoted_status');
|
const quotedStatusId = quote.get('quoted_status');
|
||||||
const quoteState = quote.get('state');
|
const quoteState = quote.get('state');
|
||||||
|
@ -160,6 +169,7 @@ export const QuotedStatus: React.FC<{
|
||||||
id={quotedStatusId}
|
id={quotedStatusId}
|
||||||
contextType={contextType}
|
contextType={contextType}
|
||||||
avatarSize={32}
|
avatarSize={32}
|
||||||
|
onQuoteCancel={onQuoteCancel}
|
||||||
>
|
>
|
||||||
{canRenderChildQuote && (
|
{canRenderChildQuote && (
|
||||||
<QuotedStatus
|
<QuotedStatus
|
||||||
|
|
|
@ -41,10 +41,10 @@ import {
|
||||||
translateStatus,
|
translateStatus,
|
||||||
undoStatusTranslation,
|
undoStatusTranslation,
|
||||||
} from '../actions/statuses';
|
} from '../actions/statuses';
|
||||||
|
import { setStatusQuotePolicy } from '../actions/statuses_typed';
|
||||||
import Status from '../components/status';
|
import Status from '../components/status';
|
||||||
import { deleteModal } from '../initial_state';
|
import { deleteModal } from '../initial_state';
|
||||||
import { makeGetStatus, makeGetPictureInPicture } from '../selectors';
|
import { makeGetStatus, makeGetPictureInPicture } from '../selectors';
|
||||||
import { quoteComposeCancel } from '../actions/compose_typed';
|
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
const getStatus = makeGetStatus();
|
const getStatus = makeGetStatus();
|
||||||
|
@ -112,18 +112,18 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onQuoteCancel() {
|
|
||||||
if (contextType === 'compose') {
|
|
||||||
dispatch(quoteComposeCancel());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onRevokeQuote (status) {
|
onRevokeQuote (status) {
|
||||||
dispatch(openModal({ modalType: 'CONFIRM_REVOKE_QUOTE', modalProps: { statusId: status.get('id'), quotedStatusId: status.getIn(['quote', 'quoted_status']) }}));
|
dispatch(openModal({ modalType: 'CONFIRM_REVOKE_QUOTE', modalProps: { statusId: status.get('id'), quotedStatusId: status.getIn(['quote', 'quoted_status']) }}));
|
||||||
},
|
},
|
||||||
|
|
||||||
onQuotePolicyChange(status) {
|
onQuotePolicyChange(status) {
|
||||||
dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId: status.get('id') } }));
|
const statusId = status.get('id');
|
||||||
|
const handleChange = (_, quotePolicy) => {
|
||||||
|
dispatch(
|
||||||
|
setStatusQuotePolicy({ policy: quotePolicy, statusId }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId, onChange: handleChange } }));
|
||||||
},
|
},
|
||||||
|
|
||||||
onEdit (status) {
|
onEdit (status) {
|
||||||
|
|
|
@ -15,10 +15,8 @@ import { missingAltTextModal } from 'mastodon/initial_state';
|
||||||
import AutosuggestInput from 'mastodon/components/autosuggest_input';
|
import AutosuggestInput from 'mastodon/components/autosuggest_input';
|
||||||
import AutosuggestTextarea from 'mastodon/components/autosuggest_textarea';
|
import AutosuggestTextarea from 'mastodon/components/autosuggest_textarea';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
|
||||||
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
|
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
|
||||||
import PollButtonContainer from '../containers/poll_button_container';
|
import PollButtonContainer from '../containers/poll_button_container';
|
||||||
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
|
||||||
import SpoilerButtonContainer from '../containers/spoiler_button_container';
|
import SpoilerButtonContainer from '../containers/spoiler_button_container';
|
||||||
import UploadButtonContainer from '../containers/upload_button_container';
|
import UploadButtonContainer from '../containers/upload_button_container';
|
||||||
import { countableText } from '../util/counter';
|
import { countableText } from '../util/counter';
|
||||||
|
@ -32,6 +30,7 @@ import { ReplyIndicator } from './reply_indicator';
|
||||||
import { UploadForm } from './upload_form';
|
import { UploadForm } from './upload_form';
|
||||||
import { Warning } from './warning';
|
import { Warning } from './warning';
|
||||||
import { ComposeQuotedStatus } from './quoted_post';
|
import { ComposeQuotedStatus } from './quoted_post';
|
||||||
|
import { VisibilityButton } from './visibility_button';
|
||||||
|
|
||||||
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
|
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
|
||||||
|
|
||||||
|
@ -260,7 +259,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
<EditIndicator />
|
<EditIndicator />
|
||||||
|
|
||||||
<div className='compose-form__dropdowns'>
|
<div className='compose-form__dropdowns'>
|
||||||
<PrivacyDropdownContainer disabled={this.props.isEditing} />
|
<VisibilityButton disabled={this.props.isEditing} />
|
||||||
<LanguageDropdown />
|
<LanguageDropdown />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import { useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { Map } from 'immutable';
|
import { Map } from 'immutable';
|
||||||
|
|
||||||
|
import { quoteComposeCancel } from '@/mastodon/actions/compose_typed';
|
||||||
import { QuotedStatus } from '@/mastodon/components/status_quoted';
|
import { QuotedStatus } from '@/mastodon/components/status_quoted';
|
||||||
import { useAppSelector } from '@/mastodon/store';
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
|
|
||||||
export const ComposeQuotedStatus: FC = () => {
|
export const ComposeQuotedStatus: FC = () => {
|
||||||
const quotedStatusId = useAppSelector(
|
const quotedStatusId = useAppSelector(
|
||||||
(state) => state.compose.get('quoted_status_id') as string | null,
|
(state) => state.compose.get('quoted_status_id') as string | null,
|
||||||
);
|
);
|
||||||
|
const isEditing = useAppSelector((state) => !!state.compose.get('id'));
|
||||||
const quote = useMemo(
|
const quote = useMemo(
|
||||||
() =>
|
() =>
|
||||||
quotedStatusId
|
quotedStatusId
|
||||||
|
@ -20,8 +22,17 @@ export const ComposeQuotedStatus: FC = () => {
|
||||||
: null,
|
: null,
|
||||||
[quotedStatusId],
|
[quotedStatusId],
|
||||||
);
|
);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const handleQuoteCancel = useCallback(() => {
|
||||||
|
dispatch(quoteComposeCancel());
|
||||||
|
}, [dispatch]);
|
||||||
if (!quote) {
|
if (!quote) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return <QuotedStatus quote={quote} contextType='compose' />;
|
return (
|
||||||
|
<QuotedStatus
|
||||||
|
quote={quote}
|
||||||
|
onQuoteCancel={!isEditing ? handleQuoteCancel : undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { changeComposeVisibility } from '@/mastodon/actions/compose';
|
||||||
|
import { setComposeQuotePolicy } from '@/mastodon/actions/compose_typed';
|
||||||
|
import { openModal } from '@/mastodon/actions/modal';
|
||||||
|
import type { ApiQuotePolicy } from '@/mastodon/api_types/quotes';
|
||||||
|
import type { StatusVisibility } from '@/mastodon/api_types/statuses';
|
||||||
|
import { Icon } from '@/mastodon/components/icon';
|
||||||
|
import { useAppSelector, useAppDispatch } from '@/mastodon/store';
|
||||||
|
import { isFeatureEnabled } from '@/mastodon/utils/environment';
|
||||||
|
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||||
|
import LockIcon from '@/material-icons/400-24px/lock.svg?react';
|
||||||
|
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
||||||
|
import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react';
|
||||||
|
|
||||||
|
import type { VisibilityModalCallback } from '../../ui/components/visibility_modal';
|
||||||
|
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
||||||
|
|
||||||
|
import { messages as privacyMessages } from './privacy_dropdown';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
anyone_quote: {
|
||||||
|
id: 'privacy.quote.anyone',
|
||||||
|
defaultMessage: '{visibility}, anyone can quote',
|
||||||
|
},
|
||||||
|
limited_quote: {
|
||||||
|
id: 'privacy.quote.limited',
|
||||||
|
defaultMessage: '{visibility}, quotes limited',
|
||||||
|
},
|
||||||
|
disabled_quote: {
|
||||||
|
id: 'privacy.quote.disabled',
|
||||||
|
defaultMessage: '{visibility}, quotes disabled',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface PrivacyDropdownProps {
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VisibilityButton: FC<PrivacyDropdownProps> = (props) => {
|
||||||
|
if (!isFeatureEnabled('outgoing_quotes')) {
|
||||||
|
return <PrivacyDropdownContainer {...props} />;
|
||||||
|
}
|
||||||
|
return <PrivacyModalButton {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const visibilityOptions = {
|
||||||
|
public: {
|
||||||
|
icon: 'globe',
|
||||||
|
iconComponent: PublicIcon,
|
||||||
|
value: 'public',
|
||||||
|
text: privacyMessages.public_short,
|
||||||
|
},
|
||||||
|
unlisted: {
|
||||||
|
icon: 'unlock',
|
||||||
|
iconComponent: QuietTimeIcon,
|
||||||
|
value: 'unlisted',
|
||||||
|
text: privacyMessages.unlisted_short,
|
||||||
|
},
|
||||||
|
private: {
|
||||||
|
icon: 'lock',
|
||||||
|
iconComponent: LockIcon,
|
||||||
|
value: 'private',
|
||||||
|
text: privacyMessages.private_short,
|
||||||
|
},
|
||||||
|
direct: {
|
||||||
|
icon: 'at',
|
||||||
|
iconComponent: AlternateEmailIcon,
|
||||||
|
value: 'direct',
|
||||||
|
text: privacyMessages.direct_short,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const PrivacyModalButton: FC<PrivacyDropdownProps> = ({ disabled = false }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const { visibility, quotePolicy } = useAppSelector((state) => ({
|
||||||
|
visibility: state.compose.get('privacy') as StatusVisibility,
|
||||||
|
quotePolicy: state.compose.get('quote_policy') as ApiQuotePolicy,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { icon, iconComponent } = useMemo(() => {
|
||||||
|
const option = visibilityOptions[visibility];
|
||||||
|
return { icon: option.icon, iconComponent: option.iconComponent };
|
||||||
|
}, [visibility]);
|
||||||
|
const text = useMemo(() => {
|
||||||
|
const visibilityText = intl.formatMessage(
|
||||||
|
visibilityOptions[visibility].text,
|
||||||
|
);
|
||||||
|
if (visibility === 'private' || visibility === 'direct') {
|
||||||
|
return visibilityText;
|
||||||
|
}
|
||||||
|
if (quotePolicy === 'nobody') {
|
||||||
|
return intl.formatMessage(messages.disabled_quote, {
|
||||||
|
visibility: visibilityText,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (quotePolicy !== 'public') {
|
||||||
|
return intl.formatMessage(messages.limited_quote, {
|
||||||
|
visibility: visibilityText,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return intl.formatMessage(messages.anyone_quote, {
|
||||||
|
visibility: visibilityText,
|
||||||
|
});
|
||||||
|
}, [quotePolicy, visibility, intl]);
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleChange: VisibilityModalCallback = useCallback(
|
||||||
|
(newVisibility, newQuotePolicy) => {
|
||||||
|
if (newVisibility !== visibility) {
|
||||||
|
dispatch(changeComposeVisibility(newVisibility));
|
||||||
|
}
|
||||||
|
if (newQuotePolicy !== quotePolicy) {
|
||||||
|
dispatch(setComposeQuotePolicy(newQuotePolicy));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch, quotePolicy, visibility],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOpen = useCallback(() => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalType: 'COMPOSE_PRIVACY',
|
||||||
|
modalProps: { onChange: handleChange },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}, [dispatch, handleChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
title={intl.formatMessage(privacyMessages.change_privacy)}
|
||||||
|
onClick={handleOpen}
|
||||||
|
disabled={disabled}
|
||||||
|
className={classNames('dropdown-button')}
|
||||||
|
>
|
||||||
|
<Icon id={icon} icon={iconComponent} />
|
||||||
|
<span className='dropdown-button__label'>{text}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
@ -15,7 +15,6 @@ import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?reac
|
||||||
import { Hotkeys } from 'mastodon/components/hotkeys';
|
import { Hotkeys } from 'mastodon/components/hotkeys';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
import { TimelineHint } from 'mastodon/components/timeline_hint';
|
|
||||||
import ScrollContainer from 'mastodon/containers/scroll_container';
|
import ScrollContainer from 'mastodon/containers/scroll_container';
|
||||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
||||||
|
@ -57,6 +56,7 @@ import {
|
||||||
translateStatus,
|
translateStatus,
|
||||||
undoStatusTranslation,
|
undoStatusTranslation,
|
||||||
} from '../../actions/statuses';
|
} from '../../actions/statuses';
|
||||||
|
import { setStatusQuotePolicy } from '../../actions/statuses_typed';
|
||||||
import ColumnHeader from '../../components/column_header';
|
import ColumnHeader from '../../components/column_header';
|
||||||
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
|
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
|
||||||
import { StatusQuoteManager } from '../../components/status_quoted';
|
import { StatusQuoteManager } from '../../components/status_quoted';
|
||||||
|
@ -266,8 +266,14 @@ class Status extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleQuotePolicyChange = (status) => {
|
handleQuotePolicyChange = (status) => {
|
||||||
|
const statusId = status.get('id');
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId: status.get('id') } }));
|
const handleChange = (_, quotePolicy) => {
|
||||||
|
dispatch(
|
||||||
|
setStatusQuotePolicy({ policy: quotePolicy, statusId }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId, onChange: handleChange } }));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleEditClick = (status) => {
|
handleEditClick = (status) => {
|
||||||
|
|
|
@ -1,25 +1,31 @@
|
||||||
import { forwardRef, useCallback, useId, useMemo } from 'react';
|
import {
|
||||||
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
|
useId,
|
||||||
|
useImperativeHandle,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { changeComposeVisibility } from '@/mastodon/actions/compose';
|
|
||||||
import { setStatusQuotePolicy } from '@/mastodon/actions/statuses_typed';
|
|
||||||
import type { ApiQuotePolicy } from '@/mastodon/api_types/quotes';
|
import type { ApiQuotePolicy } from '@/mastodon/api_types/quotes';
|
||||||
import { isQuotePolicy } from '@/mastodon/api_types/quotes';
|
import { isQuotePolicy } from '@/mastodon/api_types/quotes';
|
||||||
|
import { isStatusVisibility } from '@/mastodon/api_types/statuses';
|
||||||
import type { StatusVisibility } from '@/mastodon/api_types/statuses';
|
import type { StatusVisibility } from '@/mastodon/api_types/statuses';
|
||||||
import { Dropdown } from '@/mastodon/components/dropdown';
|
import { Dropdown } from '@/mastodon/components/dropdown';
|
||||||
import type { SelectItem } from '@/mastodon/components/dropdown_selector';
|
import type { SelectItem } from '@/mastodon/components/dropdown_selector';
|
||||||
import { IconButton } from '@/mastodon/components/icon_button';
|
import { IconButton } from '@/mastodon/components/icon_button';
|
||||||
import { messages as privacyMessages } from '@/mastodon/features/compose/components/privacy_dropdown';
|
import { messages as privacyMessages } from '@/mastodon/features/compose/components/privacy_dropdown';
|
||||||
import {
|
import { createAppSelector, useAppSelector } from '@/mastodon/store';
|
||||||
createAppSelector,
|
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||||
useAppDispatch,
|
|
||||||
useAppSelector,
|
|
||||||
} from '@/mastodon/store';
|
|
||||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||||
|
import LockIcon from '@/material-icons/400-24px/lock.svg?react';
|
||||||
|
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
||||||
|
import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react';
|
||||||
|
|
||||||
import type { BaseConfirmationModalProps } from './confirmation_modals/confirmation_modal';
|
import type { BaseConfirmationModalProps } from './confirmation_modals/confirmation_modal';
|
||||||
|
|
||||||
|
@ -43,13 +49,26 @@ const messages = defineMessages({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type VisibilityModalCallback = (
|
||||||
|
visibility: StatusVisibility,
|
||||||
|
quotePolicy: ApiQuotePolicy,
|
||||||
|
) => void;
|
||||||
|
|
||||||
interface VisibilityModalProps extends BaseConfirmationModalProps {
|
interface VisibilityModalProps extends BaseConfirmationModalProps {
|
||||||
statusId: string;
|
statusId?: string;
|
||||||
|
onChange: VisibilityModalCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectStatusPolicy = createAppSelector(
|
const selectStatusPolicy = createAppSelector(
|
||||||
[(state) => state.statuses, (_state, statusId: string) => statusId],
|
[
|
||||||
(statuses, statusId) => {
|
(state) => state.statuses,
|
||||||
|
(_state, statusId?: string) => statusId,
|
||||||
|
(state) => state.compose.get('quote_policy') as ApiQuotePolicy,
|
||||||
|
],
|
||||||
|
(statuses, statusId, composeQuotePolicy) => {
|
||||||
|
if (!statusId) {
|
||||||
|
return composeQuotePolicy;
|
||||||
|
}
|
||||||
const status = statuses.get(statusId);
|
const status = statuses.get(statusId);
|
||||||
if (!status) {
|
if (!status) {
|
||||||
return 'public';
|
return 'public';
|
||||||
|
@ -77,24 +96,25 @@ const selectStatusPolicy = createAppSelector(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
({ onClose, onChange, statusId }, ref) => {
|
||||||
({ onClose, statusId }, ref) => {
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const currentVisibility = useAppSelector(
|
const currentVisibility = useAppSelector((state) =>
|
||||||
(state) =>
|
statusId
|
||||||
(state.statuses.getIn([statusId, 'visibility'], 'public') as
|
? ((state.statuses.getIn([statusId, 'visibility'], 'public') as
|
||||||
| StatusVisibility
|
| StatusVisibility
|
||||||
| undefined) ?? 'public',
|
| undefined) ?? 'public')
|
||||||
|
: (state.compose.get('privacy') as StatusVisibility),
|
||||||
);
|
);
|
||||||
const currentQuotePolicy = useAppSelector((state) =>
|
const currentQuotePolicy = useAppSelector((state) =>
|
||||||
selectStatusPolicy(state, statusId),
|
selectStatusPolicy(state, statusId),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [visibility, setVisibility] = useState(currentVisibility);
|
||||||
|
const [quotePolicy, setQuotePolicy] = useState(currentQuotePolicy);
|
||||||
|
|
||||||
|
const disableVisibility = !!statusId;
|
||||||
const disableQuotePolicy =
|
const disableQuotePolicy =
|
||||||
currentVisibility === 'private' || currentVisibility === 'direct';
|
visibility === 'private' || visibility === 'direct';
|
||||||
const isSaving = useAppSelector(
|
|
||||||
(state) =>
|
|
||||||
state.statuses.getIn([statusId, 'isSavingQuotePolicy']) === true,
|
|
||||||
);
|
|
||||||
|
|
||||||
const visibilityItems = useMemo<SelectItem<StatusVisibility>[]>(
|
const visibilityItems = useMemo<SelectItem<StatusVisibility>[]>(
|
||||||
() => [
|
() => [
|
||||||
|
@ -102,21 +122,30 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||||
value: 'public',
|
value: 'public',
|
||||||
text: intl.formatMessage(privacyMessages.public_short),
|
text: intl.formatMessage(privacyMessages.public_short),
|
||||||
meta: intl.formatMessage(privacyMessages.public_long),
|
meta: intl.formatMessage(privacyMessages.public_long),
|
||||||
|
icon: 'globe',
|
||||||
|
iconComponent: PublicIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'unlisted',
|
value: 'unlisted',
|
||||||
text: intl.formatMessage(privacyMessages.unlisted_short),
|
text: intl.formatMessage(privacyMessages.unlisted_short),
|
||||||
meta: intl.formatMessage(privacyMessages.unlisted_long),
|
meta: intl.formatMessage(privacyMessages.unlisted_long),
|
||||||
|
extra: intl.formatMessage(privacyMessages.unlisted_extra),
|
||||||
|
icon: 'unlock',
|
||||||
|
iconComponent: QuietTimeIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'private',
|
value: 'private',
|
||||||
text: intl.formatMessage(privacyMessages.private_short),
|
text: intl.formatMessage(privacyMessages.private_short),
|
||||||
meta: intl.formatMessage(privacyMessages.private_long),
|
meta: intl.formatMessage(privacyMessages.private_long),
|
||||||
|
icon: 'lock',
|
||||||
|
iconComponent: LockIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'direct',
|
value: 'direct',
|
||||||
text: intl.formatMessage(privacyMessages.direct_short),
|
text: intl.formatMessage(privacyMessages.direct_short),
|
||||||
meta: intl.formatMessage(privacyMessages.direct_long),
|
meta: intl.formatMessage(privacyMessages.direct_long),
|
||||||
|
icon: 'at',
|
||||||
|
iconComponent: AlternateEmailIcon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[intl],
|
[intl],
|
||||||
|
@ -133,24 +162,27 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||||
[intl],
|
[intl],
|
||||||
);
|
);
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const handleVisibilityChange = useCallback((value: string) => {
|
||||||
const handleVisibilityChange = useCallback(
|
if (isStatusVisibility(value)) {
|
||||||
(value: string) => {
|
setVisibility(value);
|
||||||
// Published statuses cannot change visibility.
|
|
||||||
if (statusId) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
dispatch(changeComposeVisibility(value));
|
}, []);
|
||||||
},
|
const handleQuotePolicyChange = useCallback((value: string) => {
|
||||||
[dispatch, statusId],
|
if (isQuotePolicy(value)) {
|
||||||
);
|
setQuotePolicy(value);
|
||||||
const handleQuotePolicyChange = useCallback(
|
|
||||||
(value: string) => {
|
|
||||||
if (isQuotePolicy(value) && !disableQuotePolicy) {
|
|
||||||
void dispatch(setStatusQuotePolicy({ policy: value, statusId }));
|
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Save on close
|
||||||
|
useImperativeHandle(
|
||||||
|
ref,
|
||||||
|
() => ({
|
||||||
|
getCloseConfirmationMessage() {
|
||||||
|
onChange(visibility, quotePolicy);
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
[disableQuotePolicy, dispatch, statusId],
|
}),
|
||||||
|
[onChange, quotePolicy, visibility],
|
||||||
);
|
);
|
||||||
|
|
||||||
const privacyDropdownId = useId();
|
const privacyDropdownId = useId();
|
||||||
|
@ -192,7 +224,7 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||||
<label
|
<label
|
||||||
htmlFor={privacyDropdownId}
|
htmlFor={privacyDropdownId}
|
||||||
className={classNames('visibility-dropdown__label', {
|
className={classNames('visibility-dropdown__label', {
|
||||||
disabled: isSaving || !!statusId,
|
disabled: disableVisibility,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -203,10 +235,10 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||||
<Dropdown
|
<Dropdown
|
||||||
items={visibilityItems}
|
items={visibilityItems}
|
||||||
classPrefix='visibility-dropdown'
|
classPrefix='visibility-dropdown'
|
||||||
current={currentVisibility}
|
current={visibility}
|
||||||
onChange={handleVisibilityChange}
|
onChange={handleVisibilityChange}
|
||||||
title={intl.formatMessage(privacyMessages.change_privacy)}
|
title={intl.formatMessage(privacyMessages.change_privacy)}
|
||||||
disabled={isSaving || !!statusId}
|
disabled={disableVisibility}
|
||||||
id={privacyDropdownId}
|
id={privacyDropdownId}
|
||||||
/>
|
/>
|
||||||
{!!statusId && (
|
{!!statusId && (
|
||||||
|
@ -222,7 +254,7 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||||
<label
|
<label
|
||||||
htmlFor={quoteDropdownId}
|
htmlFor={quoteDropdownId}
|
||||||
className={classNames('visibility-dropdown__label', {
|
className={classNames('visibility-dropdown__label', {
|
||||||
disabled: disableQuotePolicy || isSaving,
|
disabled: disableQuotePolicy,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -234,15 +266,12 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||||
items={quoteItems}
|
items={quoteItems}
|
||||||
onChange={handleQuotePolicyChange}
|
onChange={handleQuotePolicyChange}
|
||||||
classPrefix='visibility-dropdown'
|
classPrefix='visibility-dropdown'
|
||||||
current={currentQuotePolicy}
|
current={quotePolicy}
|
||||||
title={intl.formatMessage(messages.buttonTitle)}
|
title={intl.formatMessage(messages.buttonTitle)}
|
||||||
disabled={disableQuotePolicy || isSaving}
|
disabled={disableQuotePolicy}
|
||||||
id={quoteDropdownId}
|
id={quoteDropdownId}
|
||||||
/>
|
/>
|
||||||
<QuotePolicyHelper
|
<QuotePolicyHelper policy={quotePolicy} visibility={visibility} />
|
||||||
policy={currentQuotePolicy}
|
|
||||||
visibility={currentVisibility}
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -738,11 +738,18 @@
|
||||||
"privacy.private.short": "Followers",
|
"privacy.private.short": "Followers",
|
||||||
"privacy.public.long": "Anyone on and off Mastodon",
|
"privacy.public.long": "Anyone on and off Mastodon",
|
||||||
"privacy.public.short": "Public",
|
"privacy.public.short": "Public",
|
||||||
|
"privacy.quote.anyone": "{visibility}, anyone can quote",
|
||||||
|
"privacy.quote.disabled": "{visibility}, quotes disabled",
|
||||||
|
"privacy.quote.limited": "{visibility}, quotes limited",
|
||||||
"privacy.unlisted.additional": "This behaves exactly like public, except the post will not appear in live feeds or hashtags, explore, or Mastodon search, even if you are opted-in account-wide.",
|
"privacy.unlisted.additional": "This behaves exactly like public, except the post will not appear in live feeds or hashtags, explore, or Mastodon search, even if you are opted-in account-wide.",
|
||||||
"privacy.unlisted.long": "Fewer algorithmic fanfares",
|
"privacy.unlisted.long": "Fewer algorithmic fanfares",
|
||||||
"privacy.unlisted.short": "Quiet public",
|
"privacy.unlisted.short": "Quiet public",
|
||||||
"privacy_policy.last_updated": "Last updated {date}",
|
"privacy_policy.last_updated": "Last updated {date}",
|
||||||
"privacy_policy.title": "Privacy Policy",
|
"privacy_policy.title": "Privacy Policy",
|
||||||
|
"quote_error.poll": "Quoting is not allowed with polls.",
|
||||||
|
"quote_error.quote": "Only one quote at a time is allowed.",
|
||||||
|
"quote_error.unauthorized": "You are not authorized to quote this post.",
|
||||||
|
"quote_error.upload": "Quoting is not allowed with media attachments.",
|
||||||
"recommended": "Recommended",
|
"recommended": "Recommended",
|
||||||
"refresh": "Refresh",
|
"refresh": "Refresh",
|
||||||
"regeneration_indicator.please_stand_by": "Please stand by.",
|
"regeneration_indicator.please_stand_by": "Please stand by.",
|
||||||
|
@ -944,6 +951,7 @@
|
||||||
"upload_button.label": "Add images, a video or an audio file",
|
"upload_button.label": "Add images, a video or an audio file",
|
||||||
"upload_error.limit": "File upload limit exceeded.",
|
"upload_error.limit": "File upload limit exceeded.",
|
||||||
"upload_error.poll": "File upload not allowed with polls.",
|
"upload_error.poll": "File upload not allowed with polls.",
|
||||||
|
"upload_error.quote": "File upload not allowed with quotes.",
|
||||||
"upload_form.drag_and_drop.instructions": "To pick up a media attachment, press space or enter. While dragging, use the arrow keys to move the media attachment in any given direction. Press space or enter again to drop the media attachment in its new position, or press escape to cancel.",
|
"upload_form.drag_and_drop.instructions": "To pick up a media attachment, press space or enter. While dragging, use the arrow keys to move the media attachment in any given direction. Press space or enter again to drop the media attachment in its new position, or press escape to cancel.",
|
||||||
"upload_form.drag_and_drop.on_drag_cancel": "Dragging was cancelled. Media attachment {item} was dropped.",
|
"upload_form.drag_and_drop.on_drag_cancel": "Dragging was cancelled. Media attachment {item} was dropped.",
|
||||||
"upload_form.drag_and_drop.on_drag_end": "Media attachment {item} was dropped.",
|
"upload_form.drag_and_drop.on_drag_end": "Media attachment {item} was dropped.",
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrde
|
||||||
|
|
||||||
import {
|
import {
|
||||||
changeUploadCompose,
|
changeUploadCompose,
|
||||||
quoteComposeByStatus,
|
quoteCompose,
|
||||||
quoteComposeCancel,
|
quoteComposeCancel,
|
||||||
setQuotePolicy,
|
setComposeQuotePolicy,
|
||||||
} from 'mastodon/actions/compose_typed';
|
} from 'mastodon/actions/compose_typed';
|
||||||
import { timelineDelete } from 'mastodon/actions/timelines_typed';
|
import { timelineDelete } from 'mastodon/actions/timelines_typed';
|
||||||
|
|
||||||
|
@ -329,22 +329,15 @@ export const composeReducer = (state = initialState, action) => {
|
||||||
return state.set('is_changing_upload', true);
|
return state.set('is_changing_upload', true);
|
||||||
} else if (changeUploadCompose.rejected.match(action)) {
|
} else if (changeUploadCompose.rejected.match(action)) {
|
||||||
return state.set('is_changing_upload', false);
|
return state.set('is_changing_upload', false);
|
||||||
} else if (quoteComposeByStatus.match(action)) {
|
} else if (quoteCompose.match(action)) {
|
||||||
const status = action.payload;
|
const status = action.payload;
|
||||||
if (
|
|
||||||
status.getIn(['quote_approval', 'current_user']) === 'automatic' &&
|
|
||||||
state.get('media_attachments').size === 0 &&
|
|
||||||
!state.get('is_uploading') &&
|
|
||||||
!state.get('poll')
|
|
||||||
) {
|
|
||||||
return state
|
return state
|
||||||
.set('quoted_status_id', status.get('id'))
|
.set('quoted_status_id', status.get('id'))
|
||||||
.set('spoiler', status.get('sensitive'))
|
.set('spoiler', status.get('sensitive'))
|
||||||
.set('spoiler_text', status.get('spoiler_text'));
|
.set('spoiler_text', status.get('spoiler_text'));
|
||||||
}
|
|
||||||
} else if (quoteComposeCancel.match(action)) {
|
} else if (quoteComposeCancel.match(action)) {
|
||||||
return state.set('quoted_status_id', null);
|
return state.set('quoted_status_id', null);
|
||||||
} else if (setQuotePolicy.match(action)) {
|
} else if (setComposeQuotePolicy.match(action)) {
|
||||||
return state.set('quote_policy', action.payload);
|
return state.set('quote_policy', action.payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,6 +513,7 @@ export const composeReducer = (state = initialState, action) => {
|
||||||
map.set('sensitive', action.status.get('sensitive'));
|
map.set('sensitive', action.status.get('sensitive'));
|
||||||
map.set('language', action.status.get('language'));
|
map.set('language', action.status.get('language'));
|
||||||
map.set('id', null);
|
map.set('id', null);
|
||||||
|
map.set('quoted_status_id', action.status.getIn(['quote', 'quoted_status']));
|
||||||
// Mastodon-authored posts can be expected to have at most one automatic approval policy
|
// Mastodon-authored posts can be expected to have at most one automatic approval policy
|
||||||
map.set('quote_policy', action.status.getIn(['quote_approval', 'automatic', 0]) || 'nobody');
|
map.set('quote_policy', action.status.getIn(['quote_approval', 'automatic', 0]) || 'nobody');
|
||||||
|
|
||||||
|
@ -551,6 +545,7 @@ export const composeReducer = (state = initialState, action) => {
|
||||||
map.set('idempotencyKey', uuid());
|
map.set('idempotencyKey', uuid());
|
||||||
map.set('sensitive', action.status.get('sensitive'));
|
map.set('sensitive', action.status.get('sensitive'));
|
||||||
map.set('language', action.status.get('language'));
|
map.set('language', action.status.get('language'));
|
||||||
|
map.set('quoted_status_id', action.status.getIn(['quote', 'quoted_status']));
|
||||||
// Mastodon-authored posts can be expected to have at most one automatic approval policy
|
// Mastodon-authored posts can be expected to have at most one automatic approval policy
|
||||||
map.set('quote_policy', action.status.getIn(['quote_approval', 'automatic', 0]) || 'nobody');
|
map.set('quote_policy', action.status.getIn(['quote_approval', 'automatic', 0]) || 'nobody');
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const getFilters = createSelector(
|
||||||
(_, { contextType }: { contextType: string }) => contextType,
|
(_, { contextType }: { contextType: string }) => contextType,
|
||||||
],
|
],
|
||||||
(filters, contextType) => {
|
(filters, contextType) => {
|
||||||
if (!contextType || contextType === 'compose') {
|
if (!contextType) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5512,7 +5512,10 @@ a.status-card {
|
||||||
|
|
||||||
.privacy-dropdown__option__content,
|
.privacy-dropdown__option__content,
|
||||||
.privacy-dropdown__option__content strong,
|
.privacy-dropdown__option__content strong,
|
||||||
.privacy-dropdown__option__additional {
|
.privacy-dropdown__option__additional,
|
||||||
|
.visibility-dropdown__option__content,
|
||||||
|
.visibility-dropdown__option__content strong,
|
||||||
|
.visibility-dropdown__option__additional {
|
||||||
color: $primary-text-color;
|
color: $primary-text-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5526,13 +5529,15 @@ a.status-card {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.privacy-dropdown__option__icon {
|
.privacy-dropdown__option__icon,
|
||||||
|
.visibility-dropdown__option__icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.privacy-dropdown__option__content {
|
.privacy-dropdown__option__content,
|
||||||
|
.visibility-dropdown__option__content {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
color: $darker-text-color;
|
color: $darker-text-color;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user