From 587e3a049e045a84df5b0c4161340797e41afd91 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 4 Dec 2023 21:59:24 +0100 Subject: [PATCH] Change threads to retain sorting returned by the API in web UI --- .../mastodon/features/status/index.jsx | 74 +-------------- app/javascript/mastodon/reducers/contexts.js | 93 ++++++------------- 2 files changed, 33 insertions(+), 134 deletions(-) diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 67a9697311..089cf58f7b 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -6,11 +6,10 @@ import classNames from 'classnames'; import { Helmet } from 'react-helmet'; import { withRouter } from 'react-router-dom'; -import Immutable from 'immutable'; +import { List as ImmutableList } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; import { ReactComponent as VisibilityIcon } from '@material-symbols/svg-600/outlined/visibility.svg'; import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; @@ -72,7 +71,6 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from import ActionBar from './components/action_bar'; import DetailedStatus from './components/detailed_status'; - const messages = defineMessages({ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, @@ -91,83 +89,19 @@ const makeMapStateToProps = () => { const getStatus = makeGetStatus(); const getPictureInPicture = makeGetPictureInPicture(); - const getAncestorsIds = createSelector([ - (_, { id }) => id, - state => state.getIn(['contexts', 'inReplyTos']), - ], (statusId, inReplyTos) => { - let ancestorsIds = Immutable.List(); - ancestorsIds = ancestorsIds.withMutations(mutable => { - let id = statusId; - - while (id && !mutable.includes(id)) { - mutable.unshift(id); - id = inReplyTos.get(id); - } - }); - - return ancestorsIds; - }); - - const getDescendantsIds = createSelector([ - (_, { id }) => id, - state => state.getIn(['contexts', 'replies']), - state => state.get('statuses'), - ], (statusId, contextReplies, statuses) => { - let descendantsIds = []; - const ids = [statusId]; - - while (ids.length > 0) { - let id = ids.pop(); - const replies = contextReplies.get(id); - - if (statusId !== id) { - descendantsIds.push(id); - } - - if (replies) { - replies.reverse().forEach(reply => { - if (!ids.includes(reply) && !descendantsIds.includes(reply) && statusId !== reply) ids.push(reply); - }); - } - } - - let insertAt = descendantsIds.findIndex((id) => statuses.get(id).get('in_reply_to_account_id') !== statuses.get(id).get('account')); - if (insertAt !== -1) { - descendantsIds.forEach((id, idx) => { - if (idx > insertAt && statuses.get(id).get('in_reply_to_account_id') === statuses.get(id).get('account')) { - descendantsIds.splice(idx, 1); - descendantsIds.splice(insertAt, 0, id); - insertAt += 1; - } - }); - } - - return Immutable.List(descendantsIds); - }); - - const mapStateToProps = (state, props) => { + return (state, props) => { const status = getStatus(state, { id: props.params.statusId }); - let ancestorsIds = Immutable.List(); - let descendantsIds = Immutable.List(); - - if (status) { - ancestorsIds = getAncestorsIds(state, { id: status.get('in_reply_to_id') }); - descendantsIds = getDescendantsIds(state, { id: status.get('id') }); - } - return { isLoading: state.getIn(['statuses', props.params.statusId, 'isLoading']), status, - ancestorsIds, - descendantsIds, + ancestorsIds: state.getIn(['contexts', props.params.statusId, 'ancestors'], ImmutableList()), + descendantsIds: state.getIn(['contexts', props.params.statusId, 'descendants'], ImmutableList()), askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0, domain: state.getIn(['meta', 'domain']), pictureInPicture: getPictureInPicture(state, { id: props.params.statusId }), }; }; - - return mapStateToProps; }; const truncate = (str, num) => { diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js index f7d7419a4e..5640432629 100644 --- a/app/javascript/mastodon/reducers/contexts.js +++ b/app/javascript/mastodon/reducers/contexts.js @@ -6,65 +6,20 @@ import { } from '../actions/accounts'; import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses'; import { TIMELINE_DELETE, TIMELINE_UPDATE } from '../actions/timelines'; -import { compareId } from '../compare_id'; -const initialState = ImmutableMap({ - inReplyTos: ImmutableMap(), - replies: ImmutableMap(), -}); +const initialState = ImmutableMap(); -const normalizeContext = (immutableState, id, ancestors, descendants) => immutableState.withMutations(state => { - state.update('inReplyTos', immutableAncestors => immutableAncestors.withMutations(inReplyTos => { - state.update('replies', immutableDescendants => immutableDescendants.withMutations(replies => { - function addReply({ id, in_reply_to_id }) { - if (in_reply_to_id && !inReplyTos.has(id)) { +const normalizeContext = (state, id, ancestors, descendants) => state.set(id, ImmutableMap({ + ancestors: ImmutableList(ancestors.map(x => x.id)), + descendants: ImmutableList(descendants.map(x => x.id)), +})); - replies.update(in_reply_to_id, ImmutableList(), siblings => { - const index = siblings.findLastIndex(sibling => compareId(sibling, id) < 0); - return siblings.insert(index + 1, id); - }); - - inReplyTos.set(id, in_reply_to_id); - } - } - - // We know in_reply_to_id of statuses but `id` itself. - // So we assume that the status of the id replies to last ancestors. - - ancestors.forEach(addReply); - - if (ancestors[0]) { - addReply({ id, in_reply_to_id: ancestors[ancestors.length - 1].id }); - } - - descendants.forEach(addReply); - })); - })); -}); - -const deleteFromContexts = (immutableState, ids) => immutableState.withMutations(state => { - state.update('inReplyTos', immutableAncestors => immutableAncestors.withMutations(inReplyTos => { - state.update('replies', immutableDescendants => immutableDescendants.withMutations(replies => { - ids.forEach(id => { - const inReplyToIdOfId = inReplyTos.get(id); - const repliesOfId = replies.get(id); - const siblings = replies.get(inReplyToIdOfId); - - if (siblings) { - replies.set(inReplyToIdOfId, siblings.filterNot(sibling => sibling === id)); - } - - - if (repliesOfId) { - repliesOfId.forEach(reply => inReplyTos.delete(reply)); - } - - inReplyTos.delete(id); - replies.delete(id); - }); - })); - })); -}); +const deleteFromContexts = (state, deletedIds) => state.update(contexts => + contexts.map(context => + context.update(map => ImmutableMap({ + ancestors: map.get('ancestors').filterNot(id => deletedIds.includes(id)), + descendants: map.get('descendants').filterNot(id => deletedIds.includes(id)), + })))); const filterContexts = (state, relationship, statuses) => { const ownedStatusIds = statuses @@ -75,16 +30,26 @@ const filterContexts = (state, relationship, statuses) => { }; const updateContext = (state, status) => { - if (status.in_reply_to_id) { - return state.withMutations(mutable => { - const replies = mutable.getIn(['replies', status.in_reply_to_id], ImmutableList()); + const inReplyToId = status.in_reply_to_id; - mutable.setIn(['inReplyTos', status.id], status.in_reply_to_id); - - if (!replies.includes(status.id)) { - mutable.setIn(['replies', status.in_reply_to_id], replies.push(status.id)); + if (inReplyToId) { + return state.update(contexts => contexts.map((context, rootStatusId) => { + if (context.get('descendants').includes(status.id)) { + return context; } - }); + + if (rootStatusId === inReplyToId) { + return context.update('descendants', list => list.push(status.id)); + } + + const ancestorIndex = context.get('descendants').indexOf(inReplyToId); + + if (ancestorIndex !== -1) { + return context.update('descendants', list => list.insert(ancestorIndex + 1, status.id)); + } + + return context; + })); } return state;