From 801d17f34de5be1f8710b3a1db4fa708bc740e4b Mon Sep 17 00:00:00 2001 From: KMY Date: Thu, 6 Jul 2023 17:46:32 +0900 Subject: [PATCH] Add reference toots view as thread --- app/controllers/api/v1/statuses_controller.rb | 6 ++- app/javascript/mastodon/actions/statuses.js | 7 ++-- .../mastodon/features/status/index.jsx | 23 +++++++++-- app/javascript/mastodon/reducers/contexts.js | 39 +++++++++++-------- app/models/context.rb | 2 +- app/serializers/rest/context_serializer.rb | 1 + 6 files changed, 51 insertions(+), 27 deletions(-) diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 6b40181178..efc1289731 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -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 diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 2d987aef5c..b9779cdcf4 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -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), }; } diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index ffca0063f1..a0db0a1e16 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -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 {
+ {references} {ancestors} diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js index 32e194dd42..ed35bfa3a9 100644 --- a/app/javascript/mastodon/reducers/contexts.js +++ b/app/javascript/mastodon/reducers/contexts.js @@ -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: diff --git a/app/models/context.rb b/app/models/context.rb index cc667999ed..533d065a68 100644 --- a/app/models/context.rb +++ b/app/models/context.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class Context < ActiveModelSerializers::Model - attributes :ancestors, :descendants + attributes :ancestors, :descendants, :references end diff --git a/app/serializers/rest/context_serializer.rb b/app/serializers/rest/context_serializer.rb index 44515c85d7..21032228f5 100644 --- a/app/serializers/rest/context_serializer.rb +++ b/app/serializers/rest/context_serializer.rb @@ -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