diff --git a/app/javascript/mastodon/actions/importer/index.js b/app/javascript/mastodon/actions/importer/index.js index a527043940..7e45b749ed 100644 --- a/app/javascript/mastodon/actions/importer/index.js +++ b/app/javascript/mastodon/actions/importer/index.js @@ -70,6 +70,10 @@ export function importFetchedStatuses(statuses) { processStatus(status.reblog); } + if (status.quote?.quoted_status) { + processStatus(status.quote.quoted_status); + } + if (status.poll?.id) { pushUnique(polls, createPollFromServerJSON(status.poll, getState().polls[status.poll.id])); } diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index c2918ef8d5..c892f0eefa 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -23,12 +23,17 @@ export function normalizeFilterResult(result) { export function normalizeStatus(status, normalOldStatus) { const normalStatus = { ...status }; + normalStatus.account = status.account.id; if (status.reblog && status.reblog.id) { normalStatus.reblog = status.reblog.id; } + if (status.quote?.quoted_status) { + normalStatus.quote = { ...status.quote, quoted_status: status.quote.quoted_status.id }; + } + if (status.poll && status.poll.id) { normalStatus.poll = status.poll.id; } diff --git a/app/javascript/mastodon/components/quote.tsx b/app/javascript/mastodon/components/quote.tsx new file mode 100644 index 0000000000..661b8a6956 --- /dev/null +++ b/app/javascript/mastodon/components/quote.tsx @@ -0,0 +1,73 @@ +import { FormattedMessage } from 'react-intl'; + +import { Map as ImmutableMap } from 'immutable'; + +import { useAppSelector } from 'mastodon/store'; +import { EmbeddedStatus } from 'mastodon/features/notifications_v2/components/embedded_status'; + +type QuoteMap = ImmutableMap<'state' | 'quoted_status', string | null>; + +export const Quote: React.FC<{ quote: QuoteMap }> = ({ quote }) => { + const quotedStatusId = quote.get('quoted_status'); + const state = quote.get('state'); + const status = useAppSelector((state) => + quotedStatusId ? state.statuses.get(quotedStatusId) : undefined, + ); + const accountId = status?.get('account') as string | undefined; + const account = useAppSelector((state) => + accountId ? state.accounts.get(accountId) : undefined, + ); + + if (!status || !quotedStatusId) { + return ( +
+ +
+ ); + } else if (state === 'deleted') { + return ( +
+ +
+ ); + } else if (state === 'unauthorized') { + return ( +
+ +
+ ); + } else if (state === 'pending') { + return ( +
+ +
+ ); + } else if (state === 'rejected' || state === 'revoked') { + return ( +
+ +
+ ); + } + + return ( +
+ +
+ ); +}; diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 1fe9b096e1..051e2b79e5 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -37,6 +37,7 @@ import StatusActionBar from './status_action_bar'; import StatusContent from './status_content'; import { StatusThreadLabel } from './status_thread_label'; import { VisibilityIcon } from './visibility_icon'; +import { Quote } from './quote'; const domParser = new DOMParser(); @@ -578,6 +579,8 @@ class Status extends ImmutablePureComponent { {expanded && ( <> + {status.get('quote') && } + + {status.get('quote') && } + [data-popper-placement] { } } + $thread-margin: 46px + 8px; + &--in-thread { - $thread-margin: 46px + 10px; - border-bottom: 0; + } - .status__content, - .status__action-bar, - .media-gallery, - .video-player, - .audio-player, - .attachment-list, - .picture-in-picture-placeholder, - .more-from-author, - .status-card, - .hashtag-bar, - .content-warning, - .filter-warning { - margin-inline-start: $thread-margin; - width: calc(100% - $thread-margin); - } + .status__content, + .status__action-bar, + .media-gallery, + .video-player, + .audio-player, + .attachment-list, + .picture-in-picture-placeholder, + .more-from-author, + .status-card, + .hashtag-bar, + .content-warning, + .filter-warning { + margin-inline-start: $thread-margin; + width: calc(100% - $thread-margin); + } - .more-from-author { - width: calc(100% - $thread-margin + 2px); - } + .more-from-author { + width: calc(100% - $thread-margin + 2px); + } - .status__content__read-more-button { - margin-inline-start: $thread-margin; - } + .status__content__read-more-button { + margin-inline-start: $thread-margin; } &__action-bar__button-wrapper { @@ -1571,8 +1571,6 @@ body > [data-popper-placement] { .status__relative-time { display: block; - font-size: 15px; - line-height: 22px; height: 40px; order: 2; flex: 0 0 auto; @@ -1603,10 +1601,8 @@ body > [data-popper-placement] { .status__info .status__display-name { max-width: 100%; display: flex; - font-size: 15px; - line-height: 22px; align-items: center; - gap: 10px; + gap: 8px; overflow: hidden; .display-name { @@ -1630,7 +1626,7 @@ body > [data-popper-placement] { display: flex; align-items: center; justify-content: space-between; - gap: 10px; + gap: 8px; cursor: pointer; } @@ -1674,7 +1670,7 @@ body > [data-popper-placement] { padding-bottom: 0; display: flex; align-items: center; - gap: 8px; + gap: 12px; font-size: 15px; line-height: 22px; font-weight: 500; @@ -1685,6 +1681,7 @@ body > [data-popper-placement] { align-items: center; justify-content: center; flex: 0 0 auto; + margin-inline-start: 30px; .icon { width: 16px; @@ -10402,6 +10399,7 @@ noscript { .account__avatar { flex: 0 0 auto; + --avatar-border-radius: 4px; } } @@ -10835,3 +10833,43 @@ noscript { .lists-scrollable { min-height: 50vh; } + +.status__quote { + padding-inline-start: 46px + 8px; + position: relative; + margin-bottom: 16px; + + & > div { + background: var(--rich-text-container-color); + padding: 8px; + border-radius: 8px; + color: var(--rich-text-text-color); + } + + .notification-group__embedded-status__account bdi, + .notification-group__embedded-status__content, + .notification-group__embedded-status__content a { + color: var(--rich-text-text-color); + } + + .notification-group__embedded-status__account .display-name__account { + color: var(--rich-text-text-color); + opacity: 0.7; + } + + .notification-group__embedded-status__account { + margin-bottom: 4px; + } + + &::before { + display: block; + content: ''; + width: 24px; + height: 20px; + mask-image: url('../images/quote.svg'); + background-color: var(--rich-text-decorations-color); + position: absolute; + inset-inline-start: 20px; + top: 4px; + } +}