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) 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) descendants_results = @status.descendants(descendants_limit, current_account, descendants_depth_limit)
references_results = @status.references
loaded_ancestors = cache_collection(ancestors_results, Status) loaded_ancestors = cache_collection(ancestors_results, Status)
loaded_descendants = cache_collection(descendants_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) @context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants, references: loaded_references)
statuses = [@status] + @context.ancestors + @context.descendants statuses = [@status] + @context.ancestors + @context.descendants + @context.references
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id) render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
end end

View file

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

View file

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

View file

@ -11,33 +11,38 @@ import { compareId } from '../compare_id';
const initialState = ImmutableMap({ const initialState = ImmutableMap({
inReplyTos: ImmutableMap(), inReplyTos: ImmutableMap(),
replies: 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('inReplyTos', immutableAncestors => immutableAncestors.withMutations(inReplyTos => {
state.update('replies', immutableDescendants => immutableDescendants.withMutations(replies => { state.update('replies', immutableDescendants => immutableDescendants.withMutations(replies => {
function addReply({ id, in_reply_to_id }) { state.update('references', immutableReferences => immutableReferences.withMutations(referencePosts => {
if (in_reply_to_id && !inReplyTos.has(id)) { function addReply({ id, in_reply_to_id }) {
if (in_reply_to_id && !inReplyTos.has(id)) {
replies.update(in_reply_to_id, ImmutableList(), siblings => { replies.update(in_reply_to_id, ImmutableList(), siblings => {
const index = siblings.findLastIndex(sibling => compareId(sibling, id) < 0); const index = siblings.findLastIndex(sibling => compareId(sibling, id) < 0);
return siblings.insert(index + 1, id); 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. // We know in_reply_to_id of statuses but `id` itself.
// So we assume that the status of the id replies to last ancestors. // So we assume that the status of the id replies to last ancestors.
ancestors.forEach(addReply); ancestors.forEach(addReply);
if (ancestors[0]) { if (ancestors[0]) {
addReply({ id, in_reply_to_id: ancestors[ancestors.length - 1].id }); 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: case ACCOUNT_MUTE_SUCCESS:
return filterContexts(state, action.relationship, action.statuses); return filterContexts(state, action.relationship, action.statuses);
case CONTEXT_FETCH_SUCCESS: 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: case TIMELINE_DELETE:
return deleteFromContexts(state, [action.id]); return deleteFromContexts(state, [action.id]);
case TIMELINE_UPDATE: case TIMELINE_UPDATE:

View file

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

View file

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