mirror of
https://github.com/mastodon/mastodon.git
synced 2024-12-22 18:45:41 +01:00
Change threads to retain sorting returned by the API in web UI
This commit is contained in:
parent
456597dae5
commit
587e3a049e
2 changed files with 33 additions and 134 deletions
|
@ -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) => {
|
||||
|
|
|
@ -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)) {
|
||||
|
||||
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 normalizeContext = (state, id, ancestors, descendants) => state.set(id, ImmutableMap({
|
||||
ancestors: ImmutableList(ancestors.map(x => x.id)),
|
||||
descendants: ImmutableList(descendants.map(x => x.id)),
|
||||
}));
|
||||
}));
|
||||
});
|
||||
|
||||
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;
|
||||
|
|
Loading…
Reference in a new issue