Composer redesign: Replies (#40186)

Co-authored-by: diondiondion <mail@diondiondion.com>
This commit is contained in:
Echo
2026-08-19 10:10:57 +00:00
committed by GitHub
parent 1855e57bfa
commit f6e62f82e6
6 changed files with 147 additions and 16 deletions

View File

@@ -17,7 +17,7 @@ import { EmojiHTML } from '../emoji/html';
import { Icon } from '../icon';
import { Poll } from '../poll';
import { useElementHandledLink } from './handled_link';
import { useHandlersForStatus } from './hooks';
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
@@ -72,20 +72,7 @@ export const StatusContent: React.FC<{
[onClick],
);
const hrefToMention = useCallback(
(href: string) => status?.mentions.find((item) => item.url === href),
[status?.mentions],
);
const hrefToCollectionId = useCallback(
(href: string) =>
status?.tagged_collections.find((item) => item.url === href)?.id,
[status?.tagged_collections],
);
const htmlHandlers = useElementHandledLink({
hashtagAccountId: status?.account,
hrefToCollectionId,
hrefToMention,
});
const htmlHandlers = useHandlersForStatus(status);
if (!status) {
return null;

View File

@@ -11,12 +11,16 @@ import { openModal } from '@/mastodon/actions/modal';
import { toggleStatusSpoilers } from '@/mastodon/actions/statuses';
import { useExpandedStatus } from '@/mastodon/hooks/useStatus';
import { useToggle } from '@/mastodon/hooks/useToggle';
import type { ExpandedStatusShape } from '@/mastodon/models/status';
import type {
ExpandedStatusShape,
StatusShape,
} from '@/mastodon/models/status';
import { selectStatusFilters } from '@/mastodon/selectors/filters';
import { useAppSelector, useAppDispatch } from '@/mastodon/store';
import { FOCUS_TARGET } from '../navigation_focus_target';
import { useElementHandledLink } from './handled_link';
import type { StatusContextType } from './types';
const messages = defineMessages({
@@ -260,3 +264,28 @@ export function useTextForScreenReader({
return values.join(', ');
}, [intl, isQuote, reblogAcct, status]);
}
export function useHandlersForStatus(
status?: Pick<
StatusShape | ExpandedStatusShape,
'account' | 'mentions' | 'tagged_collections'
> | null,
) {
const hrefToMention = useCallback(
(href: string) => status?.mentions.find((item) => item.url === href),
[status?.mentions],
);
const hrefToCollectionId = useCallback(
(href: string) =>
status?.tagged_collections.find((item) => item.url === href)?.id,
[status?.tagged_collections],
);
return useElementHandledLink({
hashtagAccountId:
typeof status?.account === 'string'
? status.account
: status?.account.acct,
hrefToCollectionId,
hrefToMention,
});
}

View File

@@ -31,6 +31,7 @@ import type { OnEmojiPick } from './emoji';
import { ComposeFooter } from './footer';
import { ComposeFormHeader } from './header';
import { LanguageButton } from './language';
import { ComposeReply } from './reply';
import {
selectComposeCanSubmit,
selectComposeSensitive,
@@ -87,6 +88,8 @@ export const RedesignComposeForm: React.FC<RedesignComposeFormProps> = ({
>
<ComposeFormHeader id={titleId} noMinimize={noMinimize} />
<ComposeReply />
<div className={classes.toolbar}>
<div className={classes.flexGrowWrap}>
{type !== 'message' && <ComposeVisibility />}

View File

@@ -0,0 +1,55 @@
import { Link } from 'react-router-dom';
import { Avatar } from '@/mastodon/components/avatar';
import { LinkedDisplayName } from '@/mastodon/components/display_name';
import { EmojiHTML } from '@/mastodon/components/emoji/html';
import { RelativeTimestamp } from '@/mastodon/components/relative_timestamp';
import { useHandlersForStatus } from '@/mastodon/components/status/hooks';
import { selectAccountStatus } from '@/mastodon/selectors/statuses';
import { useAppSelector } from '@/mastodon/store';
import classes from './styles.module.scss';
export const ComposeReply: React.FC = () => {
const replyId = useAppSelector(
(state) => state.compose.get('in_reply_to') as null | string,
);
const status = useAppSelector((state) => selectAccountStatus(state, replyId));
const htmlHandlers = useHandlersForStatus(status);
if (!status) {
return;
}
return (
<figure className={classes.reply}>
<figcaption className={classes.replyAccount}>
<Avatar
account={status.account}
className={classes.replyAvatar}
withLink
/>
<LinkedDisplayName
displayProps={{ account: status.account, variant: 'simple' }}
/>
<span className={classes.replyTime}>
&middot;&nbsp;
<Link to={`/@${status.account.acct}/${status.id}`}>
<RelativeTimestamp timestamp={status.created_at} />
</Link>
</span>
</figcaption>
<EmojiHTML
as='blockquote'
cite={status.uri}
htmlString={status.translation?.contentHtml ?? status.contentHtml}
extraEmojis={status.emojis}
className={classes.replyText}
lang={status.translation?.language ?? status.language}
{...htmlHandlers}
/>
</figure>
);
};

View File

@@ -187,6 +187,51 @@
}
}
// Reply
.reply {
border-inline-start: 0.5px solid var(--color-border-primary);
padding: 0 var(--space-md);
}
.replyAccount {
display: flex;
margin-bottom: var(--space-2xs);
a {
color: inherit;
text-decoration: none;
}
}
.replyAvatar {
margin-inline-end: var(--space-xs);
border-radius: var(--radius-round);
overflow: hidden;
}
.replyTime {
color: var(--color-text-tertiary);
}
.replyText {
@include mixins.line-clamp(2);
@include mixins.type-body-compact;
// Round two lines to the nearest 5px.
max-height: round(2lh, 5px);
a {
color: var(--color-text-brand);
text-decoration: none;
&:hover,
&:focus {
text-decoration: underline;
}
}
}
// Attachments
.mediaSingle {

View File

@@ -156,3 +156,15 @@
color: var(--color-text-primary);
}
}
/**
* Utilities
*/
@mixin line-clamp($lines) {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: $lines;
line-clamp: $lines;
}