diff --git a/src/actions/compose.ts b/src/actions/compose.ts index 624d58ceef..eddd9ef7d2 100644 --- a/src/actions/compose.ts +++ b/src/actions/compose.ts @@ -152,9 +152,10 @@ interface ComposeReplyAction { account: Account; explicitAddressing: boolean; preserveSpoilers: boolean; + rebloggedBy?: Account; } -const replyCompose = (status: Status) => +const replyCompose = (status: Status, rebloggedBy?: Account) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); const instance = state.instance; @@ -171,6 +172,7 @@ const replyCompose = (status: Status) => account, explicitAddressing, preserveSpoilers, + rebloggedBy: rebloggedBy, }; dispatch(action); diff --git a/src/components/status-action-bar.tsx b/src/components/status-action-bar.tsx index f48151c3ef..0b12fe68ae 100644 --- a/src/components/status-action-bar.tsx +++ b/src/components/status-action-bar.tsx @@ -28,7 +28,7 @@ import { getReactForStatus, reduceEmoji } from 'soapbox/utils/emoji-reacts'; import GroupPopover from './groups/popover/group-popover'; import type { Menu } from 'soapbox/components/dropdown-menu'; -import type { Group, Status } from 'soapbox/types/entities'; +import type { Account, Group, Status } from 'soapbox/types/entities'; const messages = defineMessages({ adminAccount: { id: 'status.admin_account', defaultMessage: 'Moderate @{name}' }, @@ -99,6 +99,7 @@ const messages = defineMessages({ interface IStatusActionBar { status: Status; + rebloggedBy?: Account; withLabels?: boolean; expandable?: boolean; space?: 'sm' | 'md' | 'lg'; @@ -113,6 +114,7 @@ const StatusActionBar: React.FC = ({ space = 'sm', statusActionButtonTheme = 'default', fromBookmarks = false, + rebloggedBy, }) => { const intl = useIntl(); const history = useHistory(); @@ -148,7 +150,7 @@ const StatusActionBar: React.FC = ({ const handleReplyClick: React.MouseEventHandler = (e) => { if (me) { - dispatch(replyCompose(status)); + dispatch(replyCompose(status, rebloggedBy)); } else { onOpenUnauthorizedModal('REPLY'); } @@ -211,7 +213,7 @@ const StatusActionBar: React.FC = ({ }; const doDeleteStatus = (withRedraft = false) => { - dispatch((_, getState) => { + dispatch((_) => { if (!deleteModal) { dispatch(deleteStatus(status.id, withRedraft)); } else { diff --git a/src/components/status.tsx b/src/components/status.tsx index a8f6f3b4c5..5bf5385767 100644 --- a/src/components/status.tsx +++ b/src/components/status.tsx @@ -138,7 +138,7 @@ const Status: React.FC = (props) => { const handleHotkeyReply = (e?: KeyboardEvent): void => { e?.preventDefault(); - dispatch(replyCompose(actualStatus)); + dispatch(replyCompose(actualStatus, status.reblog && typeof status.reblog === 'object' ? status.account : undefined)); }; const handleHotkeyFavourite = (): void => { @@ -458,7 +458,7 @@ const Status: React.FC = (props) => { {!hideActionBar && (
- +
)} diff --git a/src/features/compose/components/reply-mentions.tsx b/src/features/compose/components/reply-mentions.tsx index 3fd1af36f2..1a6cbcd211 100644 --- a/src/features/compose/components/reply-mentions.tsx +++ b/src/features/compose/components/reply-mentions.tsx @@ -2,8 +2,7 @@ import React, { useCallback } from 'react'; import { FormattedList, FormattedMessage } from 'react-intl'; import { openModal } from 'soapbox/actions/modals'; -import { useAppDispatch, useAppSelector, useCompose, useFeatures, useOwnAccount } from 'soapbox/hooks'; -import { statusToMentionsAccountIdsArray } from 'soapbox/reducers/compose'; +import { useAppDispatch, useAppSelector, useCompose, useFeatures } from 'soapbox/hooks'; import { makeGetStatus } from 'soapbox/selectors'; import type { Status as StatusEntity } from 'soapbox/types/entities'; @@ -20,14 +19,11 @@ const ReplyMentions: React.FC = ({ composeId }) => { const getStatus = useCallback(makeGetStatus(), []); const status = useAppSelector(state => getStatus(state, { id: compose.in_reply_to! })); const to = compose.to; - const { account } = useOwnAccount(); if (!features.explicitAddressing || !status || !to) { return null; } - const parentTo = status && statusToMentionsAccountIdsArray(status, account!); - const handleClick = (e: React.MouseEvent) => { e.preventDefault(); @@ -36,7 +32,7 @@ const ReplyMentions: React.FC = ({ composeId }) => { })); }; - if (!parentTo || (parentTo.size === 0)) { + if (!compose.parent_reblogged_by && to.size === 0) { return null; } diff --git a/src/features/ui/components/modals/reply-mentions-modal.tsx b/src/features/ui/components/modals/reply-mentions-modal.tsx index 386402078d..c24cf343cb 100644 --- a/src/features/ui/components/modals/reply-mentions-modal.tsx +++ b/src/features/ui/components/modals/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 } = useOwnAccount(); - 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/src/reducers/compose.ts b/src/reducers/compose.ts index 5d8730ab90..559ddfcad9 100644 --- a/src/reducers/compose.ts +++ b/src/reducers/compose.ts @@ -108,6 +108,7 @@ export const ReducerCompose = ImmutableRecord({ tagHistory: ImmutableList(), text: '', to: ImmutableOrderedSet(), + parent_reblogged_by: null as string | null, }); type State = ImmutableMap; @@ -125,19 +126,21 @@ const statusToTextMentions = (status: Status, account: Account) => { .join(''); }; -export const statusToMentionsArray = (status: Status, account: Account) => { +export const statusToMentionsArray = (status: Status, account: Account, rebloggedBy?: Account) => { const author = status.getIn(['account', 'acct']) as string; const mentions = status.get('mentions')?.map((m) => m.acct) || []; return ImmutableOrderedSet([author]) + .concat(rebloggedBy ? [rebloggedBy.acct] : []) .concat(mentions) .delete(account.acct) as ImmutableOrderedSet; }; -export const statusToMentionsAccountIdsArray = (status: StatusEntity, account: Account) => { +export const statusToMentionsAccountIdsArray = (status: StatusEntity, account: Account, parentRebloggedBy?: string | null) => { const mentions = status.mentions.map((m) => m.id); - return ImmutableOrderedSet([account.id]) + return ImmutableOrderedSet([status.account.id]) + .concat(parentRebloggedBy ? [parentRebloggedBy] : []) .concat(mentions) .delete(account.id); }; @@ -306,9 +309,14 @@ export default function compose(state = initialState, action: ComposeAction | Ev return updateCompose(state, action.id, compose => compose.withMutations(map => { const defaultCompose = state.get('default')!; + const to = action.explicitAddressing + ? statusToMentionsArray(action.status, action.account, action.rebloggedBy) + : ImmutableOrderedSet(); + map.set('group_id', action.status.getIn(['group', 'id']) as string); 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?.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()); @@ -334,6 +342,7 @@ export default function compose(state = initialState, action: ComposeAction | Ev map.set('quote', action.status.get('id')); map.set('to', ImmutableOrderedSet([author])); + map.set('parent_reblogged_by', null); map.set('text', ''); map.set('privacy', privacyPreference(action.status.visibility, defaultCompose.privacy)); map.set('focusDate', new Date()); @@ -435,11 +444,13 @@ export default function compose(state = initialState, action: ComposeAction | Ev }))); 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 && !action.draftId) { map.set('id', action.status.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());