Change design of quote posts in web UI (#35584)

This commit is contained in:
Eugen Rochko 2025-07-30 17:53:42 +02:00 committed by GitHub
commit 92bf55afd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 210 additions and 109 deletions

View file

@ -0,0 +1,63 @@
import { useState, useRef, useCallback, useId } from 'react';
import { FormattedMessage } from 'react-intl';
import Overlay from 'react-overlays/Overlay';
export const LearnMoreLink: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const accessibilityId = useId();
const [open, setOpen] = useState(false);
const triggerRef = useRef(null);
const handleClick = useCallback(() => {
setOpen(!open);
}, [open, setOpen]);
return (
<>
<button
className='link-button'
ref={triggerRef}
onClick={handleClick}
aria-expanded={open}
aria-controls={accessibilityId}
>
<FormattedMessage
id='learn_more_link.learn_more'
defaultMessage='Learn more'
/>
</button>
<Overlay
show={open}
rootClose
onHide={handleClick}
offset={[5, 5]}
placement='bottom-end'
target={triggerRef}
>
{({ props }) => (
<div
{...props}
role='region'
id={accessibilityId}
className='account__domain-pill__popout learn-more__popout dropdown-animation'
>
<div className='learn-more__popout__content'>{children}</div>
<div>
<button className='link-button' onClick={handleClick}>
<FormattedMessage
id='learn_more_link.got_it'
defaultMessage='Got it'
/>
</button>
</div>
</div>
)}
</Overlay>
</>
);
};

View file

@ -138,6 +138,16 @@ class StatusContent extends PureComponent {
onCollapsedToggle(collapsed);
}
// Remove quote fallback link from the DOM so it doesn't
// mess with paragraph margins
if (!!status.get('quote')) {
const inlineQuote = node.querySelector('.quote-inline');
if (inlineQuote) {
inlineQuote.remove();
}
}
}
handleMouseEnter = ({ currentTarget }) => {

View file

@ -3,19 +3,15 @@ import { useEffect, useMemo } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import type { Map as ImmutableMap } from 'immutable';
import ArticleIcon from '@/material-icons/400-24px/article.svg?react';
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
import { Icon } from 'mastodon/components/icon';
import { LearnMoreLink } from 'mastodon/components/learn_more_link';
import StatusContainer from 'mastodon/containers/status_container';
import type { Status } from 'mastodon/models/status';
import type { RootState } from 'mastodon/store';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import QuoteIcon from '../../images/quote.svg?react';
import { fetchStatus } from '../actions/statuses';
import { makeGetStatus } from '../selectors';
@ -31,7 +27,6 @@ const QuoteWrapper: React.FC<{
'status__quote--error': isError,
})}
>
<Icon id='quote' icon={QuoteIcon} className='status__quote-icon' />
{children}
</div>
);
@ -45,27 +40,20 @@ const NestedQuoteLink: React.FC<{
accountId ? state.accounts.get(accountId) : undefined,
);
const quoteAuthorName = account?.display_name_html;
const quoteAuthorName = account?.acct;
if (!quoteAuthorName) {
return null;
}
const quoteAuthorElement = (
<span dangerouslySetInnerHTML={{ __html: quoteAuthorName }} />
);
const quoteUrl = `/@${account.get('acct')}/${status.get('id') as string}`;
return (
<Link to={quoteUrl} className='status__quote-author-button'>
<div className='status__quote-author-button'>
<FormattedMessage
id='status.quote_post_author'
defaultMessage='Post by {name}'
values={{ name: quoteAuthorElement }}
defaultMessage='Quoted a post by @{name}'
values={{ name: quoteAuthorName }}
/>
<Icon id='chevron_right' icon={ChevronRightIcon} />
<Icon id='article' icon={ArticleIcon} />
</Link>
</div>
);
};
@ -112,39 +100,42 @@ export const QuotedStatus: React.FC<{
defaultMessage='Hidden due to one of your filters'
/>
);
} else if (quoteState === 'deleted') {
quoteError = (
<FormattedMessage
id='status.quote_error.removed'
defaultMessage='This post was removed by its author.'
/>
);
} else if (quoteState === 'unauthorized') {
quoteError = (
<FormattedMessage
id='status.quote_error.unauthorized'
defaultMessage='This post cannot be displayed as you are not authorized to view it.'
/>
);
} else if (quoteState === 'pending') {
quoteError = (
<FormattedMessage
id='status.quote_error.pending_approval'
defaultMessage='This post is pending approval from the original author.'
/>
<>
<FormattedMessage
id='status.quote_error.pending_approval'
defaultMessage='Post pending'
/>
<LearnMoreLink>
<h6>
<FormattedMessage
id='status.quote_error.pending_approval_popout.title'
defaultMessage='Pending quote? Remain calm'
/>
</h6>
<p>
<FormattedMessage
id='status.quote_error.pending_approval_popout.body'
defaultMessage='Quotes shared across the Fediverse may take time to display, as different servers have different protocols.'
/>
</p>
</LearnMoreLink>
</>
);
} else if (quoteState === 'rejected' || quoteState === 'revoked') {
} else if (
!status ||
!quotedStatusId ||
quoteState === 'deleted' ||
quoteState === 'rejected' ||
quoteState === 'revoked' ||
quoteState === 'unauthorized'
) {
quoteError = (
<FormattedMessage
id='status.quote_error.rejected'
defaultMessage='This post cannot be displayed as the original author does not allow it to be quoted.'
/>
);
} else if (!status || !quotedStatusId) {
quoteError = (
<FormattedMessage
id='status.quote_error.not_found'
defaultMessage='This post cannot be displayed.'
id='status.quote_error.not_available'
defaultMessage='Post unavailable'
/>
);
}
@ -168,7 +159,7 @@ export const QuotedStatus: React.FC<{
isQuotedPost
id={quotedStatusId}
contextType={contextType}
avatarSize={40}
avatarSize={32}
>
{canRenderChildQuote && (
<QuotedStatus

View file

@ -498,6 +498,8 @@
"keyboard_shortcuts.translate": "to translate a post",
"keyboard_shortcuts.unfocus": "Unfocus compose textarea/search",
"keyboard_shortcuts.up": "Move up in the list",
"learn_more_link.got_it": "Got it",
"learn_more_link.learn_more": "Learn more",
"lightbox.close": "Close",
"lightbox.next": "Next",
"lightbox.previous": "Previous",
@ -873,12 +875,11 @@
"status.open": "Expand this post",
"status.pin": "Pin on profile",
"status.quote_error.filtered": "Hidden due to one of your filters",
"status.quote_error.not_found": "This post cannot be displayed.",
"status.quote_error.pending_approval": "This post is pending approval from the original author.",
"status.quote_error.rejected": "This post cannot be displayed as the original author does not allow it to be quoted.",
"status.quote_error.removed": "This post was removed by its author.",
"status.quote_error.unauthorized": "This post cannot be displayed as you are not authorized to view it.",
"status.quote_post_author": "Post by {name}",
"status.quote_error.not_available": "Post unavailable",
"status.quote_error.pending_approval": "Post pending",
"status.quote_error.pending_approval_popout.body": "Quotes shared across the Fediverse may take time to display, as different servers have different protocols.",
"status.quote_error.pending_approval_popout.title": "Pending quote? Remain calm",
"status.quote_post_author": "Quoted a post by @{name}",
"status.read_more": "Read more",
"status.reblog": "Boost",
"status.reblog_private": "Boost with original visibility",