Make pinning and unpinning directly modify state (#37831)
This commit is contained in:
parent
9129f98776
commit
ca9966ce2f
@ -6,7 +6,10 @@ import { fetchRelationships } from './accounts';
|
|||||||
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
||||||
import { unreblog, reblog } from './interactions_typed';
|
import { unreblog, reblog } from './interactions_typed';
|
||||||
import { openModal } from './modal';
|
import { openModal } from './modal';
|
||||||
import { timelineExpandPinnedFromStatus } from './timelines_typed';
|
import {
|
||||||
|
insertPinnedStatusIntoTimelines,
|
||||||
|
removePinnedStatusFromTimelines,
|
||||||
|
} from './timelines_typed';
|
||||||
|
|
||||||
export const REBLOGS_EXPAND_REQUEST = 'REBLOGS_EXPAND_REQUEST';
|
export const REBLOGS_EXPAND_REQUEST = 'REBLOGS_EXPAND_REQUEST';
|
||||||
export const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS';
|
export const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS';
|
||||||
@ -369,7 +372,7 @@ export function pin(status) {
|
|||||||
api().post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => {
|
api().post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => {
|
||||||
dispatch(importFetchedStatus(response.data));
|
dispatch(importFetchedStatus(response.data));
|
||||||
dispatch(pinSuccess(status));
|
dispatch(pinSuccess(status));
|
||||||
dispatch(timelineExpandPinnedFromStatus(status));
|
dispatch(insertPinnedStatusIntoTimelines(status));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(pinFail(status, error));
|
dispatch(pinFail(status, error));
|
||||||
});
|
});
|
||||||
@ -408,7 +411,7 @@ export function unpin (status) {
|
|||||||
api().post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => {
|
api().post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => {
|
||||||
dispatch(importFetchedStatus(response.data));
|
dispatch(importFetchedStatus(response.data));
|
||||||
dispatch(unpinSuccess(status));
|
dispatch(unpinSuccess(status));
|
||||||
dispatch(timelineExpandPinnedFromStatus(status));
|
dispatch(removePinnedStatusFromTimelines(status));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(unpinFail(status, error));
|
dispatch(unpinFail(status, error));
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import type { Status } from '../models/status';
|
|||||||
import { createAppThunk } from '../store/typed_functions';
|
import { createAppThunk } from '../store/typed_functions';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
expandAccountFeaturedTimeline,
|
|
||||||
expandTimeline,
|
expandTimeline,
|
||||||
|
insertIntoTimeline,
|
||||||
TIMELINE_NON_STATUS_MARKERS,
|
TIMELINE_NON_STATUS_MARKERS,
|
||||||
} from './timelines';
|
} from './timelines';
|
||||||
|
|
||||||
@ -173,9 +173,13 @@ export function parseTimelineKey(key: string): TimelineParams | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isTimelineKeyPinned(key: string) {
|
export function isTimelineKeyPinned(key: string, accountId?: string) {
|
||||||
const parsedKey = parseTimelineKey(key);
|
const parsedKey = parseTimelineKey(key);
|
||||||
return parsedKey?.type === 'account' && parsedKey.pinned;
|
const isPinned = parsedKey?.type === 'account' && parsedKey.pinned;
|
||||||
|
if (!accountId || !isPinned) {
|
||||||
|
return isPinned;
|
||||||
|
}
|
||||||
|
return parsedKey.userId === accountId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNonStatusId(value: unknown) {
|
export function isNonStatusId(value: unknown) {
|
||||||
@ -199,52 +203,71 @@ export const timelineDelete = createAction<{
|
|||||||
reblogOf: string | null;
|
reblogOf: string | null;
|
||||||
}>('timelines/delete');
|
}>('timelines/delete');
|
||||||
|
|
||||||
export const timelineExpandPinnedFromStatus = createAppThunk(
|
export const timelineDeleteStatus = createAction<{
|
||||||
|
statusId: string;
|
||||||
|
timelineKey: string;
|
||||||
|
}>('timelines/deleteStatus');
|
||||||
|
|
||||||
|
export const insertPinnedStatusIntoTimelines = createAppThunk(
|
||||||
(status: Status, { dispatch, getState }) => {
|
(status: Status, { dispatch, getState }) => {
|
||||||
const accountId = status.getIn(['account', 'id']) as string;
|
const currentAccountId = getState().meta.get('me', null) as string | null;
|
||||||
if (!accountId) {
|
if (!currentAccountId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that any of the relevant timelines are actually expanded before dispatching, to avoid unnecessary API calls.
|
const tags =
|
||||||
|
(
|
||||||
|
status.get('tags') as
|
||||||
|
| ImmutableList<ImmutableMap<'name', string>> // We only care about the tag name.
|
||||||
|
| undefined
|
||||||
|
)
|
||||||
|
?.map((tag) => tag.get('name') as string)
|
||||||
|
.toArray() ?? [];
|
||||||
|
|
||||||
const timelines = getState().timelines as ImmutableMap<string, unknown>;
|
const timelines = getState().timelines as ImmutableMap<string, unknown>;
|
||||||
if (!timelines.some((_, key) => key.startsWith(`account:${accountId}:`))) {
|
const accountTimelines = timelines.filter((_, key) => {
|
||||||
return;
|
if (!key.startsWith(`account:${currentAccountId}:`)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
void dispatch(
|
const parsed = parseTimelineKey(key);
|
||||||
expandTimelineByParams({
|
const isPinned = parsed?.type === 'account' && parsed.pinned;
|
||||||
type: 'account',
|
if (!isPinned) {
|
||||||
userId: accountId,
|
return false;
|
||||||
pinned: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
void dispatch(expandAccountFeaturedTimeline(accountId));
|
|
||||||
|
|
||||||
// Iterate over tags and clear those too.
|
|
||||||
const tags = status.get('tags') as
|
|
||||||
| ImmutableList<ImmutableMap<'name', string>> // We only care about the tag name.
|
|
||||||
| undefined;
|
|
||||||
if (!tags) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tags.forEach((tag) => {
|
|
||||||
const tagName = tag.get('name');
|
|
||||||
if (!tagName) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispatch(
|
return !parsed.tagged || tags.includes(parsed.tagged);
|
||||||
expandTimelineByParams({
|
});
|
||||||
type: 'account',
|
|
||||||
userId: accountId,
|
accountTimelines.forEach((_, key) => {
|
||||||
pinned: true,
|
dispatch(insertIntoTimeline(key, status.get('id') as string, 0));
|
||||||
tagged: tagName,
|
});
|
||||||
}),
|
},
|
||||||
);
|
);
|
||||||
void dispatch(
|
|
||||||
expandAccountFeaturedTimeline(accountId, { tagged: tagName }),
|
export const removePinnedStatusFromTimelines = createAppThunk(
|
||||||
);
|
(status: Status, { dispatch, getState }) => {
|
||||||
|
const currentAccountId = getState().meta.get('me', null) as string | null;
|
||||||
|
if (!currentAccountId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusId = status.get('id') as string;
|
||||||
|
const timelines = getState().timelines as ImmutableMap<
|
||||||
|
string,
|
||||||
|
ImmutableMap<'items' | 'pendingItems', ImmutableList<string>>
|
||||||
|
>;
|
||||||
|
|
||||||
|
timelines.forEach((timeline, key) => {
|
||||||
|
if (!isTimelineKeyPinned(key, currentAccountId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
timeline.get('items')?.includes(statusId) ||
|
||||||
|
timeline.get('pendingItems')?.includes(statusId)
|
||||||
|
) {
|
||||||
|
dispatch(timelineDeleteStatus({ statusId, timelineKey: key }));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -20,7 +20,12 @@ import {
|
|||||||
TIMELINE_GAP,
|
TIMELINE_GAP,
|
||||||
disconnectTimeline,
|
disconnectTimeline,
|
||||||
} from '../actions/timelines';
|
} from '../actions/timelines';
|
||||||
import { timelineDelete, isTimelineKeyPinned, isNonStatusId } from '../actions/timelines_typed';
|
import {
|
||||||
|
timelineDelete,
|
||||||
|
timelineDeleteStatus,
|
||||||
|
isTimelineKeyPinned,
|
||||||
|
isNonStatusId,
|
||||||
|
} from '../actions/timelines_typed';
|
||||||
import { compareId } from '../compare_id';
|
import { compareId } from '../compare_id';
|
||||||
|
|
||||||
const initialState = ImmutableMap();
|
const initialState = ImmutableMap();
|
||||||
@ -145,6 +150,11 @@ const deleteStatus = (state, id, references, exclude_account = null) => {
|
|||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteStatusFromTimeline = (state, statusId, timelineKey) => {
|
||||||
|
const helper = list => list.filterNot((status) => status === statusId);
|
||||||
|
return state.updateIn([timelineKey, 'items'], helper).updateIn([timelineKey, 'pendingItems'], helper);
|
||||||
|
}
|
||||||
|
|
||||||
const clearTimeline = (state, timeline) => {
|
const clearTimeline = (state, timeline) => {
|
||||||
return state.set(timeline, initialTimeline);
|
return state.set(timeline, initialTimeline);
|
||||||
};
|
};
|
||||||
@ -200,25 +210,12 @@ export default function timelines(state = initialState, action) {
|
|||||||
return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent, action.usePendingItems);
|
return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent, action.usePendingItems);
|
||||||
case TIMELINE_UPDATE:
|
case TIMELINE_UPDATE:
|
||||||
return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems);
|
return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems);
|
||||||
case timelineDelete.type:
|
|
||||||
return deleteStatus(state, action.payload.statusId, action.payload.references, action.payload.reblogOf);
|
|
||||||
case TIMELINE_CLEAR:
|
case TIMELINE_CLEAR:
|
||||||
return clearTimeline(state, action.timeline);
|
return clearTimeline(state, action.timeline);
|
||||||
case blockAccountSuccess.type:
|
|
||||||
case muteAccountSuccess.type:
|
|
||||||
return filterTimelines(state, action.payload.relationship, action.payload.statuses);
|
|
||||||
case unfollowAccountSuccess.type:
|
|
||||||
return filterTimeline('home', state, action.payload.relationship, action.payload.statuses);
|
|
||||||
case TIMELINE_SCROLL_TOP:
|
case TIMELINE_SCROLL_TOP:
|
||||||
return updateTop(state, action.timeline, action.top);
|
return updateTop(state, action.timeline, action.top);
|
||||||
case TIMELINE_CONNECT:
|
case TIMELINE_CONNECT:
|
||||||
return state.update(action.timeline, initialTimeline, map => reconnectTimeline(map, action.usePendingItems));
|
return state.update(action.timeline, initialTimeline, map => reconnectTimeline(map, action.usePendingItems));
|
||||||
case disconnectTimeline.type:
|
|
||||||
return state.update(
|
|
||||||
action.payload.timeline,
|
|
||||||
initialTimeline,
|
|
||||||
map => map.set('online', false).update(action.payload.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(TIMELINE_GAP) : items),
|
|
||||||
);
|
|
||||||
case TIMELINE_MARK_AS_PARTIAL:
|
case TIMELINE_MARK_AS_PARTIAL:
|
||||||
return state.update(
|
return state.update(
|
||||||
action.timeline,
|
action.timeline,
|
||||||
@ -238,6 +235,29 @@ export default function timelines(state = initialState, action) {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
if (timelineDelete.match(action)) {
|
||||||
|
return deleteStatus(state, action.payload.statusId, action.payload.references, action.payload.reblogOf);
|
||||||
|
} else if (timelineDeleteStatus.match(action)) {
|
||||||
|
return deleteStatusFromTimeline(state, action.payload.statusId, action.payload.timelineKey);
|
||||||
|
} else if (blockAccountSuccess.match(action) || muteAccountSuccess.match(action)) {
|
||||||
|
return filterTimelines(state, action.payload.relationship, action.payload.statuses);
|
||||||
|
} else if (unfollowAccountSuccess.match(action)) {
|
||||||
|
return filterTimeline('home', state, action.payload.relationship, action.payload.statuses);
|
||||||
|
} else if (disconnectTimeline.match(action)) {
|
||||||
|
return state.update(
|
||||||
|
action.payload.timeline,
|
||||||
|
initialTimeline,
|
||||||
|
(map) => map.set('online', false).update(
|
||||||
|
action.payload.usePendingItems
|
||||||
|
? 'pendingItems'
|
||||||
|
: 'items',
|
||||||
|
items => items.first()
|
||||||
|
? items.unshift(TIMELINE_GAP)
|
||||||
|
: items
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user