Room list: remove usage of algorithms of old room list store (#33975)
* feat:add missing methods to rls V3 * feat: use rls v3 methods instead of old alrgorithms * feat: move stabel function to own utils * feat: use this new moved functions * test: cleaner test
This commit is contained in:
@@ -27,7 +27,7 @@ import DMRoomMap from "../../../utils/DMRoomMap";
|
|||||||
import { calculateRoomVia } from "../../../utils/permalinks/Permalinks";
|
import { calculateRoomVia } from "../../../utils/permalinks/Permalinks";
|
||||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import { sortRooms } from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
|
import { sortRoomsByRecency } from "../../../utils/room/sortRoomsByRecency";
|
||||||
import ProgressBar from "../elements/ProgressBar";
|
import ProgressBar from "../elements/ProgressBar";
|
||||||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||||
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
||||||
@@ -172,7 +172,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const joinRule = space.getJoinRule();
|
const joinRule = space.getJoinRule();
|
||||||
return sortRooms(rooms).reduce<[spaces: Room[], rooms: Room[], dms: Room[]]>(
|
return sortRoomsByRecency(rooms, cli.getSafeUserId()).reduce<[spaces: Room[], rooms: Room[], dms: Room[]]>(
|
||||||
(arr, room) => {
|
(arr, room) => {
|
||||||
if (room.isSpaceRoom()) {
|
if (room.isSpaceRoom()) {
|
||||||
if (room !== space && !existingSubspacesSet.has(room)) {
|
if (room !== space && !existingSubspacesSet.has(room)) {
|
||||||
@@ -190,7 +190,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
|||||||
},
|
},
|
||||||
[[], [], []],
|
[[], [], []],
|
||||||
);
|
);
|
||||||
}, [visibleRooms, space, lcQuery, existingRoomsSet, existingSubspacesSet]);
|
}, [visibleRooms, space, lcQuery, existingRoomsSet, existingSubspacesSet, cli]);
|
||||||
|
|
||||||
const addRooms = async (): Promise<void> => {
|
const addRooms = async (): Promise<void> => {
|
||||||
setError(false);
|
setError(false);
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
|||||||
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
|
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
|
||||||
import NotificationBadge from "../rooms/NotificationBadge";
|
import NotificationBadge from "../rooms/NotificationBadge";
|
||||||
import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||||
import { sortRooms } from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
|
import { sortRoomsByRecency } from "../../../utils/room/sortRoomsByRecency";
|
||||||
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
||||||
import TruncatedList from "../elements/TruncatedList";
|
import TruncatedList from "../elements/TruncatedList";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
@@ -284,10 +284,11 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
|
|||||||
|
|
||||||
let rooms = useMemo(
|
let rooms = useMemo(
|
||||||
() =>
|
() =>
|
||||||
sortRooms(
|
sortRoomsByRecency(
|
||||||
cli
|
cli
|
||||||
.getVisibleRooms(msc3946DynamicRoomPredecessors)
|
.getVisibleRooms(msc3946DynamicRoomPredecessors)
|
||||||
.filter((room) => room.getMyMembership() === KnownMembership.Join && !room.isSpaceRoom()),
|
.filter((room) => room.getMyMembership() === KnownMembership.Join && !room.isSpaceRoom()),
|
||||||
|
cli.getSafeUserId(),
|
||||||
),
|
),
|
||||||
[cli, msc3946DynamicRoomPredecessors],
|
[cli, msc3946DynamicRoomPredecessors],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ import SettingsStore from "../../../../settings/SettingsStore";
|
|||||||
import { BreadcrumbsStore } from "../../../../stores/BreadcrumbsStore";
|
import { BreadcrumbsStore } from "../../../../stores/BreadcrumbsStore";
|
||||||
import { type RoomNotificationState } from "../../../../stores/notifications/RoomNotificationState";
|
import { type RoomNotificationState } from "../../../../stores/notifications/RoomNotificationState";
|
||||||
import { RoomNotificationStateStore } from "../../../../stores/notifications/RoomNotificationStateStore";
|
import { RoomNotificationStateStore } from "../../../../stores/notifications/RoomNotificationStateStore";
|
||||||
import { RecentAlgorithm } from "../../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
|
import { compareRoomsByRecency } from "../../../../utils/room/sortRoomsByRecency";
|
||||||
import { SdkContextClass } from "../../../../contexts/SDKContext";
|
import { SdkContextClass } from "../../../../contexts/SDKContext";
|
||||||
import { getMetaSpaceName, MetaSpace } from "../../../../stores/spaces";
|
import { getMetaSpaceName, MetaSpace } from "../../../../stores/spaces";
|
||||||
import SpaceStore from "../../../../stores/spaces/SpaceStore";
|
import SpaceStore from "../../../../stores/spaces/SpaceStore";
|
||||||
@@ -252,8 +252,6 @@ const toMemberResult = (member: Member | RoomMember, alreadyFiltered: boolean):
|
|||||||
query: [member.userId.toLowerCase(), member.name.toLowerCase()].filter(Boolean),
|
query: [member.userId.toLowerCase(), member.name.toLowerCase()].filter(Boolean),
|
||||||
});
|
});
|
||||||
|
|
||||||
const recentAlgorithm = new RecentAlgorithm();
|
|
||||||
|
|
||||||
export const useWebSearchMetrics = (numResults: number, queryLength: number, viaSpotlight: boolean): void => {
|
export const useWebSearchMetrics = (numResults: number, queryLength: number, viaSpotlight: boolean): void => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!queryLength) return;
|
if (!queryLength) return;
|
||||||
@@ -498,7 +496,6 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort results by most recent activity
|
// Sort results by most recent activity
|
||||||
|
|
||||||
const myUserId = cli.getSafeUserId();
|
const myUserId = cli.getSafeUserId();
|
||||||
for (const resultArray of Object.values(results)) {
|
for (const resultArray of Object.values(results)) {
|
||||||
resultArray.sort((a: Result, b: Result) => {
|
resultArray.sort((a: Result, b: Result) => {
|
||||||
@@ -507,7 +504,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||||||
if (!isRoomResult(b)) return -1;
|
if (!isRoomResult(b)) return -1;
|
||||||
if (!isRoomResult(a)) return -1;
|
if (!isRoomResult(a)) return -1;
|
||||||
|
|
||||||
return recentAlgorithm.getLastTs(b.room, myUserId) - recentAlgorithm.getLastTs(a.room, myUserId);
|
return compareRoomsByRecency(a.room, b.room, myUserId);
|
||||||
} else if (isMemberResult(a) || isMemberResult(b)) {
|
} else if (isMemberResult(a) || isMemberResult(b)) {
|
||||||
// Member results should appear just after room results
|
// Member results should appear just after room results
|
||||||
if (!isMemberResult(b)) return -1;
|
if (!isMemberResult(b)) return -1;
|
||||||
@@ -520,7 +517,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}, [trimmedQuery, filter, cli, possibleResults, userDirectorySearchResults, memberComparator]);
|
}, [cli, trimmedQuery, filter, possibleResults, userDirectorySearchResults, memberComparator]);
|
||||||
|
|
||||||
const numResults = sum(Object.values(results).map((it) => it.length));
|
const numResults = sum(Object.values(results).map((it) => it.length));
|
||||||
useWebSearchMetrics(numResults, query.length, true);
|
useWebSearchMetrics(numResults, query.length, true);
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { getLastTimestamp } from "../../stores/room-list-v3/skip-list/sorters/utils/getLastTimestamp";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort an arbitrary list of rooms by recency (most recent activity first).
|
||||||
|
*
|
||||||
|
* Unlike the main room list sorting, this is a pure recency sort: muted and
|
||||||
|
* low-priority rooms are NOT moved to the bottom. The provided array is not
|
||||||
|
* mutated; a sorted copy is returned. The given list is sorted as-is and is
|
||||||
|
* not filtered by the active space.
|
||||||
|
* @param rooms The rooms to sort.
|
||||||
|
* @param userId The mxId of the current user.
|
||||||
|
*/
|
||||||
|
export function sortRoomsByRecency(rooms: Room[], userId: string): Room[] {
|
||||||
|
const cache = new Map<string, number>();
|
||||||
|
const ts = (room: Room): number => {
|
||||||
|
let value = cache.get(room.roomId);
|
||||||
|
if (value === undefined) {
|
||||||
|
value = getLastTimestamp(room, userId);
|
||||||
|
cache.set(room.roomId, value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
return [...rooms].sort((a, b) => ts(b) - ts(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two rooms by recency, most recent activity first.
|
||||||
|
*
|
||||||
|
* Intended for sorting mixed result sets where rooms need to be ordered
|
||||||
|
* relative to each other (e.g. spotlight search results).
|
||||||
|
* @param roomA The first room.
|
||||||
|
* @param roomB The second room.
|
||||||
|
* @param userId The mxId of the current user.
|
||||||
|
*/
|
||||||
|
export function compareRoomsByRecency(roomA: Room, roomB: Room, userId: string): number {
|
||||||
|
return getLastTimestamp(roomB, userId) - getLastTimestamp(roomA, userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import { createTestClient } from "../../../test-utils";
|
||||||
|
import { getMockedRooms } from "../../stores/room-list-v3/skip-list/getMockedRooms";
|
||||||
|
import { DefaultTagID } from "../../../../src/stores/room-list-v3/skip-list/tag";
|
||||||
|
import { compareRoomsByRecency, sortRoomsByRecency } from "../../../../src/utils/room/sortRoomsByRecency";
|
||||||
|
|
||||||
|
describe("sortRoomsByRecency", () => {
|
||||||
|
let userId: string;
|
||||||
|
let rooms: Room[];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const client = createTestClient();
|
||||||
|
userId = client.getSafeUserId();
|
||||||
|
rooms = getMockedRooms(client);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("sortRoomsByRecency", () => {
|
||||||
|
it("sorts an arbitrary list by recency without mutating the input", () => {
|
||||||
|
const input = [rooms[0], rooms[5], rooms[2]];
|
||||||
|
const inputCopy = [...input];
|
||||||
|
|
||||||
|
const sorted = sortRoomsByRecency(input, userId);
|
||||||
|
|
||||||
|
// ts: room5 (6) > room2 (3) > room0 (1)
|
||||||
|
expect(sorted).toEqual([rooms[5], rooms[2], rooms[0]]);
|
||||||
|
// The input array is not mutated.
|
||||||
|
expect(input).toEqual(inputCopy);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not move muted or low-priority rooms (pure recency)", () => {
|
||||||
|
const recent = rooms[99]; // highest ts
|
||||||
|
recent.tags = { [DefaultTagID.LowPriority]: { order: 0 } };
|
||||||
|
|
||||||
|
const sorted = sortRoomsByRecency([rooms[0], rooms[50], recent], userId);
|
||||||
|
|
||||||
|
// A pure recency sort keeps the most recent room first even though it is
|
||||||
|
// low priority (the full RecencySorter would sink it).
|
||||||
|
expect(sorted[0]).toBe(recent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("compareRoomsByRecency", () => {
|
||||||
|
it("orders the more recent room first", () => {
|
||||||
|
// rooms[10] (ts 11) is more recent than rooms[3] (ts 4)
|
||||||
|
expect(compareRoomsByRecency(rooms[10], rooms[3], userId)).toBeLessThan(0);
|
||||||
|
expect(compareRoomsByRecency(rooms[3], rooms[10], userId)).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user