diff --git a/app/soapbox/actions/compose.ts b/app/soapbox/actions/compose.ts index 57e7909a1..fc576a354 100644 --- a/app/soapbox/actions/compose.ts +++ b/app/soapbox/actions/compose.ts @@ -121,7 +121,7 @@ const changeCompose = (composeId: string, text: string) => ({ text: text, }); -const replyCompose = (status: Status) => +const replyCompose = (status: Status, rebloggedBy?: Account) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); const instance = state.instance; @@ -133,6 +133,7 @@ const replyCompose = (status: Status) => status: status, account: state.accounts.get(state.me), explicitAddressing, + rebloggedBy, }); dispatch(openModal('COMPOSE')); diff --git a/app/soapbox/components/status-action-bar.tsx b/app/soapbox/components/status-action-bar.tsx index 9153cb256..dffba35b0 100644 --- a/app/soapbox/components/status-action-bar.tsx +++ b/app/soapbox/components/status-action-bar.tsx @@ -82,6 +82,7 @@ const messages = defineMessages({ interface IStatusActionBar { status: Status, + rebloggedBy?: Account, withDismiss?: boolean, withLabels?: boolean, expandable?: boolean, @@ -94,6 +95,7 @@ const StatusActionBar: React.FC = ({ withLabels = false, expandable = true, space = 'compact', + rebloggedBy, }) => { const intl = useIntl(); const history = useHistory(); @@ -123,7 +125,7 @@ const StatusActionBar: React.FC = ({ const handleReplyClick: React.MouseEventHandler = (e) => { if (me) { - dispatch(replyCompose(status)); + dispatch(replyCompose(status, rebloggedBy)); } else { onOpenUnauthorizedModal('REPLY'); } diff --git a/app/soapbox/components/status.tsx b/app/soapbox/components/status.tsx index f01ded300..16e06986f 100644 --- a/app/soapbox/components/status.tsx +++ b/app/soapbox/components/status.tsx @@ -130,7 +130,7 @@ const Status: React.FC = (props) => { const handleHotkeyReply = (e?: KeyboardEvent): void => { e?.preventDefault(); - dispatch(replyCompose(actualStatus)); + dispatch(replyCompose(status, status.reblog && typeof status.reblog === 'object' ? status.account as AccountEntity : undefined)); }; const handleHotkeyFavourite = (): void => { @@ -190,7 +190,7 @@ const Status: React.FC = (props) => { }; if (!status) return null; - let rebloggedByText, reblogElement, reblogElementMobile; + let rebloggedBy, rebloggedByText, reblogElement, reblogElementMobile; if (hidden) { return ( @@ -269,6 +269,8 @@ const Status: React.FC = (props) => { messages.reblogged_by, { name: String(status.getIn(['account', 'acct'])) }, ); + + rebloggedBy = status.account; } let quote; @@ -400,7 +402,7 @@ const Status: React.FC = (props) => { {(!hideActionBar && !isUnderReview) && (
- +
)} diff --git a/app/soapbox/features/compose/components/reply_mentions.tsx b/app/soapbox/features/compose/components/reply_mentions.tsx index 865f6539d..0cdfeacba 100644 --- a/app/soapbox/features/compose/components/reply_mentions.tsx +++ b/app/soapbox/features/compose/components/reply_mentions.tsx @@ -41,7 +41,7 @@ const ReplyMentions: React.FC = ({ composeId }) => { })); }; - if (!parentTo || (parentTo.size === 0)) { + if (!compose.parent_reblogged_by && (!parentTo || (parentTo.size === 0))) { return null; } diff --git a/app/soapbox/features/ui/components/reply_mentions_modal.tsx b/app/soapbox/features/ui/components/reply_mentions_modal.tsx index 2c8fc73b1..de76cf744 100644 --- a/app/soapbox/features/ui/components/reply_mentions_modal.tsx +++ b/app/soapbox/features/ui/components/reply_mentions_modal.tsx @@ -22,7 +22,7 @@ const ReplyMentionsModal: React.FC = ({ composeId, onClose const status = useAppSelector(state => getStatus(state, { id: compose.in_reply_to! })); const account = useAppSelector((state) => state.accounts.get(state.me)); - const mentions = statusToMentionsAccountIdsArray(status!, account!); + const mentions = statusToMentionsAccountIdsArray(status!, account!, compose.parent_reblogged_by); const author = (status?.account as AccountEntity).id; const onClickClose = () => { diff --git a/app/soapbox/reducers/compose.ts b/app/soapbox/reducers/compose.ts index 3f02f8cac..2085b02c9 100644 --- a/app/soapbox/reducers/compose.ts +++ b/app/soapbox/reducers/compose.ts @@ -99,6 +99,7 @@ export const ReducerCompose = ImmutableRecord({ tagHistory: ImmutableList(), text: '', to: ImmutableOrderedSet(), + parent_reblogged_by: null as string | null, }); type State = ImmutableMap; @@ -116,20 +117,22 @@ const statusToTextMentions = (status: ImmutableMap, account: Accoun .join(''); }; -export const statusToMentionsArray = (status: ImmutableMap, account: AccountEntity) => { +export const statusToMentionsArray = (status: ImmutableMap, account: AccountEntity, rebloggedBy?: AccountEntity) => { const author = status.getIn(['account', 'acct']) as string; const mentions = status.get('mentions')?.map((m: ImmutableMap) => m.get('acct')) || []; return ImmutableOrderedSet([author]) + .concat(rebloggedBy ? [rebloggedBy.acct] : []) .concat(mentions) .delete(account.get('acct')) as ImmutableOrderedSet; }; -export const statusToMentionsAccountIdsArray = (status: StatusEntity, account: AccountEntity) => { +export const statusToMentionsAccountIdsArray = (status: StatusEntity, account: AccountEntity, parentRebloggedBy?: string | null) => { const author = (status.account as AccountEntity).id; const mentions = status.mentions.map((m) => m.id); return ImmutableOrderedSet([author]) + .concat(parentRebloggedBy ? [parentRebloggedBy] : []) .concat(mentions) .delete(account.id) as ImmutableOrderedSet; }; @@ -308,8 +311,13 @@ export default function compose(state = initialState, action: AnyAction) { return updateCompose(state, 'compose-modal', compose => compose.withMutations(map => { const defaultCompose = state.get('default')!; + const to = action.explicitAddressing + ? statusToMentionsArray(action.status, action.account, action.rebloggedBy) + : ImmutableOrderedSet(); + map.set('in_reply_to', action.status.get('id')); - map.set('to', action.explicitAddressing ? statusToMentionsArray(action.status, action.account) : ImmutableOrderedSet()); + map.set('to', to); + map.set('parent_reblogged_by', (action.rebloggedBy as AccountEntity)?.id || null); map.set('text', !action.explicitAddressing ? statusToTextMentions(action.status, action.account) : ''); map.set('privacy', privacyPreference(action.status.visibility, defaultCompose.privacy)); map.set('focusDate', new Date()); @@ -323,6 +331,7 @@ export default function compose(state = initialState, action: AnyAction) { map.set('quote', action.status.get('id')); map.set('to', ImmutableOrderedSet()); + map.set('parent_reblogged_by', null); map.set('text', ''); map.set('privacy', privacyPreference(action.status.visibility, defaultCompose.privacy)); map.set('focusDate', new Date()); @@ -404,11 +413,13 @@ export default function compose(state = initialState, action: AnyAction) { }))); case COMPOSE_SET_STATUS: return updateCompose(state, 'compose-modal', compose => compose.withMutations(map => { + const to = action.explicitAddressing ? getExplicitMentions(action.status.account.id, action.status) : ImmutableOrderedSet(); if (!action.withRedraft) { map.set('id', action.status.get('id')); } map.set('text', action.rawText || unescapeHTML(expandMentions(action.status))); - map.set('to', action.explicitAddressing ? getExplicitMentions(action.status.account.id, action.status) : ImmutableOrderedSet()); + map.set('to', to); + map.set('parent_reblogged_by', null); map.set('in_reply_to', action.status.get('in_reply_to_id')); map.set('privacy', action.status.get('visibility')); map.set('focusDate', new Date());