Add reference toots view as thread

This commit is contained in:
KMY 2023-07-06 17:46:32 +09:00
parent 44a987810b
commit 801d17f34d
6 changed files with 51 additions and 27 deletions

View file

@ -44,11 +44,13 @@ class Api::V1::StatusesController < Api::BaseController
ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(ancestors_limit, current_account)
descendants_results = @status.descendants(descendants_limit, current_account, descendants_depth_limit)
references_results = @status.references
loaded_ancestors = cache_collection(ancestors_results, Status)
loaded_descendants = cache_collection(descendants_results, Status)
loaded_references = cache_collection(references_results, Status)
@context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
statuses = [@status] + @context.ancestors + @context.descendants
@context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants, references: loaded_references)
statuses = [@status] + @context.ancestors + @context.descendants + @context.references
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
end

View file

@ -181,8 +181,8 @@ export function fetchContext(id) {
dispatch(fetchContextRequest(id));
api(getState).get(`/api/v1/statuses/${id}/context`).then(response => {
dispatch(importFetchedStatuses(response.data.ancestors.concat(response.data.descendants)));
dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants));
dispatch(importFetchedStatuses(response.data.ancestors.concat(response.data.descendants).concat(response.data.references)));
dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants, response.data.references));
}).catch(error => {
if (error.response && error.response.status === 404) {
@ -201,12 +201,13 @@ export function fetchContextRequest(id) {
};
}
export function fetchContextSuccess(id, ancestors, descendants) {
export function fetchContextSuccess(id, ancestors, descendants, references) {
return {
type: CONTEXT_FETCH_SUCCESS,
id,
ancestors,
descendants,
references,
statuses: ancestors.concat(descendants),
};
}

View file

@ -89,6 +89,12 @@ const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const getPictureInPicture = makeGetPictureInPicture();
const getReferenceIds = createSelector([
(state, { id }) => state.getIn(['contexts', 'references', id]),
], (references) => {
return references;
});
const getAncestorsIds = createSelector([
(_, { id }) => id,
state => state.getIn(['contexts', 'inReplyTos']),
@ -148,10 +154,12 @@ const makeMapStateToProps = () => {
let ancestorsIds = Immutable.List();
let descendantsIds = Immutable.List();
let referenceIds = Immutable.List();
if (status) {
ancestorsIds = getAncestorsIds(state, { id: status.get('in_reply_to_id') });
descendantsIds = getDescendantsIds(state, { id: status.get('id') });
referenceIds = getReferenceIds(state, { id: status.get('id') });
}
return {
@ -159,6 +167,7 @@ const makeMapStateToProps = () => {
status,
ancestorsIds,
descendantsIds,
referenceIds,
askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
domain: state.getIn(['meta', 'domain']),
pictureInPicture: getPictureInPicture(state, { id: props.params.statusId }),
@ -201,6 +210,7 @@ class Status extends ImmutablePureComponent {
isLoading: PropTypes.bool,
ancestorsIds: ImmutablePropTypes.list,
descendantsIds: ImmutablePropTypes.list,
referenceIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
askReplyConfirmation: PropTypes.bool,
multiColumn: PropTypes.bool,
@ -447,8 +457,8 @@ class Status extends ImmutablePureComponent {
};
handleToggleAll = () => {
const { status, ancestorsIds, descendantsIds } = this.props;
const statusIds = [status.get('id')].concat(ancestorsIds.toJS(), descendantsIds.toJS());
const { status, ancestorsIds, descendantsIds, referenceIds } = this.props;
const statusIds = [status.get('id')].concat(ancestorsIds.toJS(), descendantsIds.toJS(), referenceIds.toJS());
if (status.get('hidden')) {
this.props.dispatch(revealStatus(statusIds));
@ -641,8 +651,8 @@ class Status extends ImmutablePureComponent {
};
render () {
let ancestors, descendants;
const { isLoading, status, ancestorsIds, descendantsIds, intl, domain, multiColumn, pictureInPicture } = this.props;
let ancestors, descendants, references;
const { isLoading, status, ancestorsIds, descendantsIds, referenceIds, intl, domain, multiColumn, pictureInPicture } = this.props;
const { fullscreen } = this.state;
if (isLoading) {
@ -659,6 +669,10 @@ class Status extends ImmutablePureComponent {
);
}
if (referenceIds && referenceIds.size > 0) {
references = <>{this.renderChildren(referenceIds, true)}</>;
}
if (ancestorsIds && ancestorsIds.size > 0) {
ancestors = <>{this.renderChildren(ancestorsIds, true)}</>;
}
@ -695,6 +709,7 @@ class Status extends ImmutablePureComponent {
<ScrollContainer scrollKey='thread'>
<div className={classNames('scrollable', { fullscreen })} ref={this.setRef}>
{references}
{ancestors}
<HotKeys handlers={handlers}>

View file

@ -11,33 +11,38 @@ import { compareId } from '../compare_id';
const initialState = ImmutableMap({
inReplyTos: ImmutableMap(),
replies: ImmutableMap(),
references: ImmutableMap(),
});
const normalizeContext = (immutableState, id, ancestors, descendants) => immutableState.withMutations(state => {
const normalizeContext = (immutableState, id, ancestors, descendants, references) => 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)) {
state.update('references', immutableReferences => immutableReferences.withMutations(referencePosts => {
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);
});
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);
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.
// 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);
ancestors.forEach(addReply);
if (ancestors[0]) {
addReply({ id, in_reply_to_id: ancestors[ancestors.length - 1].id });
}
if (ancestors[0]) {
addReply({ id, in_reply_to_id: ancestors[ancestors.length - 1].id });
}
descendants.forEach(addReply);
descendants.forEach(addReply);
referencePosts.set(id, ImmutableList(references.map((r) => r.id)));
}));
}));
}));
});
@ -96,7 +101,7 @@ export default function replies(state = initialState, action) {
case ACCOUNT_MUTE_SUCCESS:
return filterContexts(state, action.relationship, action.statuses);
case CONTEXT_FETCH_SUCCESS:
return normalizeContext(state, action.id, action.ancestors, action.descendants);
return normalizeContext(state, action.id, action.ancestors, action.descendants, action.references);
case TIMELINE_DELETE:
return deleteFromContexts(state, [action.id]);
case TIMELINE_UPDATE:

View file

@ -1,5 +1,5 @@
# frozen_string_literal: true
class Context < ActiveModelSerializers::Model
attributes :ancestors, :descendants
attributes :ancestors, :descendants, :references
end

View file

@ -3,4 +3,5 @@
class REST::ContextSerializer < ActiveModel::Serializer
has_many :ancestors, serializer: REST::StatusSerializer
has_many :descendants, serializer: REST::StatusSerializer
has_many :references, serializer: REST::StatusSerializer
end