Quote Posts: Add notifications for DMs and private posts (#36696)
This commit is contained in:
@@ -56,7 +56,6 @@ export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT';
|
|||||||
export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE';
|
export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE';
|
||||||
export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
|
export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
|
||||||
export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
|
export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
|
||||||
export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE';
|
|
||||||
export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE';
|
export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE';
|
||||||
export const COMPOSE_LANGUAGE_CHANGE = 'COMPOSE_LANGUAGE_CHANGE';
|
export const COMPOSE_LANGUAGE_CHANGE = 'COMPOSE_LANGUAGE_CHANGE';
|
||||||
|
|
||||||
@@ -794,13 +793,6 @@ export function changeComposeSpoilerText(text) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function changeComposeVisibility(value) {
|
|
||||||
return {
|
|
||||||
type: COMPOSE_VISIBILITY_CHANGE,
|
|
||||||
value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function insertEmojiCompose(position, emoji, needsSpace) {
|
export function insertEmojiCompose(position, emoji, needsSpace) {
|
||||||
return {
|
return {
|
||||||
type: COMPOSE_EMOJI_INSERT,
|
type: COMPOSE_EMOJI_INSERT,
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import {
|
|||||||
} from 'mastodon/store/typed_functions';
|
} from 'mastodon/store/typed_functions';
|
||||||
|
|
||||||
import type { ApiQuotePolicy } from '../api_types/quotes';
|
import type { ApiQuotePolicy } from '../api_types/quotes';
|
||||||
import type { Status } from '../models/status';
|
import type { Status, StatusVisibility } from '../models/status';
|
||||||
|
|
||||||
import { showAlert } from './alerts';
|
import { showAlert } from './alerts';
|
||||||
import { focusCompose } from './compose';
|
import { changeCompose, focusCompose } from './compose';
|
||||||
import { importFetchedStatuses } from './importer';
|
import { importFetchedStatuses } from './importer';
|
||||||
import { openModal } from './modal';
|
import { openModal } from './modal';
|
||||||
|
|
||||||
@@ -67,6 +67,39 @@ const simulateModifiedApiResponse = (
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const changeComposeVisibility = createAppThunk(
|
||||||
|
'compose/visibility_change',
|
||||||
|
(visibility: StatusVisibility, { dispatch, getState }) => {
|
||||||
|
if (visibility !== 'direct') {
|
||||||
|
return visibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = getState();
|
||||||
|
const quotedStatusId = state.compose.get('quoted_status_id') as
|
||||||
|
| string
|
||||||
|
| null;
|
||||||
|
if (!quotedStatusId) {
|
||||||
|
return visibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the quoted status
|
||||||
|
dispatch(quoteComposeCancel());
|
||||||
|
const quotedStatus = state.statuses.get(quotedStatusId) as Status | null;
|
||||||
|
if (!quotedStatus) {
|
||||||
|
return visibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the quoted status URL to the compose text
|
||||||
|
const url = quotedStatus.get('url') as string;
|
||||||
|
const text = state.compose.get('text') as string;
|
||||||
|
if (!text.includes(url)) {
|
||||||
|
const newText = text.trim() ? `${text}\n\n${url}` : url;
|
||||||
|
dispatch(changeCompose(newText));
|
||||||
|
}
|
||||||
|
return visibility;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const changeUploadCompose = createDataLoadingThunk(
|
export const changeUploadCompose = createDataLoadingThunk(
|
||||||
'compose/changeUpload',
|
'compose/changeUpload',
|
||||||
async (
|
async (
|
||||||
|
|||||||
@@ -140,7 +140,10 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onSubmit(missingAltTextModal && this.props.missingAltText && this.props.privacy !== 'direct');
|
this.props.onSubmit({
|
||||||
|
missingAltText: missingAltTextModal && this.props.missingAltText && this.props.privacy !== 'direct',
|
||||||
|
quoteToPrivate: this.props.quoteToPrivate,
|
||||||
|
});
|
||||||
|
|
||||||
if (e) {
|
if (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { changeComposeVisibility } from '@/mastodon/actions/compose';
|
import {
|
||||||
import { setComposeQuotePolicy } from '@/mastodon/actions/compose_typed';
|
changeComposeVisibility,
|
||||||
|
setComposeQuotePolicy,
|
||||||
|
} from '@/mastodon/actions/compose_typed';
|
||||||
import { openModal } from '@/mastodon/actions/modal';
|
import { openModal } from '@/mastodon/actions/modal';
|
||||||
import type { ApiQuotePolicy } from '@/mastodon/api_types/quotes';
|
import type { ApiQuotePolicy } from '@/mastodon/api_types/quotes';
|
||||||
import type { StatusVisibility } from '@/mastodon/api_types/statuses';
|
import type { StatusVisibility } from '@/mastodon/api_types/statuses';
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from 'mastodon/actions/compose';
|
} from 'mastodon/actions/compose';
|
||||||
import { pasteLinkCompose } from 'mastodon/actions/compose_typed';
|
import { pasteLinkCompose } from 'mastodon/actions/compose_typed';
|
||||||
import { openModal } from 'mastodon/actions/modal';
|
import { openModal } from 'mastodon/actions/modal';
|
||||||
|
import { PRIVATE_QUOTE_MODAL_ID } from 'mastodon/features/ui/components/confirmation_modals/private_quote_notify';
|
||||||
|
|
||||||
import ComposeForm from '../components/compose_form';
|
import ComposeForm from '../components/compose_form';
|
||||||
|
|
||||||
@@ -32,6 +33,10 @@ const mapStateToProps = state => ({
|
|||||||
isUploading: state.getIn(['compose', 'is_uploading']),
|
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||||
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||||
missingAltText: state.getIn(['compose', 'media_attachments']).some(media => ['image', 'gifv'].includes(media.get('type')) && (media.get('description') ?? '').length === 0),
|
missingAltText: state.getIn(['compose', 'media_attachments']).some(media => ['image', 'gifv'].includes(media.get('type')) && (media.get('description') ?? '').length === 0),
|
||||||
|
quoteToPrivate:
|
||||||
|
!!state.getIn(['compose', 'quoted_status_id'])
|
||||||
|
&& state.getIn(['compose', 'privacy']) === 'private'
|
||||||
|
&& !state.getIn(['settings', 'dismissed_banners', PRIVATE_QUOTE_MODAL_ID]),
|
||||||
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
|
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
|
||||||
lang: state.getIn(['compose', 'language']),
|
lang: state.getIn(['compose', 'language']),
|
||||||
maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500),
|
maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500),
|
||||||
@@ -43,12 +48,17 @@ const mapDispatchToProps = (dispatch, props) => ({
|
|||||||
dispatch(changeCompose(text));
|
dispatch(changeCompose(text));
|
||||||
},
|
},
|
||||||
|
|
||||||
onSubmit (missingAltText) {
|
onSubmit ({ missingAltText, quoteToPrivate }) {
|
||||||
if (missingAltText) {
|
if (missingAltText) {
|
||||||
dispatch(openModal({
|
dispatch(openModal({
|
||||||
modalType: 'CONFIRM_MISSING_ALT_TEXT',
|
modalType: 'CONFIRM_MISSING_ALT_TEXT',
|
||||||
modalProps: {},
|
modalProps: {},
|
||||||
}));
|
}));
|
||||||
|
} else if (quoteToPrivate) {
|
||||||
|
dispatch(openModal({
|
||||||
|
modalType: 'CONFIRM_PRIVATE_QUOTE_NOTIFY',
|
||||||
|
modalProps: {},
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
dispatch(submitCompose((status) => {
|
dispatch(submitCompose((status) => {
|
||||||
if (props.redirectOnSuccess) {
|
if (props.redirectOnSuccess) {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { changeComposeVisibility } from '../../../actions/compose';
|
import { changeComposeVisibility } from '@/mastodon/actions/compose_typed';
|
||||||
import { openModal, closeModal } from '../../../actions/modal';
|
|
||||||
import { isUserTouching } from '../../../is_mobile';
|
|
||||||
import PrivacyDropdown from '../components/privacy_dropdown';
|
import PrivacyDropdown from '../components/privacy_dropdown';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export const ConfirmationModal: React.FC<
|
|||||||
onSecondary?: () => void;
|
onSecondary?: () => void;
|
||||||
onConfirm: () => void;
|
onConfirm: () => void;
|
||||||
closeWhenConfirm?: boolean;
|
closeWhenConfirm?: boolean;
|
||||||
|
extraContent?: React.ReactNode;
|
||||||
} & BaseConfirmationModalProps
|
} & BaseConfirmationModalProps
|
||||||
> = ({
|
> = ({
|
||||||
title,
|
title,
|
||||||
@@ -29,6 +30,7 @@ export const ConfirmationModal: React.FC<
|
|||||||
secondary,
|
secondary,
|
||||||
onSecondary,
|
onSecondary,
|
||||||
closeWhenConfirm = true,
|
closeWhenConfirm = true,
|
||||||
|
extraContent,
|
||||||
}) => {
|
}) => {
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
if (closeWhenConfirm) {
|
if (closeWhenConfirm) {
|
||||||
@@ -49,6 +51,8 @@ export const ConfirmationModal: React.FC<
|
|||||||
<div className='safety-action-modal__confirmation'>
|
<div className='safety-action-modal__confirmation'>
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
{message && <p>{message}</p>}
|
{message && <p>{message}</p>}
|
||||||
|
|
||||||
|
{extraContent}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { forwardRef, useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { submitCompose } from '@/mastodon/actions/compose';
|
||||||
|
import { changeSetting } from '@/mastodon/actions/settings';
|
||||||
|
import { CheckBox } from '@/mastodon/components/check_box';
|
||||||
|
import { useAppDispatch } from '@/mastodon/store';
|
||||||
|
|
||||||
|
import { ConfirmationModal } from './confirmation_modal';
|
||||||
|
import type { BaseConfirmationModalProps } from './confirmation_modal';
|
||||||
|
import classes from './styles.module.css';
|
||||||
|
|
||||||
|
export const PRIVATE_QUOTE_MODAL_ID = 'quote/private_notify';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
title: {
|
||||||
|
id: 'confirmations.private_quote_notify.title',
|
||||||
|
defaultMessage: 'Share with followers and mentioned users?',
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
id: 'confirmations.private_quote_notify.message',
|
||||||
|
defaultMessage:
|
||||||
|
'The person you are quoting and other mentions ' +
|
||||||
|
"will be notified and will be able to view your post, even if they're not following you.",
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
id: 'confirmations.private_quote_notify.confirm',
|
||||||
|
defaultMessage: 'Publish post',
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
id: 'confirmations.private_quote_notify.cancel',
|
||||||
|
defaultMessage: 'Back to editing',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PrivateQuoteNotify = forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
BaseConfirmationModalProps
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{ onClose },
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
_ref,
|
||||||
|
) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const [dismiss, setDismissed] = useState(false);
|
||||||
|
const handleDismissToggle = useCallback(() => {
|
||||||
|
setDismissed((prev) => !prev);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const handleConfirm = useCallback(() => {
|
||||||
|
dispatch(submitCompose());
|
||||||
|
if (dismiss) {
|
||||||
|
dispatch(
|
||||||
|
changeSetting(['dismissed_banners', PRIVATE_QUOTE_MODAL_ID], true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [dismiss, dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmationModal
|
||||||
|
title={intl.formatMessage(messages.title)}
|
||||||
|
message={intl.formatMessage(messages.message)}
|
||||||
|
confirm={intl.formatMessage(messages.confirm)}
|
||||||
|
cancel={intl.formatMessage(messages.cancel)}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
onClose={onClose}
|
||||||
|
extraContent={
|
||||||
|
<label className={classes.checkbox_wrapper}>
|
||||||
|
<CheckBox
|
||||||
|
value='hide'
|
||||||
|
checked={dismiss}
|
||||||
|
onChange={handleDismissToggle}
|
||||||
|
/>{' '}
|
||||||
|
<FormattedMessage
|
||||||
|
id='confirmations.private_quote_notify.do_not_show_again'
|
||||||
|
defaultMessage="Don't show me this message again"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
PrivateQuoteNotify.displayName = 'PrivateQuoteNotify';
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
.checkbox_wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
@@ -47,6 +47,7 @@ import MediaModal from './media_modal';
|
|||||||
import { ModalPlaceholder } from './modal_placeholder';
|
import { ModalPlaceholder } from './modal_placeholder';
|
||||||
import VideoModal from './video_modal';
|
import VideoModal from './video_modal';
|
||||||
import { VisibilityModal } from './visibility_modal';
|
import { VisibilityModal } from './visibility_modal';
|
||||||
|
import { PrivateQuoteNotify } from './confirmation_modals/private_quote_notify';
|
||||||
|
|
||||||
export const MODAL_COMPONENTS = {
|
export const MODAL_COMPONENTS = {
|
||||||
'MEDIA': () => Promise.resolve({ default: MediaModal }),
|
'MEDIA': () => Promise.resolve({ default: MediaModal }),
|
||||||
@@ -66,6 +67,7 @@ export const MODAL_COMPONENTS = {
|
|||||||
'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
|
'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
|
||||||
'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }),
|
'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }),
|
||||||
'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }),
|
'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }),
|
||||||
|
'CONFIRM_PRIVATE_QUOTE_NOTIFY': () => Promise.resolve({ default: PrivateQuoteNotify }),
|
||||||
'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }),
|
'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }),
|
||||||
'CONFIRM_QUIET_QUOTE': () => Promise.resolve({ default: QuietPostQuoteInfoModal }),
|
'CONFIRM_QUIET_QUOTE': () => Promise.resolve({ default: QuietPostQuoteInfoModal }),
|
||||||
'MUTE': MuteModal,
|
'MUTE': MuteModal,
|
||||||
|
|||||||
@@ -128,9 +128,12 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
|||||||
const disableVisibility = !!statusId;
|
const disableVisibility = !!statusId;
|
||||||
const disableQuotePolicy =
|
const disableQuotePolicy =
|
||||||
visibility === 'private' || visibility === 'direct';
|
visibility === 'private' || visibility === 'direct';
|
||||||
const disablePublicVisibilities: boolean = useAppSelector(
|
const disablePublicVisibilities = useAppSelector(
|
||||||
selectDisablePublicVisibilities,
|
selectDisablePublicVisibilities,
|
||||||
);
|
);
|
||||||
|
const isQuotePost = useAppSelector(
|
||||||
|
(state) => state.compose.get('quoted_status_id') !== null,
|
||||||
|
);
|
||||||
|
|
||||||
const visibilityItems = useMemo<SelectItem<StatusVisibility>[]>(() => {
|
const visibilityItems = useMemo<SelectItem<StatusVisibility>[]>(() => {
|
||||||
const items: SelectItem<StatusVisibility>[] = [
|
const items: SelectItem<StatusVisibility>[] = [
|
||||||
@@ -315,6 +318,21 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
|||||||
id={quoteDescriptionId}
|
id={quoteDescriptionId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isQuotePost && visibility === 'direct' && (
|
||||||
|
<div className='visibility-modal__quote-warning'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='visibility_modal.direct_quote_warning.title'
|
||||||
|
defaultMessage="Quotes can't be embedded in private mentions"
|
||||||
|
tagName='h3'
|
||||||
|
/>
|
||||||
|
<FormattedMessage
|
||||||
|
id='visibility_modal.direct_quote_warning.text'
|
||||||
|
defaultMessage='If you save the current settings, the embedded quote will be converted to a link.'
|
||||||
|
tagName='p'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='dialog-modal__content__actions'>
|
<div className='dialog-modal__content__actions'>
|
||||||
<Button onClick={onClose} secondary>
|
<Button onClick={onClose} secondary>
|
||||||
|
|||||||
@@ -247,6 +247,11 @@
|
|||||||
"confirmations.missing_alt_text.secondary": "Post anyway",
|
"confirmations.missing_alt_text.secondary": "Post anyway",
|
||||||
"confirmations.missing_alt_text.title": "Add alt text?",
|
"confirmations.missing_alt_text.title": "Add alt text?",
|
||||||
"confirmations.mute.confirm": "Mute",
|
"confirmations.mute.confirm": "Mute",
|
||||||
|
"confirmations.private_quote_notify.cancel": "Back to editing",
|
||||||
|
"confirmations.private_quote_notify.confirm": "Publish post",
|
||||||
|
"confirmations.private_quote_notify.do_not_show_again": "Don't show me this message again",
|
||||||
|
"confirmations.private_quote_notify.message": "The person you are quoting and other mentions will be notified and will be able to view your post, even if they're not following you.",
|
||||||
|
"confirmations.private_quote_notify.title": "Share with followers and mentioned users?",
|
||||||
"confirmations.quiet_post_quote_info.dismiss": "Don't remind me again",
|
"confirmations.quiet_post_quote_info.dismiss": "Don't remind me again",
|
||||||
"confirmations.quiet_post_quote_info.got_it": "Got it",
|
"confirmations.quiet_post_quote_info.got_it": "Got it",
|
||||||
"confirmations.quiet_post_quote_info.message": "When quoting a quiet public post, your post will be hidden from trending timelines.",
|
"confirmations.quiet_post_quote_info.message": "When quoting a quiet public post, your post will be hidden from trending timelines.",
|
||||||
@@ -1012,6 +1017,8 @@
|
|||||||
"video.volume_down": "Volume down",
|
"video.volume_down": "Volume down",
|
||||||
"video.volume_up": "Volume up",
|
"video.volume_up": "Volume up",
|
||||||
"visibility_modal.button_title": "Set visibility",
|
"visibility_modal.button_title": "Set visibility",
|
||||||
|
"visibility_modal.direct_quote_warning.text": "If you save the current settings, the embedded quote will be converted to a link.",
|
||||||
|
"visibility_modal.direct_quote_warning.title": "Quotes can't be embedded in private mentions",
|
||||||
"visibility_modal.header": "Visibility and interaction",
|
"visibility_modal.header": "Visibility and interaction",
|
||||||
"visibility_modal.helper.direct_quoting": "Private mentions authored on Mastodon can't be quoted by others.",
|
"visibility_modal.helper.direct_quoting": "Private mentions authored on Mastodon can't be quoted by others.",
|
||||||
"visibility_modal.helper.privacy_editing": "Visibility can't be changed after a post is published.",
|
"visibility_modal.helper.privacy_editing": "Visibility can't be changed after a post is published.",
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
changeComposeVisibility,
|
||||||
changeUploadCompose,
|
changeUploadCompose,
|
||||||
quoteCompose,
|
quoteCompose,
|
||||||
quoteComposeCancel,
|
quoteComposeCancel,
|
||||||
setComposeQuotePolicy,
|
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';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -38,7 +39,6 @@ import {
|
|||||||
COMPOSE_SENSITIVITY_CHANGE,
|
COMPOSE_SENSITIVITY_CHANGE,
|
||||||
COMPOSE_SPOILERNESS_CHANGE,
|
COMPOSE_SPOILERNESS_CHANGE,
|
||||||
COMPOSE_SPOILER_TEXT_CHANGE,
|
COMPOSE_SPOILER_TEXT_CHANGE,
|
||||||
COMPOSE_VISIBILITY_CHANGE,
|
|
||||||
COMPOSE_LANGUAGE_CHANGE,
|
COMPOSE_LANGUAGE_CHANGE,
|
||||||
COMPOSE_COMPOSING_CHANGE,
|
COMPOSE_COMPOSING_CHANGE,
|
||||||
COMPOSE_EMOJI_INSERT,
|
COMPOSE_EMOJI_INSERT,
|
||||||
@@ -315,7 +315,11 @@ const calculateProgress = (loaded, total) => Math.min(Math.round((loaded / total
|
|||||||
|
|
||||||
/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
|
/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
|
||||||
export const composeReducer = (state = initialState, action) => {
|
export const composeReducer = (state = initialState, action) => {
|
||||||
if (changeUploadCompose.fulfilled.match(action)) {
|
if (changeComposeVisibility.match(action)) {
|
||||||
|
return state
|
||||||
|
.set('privacy', action.payload)
|
||||||
|
.set('idempotencyKey', uuid());
|
||||||
|
} else if (changeUploadCompose.fulfilled.match(action)) {
|
||||||
return state
|
return state
|
||||||
.set('is_changing_upload', false)
|
.set('is_changing_upload', false)
|
||||||
.update('media_attachments', list => list.map(item => {
|
.update('media_attachments', list => list.map(item => {
|
||||||
@@ -331,11 +335,27 @@ export const composeReducer = (state = initialState, action) => {
|
|||||||
return state.set('is_changing_upload', false);
|
return state.set('is_changing_upload', false);
|
||||||
} else if (quoteCompose.match(action)) {
|
} else if (quoteCompose.match(action)) {
|
||||||
const status = action.payload;
|
const status = action.payload;
|
||||||
|
const isDirect = state.get('privacy') === 'direct';
|
||||||
return state
|
return state
|
||||||
.set('quoted_status_id', status.get('id'))
|
.set('quoted_status_id', isDirect ? null : 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'))
|
||||||
.update('privacy', (visibility) => ['public', 'unlisted'].includes(visibility) && status.get('visibility') === 'private' ? 'private' : visibility);
|
.update('privacy', (visibility) => {
|
||||||
|
if (['public', 'unlisted'].includes(visibility) && status.get('visibility') === 'private') {
|
||||||
|
return 'private';
|
||||||
|
}
|
||||||
|
return visibility;
|
||||||
|
})
|
||||||
|
.update('text', (text) => {
|
||||||
|
if (!isDirect) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
const url = status.get('url');
|
||||||
|
if (text.includes(url)) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
return text.trim() ? `${text}\n\n${url}` : url;
|
||||||
|
});
|
||||||
} 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 (setComposeQuotePolicy.match(action)) {
|
} else if (setComposeQuotePolicy.match(action)) {
|
||||||
@@ -383,10 +403,6 @@ export const composeReducer = (state = initialState, action) => {
|
|||||||
return state
|
return state
|
||||||
.set('spoiler_text', action.text)
|
.set('spoiler_text', action.text)
|
||||||
.set('idempotencyKey', uuid());
|
.set('idempotencyKey', uuid());
|
||||||
case COMPOSE_VISIBILITY_CHANGE:
|
|
||||||
return state
|
|
||||||
.set('privacy', action.value)
|
|
||||||
.set('idempotencyKey', uuid());
|
|
||||||
case COMPOSE_CHANGE:
|
case COMPOSE_CHANGE:
|
||||||
return state
|
return state
|
||||||
.set('text', action.text)
|
.set('text', action.text)
|
||||||
|
|||||||
@@ -5743,6 +5743,34 @@ a.status-card {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.visibility-modal {
|
||||||
|
&__quote-warning {
|
||||||
|
color: var(--nested-card-text);
|
||||||
|
background:
|
||||||
|
/* This is a bit of a silly hack for layering two background colours
|
||||||
|
* since --nested-card-background is too transparent for a tooltip */
|
||||||
|
linear-gradient(
|
||||||
|
var(--nested-card-background),
|
||||||
|
var(--nested-card-background)
|
||||||
|
),
|
||||||
|
linear-gradient(var(--background-color), var(--background-color));
|
||||||
|
border: var(--nested-card-border);
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: $darker-text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: $dark-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.visibility-dropdown {
|
.visibility-dropdown {
|
||||||
&__overlay[data-popper-placement] {
|
&__overlay[data-popper-placement] {
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
|
|||||||
Reference in New Issue
Block a user