mirror of
https://github.com/mastodon/mastodon.git
synced 2025-09-08 02:41:06 +00:00
Change design of quote posts in web UI (#35584)
This commit is contained in:
parent
39250ab961
commit
92bf55afd0
63
app/javascript/mastodon/components/learn_more_link.tsx
Normal file
63
app/javascript/mastodon/components/learn_more_link.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -138,6 +138,16 @@ class StatusContent extends PureComponent {
|
||||||
|
|
||||||
onCollapsedToggle(collapsed);
|
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 }) => {
|
handleMouseEnter = ({ currentTarget }) => {
|
||||||
|
|
|
@ -3,19 +3,15 @@ import { useEffect, useMemo } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import type { Map as ImmutableMap } from 'immutable';
|
import type { Map as ImmutableMap } from 'immutable';
|
||||||
|
|
||||||
import ArticleIcon from '@/material-icons/400-24px/article.svg?react';
|
import { LearnMoreLink } from 'mastodon/components/learn_more_link';
|
||||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
|
||||||
import { Icon } from 'mastodon/components/icon';
|
|
||||||
import StatusContainer from 'mastodon/containers/status_container';
|
import StatusContainer from 'mastodon/containers/status_container';
|
||||||
import type { Status } from 'mastodon/models/status';
|
import type { Status } from 'mastodon/models/status';
|
||||||
import type { RootState } from 'mastodon/store';
|
import type { RootState } from 'mastodon/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
import QuoteIcon from '../../images/quote.svg?react';
|
|
||||||
import { fetchStatus } from '../actions/statuses';
|
import { fetchStatus } from '../actions/statuses';
|
||||||
import { makeGetStatus } from '../selectors';
|
import { makeGetStatus } from '../selectors';
|
||||||
|
|
||||||
|
@ -31,7 +27,6 @@ const QuoteWrapper: React.FC<{
|
||||||
'status__quote--error': isError,
|
'status__quote--error': isError,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Icon id='quote' icon={QuoteIcon} className='status__quote-icon' />
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -45,27 +40,20 @@ const NestedQuoteLink: React.FC<{
|
||||||
accountId ? state.accounts.get(accountId) : undefined,
|
accountId ? state.accounts.get(accountId) : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
const quoteAuthorName = account?.display_name_html;
|
const quoteAuthorName = account?.acct;
|
||||||
|
|
||||||
if (!quoteAuthorName) {
|
if (!quoteAuthorName) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const quoteAuthorElement = (
|
|
||||||
<span dangerouslySetInnerHTML={{ __html: quoteAuthorName }} />
|
|
||||||
);
|
|
||||||
const quoteUrl = `/@${account.get('acct')}/${status.get('id') as string}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={quoteUrl} className='status__quote-author-button'>
|
<div className='status__quote-author-button'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='status.quote_post_author'
|
id='status.quote_post_author'
|
||||||
defaultMessage='Post by {name}'
|
defaultMessage='Quoted a post by @{name}'
|
||||||
values={{ name: quoteAuthorElement }}
|
values={{ name: quoteAuthorName }}
|
||||||
/>
|
/>
|
||||||
<Icon id='chevron_right' icon={ChevronRightIcon} />
|
</div>
|
||||||
<Icon id='article' icon={ArticleIcon} />
|
|
||||||
</Link>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -112,39 +100,42 @@ export const QuotedStatus: React.FC<{
|
||||||
defaultMessage='Hidden due to one of your filters'
|
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') {
|
} else if (quoteState === 'pending') {
|
||||||
quoteError = (
|
quoteError = (
|
||||||
<FormattedMessage
|
<>
|
||||||
id='status.quote_error.pending_approval'
|
<FormattedMessage
|
||||||
defaultMessage='This post is pending approval from the original author.'
|
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 = (
|
quoteError = (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='status.quote_error.rejected'
|
id='status.quote_error.not_available'
|
||||||
defaultMessage='This post cannot be displayed as the original author does not allow it to be quoted.'
|
defaultMessage='Post unavailable'
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (!status || !quotedStatusId) {
|
|
||||||
quoteError = (
|
|
||||||
<FormattedMessage
|
|
||||||
id='status.quote_error.not_found'
|
|
||||||
defaultMessage='This post cannot be displayed.'
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -168,7 +159,7 @@ export const QuotedStatus: React.FC<{
|
||||||
isQuotedPost
|
isQuotedPost
|
||||||
id={quotedStatusId}
|
id={quotedStatusId}
|
||||||
contextType={contextType}
|
contextType={contextType}
|
||||||
avatarSize={40}
|
avatarSize={32}
|
||||||
>
|
>
|
||||||
{canRenderChildQuote && (
|
{canRenderChildQuote && (
|
||||||
<QuotedStatus
|
<QuotedStatus
|
||||||
|
|
|
@ -498,6 +498,8 @@
|
||||||
"keyboard_shortcuts.translate": "to translate a post",
|
"keyboard_shortcuts.translate": "to translate a post",
|
||||||
"keyboard_shortcuts.unfocus": "Unfocus compose textarea/search",
|
"keyboard_shortcuts.unfocus": "Unfocus compose textarea/search",
|
||||||
"keyboard_shortcuts.up": "Move up in the list",
|
"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.close": "Close",
|
||||||
"lightbox.next": "Next",
|
"lightbox.next": "Next",
|
||||||
"lightbox.previous": "Previous",
|
"lightbox.previous": "Previous",
|
||||||
|
@ -873,12 +875,11 @@
|
||||||
"status.open": "Expand this post",
|
"status.open": "Expand this post",
|
||||||
"status.pin": "Pin on profile",
|
"status.pin": "Pin on profile",
|
||||||
"status.quote_error.filtered": "Hidden due to one of your filters",
|
"status.quote_error.filtered": "Hidden due to one of your filters",
|
||||||
"status.quote_error.not_found": "This post cannot be displayed.",
|
"status.quote_error.not_available": "Post unavailable",
|
||||||
"status.quote_error.pending_approval": "This post is pending approval from the original author.",
|
"status.quote_error.pending_approval": "Post pending",
|
||||||
"status.quote_error.rejected": "This post cannot be displayed as the original author does not allow it to be quoted.",
|
"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.removed": "This post was removed by its author.",
|
"status.quote_error.pending_approval_popout.title": "Pending quote? Remain calm",
|
||||||
"status.quote_error.unauthorized": "This post cannot be displayed as you are not authorized to view it.",
|
"status.quote_post_author": "Quoted a post by @{name}",
|
||||||
"status.quote_post_author": "Post by {name}",
|
|
||||||
"status.read_more": "Read more",
|
"status.read_more": "Read more",
|
||||||
"status.reblog": "Boost",
|
"status.reblog": "Boost",
|
||||||
"status.reblog_private": "Boost with original visibility",
|
"status.reblog_private": "Boost with original visibility",
|
||||||
|
|
|
@ -12,6 +12,8 @@ body {
|
||||||
--background-color: #fff;
|
--background-color: #fff;
|
||||||
--background-color-tint: rgba(255, 255, 255, 80%);
|
--background-color-tint: rgba(255, 255, 255, 80%);
|
||||||
--background-filter: blur(10px);
|
--background-filter: blur(10px);
|
||||||
|
--surface-variant-background-color: #f1ebfb;
|
||||||
|
--surface-border-color: #cac4d0;
|
||||||
--on-surface-color: #{color.adjust($ui-base-color, $alpha: -0.65)};
|
--on-surface-color: #{color.adjust($ui-base-color, $alpha: -0.65)};
|
||||||
--rich-text-container-color: rgba(255, 216, 231, 100%);
|
--rich-text-container-color: rgba(255, 216, 231, 100%);
|
||||||
--rich-text-text-color: rgba(114, 47, 83, 100%);
|
--rich-text-text-color: rgba(114, 47, 83, 100%);
|
||||||
|
|
|
@ -1433,10 +1433,6 @@ body > [data-popper-placement] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status--has-quote .quote-inline {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
min-height: 54px;
|
min-height: 54px;
|
||||||
|
@ -1470,10 +1466,6 @@ body > [data-popper-placement] {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--is-quote {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--in-thread {
|
&--in-thread {
|
||||||
--thread-margin: calc(46px + 8px);
|
--thread-margin: calc(46px + 8px);
|
||||||
|
|
||||||
|
@ -1860,79 +1852,99 @@ body > [data-popper-placement] {
|
||||||
// --status-gutter-width is currently only set inside of
|
// --status-gutter-width is currently only set inside of
|
||||||
// .notification-ungrouped, so everywhere else this will fall back
|
// .notification-ungrouped, so everywhere else this will fall back
|
||||||
// to the pixel values
|
// to the pixel values
|
||||||
--quote-margin: var(--status-gutter-width, 36px);
|
--quote-margin: var(--status-gutter-width);
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-block-start: 16px;
|
margin-block-start: 16px;
|
||||||
margin-inline-start: calc(var(--quote-margin) + var(--thread-margin, 0px));
|
margin-inline-start: calc(var(--quote-margin) + var(--thread-margin, 0px));
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
color: var(--nested-card-text);
|
color: var(--nested-card-text);
|
||||||
background: var(--nested-card-background);
|
border: 1px solid var(--surface-border-color);
|
||||||
border: var(--nested-card-border);
|
|
||||||
|
|
||||||
@container (width > 460px) {
|
|
||||||
--quote-margin: var(--status-gutter-width, 56px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__quote--error {
|
.status__quote--error {
|
||||||
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
font-size: 15px;
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: 0.25px;
|
||||||
|
min-height: 56px;
|
||||||
|
|
||||||
|
.link-button {
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
letter-spacing: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__quote-author-button {
|
.status__quote-author-button {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
width: auto;
|
margin-top: 8px;
|
||||||
margin-block-start: 10px;
|
padding: 8px 12px;
|
||||||
padding: 5px 12px;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 700;
|
font-weight: 400;
|
||||||
line-height: normal;
|
line-height: 20px;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0.25px;
|
||||||
text-decoration: none;
|
color: $darker-text-color;
|
||||||
color: $highlight-text-color;
|
background: var(--surface-variant-background-color);
|
||||||
background: var(--nested-card-background);
|
border-radius: 8px;
|
||||||
border: var(--nested-card-border);
|
cursor: default;
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
&:active,
|
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
border-color: lighten($highlight-text-color, 4%);
|
|
||||||
color: lighten($highlight-text-color, 4%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus-visible {
|
|
||||||
outline: $ui-button-icon-focus-outline;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__quote-icon {
|
.status--is-quote {
|
||||||
position: absolute;
|
border: none;
|
||||||
inset-block-start: 18px;
|
padding: 12px;
|
||||||
inset-inline-start: -40px;
|
|
||||||
display: block;
|
|
||||||
width: 26px;
|
|
||||||
height: 26px;
|
|
||||||
padding: 5px;
|
|
||||||
color: #6a49ba;
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
.status__quote--error & {
|
.status__info {
|
||||||
inset-block-start: 50%;
|
padding-bottom: 8px;
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@container (width > 460px) {
|
.display-name,
|
||||||
inset-inline-start: -50px;
|
.status__relative-time {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: 0.1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-name__account {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status__content {
|
||||||
|
display: -webkit-box;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 0.25px;
|
||||||
|
line-height: 20px;
|
||||||
|
-webkit-line-clamp: 4;
|
||||||
|
line-clamp: 4;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-gallery,
|
||||||
|
.video-player,
|
||||||
|
.audio-player,
|
||||||
|
.attachment-list,
|
||||||
|
.poll {
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2152,6 +2164,27 @@ body > [data-popper-placement] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.learn-more__popout {
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: inherit;
|
||||||
|
letter-spacing: 0.1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-button {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.account__wrapper {
|
.account__wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
--surface-background-color: #{darken($ui-base-color, 4%)};
|
--surface-background-color: #{darken($ui-base-color, 4%)};
|
||||||
--surface-variant-background-color: #{$ui-base-color};
|
--surface-variant-background-color: #{$ui-base-color};
|
||||||
--surface-variant-active-background-color: #{lighten($ui-base-color, 4%)};
|
--surface-variant-active-background-color: #{lighten($ui-base-color, 4%)};
|
||||||
|
--surface-border-color: #{lighten($ui-base-color, 8%)};
|
||||||
--on-surface-color: #{color.adjust($ui-base-color, $alpha: -0.5)};
|
--on-surface-color: #{color.adjust($ui-base-color, $alpha: -0.5)};
|
||||||
--avatar-border-radius: 8px;
|
--avatar-border-radius: 8px;
|
||||||
--media-outline-color: #{rgba(#fcf8ff, 0.15)};
|
--media-outline-color: #{rgba(#fcf8ff, 0.15)};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user