Composer redesign: Quote modal (#40437)

This commit is contained in:
Echo
2026-09-09 13:27:06 +00:00
committed by GitHub
parent 4447410702
commit d621d2e96e
19 changed files with 120 additions and 18 deletions

View File

@@ -15,6 +15,8 @@ import {
createAppThunk,
} from '@/mastodon/store/typed_functions';
import { isRedesignEnabled } from '../utils/environment';
import { showAlert } from './alerts';
import { changeCompose, focusCompose, uploadCompose } from './compose';
import { importFetchedStatuses } from './importer';
@@ -170,6 +172,8 @@ export const quoteComposeByStatus = createAppThunk(
false,
);
const statusId = status.get('id') as string;
if (composeState.get('id')) {
dispatch(showAlert({ message: messages.quoteErrorEdit }));
} else if (composeState.get('privacy') === 'direct') {
@@ -202,6 +206,17 @@ export const quoteComposeByStatus = createAppThunk(
modalProps: { status },
}),
);
} else if (
composeState.get('in_reply_to') &&
statusId &&
isRedesignEnabled()
) {
dispatch(
openModal({
modalType: 'COMPOSER_ADD_QUOTE',
modalProps: { statusId },
}),
);
} else {
dispatch(quoteCompose(status));
}

View File

@@ -4,6 +4,7 @@
@include mixins.elevation-1;
border-radius: var(--radius-xl);
box-sizing: border-box;
background: var(--color-bg-primary);
display: flex;
flex-direction: column;
@@ -50,3 +51,8 @@
.actionsRight {
justify-content: flex-end;
}
.actionsVertical {
flex-direction: column;
align-items: stretch;
}

View File

@@ -102,7 +102,7 @@ export const ModalTitle: React.FC<
return title;
};
export const ModalActions = <As extends React.ElementType>({
export const ModalActions = <As extends React.ElementType = 'div'>({
align,
as: asComp,
children,
@@ -110,7 +110,7 @@ export const ModalActions = <As extends React.ElementType>({
...props
}: PolymorphicProps<
{
align?: 'left' | 'right' | 'stretch';
align?: 'left' | 'right' | 'stretch' | 'vertical';
children?: React.ReactNode;
className?: string;
},
@@ -126,6 +126,7 @@ export const ModalActions = <As extends React.ElementType>({
classes.actions,
align === 'left' && classes.actionsLeft,
align === 'right' && classes.actionsRight,
align === 'vertical' && classes.actionsVertical,
)}
>
{children}

View File

@@ -78,5 +78,4 @@ const AnnualReportModal: React.FC<{
);
};
// eslint-disable-next-line import/no-default-export
export default AnnualReportModal;

View File

@@ -69,5 +69,4 @@ const ComposerModalCancelConfirm: React.FC<{ openNew?: boolean }> = ({
);
};
// eslint-disable-next-line import/no-default-export -- Modals import from default
export default ComposerModalCancelConfirm;

View File

@@ -0,0 +1,78 @@
import { useCallback } from 'react';
import { FormattedMessage } from 'react-intl';
import { resetCompose } from '@/mastodon/actions/compose';
import { quoteCompose } from '@/mastodon/actions/compose_typed';
import { closeModal } from '@/mastodon/actions/modal';
import { Button } from '@/mastodon/components/button/redesign';
import {
ModalShell,
ModalActions,
ModalTitle,
} from '@/mastodon/components/modal_shell/redesign';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
const ComposerQuoteAddConfirm: React.FC<{ statusId: string }> = ({
statusId,
}) => {
const dispatch = useAppDispatch();
const status = useAppSelector((state) => state.statuses.get(statusId));
const handleClose = useCallback(() => {
dispatch(
closeModal({
modalType: 'COMPOSER_ADD_QUOTE',
ignoreFocus: true,
}),
);
}, [dispatch]);
const handleAdd = useCallback(() => {
if (status) {
dispatch(quoteCompose(status));
}
handleClose();
}, [dispatch, handleClose, status]);
const handleDelete = useCallback(() => {
dispatch(resetCompose());
handleAdd();
}, [dispatch, handleAdd]);
return (
<ModalShell maxWidth={400}>
<ModalTitle>
<FormattedMessage
id='compose.quote_modal.title'
defaultMessage='Draft in progress'
/>
</ModalTitle>
<FormattedMessage
id='compose.cancel_modal.body'
defaultMessage='You have a draft already in progress. What would you like to do?'
/>
<ModalActions align='vertical'>
<Button variant='solid' onClick={handleAdd}>
<FormattedMessage
id='compose.quote_modal.add'
defaultMessage='Add quote to existing draft'
/>
</Button>
<Button variant='solid' color='destructive' onClick={handleDelete}>
<FormattedMessage
id='compose.quote_modal.delete'
defaultMessage='Delete draft and start a new post'
/>
</Button>
<Button variant='ghost' onClick={handleClose}>
<FormattedMessage
id='compose.quote_modal.cancel'
defaultMessage='Go back'
/>
</Button>
</ModalActions>
</ModalShell>
);
};
export default ComposerQuoteAddConfirm;

View File

@@ -189,5 +189,4 @@ const ComposeRearrangeItemDisplay: React.FC<
);
};
// eslint-disable-next-line import/no-default-export -- Modals import from default
export default ComposerModalRearrange;

View File

@@ -67,5 +67,4 @@ const ComposerModalSwitch: React.FC = () => {
);
};
// eslint-disable-next-line import/no-default-export
export default ComposerModalSwitch;

View File

@@ -44,5 +44,4 @@ const HashtagSettingsModal: React.FC<{ columnId: string; tagId: string }> = ({
);
};
// eslint-disable-next-line import/no-default-export
export default HashtagSettingsModal;

View File

@@ -40,5 +40,4 @@ const NotificationSettingsModal: React.FC = () => {
);
};
// eslint-disable-next-line import/no-default-export
export default NotificationSettingsModal;

View File

@@ -74,5 +74,4 @@ const AudioModal: React.FC<{
);
};
// eslint-disable-next-line import/no-default-export
export default AudioModal;

View File

@@ -97,14 +97,14 @@ export const BlockModal = ({ accountId, acct }) => {
<div className='safety-action-modal__actions'>
{domain && (
<button onClick={handleToggleLearnMore} className='link-button'>
<button type='button' onClick={handleToggleLearnMore} className='link-button'>
{expanded ? <FormattedMessage id='block_modal.show_less' defaultMessage='Show less' /> : <FormattedMessage id='block_modal.show_more' defaultMessage='Show more' />}
</button>
)}
<div className='spacer' />
<button onClick={handleCancel} className='link-button'>
<button type='button' onClick={handleCancel} className='link-button'>
<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
</button>

View File

@@ -218,5 +218,4 @@ export const DomainBlockModal: React.FC<{
);
};
// eslint-disable-next-line import/no-default-export
export default DomainBlockModal;

View File

@@ -115,5 +115,4 @@ const EmbedModal: React.FC<{
);
};
// eslint-disable-next-line import/no-default-export
export default EmbedModal;

View File

@@ -92,11 +92,11 @@ export const IgnoreNotificationsModal = ({ filterType }) => {
<div className='spacer' />
<button onClick={handleCancel} className='link-button'>
<button type='button' onClick={handleCancel} className='link-button'>
<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
</button>
<button onClick={handleClick} className='link-button'>
<button type='button' onClick={handleClick} className='link-button'>
<FormattedMessage id='ignore_notifications_modal.ignore' defaultMessage='Ignore notifications' />
</button>
</div>

View File

@@ -109,11 +109,12 @@ export const MODAL_COMPONENTS = {
'COMPOSER_DRAFT_DELETE': () => import('@/mastodon/features/compose/redesign/modal_cancel'),
'COMPOSER_REARRANGE': () => import('@/mastodon/features/compose/redesign/modal_rearrange'),
'COMPOSER_SWITCH_TO_POST': () => import('@/mastodon/features/compose/redesign/modal_switch'),
'COMPOSER_ADD_QUOTE': () => import('@/mastodon/features/compose/redesign/modal_quote'),
'NOTIFICATION_SETTINGS': () => import('@/mastodon/features/notifications_v2/components/notification_settings_modal'),
'HASHTAG_SETTINGS': () => import('@/mastodon/features/hashtag_timeline/components/column_settings_modal'),
};
/** @arg {keyof import('@/mastodon/features/account_edit/modals')} type */
/** @param {keyof import('@/mastodon/features/account_edit/modals')} type */
function accountEditModal(type) {
return () => import('@/mastodon/features/account_edit/modals').then(module => ({ default: module[type] }));
}

View File

@@ -131,13 +131,13 @@ export const MuteModal = ({ accountId, acct }) => {
</div>
<div className='safety-action-modal__actions'>
<button onClick={handleToggleSettings} className='link-button'>
<button type='button' onClick={handleToggleSettings} className='link-button'>
{expanded ? <FormattedMessage id='mute_modal.hide_options' defaultMessage='Hide options' /> : <FormattedMessage id='mute_modal.show_options' defaultMessage='Show options' />}
</button>
<div className='spacer' />
<button onClick={handleCancel} className='link-button'>
<button type='button' onClick={handleCancel} className='link-button'>
<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
</button>

View File

@@ -545,6 +545,10 @@
"compose.quote.audio": "Audio file {duration}",
"compose.quote.poll": "Poll {sep} {isOpen, select, open {Accepting responses} other {Closed}}",
"compose.quote.spoiler": "Content:",
"compose.quote_modal.add": "Add quote to existing draft",
"compose.quote_modal.cancel": "Go back",
"compose.quote_modal.delete": "Delete draft and start a new post",
"compose.quote_modal.title": "Draft in progress",
"compose.rearrange_modal.cancel": "Cancel",
"compose.rearrange_modal.drag_cancel": "Dragging was cancelled. Attachment index {item, number} was dropped.",
"compose.rearrange_modal.drag_end": "Attachment index {item, number} was moved to index {over, number}.",

View File

@@ -342,6 +342,12 @@ export default tseslint.config([
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
},
},
{
files: ['**/*modal*.{j,t}sx'],
rules: {
'import/no-default-export': 'off',
},
},
{
files: ['**/__tests__/*.js', '**/__tests__/*.jsx'],