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;
+ }
+}