diff --git a/app/javascript/mastodon/features/compose/components/quoted_post.tsx b/app/javascript/mastodon/features/compose/components/quoted_post.tsx
new file mode 100644
index 00000000000..ee12e4ae75f
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/components/quoted_post.tsx
@@ -0,0 +1,27 @@
+import { useMemo } from 'react';
+import type { FC } from 'react';
+
+import { Map } from 'immutable';
+
+import { QuotedStatus } from '@/mastodon/components/status_quoted';
+import { useAppSelector } from '@/mastodon/store';
+
+export const ComposeQuotedStatus: FC = () => {
+ const quotedStatusId = useAppSelector(
+ (state) => state.compose.get('quoted_status_id') as string | null,
+ );
+ const quote = useMemo(
+ () =>
+ quotedStatusId
+ ? Map<'state' | 'quoted_status', string>([
+ ['state', 'accepted'],
+ ['quoted_status', quotedStatusId],
+ ])
+ : null,
+ [quotedStatusId],
+ );
+ if (!quote) {
+ return null;
+ }
+ return
;
+};
diff --git a/app/javascript/mastodon/features/compose/containers/poll_button_container.js b/app/javascript/mastodon/features/compose/containers/poll_button_container.js
index 9de388f64aa..e8fa9798c56 100644
--- a/app/javascript/mastodon/features/compose/containers/poll_button_container.js
+++ b/app/javascript/mastodon/features/compose/containers/poll_button_container.js
@@ -3,10 +3,16 @@ import { connect } from 'react-redux';
import { addPoll, removePoll } from '../../../actions/compose';
import PollButton from '../components/poll_button';
-const mapStateToProps = state => ({
- disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 0),
- active: state.getIn(['compose', 'poll']) !== null,
-});
+const mapStateToProps = state => {
+ const readyAttachmentsSize = state.compose.get('media_attachments').size ?? 0;
+ const hasAttachments = readyAttachmentsSize > 0 || !!state.compose.get('is_uploading');
+ const hasQuote = !!state.compose.get('quoted_status_id');
+
+ return ({
+ disabled: hasAttachments || hasQuote,
+ active: state.getIn(['compose', 'poll']) !== null,
+ });
+};
const mapDispatchToProps = dispatch => ({
diff --git a/app/javascript/mastodon/features/compose/containers/upload_button_container.js b/app/javascript/mastodon/features/compose/containers/upload_button_container.js
index 7cdc12663d7..a5ae874b066 100644
--- a/app/javascript/mastodon/features/compose/containers/upload_button_container.js
+++ b/app/javascript/mastodon/features/compose/containers/upload_button_container.js
@@ -11,9 +11,10 @@ const mapStateToProps = state => {
const attachmentsSize = readyAttachmentsSize + pendingAttachmentsSize;
const isOverLimit = attachmentsSize > state.getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments'])-1;
const hasVideoOrAudio = state.getIn(['compose', 'media_attachments']).some(m => ['video', 'audio'].includes(m.get('type')));
+ const hasQuote = !!state.compose.get('quoted_status_id');
return {
- disabled: isPoll || isUploading || isOverLimit || hasVideoOrAudio,
+ disabled: isPoll || isUploading || isOverLimit || hasVideoOrAudio || hasQuote,
resetFileKey: state.getIn(['compose', 'resetFileKey']),
};
};
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index bee9b0410f0..6362d0b4628 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -880,6 +880,7 @@
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this post",
"status.pin": "Pin on profile",
+ "status.quote.cancel": "Cancel quote",
"status.quote_error.filtered": "Hidden due to one of your filters",
"status.quote_error.not_available": "Post unavailable",
"status.quote_error.pending_approval": "Post pending",
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index bb4d917cca5..0aec0f54065 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -331,8 +331,16 @@ export const composeReducer = (state = initialState, action) => {
return state.set('is_changing_upload', false);
} else if (quoteComposeByStatus.match(action)) {
const status = action.payload;
- if (status.getIn(['quote_approval', 'current_user']) === 'automatic') {
- return state.set('quoted_status_id', status.get('id'));
+ if (
+ status.getIn(['quote_approval', 'current_user']) === 'automatic' &&
+ state.get('media_attachments').size === 0 &&
+ !state.get('is_uploading') &&
+ !state.get('poll')
+ ) {
+ return state
+ .set('quoted_status_id', status.get('id'))
+ .set('spoiler', status.get('sensitive'))
+ .set('spoiler_text', status.get('spoiler_text'));
}
} else if (quoteComposeCancel.match(action)) {
return state.set('quoted_status_id', null);
diff --git a/app/javascript/mastodon/selectors/filters.ts b/app/javascript/mastodon/selectors/filters.ts
index f84d01216ad..2eea7853359 100644
--- a/app/javascript/mastodon/selectors/filters.ts
+++ b/app/javascript/mastodon/selectors/filters.ts
@@ -15,7 +15,7 @@ export const getFilters = createSelector(
(_, { contextType }: { contextType: string }) => contextType,
],
(filters, contextType) => {
- if (!contextType) {
+ if (!contextType || contextType === 'compose') {
return null;
}
diff --git a/app/javascript/mastodon/store/typed_functions.ts b/app/javascript/mastodon/store/typed_functions.ts
index 3204d13ee42..4d7341b0c82 100644
--- a/app/javascript/mastodon/store/typed_functions.ts
+++ b/app/javascript/mastodon/store/typed_functions.ts
@@ -129,7 +129,7 @@ export function createAppThunk
(
},
}));
- return Object.assign({}, action, actionCreator);
+ return Object.assign(actionCreator, action);
}
const createBaseAsyncThunk = rtkCreateAsyncThunk.withTypes();
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 48a7ed12cc1..d4826eca058 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -946,6 +946,24 @@ body > [data-popper-placement] {
}
}
}
+
+ .status__quote {
+ margin: 0 8px;
+ max-height: 220px;
+ overflow: hidden;
+
+ // Override .status__content .status__content__text.status__content__text--visible
+ .status__content__text.status__content__text {
+ display: -webkit-box;
+ }
+
+ .status__content__text {
+ -webkit-line-clamp: 4;
+ line-clamp: 4;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ }
+ }
}
.dropdown-button {
@@ -1583,6 +1601,7 @@ body > [data-popper-placement] {
align-items: center;
gap: 10px;
overflow: hidden;
+ flex-grow: 1;
.display-name {
bdi {
@@ -1599,6 +1618,11 @@ body > [data-popper-placement] {
}
}
+.status__quote-cancel {
+ align-self: self-start;
+ order: 5;
+}
+
.status__info {
font-size: 15px;
padding-bottom: 10px;