diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index bb772faa80..65cddbd225 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -279,6 +279,8 @@ export default class RightPanel extends React.Component { resizeNotifier={this.props.resizeNotifier} onClose={this.onClose} permalinkCreator={this.props.permalinkCreator} + allThreadsTimelineSet={this.props.room.threadsTimelineSets[0]} + myThreadsTimelineSet={this.props.room.threadsTimelineSets[1]} />; break; diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index 7544271186..f32fb52a2b 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -16,20 +16,7 @@ limitations under the License. import React, { useContext, useEffect, useRef, useState } from 'react'; import { EventTimelineSet } from 'matrix-js-sdk/src/models/event-timeline-set'; -import { Room } from 'matrix-js-sdk/src/models/room'; -import { MatrixClient } from 'matrix-js-sdk/src/client'; -import { - Filter, - IFilterDefinition, -} from 'matrix-js-sdk/src/filter'; -import { - FILTER_RELATED_BY_REL_TYPES, - FILTER_RELATED_BY_SENDERS, - Thread, - ThreadEvent, - THREAD_RELATION_TYPE, -} from 'matrix-js-sdk/src/models/thread'; -import { EventTimeline } from 'matrix-js-sdk/src/models/event-timeline'; +import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread'; import BaseCard from "../views/right_panel/BaseCard"; import ResizeNotifier from '../../utils/ResizeNotifier'; @@ -45,72 +32,13 @@ import Measured from '../views/elements/Measured'; import PosthogTrackers from "../../PosthogTrackers"; import { ButtonEvent } from "../views/elements/AccessibleButton"; -export async function getThreadTimelineSet( - client: MatrixClient, - room: Room, - filterType = ThreadFilterType.All, -): Promise { - if (Thread.hasServerSideSupport) { - const myUserId = client.getUserId(); - const filter = new Filter(myUserId); - - const definition: IFilterDefinition = { - "room": { - "timeline": { - [FILTER_RELATED_BY_REL_TYPES.name]: [THREAD_RELATION_TYPE.name], - }, - }, - }; - - if (filterType === ThreadFilterType.My) { - definition.room.timeline[FILTER_RELATED_BY_SENDERS.name] = [myUserId]; - } - - filter.setDefinition(definition); - const filterId = await client.getOrCreateFilter( - `THREAD_PANEL_${room.roomId}_${filterType}`, - filter, - ); - filter.filterId = filterId; - const timelineSet = room.getOrCreateFilteredTimelineSet( - filter, - { - prepopulateTimeline: false, - pendingEvents: false, - }, - ); - - // An empty pagination token allows to paginate from the very bottom of - // the timeline set. - timelineSet.getLiveTimeline().setPaginationToken("", EventTimeline.BACKWARDS); - - return timelineSet; - } else { - // Filter creation fails if HomeServer does not support the new relation - // filter fields. We fallback to the threads that have been discovered in - // the main timeline - const timelineSet = new EventTimelineSet(room, { - pendingEvents: false, - }); - - Array.from(room.threads) - .forEach(([, thread]) => { - if (thread.length === 0) return; - const currentUserParticipated = thread.events.some(event => event.getSender() === client.getUserId()); - if (filterType !== ThreadFilterType.My || currentUserParticipated) { - timelineSet.getLiveTimeline().addEvent(thread.rootEvent, false); - } - }); - - return timelineSet; - } -} - interface IProps { roomId: string; onClose: () => void; resizeNotifier: ResizeNotifier; permalinkCreator: RoomPermalinkCreator; + allThreadsTimelineSet: EventTimelineSet; + myThreadsTimelineSet: EventTimelineSet; } export enum ThreadFilterType { @@ -224,7 +152,13 @@ const EmptyThread: React.FC = ({ filterOption, showAllThreads ; }; -const ThreadPanel: React.FC = ({ roomId, onClose, permalinkCreator }) => { +const ThreadPanel: React.FC = ({ + roomId, + onClose, + permalinkCreator, + myThreadsTimelineSet, + allThreadsTimelineSet, +}) => { const mxClient = useContext(MatrixClientContext); const roomContext = useContext(RoomContext); const timelinePanel = useRef(); @@ -241,17 +175,8 @@ const ThreadPanel: React.FC = ({ roomId, onClose, permalinkCreator }) => }, [mxClient, roomId]); useEffect(() => { - async function onNewThread(thread: Thread, toStartOfTimeline: boolean): Promise { + function onNewThread(): void { setThreadCount(room.threads.size); - if (timelineSet) { - // When the server support threads we're only interested in adding - // the newly created threads to the list. - // The ones discovered when scrolling back should be discarded as - // they will be discovered by the `/messages` filter - if (!Thread.hasServerSideSupport || !toStartOfTimeline) { - timelineSet.addEventToTimeline(thread.rootEvent, timelineSet.getLiveTimeline(), toStartOfTimeline); - } - } } function refreshTimeline() { @@ -270,13 +195,17 @@ const ThreadPanel: React.FC = ({ roomId, onClose, permalinkCreator }) => }, [room, mxClient, timelineSet]); useEffect(() => { - getThreadTimelineSet(mxClient, room, filterOption) - .then(timelineSet => { setTimelineSet(timelineSet); }) - .catch(() => setTimelineSet(null)); - }, [mxClient, room, filterOption]); + if (filterOption === ThreadFilterType.My) { + setTimelineSet(myThreadsTimelineSet); + } else { + setTimelineSet(allThreadsTimelineSet); + } + }, [filterOption, allThreadsTimelineSet, myThreadsTimelineSet]); useEffect(() => { - if (timelineSet) timelinePanel.current.refreshTimeline(); + if (timelineSet && !Thread.hasServerSideSupport) { + timelinePanel.current.refreshTimeline(); + } }, [timelineSet, timelinePanel]); return ( diff --git a/test/components/structures/ThreadPanel-test.tsx b/test/components/structures/ThreadPanel-test.tsx index 4fea571968..aaee488409 100644 --- a/test/components/structures/ThreadPanel-test.tsx +++ b/test/components/structures/ThreadPanel-test.tsx @@ -16,24 +16,16 @@ limitations under the License. import React from 'react'; import { shallow, mount } from "enzyme"; -import { - MatrixClient, - Room, -} from 'matrix-js-sdk/src/matrix'; -import { mocked } from 'jest-mock'; import '../../skinned-sdk'; -import { Thread } from 'matrix-js-sdk/src/models/thread'; import { ThreadFilterType, ThreadPanelHeader, ThreadPanelHeaderFilterOptionItem, - getThreadTimelineSet, } from '../../../src/components/structures/ThreadPanel'; import { ContextMenuButton } from '../../../src/accessibility/context_menu/ContextMenuButton'; import ContextMenu from '../../../src/components/structures/ContextMenu'; import { _t } from '../../../src/languageHandler'; -import { makeThread } from '../../test-utils/threads'; describe('ThreadPanel', () => { describe('Header', () => { @@ -87,109 +79,4 @@ describe('ThreadPanel', () => { expect(foundButton).toMatchSnapshot(); }); }); - - describe('getThreadTimelineSet()', () => { - const filterId = '123'; - const client = { - getUserId: jest.fn(), - doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(false), - decryptEventIfNeeded: jest.fn().mockResolvedValue(undefined), - getOrCreateFilter: jest.fn().mockResolvedValue(filterId), - paginateEventTimeline: jest.fn().mockResolvedValue(undefined), - } as unknown as MatrixClient; - - const aliceId = '@alice:server.org'; - const bobId = '@bob:server.org'; - const charlieId = '@charlie:server.org'; - const room = new Room('!room1:server.org', client, aliceId); - - const roomWithThreads = new Room('!room2:server.org', client, aliceId); - const aliceAndBobThread = makeThread(client, roomWithThreads, { - authorId: aliceId, - participantUserIds: [aliceId, bobId], - roomId: roomWithThreads.roomId, - }); - const justBobThread = makeThread(client, roomWithThreads, { - authorId: bobId, - participantUserIds: [bobId], - roomId: roomWithThreads.roomId, - }); - const everyoneThread = makeThread(client, roomWithThreads, { - authorId: charlieId, - participantUserIds: [aliceId, bobId, charlieId], - length: 5, - roomId: roomWithThreads.roomId, - }); - roomWithThreads.threads.set(aliceAndBobThread.id, aliceAndBobThread); - roomWithThreads.threads.set(justBobThread.id, justBobThread); - roomWithThreads.threads.set(everyoneThread.id, everyoneThread); - - beforeEach(() => { - mocked(client.getUserId).mockReturnValue(aliceId); - mocked(client.doesServerSupportUnstableFeature).mockResolvedValue(false); - }); - - describe('when extra capabilities are not enabled on server', () => { - it('returns an empty timelineset when room has no threads', async () => { - const result = await getThreadTimelineSet(client, room); - - expect(result.getLiveTimeline().getEvents()).toEqual([]); - }); - - it('returns a timelineset with thread root events for room when filter is All', async () => { - const result = await getThreadTimelineSet(client, roomWithThreads); - - const resultEvents = result.getLiveTimeline().getEvents(); - expect(resultEvents.length).toEqual(3); - expect(resultEvents).toEqual(expect.arrayContaining([ - justBobThread.rootEvent, - aliceAndBobThread.rootEvent, - everyoneThread.rootEvent, - ])); - }); - - it('returns a timelineset with threads user has participated in when filter is My', async () => { - // current user is alice - mocked(client).getUserId.mockReturnValue(aliceId); - - const result = await getThreadTimelineSet(client, roomWithThreads, ThreadFilterType.My); - const resultEvents = result.getLiveTimeline().getEvents(); - expect(resultEvents).toEqual(expect.arrayContaining([ - // alice authored root event - aliceAndBobThread.rootEvent, - // alive replied to this thread - everyoneThread.rootEvent, - ])); - }); - }); - - describe('when extra capabilities are enabled on server', () => { - beforeEach(() => { - jest.clearAllMocks(); - Thread.hasServerSideSupport = true; - mocked(client.doesServerSupportUnstableFeature).mockResolvedValue(true); - }); - - it('creates a filter with correct definition when filterType is All', async () => { - await getThreadTimelineSet(client, room); - - const [filterKey, filter] = mocked(client).getOrCreateFilter.mock.calls[0]; - expect(filterKey).toEqual(`THREAD_PANEL_${room.roomId}_${ThreadFilterType.All}`); - expect(filter.getDefinition().room.timeline).toEqual({ - related_by_rel_types: ["m.thread"], - }); - }); - - it('creates a filter with correct definition when filterType is My', async () => { - await getThreadTimelineSet(client, room, ThreadFilterType.My); - - const [filterKey, filter] = mocked(client).getOrCreateFilter.mock.calls[0]; - expect(filterKey).toEqual(`THREAD_PANEL_${room.roomId}_${ThreadFilterType.My}`); - expect(filter.getDefinition().room.timeline).toEqual({ - related_by_rel_types: ["m.thread"], - related_by_senders: [aliceId], - }); - }); - }); - }); });