diff --git a/app/soapbox/actions/importer/index.js b/app/soapbox/actions/importer/index.js
index 407bb7c30..62e9e7860 100644
--- a/app/soapbox/actions/importer/index.js
+++ b/app/soapbox/actions/importer/index.js
@@ -75,6 +75,10 @@ export function importFetchedStatus(status, idempotencyKey) {
dispatch(importFetchedStatus(status.reblog));
}
+ if (status.pleroma && status.pleroma.quote && status.pleroma.quote.id) {
+ dispatch(importFetchedStatus(status.pleroma.quote));
+ }
+
if (status.poll && status.poll.id) {
dispatch(importFetchedPoll(status.poll));
}
diff --git a/app/soapbox/components/status.js b/app/soapbox/components/status.js
index 2f5e12705..5637061e2 100644
--- a/app/soapbox/components/status.js
+++ b/app/soapbox/components/status.js
@@ -10,6 +10,7 @@ import { Link, NavLink } from 'react-router-dom';
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
import Icon from 'soapbox/components/icon';
import PlaceholderCard from 'soapbox/features/placeholder/components/placeholder_card';
+import QuotedStatus from 'soapbox/features/status/components/quoted_status';
import { getDomain } from 'soapbox/utils/accounts';
import Card from '../features/status/components/card';
@@ -472,6 +473,12 @@ class Status extends ImmutablePureComponent {
);
}
+ let quote;
+
+ if (status.getIn(['pleroma', 'quote'])) {
+ quote = ;
+ }
+
if (otherAccounts && otherAccounts.size > 1) {
statusAvatar = ;
} else if (account === undefined || account === null) {
@@ -551,6 +558,7 @@ class Status extends ImmutablePureComponent {
{media}
{poll}
+ {quote}
;
}
+ let quote;
+
+ if (status.getIn(['pleroma', 'quote'])) {
+ quote = ;
+ }
+
if (status.get('visibility') === 'direct') {
statusTypeIcon = ;
} else if (status.get('visibility') === 'private') {
@@ -201,6 +208,7 @@ class DetailedStatus extends ImmutablePureComponent {
/>
{media}
+ {quote}
diff --git a/app/soapbox/features/status/components/quoted_status.js b/app/soapbox/features/status/components/quoted_status.js
new file mode 100644
index 000000000..c62e287f8
--- /dev/null
+++ b/app/soapbox/features/status/components/quoted_status.js
@@ -0,0 +1,132 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { FormattedMessage, injectIntl } from 'react-intl';
+import { connect } from 'react-redux';
+import { NavLink } from 'react-router-dom';
+
+import AttachmentThumbs from 'soapbox/components/attachment_thumbs';
+import Avatar from 'soapbox/components/avatar';
+import DisplayName from 'soapbox/components/display_name';
+import RelativeTimestamp from 'soapbox/components/relative_timestamp';
+import { isRtl } from 'soapbox/rtl';
+import { makeGetStatus } from 'soapbox/selectors';
+
+const makeMapStateToProps = () => {
+ const getStatus = makeGetStatus();
+
+ const mapStateToProps = (state, props) => ({
+ status: getStatus(state, { id: props.statusId }),
+ });
+
+ return mapStateToProps;
+};
+
+export default @connect(makeMapStateToProps)
+@injectIntl
+class QuotedStatus extends ImmutablePureComponent {
+
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
+ static propTypes = {
+ status: ImmutablePropTypes.map,
+ };
+
+ handleExpandClick = (e) => {
+ if (e.button === 0) {
+ if (!this.context.router) {
+ return;
+ }
+
+ this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/posts/${this.props.status.get('id')}`);
+ }
+ }
+
+ renderReplyMentions = () => {
+ const { status } = this.props;
+
+ if (!status.get('in_reply_to_id')) {
+ return null;
+ }
+
+ const to = status.get('mentions', []);
+
+ if (to.size === 0) {
+ if (status.get('in_reply_to_account_id') === status.getIn(['account', 'id'])) {
+ return (
+
+
+
+ );
+ } else {
+ return (
+
+
+
+ );
+ }
+ }
+
+ return (
+
+ `@${account.get('username')} `),
+ more: to.size > 2 && ,
+ }}
+ />
+
+ );
+ }
+
+ render() {
+ const { status } = this.props;
+
+ if (!status) {
+ return null;
+ }
+
+ const content = { __html: status.get('contentHtml') };
+ const style = {
+ direction: isRtl(status.get('search_index')) ? 'rtl' : 'ltr',
+ };
+
+ return (
+
+
+
+ {this.renderReplyMentions()}
+
+
+
+ {status.get('media_attachments').size > 0 && (
+
+ )}
+
+ );
+ }
+
+}
diff --git a/app/styles/components/status.scss b/app/styles/components/status.scss
index c8fd1e55b..b3c982554 100644
--- a/app/styles/components/status.scss
+++ b/app/styles/components/status.scss
@@ -746,3 +746,65 @@ a.status-card.compact:hover {
.pending-status {
opacity: 0.5;
}
+
+.quoted-status {
+ margin-top: 14px;
+ border: 1px solid var(--brand-color--med);
+ border-radius: 10px;
+ padding: 12px;
+ overflow-y: auto;
+ flex: 0 2 auto;
+ transition: background 0.2s;
+ cursor: pointer;
+
+ &:hover,
+ &:focus,
+ &:active {
+ background: var(--brand-color--faint);
+ }
+
+ &__relative-time {
+ padding-top: 4px;
+ }
+
+ &__display-name {
+ color: var(--primary-text-color);
+ display: block;
+ max-width: 100%;
+ line-height: 24px;
+ overflow: hidden;
+ padding-right: 25px;
+ text-decoration: none;
+
+ .display-name__account {
+ color: var(--primary-text-color--faint);
+ }
+ }
+
+ &__display-avatar {
+ float: left;
+ margin-right: 5px;
+ }
+
+ .reply-mentions {
+ font-size: 14px;
+ }
+
+ &__content {
+ margin-top: 5px;
+ font-size: 14px;
+
+ a {
+ color: var(--highlight-text-color);
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ .attachment-thumbs .media-gallery {
+ margin-top: 5px !important;
+ }
+}
diff --git a/app/styles/ui.scss b/app/styles/ui.scss
index a594752e5..de2ce5e3b 100644
--- a/app/styles/ui.scss
+++ b/app/styles/ui.scss
@@ -221,7 +221,8 @@
}
.status__relative-time,
-.notification__relative_time {
+.notification__relative_time,
+.quoted-status__relative-time {
color: var(--primary-text-color--faint);
float: right;
font-size: 14px;