Composer redesign: Private replies (#40435)

This commit is contained in:
Echo
2026-09-09 11:23:44 +00:00
committed by GitHub
parent 7a9358acd4
commit 28b3695b6b
6 changed files with 58 additions and 20 deletions

View File

@@ -36,9 +36,17 @@ const messages = defineMessages({
defaultMessage: 'New reply',
},
replyEdit: {
id: 'compose_form.reply.title.edit',
id: 'compose.reply.title.edit',
defaultMessage: 'Edit reply',
},
replyPrivateNew: {
id: 'compose.reply_private.title.new',
defaultMessage: 'New private reply',
},
replyPrivateEdit: {
id: 'compose.reply_private.title.edit',
defaultMessage: 'Edit private reply',
},
messageNew: {
id: 'compose_form.message.title.new',
defaultMessage: 'New message',

View File

@@ -72,7 +72,9 @@ export const RedesignComposeForm: React.FC<
aria-labelledby={titleId}
className={classNames(className, classes.root)}
>
{type === 'message' && <div className={classes.background} />}
{(type === 'message' || type === 'replyPrivate') && (
<div className={classes.background} />
)}
<ComposeFormHeader id={titleId} noMinimize={noMinimize} />

View File

@@ -22,15 +22,20 @@ export const selectComposeType = createAppSelector(
(state) => state.compose.get('in_reply_to') as string | null,
selectComposePrivacy,
],
(inReplyToId, privacy) => {
let type: ComposeType = 'post';
if (inReplyToId) {
type = 'reply';
} else if (privacy === 'direct') {
type = 'message';
(inReplyToId, privacy): ComposeType => {
if (inReplyToId && privacy === 'direct') {
return 'replyPrivate';
}
return type;
if (privacy === 'direct') {
return 'message';
}
if (inReplyToId) {
return 'reply';
}
return 'post';
},
);

View File

@@ -112,6 +112,8 @@ const ComposeVisibilityMenu: React.FC = () => {
);
const quotePolicy = currentQuotePolicy ?? defaultQuotePolicy;
const isReply = useAppSelector((state) => !!state.compose.get('in_reply_to'));
const dispatch = useAppDispatch();
const handlePrivacyChange = useCallback(
({ value }: { value: string }) => {
@@ -131,6 +133,7 @@ const ComposeVisibilityMenu: React.FC = () => {
},
[defaultPrivacy, dispatch, privacy],
);
const handleQuotePolicyChange = useCallback(
({ value, checked }: { value: string; checked?: boolean }) => {
let newQuotePolicy: ApiQuotePolicy = 'nobody';
@@ -264,11 +267,18 @@ const ComposeVisibilityMenu: React.FC = () => {
<MenuItemDivider />
<MenuItem icon={ChatCircleIcon} onClick={handleSwitchToMessage}>
<FormattedMessage
id='compose.post.to_message'
defaultMessage='Compose a message instead'
description='Message refers to a direct message. For languages where this is confusing, "chat" or "direct message" can be used.'
/>
{isReply ? (
<FormattedMessage
id='compose.post.to_private_reply'
defaultMessage='Reply privately instead'
/>
) : (
<FormattedMessage
id='compose.post.to_message'
defaultMessage='Compose a message instead'
description='Message refers to a direct message. For languages where this is confusing, "chat" or "direct message" can be used.'
/>
)}
</MenuItem>
</MenuList>
);
@@ -283,6 +293,8 @@ const ComposeDirectMenu: React.FC = () => {
);
}, [dispatch]);
const isReply = useAppSelector((state) => !!state.compose.get('in_reply_to'));
return (
<MenuList placement='bottom-start' offset={4} maxWidth={280}>
<MenuItemGroup
@@ -304,10 +316,17 @@ const ComposeDirectMenu: React.FC = () => {
<MenuItemDivider />
<MenuItem icon={NewspaperIcon} onClick={handleSwitchToPost}>
<FormattedMessage
id='compose.visibility.to_post'
defaultMessage='Compose a post instead'
/>
{isReply ? (
<FormattedMessage
id='compose.visibility.to_reply'
defaultMessage='Reply publicly instead'
/>
) : (
<FormattedMessage
id='compose.visibility.to_post'
defaultMessage='Compose a post instead'
/>
)}
</MenuItem>
</MenuList>
);

View File

@@ -537,6 +537,7 @@
"compose.post.title.new": "New post",
"compose.post.to": "To:",
"compose.post.to_message": "Compose a message instead",
"compose.post.to_private_reply": "Reply privately instead",
"compose.publish": "Publish",
"compose.published.body": "Post published.",
"compose.published.open": "Open",
@@ -554,7 +555,10 @@
"compose.rearrange_modal.handle": "Drag attachment at position {index, number}",
"compose.rearrange_modal.save": "Save",
"compose.rearrange_modal.title": "Rearrange media",
"compose.reply.title.edit": "Edit reply",
"compose.reply.title.new": "New reply",
"compose.reply_private.title.edit": "Edit private reply",
"compose.reply_private.title.new": "New private reply",
"compose.saved.body": "Post saved.",
"compose.sensitive": "Sensitive",
"compose.sensitive.text": "Sensitive content description",
@@ -575,6 +579,7 @@
"compose.visibility.quote_policy.followers": "Followers",
"compose.visibility.title": "Visibility",
"compose.visibility.to_post": "Compose a post instead",
"compose.visibility.to_reply": "Reply publicly instead",
"compose_form.direct_message_warning_learn_more": "Learn more",
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any sensitive information over Mastodon.",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is not public. Only public posts can be searched by hashtag.",
@@ -592,7 +597,6 @@
"compose_form.poll.type": "Style",
"compose_form.publish": "Post",
"compose_form.reply": "Reply",
"compose_form.reply.title.edit": "Edit reply",
"compose_form.save_changes": "Update",
"compose_form.spoiler.marked": "Remove content warning",
"compose_form.spoiler.unmarked": "Add content warning",

View File

@@ -52,7 +52,7 @@ export function focusComposerTextarea(defer = false) {
type DisplayState = 'hidden' | 'showing' | 'minimized';
export type ComposeType = 'post' | 'message' | 'reply';
export type ComposeType = 'post' | 'message' | 'reply' | 'replyPrivate';
interface ComposerState {
displayState: DisplayState;