diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 1e533b3279e..b9cc7a43869 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -14,6 +14,7 @@ import { useEmoji } from './emojis'; import { importFetchedAccounts, importFetchedStatus } from './importer'; import { openModal } from './modal'; import { updateTimeline } from './timelines'; +import { insertStatusIntoAccountTimelines } from './timelines_typed'; /** @type {AbortController | undefined} */ let fetchComposeSuggestionsAccountsController; @@ -189,13 +190,13 @@ export function directCompose(account) { export function submitCompose(successCallback) { return function (dispatch, getState) { - const status = getState().getIn(['compose', 'text'], ''); - const media = getState().getIn(['compose', 'media_attachments']); - const statusId = getState().getIn(['compose', 'id'], null); - const hasQuote = !!getState().getIn(['compose', 'quoted_status_id']); + const statusText = getState().getIn(['compose', 'text'], ''); + const media = getState().getIn(['compose', 'media_attachments']); + const statusId = getState().getIn(['compose', 'id'], null); + const hasQuote = !!getState().getIn(['compose', 'quoted_status_id']); const spoiler_text = getState().getIn(['compose', 'spoiler']) ? getState().getIn(['compose', 'spoiler_text'], '') : ''; - const fulltext = `${spoiler_text ?? ''}${countableText(status ?? '')}`; + const fulltext = `${spoiler_text ?? ''}${countableText(statusText ?? '')}`; const hasText = fulltext.trim().length > 0; if (!(hasText || media.size !== 0 || (hasQuote && spoiler_text?.length))) { @@ -234,7 +235,7 @@ export function submitCompose(successCallback) { url: statusId === null ? '/api/v1/statuses' : `/api/v1/statuses/${statusId}`, method: statusId === null ? 'post' : 'put', data: { - status, + status: statusText, spoiler_text, in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), media_ids: media.map(item => item.get('id')), @@ -254,7 +255,7 @@ export function submitCompose(successCallback) { browserHistory.goBack(); } - dispatch(insertIntoTagHistory(response.data.tags, status)); + dispatch(insertIntoTagHistory(response.data.tags, statusText)); dispatch(submitComposeSuccess({ ...response.data })); if (typeof successCallback === 'function') { successCallback(response.data); @@ -284,6 +285,8 @@ export function submitCompose(successCallback) { insertIfOnline(`account:${response.data.account.id}`); } + dispatch(insertStatusIntoAccountTimelines({ ...response.data })) + dispatch(showAlert({ message: statusId === null ? messages.published : messages.saved, action: messages.open, diff --git a/app/javascript/mastodon/actions/timelines_typed.ts b/app/javascript/mastodon/actions/timelines_typed.ts index 98165617790..e86b76d4a2a 100644 --- a/app/javascript/mastodon/actions/timelines_typed.ts +++ b/app/javascript/mastodon/actions/timelines_typed.ts @@ -3,6 +3,7 @@ import type { List as ImmutableList, Map as ImmutableMap } from 'immutable'; import { usePendingItems as preferPendingItems } from 'mastodon/initial_state'; +import type { ApiStatusJSON } from '../api_types/statuses'; import type { Status } from '../models/status'; import { createAppThunk } from '../store/typed_functions'; @@ -10,6 +11,7 @@ import { expandTimeline, insertIntoTimeline, TIMELINE_NON_STATUS_MARKERS, + updateTimeline, } from './timelines'; export const expandTimelineByKey = createAppThunk( @@ -208,6 +210,34 @@ export const timelineDeleteStatus = createAction<{ timelineKey: string; }>('timelines/deleteStatus'); +export const insertStatusIntoAccountTimelines = createAppThunk( + (status: ApiStatusJSON, { dispatch, getState }) => { + const currentAccountId = getState().meta.get('me', null) as string | null; + if (!currentAccountId) { + return; + } + + const tags = status.tags.map((tag) => tag.name); + + const timelines = getState().timelines as ImmutableMap; + const accountTimelines = timelines.filter((_, key) => { + if (!key.startsWith(`account:${currentAccountId}:`)) { + return false; + } + const parsed = parseTimelineKey(key); + if (parsed?.type !== 'account' || parsed.pinned) { + return false; + } + + return !parsed.tagged || tags.includes(parsed.tagged); + }); + + accountTimelines.forEach((_, timelineKey) => { + dispatch(updateTimeline(timelineKey, status)); + }); + }, +); + export const insertPinnedStatusIntoTimelines = createAppThunk( (status: Status, { dispatch, getState }) => { const currentAccountId = getState().meta.get('me', null) as string | null; diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index add56179308..c2d490cb2d8 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -109,19 +109,19 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is })); }; -const updateTimeline = (state, timeline, status, usePendingItems) => { +const updateTimeline = (state, timeline, statusId, usePendingItems) => { const top = state.getIn([timeline, 'top']); if (usePendingItems || !state.getIn([timeline, 'pendingItems']).isEmpty()) { - if (state.getIn([timeline, 'pendingItems'], ImmutableList()).includes(status.get('id')) || state.getIn([timeline, 'items'], ImmutableList()).includes(status.get('id'))) { + if (state.getIn([timeline, 'pendingItems'], ImmutableList()).includes(statusId) || state.getIn([timeline, 'items'], ImmutableList()).includes(statusId)) { return state; } - return state.update(timeline, initialTimeline, map => map.update('pendingItems', list => list.unshift(status.get('id'))).update('unread', unread => unread + 1)); + return state.update(timeline, initialTimeline, map => map.update('pendingItems', list => list.unshift(statusId)).update('unread', unread => unread + 1)); } const ids = state.getIn([timeline, 'items'], ImmutableList()); - const includesId = ids.includes(status.get('id')); + const includesId = ids.includes(statusId); const unread = state.getIn([timeline, 'unread'], 0); if (includesId) { @@ -133,7 +133,7 @@ const updateTimeline = (state, timeline, status, usePendingItems) => { return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { if (!top) mMap.set('unread', unread + 1); if (top && ids.size > 40) newIds = newIds.take(20); - mMap.set('items', newIds.unshift(status.get('id'))); + mMap.set('items', newIds.unshift(statusId)); })); }; @@ -213,7 +213,7 @@ export default function timelines(state = initialState, action) { case TIMELINE_EXPAND_SUCCESS: return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent, action.usePendingItems); case TIMELINE_UPDATE: - return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems); + return updateTimeline(state, action.timeline, action.status.id, action.usePendingItems); case TIMELINE_CLEAR: return clearTimeline(state, action.timeline); case TIMELINE_SCROLL_TOP: