mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-11 14:30:35 +00:00
Merge pull request #3266 from ClearlyClaire/glitch-soc/merge-4.5
Port missing changes to stable-4.5
This commit is contained in:
@@ -58,7 +58,6 @@ export const COMPOSE_ADVANCED_OPTIONS_CHANGE = 'COMPOSE_ADVANCED_OPTIONS_CHANGE'
|
||||
export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE';
|
||||
export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_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_CONTENT_TYPE_CHANGE = 'COMPOSE_CONTENT_TYPE_CHANGE';
|
||||
export const COMPOSE_LANGUAGE_CHANGE = 'COMPOSE_LANGUAGE_CHANGE';
|
||||
@@ -825,13 +824,6 @@ export function changeComposeSpoilerText(text) {
|
||||
};
|
||||
}
|
||||
|
||||
export function changeComposeVisibility(value) {
|
||||
return {
|
||||
type: COMPOSE_VISIBILITY_CHANGE,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
export function insertEmojiCompose(position, emoji, needsSpace) {
|
||||
return {
|
||||
type: COMPOSE_EMOJI_INSERT,
|
||||
|
||||
@@ -13,10 +13,11 @@ import {
|
||||
} from 'flavours/glitch/store/typed_functions';
|
||||
|
||||
import type { ApiQuotePolicy } from '../api_types/quotes';
|
||||
import type { Status } from '../models/status';
|
||||
import type { Status, StatusVisibility } from '../models/status';
|
||||
import type { RootState } from '../store';
|
||||
|
||||
import { showAlert } from './alerts';
|
||||
import { focusCompose } from './compose';
|
||||
import { changeCompose, focusCompose } from './compose';
|
||||
import { importFetchedStatuses } from './importer';
|
||||
import { openModal } from './modal';
|
||||
|
||||
@@ -41,6 +42,10 @@ const messages = defineMessages({
|
||||
id: 'quote_error.unauthorized',
|
||||
defaultMessage: 'You are not authorized to quote this post.',
|
||||
},
|
||||
quoteErrorPrivateMention: {
|
||||
id: 'quote_error.private_mentions',
|
||||
defaultMessage: 'Quoting is not allowed with direct mentions.',
|
||||
},
|
||||
});
|
||||
|
||||
type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & {
|
||||
@@ -67,6 +72,39 @@ const simulateModifiedApiResponse = (
|
||||
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(
|
||||
'compose/changeUpload',
|
||||
async (
|
||||
@@ -130,6 +168,8 @@ export const quoteComposeByStatus = createAppThunk(
|
||||
|
||||
if (composeState.get('id')) {
|
||||
dispatch(showAlert({ message: messages.quoteErrorEdit }));
|
||||
} else if (composeState.get('privacy') === 'direct') {
|
||||
dispatch(showAlert({ message: messages.quoteErrorPrivateMention }));
|
||||
} else if (composeState.get('poll')) {
|
||||
dispatch(showAlert({ message: messages.quoteErrorPoll }));
|
||||
} else if (
|
||||
@@ -173,6 +213,17 @@ export const quoteComposeById = createAppThunk(
|
||||
},
|
||||
);
|
||||
|
||||
const composeStateForbidsLink = (composeState: RootState['compose']) => {
|
||||
return (
|
||||
composeState.get('quoted_status_id') ||
|
||||
composeState.get('is_submitting') ||
|
||||
composeState.get('poll') ||
|
||||
composeState.get('is_uploading') ||
|
||||
composeState.get('id') ||
|
||||
composeState.get('privacy') === 'direct'
|
||||
);
|
||||
};
|
||||
|
||||
export const pasteLinkCompose = createDataLoadingThunk(
|
||||
'compose/pasteLink',
|
||||
async ({ url }: { url: string }) => {
|
||||
@@ -183,15 +234,12 @@ export const pasteLinkCompose = createDataLoadingThunk(
|
||||
limit: 2,
|
||||
});
|
||||
},
|
||||
(data, { dispatch, getState }) => {
|
||||
(data, { dispatch, getState, requestId }) => {
|
||||
const composeState = getState().compose;
|
||||
|
||||
if (
|
||||
composeState.get('quoted_status_id') ||
|
||||
composeState.get('is_submitting') ||
|
||||
composeState.get('poll') ||
|
||||
composeState.get('is_uploading') ||
|
||||
composeState.get('id')
|
||||
composeStateForbidsLink(composeState) ||
|
||||
composeState.get('fetching_link') !== requestId // Request has been cancelled
|
||||
)
|
||||
return;
|
||||
|
||||
@@ -207,6 +255,17 @@ export const pasteLinkCompose = createDataLoadingThunk(
|
||||
dispatch(quoteComposeById(data.statuses[0].id));
|
||||
}
|
||||
},
|
||||
{
|
||||
useLoadingBar: false,
|
||||
condition: (_, { getState }) =>
|
||||
!getState().compose.get('fetching_link') &&
|
||||
!composeStateForbidsLink(getState().compose),
|
||||
},
|
||||
);
|
||||
|
||||
// Ideally this would cancel the action and the HTTP request, but this is good enough
|
||||
export const cancelPasteLinkCompose = createAction(
|
||||
'compose/cancelPasteLinkCompose',
|
||||
);
|
||||
|
||||
export const quoteComposeCancel = createAction('compose/quoteComposeCancel');
|
||||
|
||||
@@ -155,7 +155,11 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onSubmit(missingAltTextModal && this.props.missingAltText && this.props.privacy !== 'direct', overridePrivacy);
|
||||
this.props.onSubmit({
|
||||
missingAltTextModal: missingAltTextModal && this.props.missingAltText && this.props.privacy !== 'direct',
|
||||
quoteToPrivate: this.props.quoteToPrivate,
|
||||
overridePrivacy,
|
||||
});
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useCallback } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { cancelPasteLinkCompose } from '@/flavours/glitch/actions/compose_typed';
|
||||
import { useAppDispatch } from '@/flavours/glitch/store';
|
||||
import CancelFillIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
|
||||
import { DisplayName } from 'flavours/glitch/components/display_name';
|
||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||
import { Skeleton } from 'flavours/glitch/components/skeleton';
|
||||
|
||||
const messages = defineMessages({
|
||||
quote_cancel: { id: 'status.quote.cancel', defaultMessage: 'Cancel quote' },
|
||||
});
|
||||
|
||||
export const QuotePlaceholder: FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const handleQuoteCancel = useCallback(() => {
|
||||
dispatch(cancelPasteLinkCompose());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<div className='status__quote'>
|
||||
<div className='status'>
|
||||
<div className='status__info'>
|
||||
<div className='status__avatar'>
|
||||
<Skeleton width='32px' height='32px' />
|
||||
</div>
|
||||
<div className='status__display-name'>
|
||||
<DisplayName />
|
||||
</div>
|
||||
<IconButton
|
||||
onClick={handleQuoteCancel}
|
||||
className='status__quote-cancel'
|
||||
title={intl.formatMessage(messages.quote_cancel)}
|
||||
icon='cancel-fill'
|
||||
iconComponent={CancelFillIcon}
|
||||
/>
|
||||
</div>
|
||||
<div className='status__content'>
|
||||
<Skeleton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -7,11 +7,17 @@ import { quoteComposeCancel } from '@/flavours/glitch/actions/compose_typed';
|
||||
import { QuotedStatus } from '@/flavours/glitch/components/status_quoted';
|
||||
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
||||
|
||||
import { QuotePlaceholder } from './quote_placeholder';
|
||||
|
||||
export const ComposeQuotedStatus: FC = () => {
|
||||
const quotedStatusId = useAppSelector(
|
||||
(state) => state.compose.get('quoted_status_id') as string | null,
|
||||
);
|
||||
|
||||
const isFetchingLink = useAppSelector(
|
||||
(state) => !!state.compose.get('fetching_link'),
|
||||
);
|
||||
|
||||
const isEditing = useAppSelector((state) => !!state.compose.get('id'));
|
||||
|
||||
const quote = useMemo(
|
||||
@@ -30,7 +36,9 @@ export const ComposeQuotedStatus: FC = () => {
|
||||
dispatch(quoteComposeCancel());
|
||||
}, [dispatch]);
|
||||
|
||||
if (!quote) {
|
||||
if (isFetchingLink && !quote) {
|
||||
return <QuotePlaceholder />;
|
||||
} else if (!quote) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { changeComposeVisibility } from '@/flavours/glitch/actions/compose';
|
||||
import { setComposeQuotePolicy } from '@/flavours/glitch/actions/compose_typed';
|
||||
import {
|
||||
changeComposeVisibility,
|
||||
setComposeQuotePolicy,
|
||||
} from '@/flavours/glitch/actions/compose_typed';
|
||||
import { openModal } from '@/flavours/glitch/actions/modal';
|
||||
import type { ApiQuotePolicy } from '@/flavours/glitch/api_types/quotes';
|
||||
import type { StatusVisibility } from '@/flavours/glitch/api_types/statuses';
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from 'flavours/glitch/actions/compose';
|
||||
import { pasteLinkCompose } from 'flavours/glitch/actions/compose_typed';
|
||||
import { openModal } from 'flavours/glitch/actions/modal';
|
||||
import { PRIVATE_QUOTE_MODAL_ID } from 'flavours/glitch/features/ui/components/confirmation_modals/private_quote_notify';
|
||||
import { privacyPreference } from 'flavours/glitch/utils/privacy_preference';
|
||||
|
||||
import ComposeForm from '../components/compose_form';
|
||||
@@ -52,6 +53,10 @@ const mapStateToProps = state => ({
|
||||
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||
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),
|
||||
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,
|
||||
lang: state.getIn(['compose', 'language']),
|
||||
sideArm: sideArmPrivacy(state),
|
||||
@@ -65,12 +70,17 @@ const mapDispatchToProps = (dispatch, props) => ({
|
||||
dispatch(changeCompose(text));
|
||||
},
|
||||
|
||||
onSubmit (missingAltText, overridePrivacy = null) {
|
||||
onSubmit ({ missingAltText, quoteToPrivate, overridePrivacy = null }) {
|
||||
if (missingAltText) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM_MISSING_ALT_TEXT',
|
||||
modalProps: { overridePrivacy },
|
||||
}));
|
||||
} else if (quoteToPrivate) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM_PRIVATE_QUOTE_NOTIFY',
|
||||
modalProps: {},
|
||||
}));
|
||||
} else {
|
||||
dispatch(submitCompose(overridePrivacy, (status) => {
|
||||
if (props.redirectOnSuccess) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { changeComposeVisibility } from '../../../actions/compose';
|
||||
import { openModal, closeModal } from '../../../actions/modal';
|
||||
import { isUserTouching } from '../../../is_mobile';
|
||||
import { changeComposeVisibility } from '@/flavours/glitch/actions/compose_typed';
|
||||
|
||||
import PrivacyDropdown from '../components/privacy_dropdown';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
||||
@@ -329,6 +329,12 @@ class Status extends ImmutablePureComponent {
|
||||
dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId, onChange: handleChange } }));
|
||||
};
|
||||
|
||||
handleQuote = (status) => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(quoteComposeById(status.get('id')));
|
||||
};
|
||||
|
||||
handleEditClick = (status) => {
|
||||
const { dispatch, askReplyConfirmation } = this.props;
|
||||
|
||||
@@ -659,6 +665,7 @@ class Status extends ImmutablePureComponent {
|
||||
onDelete={this.handleDeleteClick}
|
||||
onRevokeQuote={this.handleRevokeQuoteClick}
|
||||
onQuotePolicyChange={this.handleQuotePolicyChange}
|
||||
onQuote={this.handleQuote}
|
||||
onEdit={this.handleEditClick}
|
||||
onDirect={this.handleDirectClick}
|
||||
onMention={this.handleMentionClick}
|
||||
|
||||
@@ -26,6 +26,7 @@ export const ConfirmationModal: React.FC<
|
||||
onSecondary?: () => void;
|
||||
onConfirm: () => void;
|
||||
closeWhenConfirm?: boolean;
|
||||
extraContent?: React.ReactNode;
|
||||
} & BaseConfirmationModalProps
|
||||
> = ({
|
||||
title,
|
||||
@@ -37,6 +38,7 @@ export const ConfirmationModal: React.FC<
|
||||
secondary,
|
||||
onSecondary,
|
||||
closeWhenConfirm = true,
|
||||
extraContent,
|
||||
}) => {
|
||||
const handleClick = useCallback(() => {
|
||||
if (closeWhenConfirm) {
|
||||
@@ -57,6 +59,8 @@ export const ConfirmationModal: React.FC<
|
||||
<div className='safety-action-modal__confirmation'>
|
||||
<h1>{title}</h1>
|
||||
{message && <p>{message}</p>}
|
||||
|
||||
{extraContent}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { forwardRef, useCallback, useState } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { submitCompose } from '@/flavours/glitch/actions/compose';
|
||||
import { changeSetting } from '@/flavours/glitch/actions/settings';
|
||||
import { CheckBox } from '@/flavours/glitch/components/check_box';
|
||||
import { useAppDispatch } from '@/flavours/glitch/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;
|
||||
}
|
||||
@@ -51,6 +51,7 @@ import MediaModal from './media_modal';
|
||||
import { ModalPlaceholder } from './modal_placeholder';
|
||||
import VideoModal from './video_modal';
|
||||
import { VisibilityModal } from './visibility_modal';
|
||||
import { PrivateQuoteNotify } from './confirmation_modals/private_quote_notify';
|
||||
|
||||
export const MODAL_COMPONENTS = {
|
||||
'MEDIA': () => Promise.resolve({ default: MediaModal }),
|
||||
@@ -72,6 +73,7 @@ export const MODAL_COMPONENTS = {
|
||||
'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
|
||||
'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }),
|
||||
'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }),
|
||||
'CONFIRM_PRIVATE_QUOTE_NOTIFY': () => Promise.resolve({ default: PrivateQuoteNotify }),
|
||||
'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }),
|
||||
'CONFIRM_QUIET_QUOTE': () => Promise.resolve({ default: QuietPostQuoteInfoModal }),
|
||||
'MUTE': MuteModal,
|
||||
|
||||
@@ -128,9 +128,12 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||
const disableVisibility = !!statusId;
|
||||
const disableQuotePolicy =
|
||||
visibility === 'private' || visibility === 'direct';
|
||||
const disablePublicVisibilities: boolean = useAppSelector(
|
||||
const disablePublicVisibilities = useAppSelector(
|
||||
selectDisablePublicVisibilities,
|
||||
);
|
||||
const isQuotePost = useAppSelector(
|
||||
(state) => state.compose.get('quoted_status_id') !== null,
|
||||
);
|
||||
|
||||
const visibilityItems = useMemo<SelectItem<StatusVisibility>[]>(() => {
|
||||
const items: SelectItem<StatusVisibility>[] = [
|
||||
@@ -315,6 +318,21 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||
id={quoteDescriptionId}
|
||||
/>
|
||||
</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 className='dialog-modal__content__actions'>
|
||||
<Button onClick={onClose} secondary>
|
||||
|
||||
@@ -37,7 +37,7 @@ interface InitialStateMeta {
|
||||
streaming_api_base_url: string;
|
||||
local_live_feed_access: 'public' | 'authenticated' | 'disabled';
|
||||
remote_live_feed_access: 'public' | 'authenticated' | 'disabled';
|
||||
local_topic_feed_access: 'public' | 'authenticated' | 'disabled';
|
||||
local_topic_feed_access: 'public' | 'authenticated';
|
||||
remote_topic_feed_access: 'public' | 'authenticated' | 'disabled';
|
||||
title: string;
|
||||
show_trends: boolean;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
||||
|
||||
import {
|
||||
changeComposeVisibility,
|
||||
changeUploadCompose,
|
||||
quoteCompose,
|
||||
quoteComposeCancel,
|
||||
setComposeQuotePolicy,
|
||||
} from 'flavours/glitch/actions/compose_typed';
|
||||
pasteLinkCompose,
|
||||
cancelPasteLinkCompose,
|
||||
} from '@/flavours/glitch/actions/compose_typed';
|
||||
import { timelineDelete } from 'flavours/glitch/actions/timelines_typed';
|
||||
|
||||
import {
|
||||
@@ -39,7 +42,6 @@ import {
|
||||
COMPOSE_SENSITIVITY_CHANGE,
|
||||
COMPOSE_SPOILERNESS_CHANGE,
|
||||
COMPOSE_SPOILER_TEXT_CHANGE,
|
||||
COMPOSE_VISIBILITY_CHANGE,
|
||||
COMPOSE_LANGUAGE_CHANGE,
|
||||
COMPOSE_COMPOSING_CHANGE,
|
||||
COMPOSE_CONTENT_TYPE_CHANGE,
|
||||
@@ -119,6 +121,7 @@ const initialState = ImmutableMap({
|
||||
quoted_status_id: null,
|
||||
quote_policy: 'public',
|
||||
default_quote_policy: 'public', // Set in hydration.
|
||||
fetching_link: null,
|
||||
});
|
||||
|
||||
const initialPoll = ImmutableMap({
|
||||
@@ -391,7 +394,11 @@ const calculateProgress = (loaded, total) => Math.min(Math.round((loaded / total
|
||||
|
||||
/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
|
||||
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
|
||||
.set('is_changing_upload', false)
|
||||
.update('media_attachments', list => list.map(item => {
|
||||
@@ -407,15 +414,27 @@ export const composeReducer = (state = initialState, action) => {
|
||||
return state.set('is_changing_upload', false);
|
||||
} else if (quoteCompose.match(action)) {
|
||||
const status = action.payload;
|
||||
const isDirect = state.get('privacy') === 'direct';
|
||||
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_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;
|
||||
});
|
||||
} else if (quoteComposeCancel.match(action)) {
|
||||
return state.set('quoted_status_id', null);
|
||||
} else if (setComposeQuotePolicy.match(action)) {
|
||||
return state.set('quote_policy', action.payload);
|
||||
} else if (pasteLinkCompose.pending.match(action)) {
|
||||
return state.set('fetching_link', action.meta.requestId);
|
||||
} else if (pasteLinkCompose.fulfilled.match(action) || pasteLinkCompose.rejected.match(action)) {
|
||||
return action.meta.requestId === state.get('fetching_link') ? state.set('fetching_link', null) : state;
|
||||
} else if (cancelPasteLinkCompose.match(action)) {
|
||||
return state.set('fetching_link', null);
|
||||
}
|
||||
|
||||
switch(action.type) {
|
||||
@@ -462,10 +481,6 @@ export const composeReducer = (state = initialState, action) => {
|
||||
return state
|
||||
.set('spoiler_text', action.text)
|
||||
.set('idempotencyKey', uuid());
|
||||
case COMPOSE_VISIBILITY_CHANGE:
|
||||
return state
|
||||
.set('privacy', action.value)
|
||||
.set('idempotencyKey', uuid());
|
||||
case COMPOSE_CONTENT_TYPE_CHANGE:
|
||||
return state
|
||||
.set('content_type', action.value)
|
||||
|
||||
@@ -42,7 +42,7 @@ interface AppThunkConfig {
|
||||
}
|
||||
export type AppThunkApi = Pick<
|
||||
GetThunkAPI<AppThunkConfig>,
|
||||
'getState' | 'dispatch'
|
||||
'getState' | 'dispatch' | 'requestId'
|
||||
>;
|
||||
|
||||
interface AppThunkOptions<Arg> {
|
||||
@@ -60,7 +60,7 @@ type AppThunk<Arg = void, Returned = void> = (
|
||||
|
||||
type AppThunkCreator<Arg = void, Returned = void, ExtraArg = unknown> = (
|
||||
arg: Arg,
|
||||
api: AppThunkApi,
|
||||
api: Pick<AppThunkApi, 'getState' | 'dispatch'>,
|
||||
extra?: ExtraArg,
|
||||
) => Returned;
|
||||
|
||||
@@ -143,10 +143,10 @@ export function createAsyncThunk<Arg = void, Returned = void>(
|
||||
name,
|
||||
async (
|
||||
arg: Arg,
|
||||
{ getState, dispatch, fulfillWithValue, rejectWithValue },
|
||||
{ getState, dispatch, requestId, fulfillWithValue, rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
const result = await creator(arg, { dispatch, getState });
|
||||
const result = await creator(arg, { dispatch, getState, requestId });
|
||||
|
||||
return fulfillWithValue(result, {
|
||||
useLoadingBar: options.useLoadingBar,
|
||||
@@ -280,10 +280,11 @@ export function createDataLoadingThunk<
|
||||
|
||||
return createAsyncThunk<Args, Returned>(
|
||||
name,
|
||||
async (arg, { getState, dispatch }) => {
|
||||
async (arg, { getState, dispatch, requestId }) => {
|
||||
const data = await loadData(arg, {
|
||||
dispatch,
|
||||
getState,
|
||||
requestId,
|
||||
});
|
||||
|
||||
if (!onData) return data as Returned;
|
||||
@@ -291,6 +292,7 @@ export function createDataLoadingThunk<
|
||||
const result = await onData(data, {
|
||||
dispatch,
|
||||
getState,
|
||||
requestId,
|
||||
discardLoadData: discardLoadDataInPayload,
|
||||
actionArg: arg,
|
||||
});
|
||||
|
||||
@@ -1330,6 +1330,10 @@ a.sparkline {
|
||||
line-height: 1;
|
||||
width: 100%;
|
||||
animation: skeleton 1.2s ease-in-out infinite;
|
||||
|
||||
.reduce-motion & {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes skeleton {
|
||||
|
||||
@@ -5980,6 +5980,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 {
|
||||
&__overlay[data-popper-placement] {
|
||||
z-index: 9999;
|
||||
|
||||
Reference in New Issue
Block a user