diff --git a/app/soapbox/features/status/index.js b/app/soapbox/features/status/index.js index fbb17bae6..9b143b604 100644 --- a/app/soapbox/features/status/index.js +++ b/app/soapbox/features/status/index.js @@ -80,7 +80,7 @@ const makeMapStateToProps = () => { let ancestorsIds = ImmutableOrderedSet(); let id = statusId; - while (id) { + while (id && !ancestorsIds.includes(id)) { ancestorsIds = ImmutableOrderedSet([id]).union(ancestorsIds); id = inReplyTos.get(id); } @@ -99,6 +99,10 @@ const makeMapStateToProps = () => { const id = ids.shift(); const replies = contextReplies.get(id); + if (descendantsIds.includes(id)) { + break; + } + if (statusId !== id) { descendantsIds = descendantsIds.union([id]); } @@ -119,8 +123,11 @@ const makeMapStateToProps = () => { let descendantsIds = ImmutableOrderedSet(); if (status) { - ancestorsIds = getAncestorsIds(state, { id: state.getIn(['contexts', 'inReplyTos', status.get('id')]) }); - descendantsIds = getDescendantsIds(state, { id: status.get('id') }); + const statusId = status.get('id'); + ancestorsIds = getAncestorsIds(state, { id: state.getIn(['contexts', 'inReplyTos', statusId]) }); + descendantsIds = getDescendantsIds(state, { id: statusId }); + ancestorsIds = ancestorsIds.delete(statusId).subtract(descendantsIds); + descendantsIds = descendantsIds.delete(statusId).subtract(ancestorsIds); } const soapbox = getSoapboxConfig(state); diff --git a/app/soapbox/reducers/contexts.js b/app/soapbox/reducers/contexts.js index 9bcf87b9a..d6780c124 100644 --- a/app/soapbox/reducers/contexts.js +++ b/app/soapbox/reducers/contexts.js @@ -39,7 +39,25 @@ const importStatuses = (state, statuses) => { }); }; +const isReplyTo = (state, childId, parentId, initialId = null) => { + if (!childId) return false; + + // Prevent cycles + if (childId === initialId) return false; + initialId = initialId || childId; + + if (childId === parentId) { + return true; + } else { + const nextId = state.getIn(['inReplyTos', childId]); + return isReplyTo(state, nextId, parentId, initialId); + } +}; + const insertTombstone = (state, ancestorId, descendantId) => { + // Prevent infinite loop if the API returns a bogus response + if (isReplyTo(state, ancestorId, descendantId)) return state; + const tombstoneId = `${descendantId}-tombstone`; return state.withMutations(state => { importStatus(state, { id: tombstoneId, in_reply_to_id: ancestorId });