From 91632aa1932b14b1a30df8fd44bdbff238699ac3 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Tue, 10 Jun 2025 10:14:17 +0530 Subject: [PATCH 1/3] Fix space navigation & view space timeline dev-option (#2358) * fix inaccessible space on alias change * fix new room in space open in home * allow opening space timeline * hide event timeline feature behind dev tool * add navToActivePath to clear cache function --- src/app/hooks/useRoomNavigate.ts | 16 ++++++-- src/app/pages/client/sidebar/SpaceTabs.tsx | 5 ++- src/app/pages/client/space/RoomProvider.tsx | 45 ++++++++++++++++----- src/app/pages/client/space/Space.tsx | 20 +++++++++ src/app/state/navToActivePath.ts | 8 +++- src/client/initMatrix.ts | 2 + 6 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/app/hooks/useRoomNavigate.ts b/src/app/hooks/useRoomNavigate.ts index 0f9f365c..e626c06b 100644 --- a/src/app/hooks/useRoomNavigate.ts +++ b/src/app/hooks/useRoomNavigate.ts @@ -13,6 +13,8 @@ import { getOrphanParents } from '../utils/room'; import { roomToParentsAtom } from '../state/room/roomToParents'; import { mDirectAtom } from '../state/mDirectList'; import { useSelectedSpace } from './router/useSelectedSpace'; +import { settingsAtom } from '../state/settings'; +import { useSetting } from '../state/hooks/settings'; export const useRoomNavigate = () => { const navigate = useNavigate(); @@ -20,6 +22,7 @@ export const useRoomNavigate = () => { const roomToParents = useAtomValue(roomToParentsAtom); const mDirects = useAtomValue(mDirectAtom); const spaceSelectedId = useSelectedSpace(); + const [developerTools] = useSetting(settingsAtom, 'developerTools'); const navigateSpace = useCallback( (roomId: string) => { @@ -32,15 +35,22 @@ export const useRoomNavigate = () => { const navigateRoom = useCallback( (roomId: string, eventId?: string, opts?: NavigateOptions) => { const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, roomId); + const openSpaceTimeline = developerTools && spaceSelectedId === roomId; - const orphanParents = getOrphanParents(roomToParents, roomId); + const orphanParents = openSpaceTimeline ? [roomId] : getOrphanParents(roomToParents, roomId); if (orphanParents.length > 0) { const pSpaceIdOrAlias = getCanonicalAliasOrRoomId( mx, spaceSelectedId && orphanParents.includes(spaceSelectedId) ? spaceSelectedId - : orphanParents[0] + : orphanParents[0] // TODO: better orphan parent selection. ); + + if (openSpaceTimeline) { + navigate(getSpaceRoomPath(pSpaceIdOrAlias, roomId, eventId), opts); + return; + } + navigate(getSpaceRoomPath(pSpaceIdOrAlias, roomIdOrAlias, eventId), opts); return; } @@ -52,7 +62,7 @@ export const useRoomNavigate = () => { navigate(getHomeRoomPath(roomIdOrAlias, eventId), opts); }, - [mx, navigate, spaceSelectedId, roomToParents, mDirects] + [mx, navigate, spaceSelectedId, roomToParents, mDirects, developerTools] ); return { diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx index 5b47cb52..011741ee 100644 --- a/src/app/pages/client/sidebar/SpaceTabs.tsx +++ b/src/app/pages/client/sidebar/SpaceTabs.tsx @@ -744,13 +744,14 @@ export function SpaceTabs({ scrollRef }: SpaceTabsProps) { const targetSpaceId = target.getAttribute('data-id'); if (!targetSpaceId) return; + const spacePath = getSpacePath(getCanonicalAliasOrRoomId(mx, targetSpaceId)); if (screenSize === ScreenSize.Mobile) { - navigate(getSpacePath(getCanonicalAliasOrRoomId(mx, targetSpaceId))); + navigate(spacePath); return; } const activePath = navToActivePath.get(targetSpaceId); - if (activePath) { + if (activePath && activePath.pathname.startsWith(spacePath)) { navigate(joinPathComponent(activePath)); return; } diff --git a/src/app/pages/client/space/RoomProvider.tsx b/src/app/pages/client/space/RoomProvider.tsx index a9632137..0fd52ab6 100644 --- a/src/app/pages/client/space/RoomProvider.tsx +++ b/src/app/pages/client/space/RoomProvider.tsx @@ -1,21 +1,24 @@ import React, { ReactNode } from 'react'; import { useParams } from 'react-router-dom'; -import { useAtomValue } from 'jotai'; +import { useAtom, useAtomValue } from 'jotai'; import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom'; import { IsDirectRoomProvider, RoomProvider } from '../../../hooks/useRoom'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { JoinBeforeNavigate } from '../../../features/join-before-navigate'; import { useSpace } from '../../../hooks/useSpace'; -import { getAllParents } from '../../../utils/room'; +import { getAllParents, getSpaceChildren } from '../../../utils/room'; import { roomToParentsAtom } from '../../../state/room/roomToParents'; import { allRoomsAtom } from '../../../state/room-list/roomList'; import { useSearchParamsViaServers } from '../../../hooks/router/useSearchParamsViaServers'; import { mDirectAtom } from '../../../state/mDirectList'; +import { settingsAtom } from '../../../state/settings'; +import { useSetting } from '../../../state/hooks/settings'; export function SpaceRouteRoomProvider({ children }: { children: ReactNode }) { const mx = useMatrixClient(); const space = useSpace(); - const roomToParents = useAtomValue(roomToParentsAtom); + const [developerTools] = useSetting(settingsAtom, 'developerTools'); + const [roomToParents, setRoomToParents] = useAtom(roomToParentsAtom); const mDirects = useAtomValue(mDirectAtom); const allRooms = useAtomValue(allRoomsAtom); @@ -24,12 +27,36 @@ export function SpaceRouteRoomProvider({ children }: { children: ReactNode }) { const roomId = useSelectedRoom(); const room = mx.getRoom(roomId); - if ( - !room || - room.isSpaceRoom() || - !allRooms.includes(room.roomId) || - !getAllParents(roomToParents, room.roomId).has(space.roomId) - ) { + if (!room || !allRooms.includes(room.roomId)) { + // room is not joined + return ( + + ); + } + + if (developerTools && room.isSpaceRoom() && room.roomId === space.roomId) { + // allow to view space timeline + return ( + + {children} + + ); + } + + if (!getAllParents(roomToParents, room.roomId).has(space.roomId)) { + if (getSpaceChildren(space).includes(room.roomId)) { + // fill missing roomToParent mapping + setRoomToParents({ + type: 'PUT', + parent: space.roomId, + children: [room.roomId], + }); + } + return ( (({ room, requestClose }, ref) => { const mx = useMatrixClient(); const [hideActivity] = useSetting(settingsAtom, 'hideActivity'); + const [developerTools] = useSetting(settingsAtom, 'developerTools'); const roomToParents = useAtomValue(roomToParentsAtom); const powerLevels = usePowerLevels(room); const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels); const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? '')); const openSpaceSettings = useOpenSpaceSettings(); + const { navigateRoom } = useRoomNavigate(); const allChild = useSpaceChildren( allRoomsAtom, @@ -118,6 +121,11 @@ const SpaceMenu = forwardRef(({ room, requestClo requestClose(); }; + const handleOpenTimeline = () => { + navigateRoom(room.roomId); + requestClose(); + }; + return ( @@ -168,6 +176,18 @@ const SpaceMenu = forwardRef(({ room, requestClo Space Settings + {developerTools && ( + } + radii="300" + > + + Event Timeline + + + )} diff --git a/src/app/state/navToActivePath.ts b/src/app/state/navToActivePath.ts index 80869146..af90c914 100644 --- a/src/app/state/navToActivePath.ts +++ b/src/app/state/navToActivePath.ts @@ -9,6 +9,8 @@ import { const NAV_TO_ACTIVE_PATH = 'navToActivePath'; +const getStoreKey = (userId: string): string => `${NAV_TO_ACTIVE_PATH}${userId}`; + type NavToActivePath = Map; type NavToActivePathAction = @@ -25,7 +27,7 @@ type NavToActivePathAction = export type NavToActivePathAtom = WritableAtom; export const makeNavToActivePathAtom = (userId: string): NavToActivePathAtom => { - const storeKey = `${NAV_TO_ACTIVE_PATH}${userId}`; + const storeKey = getStoreKey(userId); const baseNavToActivePathAtom = atomWithLocalStorage( storeKey, @@ -64,3 +66,7 @@ export const makeNavToActivePathAtom = (userId: string): NavToActivePathAtom => return navToActivePathAtom; }; + +export const clearNavToActivePathStore = (userId: string) => { + localStorage.removeItem(getStoreKey(userId)); +}; diff --git a/src/client/initMatrix.ts b/src/client/initMatrix.ts index b513e27c..b80a080f 100644 --- a/src/client/initMatrix.ts +++ b/src/client/initMatrix.ts @@ -1,6 +1,7 @@ import { createClient, MatrixClient, IndexedDBStore, IndexedDBCryptoStore } from 'matrix-js-sdk'; import { cryptoCallbacks } from './state/secretStorageKeys'; +import { clearNavToActivePathStore } from '../app/state/navToActivePath'; type Session = { baseUrl: string; @@ -46,6 +47,7 @@ export const startClient = async (mx: MatrixClient) => { export const clearCacheAndReload = async (mx: MatrixClient) => { mx.stopClient(); + clearNavToActivePathStore(mx.getSafeUserId()); await mx.store.deleteAllData(); window.location.reload(); }; From 44347db6e4f53cfe71f81d10891d3e6d43852d37 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Tue, 10 Jun 2025 19:17:46 +0530 Subject: [PATCH 2/3] Add allow from currently selected space if no m.space.parent found (#2359) --- .../common-settings/general/RoomJoinRules.tsx | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/app/features/common-settings/general/RoomJoinRules.tsx b/src/app/features/common-settings/general/RoomJoinRules.tsx index ebd4cad5..c0d62a6a 100644 --- a/src/app/features/common-settings/general/RoomJoinRules.tsx +++ b/src/app/features/common-settings/general/RoomJoinRules.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useMemo } from 'react'; import { color, Text } from 'folds'; import { JoinRule, MatrixError, RestrictedAllowType } from 'matrix-js-sdk'; import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types'; +import { useAtomValue } from 'jotai'; import { IPowerLevels, powerLevelAPI } from '../../../hooks/usePowerLevels'; import { ExtendedJoinRules, @@ -20,6 +21,12 @@ import { useStateEvent } from '../../../hooks/useStateEvent'; import { useSpaceOptionally } from '../../../hooks/useSpace'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { getStateEvents } from '../../../utils/room'; +import { + useRecursiveChildSpaceScopeFactory, + useSpaceChildren, +} from '../../../state/hooks/roomList'; +import { allRoomsAtom } from '../../../state/room-list/roomList'; +import { roomToParentsAtom } from '../../../state/room/roomToParents'; type RestrictedRoomAllowContent = { room_id: string; @@ -36,7 +43,11 @@ export function RoomJoinRules({ powerLevels }: RoomJoinRulesProps) { const allowKnockRestricted = roomVersion >= 10; const allowRestricted = roomVersion >= 8; const allowKnock = roomVersion >= 7; + + const roomIdToParents = useAtomValue(roomToParentsAtom); const space = useSpaceOptionally(); + const subspacesScope = useRecursiveChildSpaceScopeFactory(mx, roomIdToParents); + const subspaces = useSpaceChildren(allRoomsAtom, space?.roomId ?? '', subspacesScope); const userPowerLevel = powerLevelAPI.getPowerLevel(powerLevels, mx.getSafeUserId()); const canEdit = powerLevelAPI.canSendStateEvent( @@ -74,9 +85,22 @@ export function RoomJoinRules({ powerLevels }: RoomJoinRulesProps) { async (joinRule: ExtendedJoinRules) => { const allow: RestrictedRoomAllowContent[] = []; if (joinRule === JoinRule.Restricted || joinRule === 'knock_restricted') { - const parents = getStateEvents(room, StateEvent.SpaceParent).map((event) => - event.getStateKey() - ); + const roomParents = roomIdToParents.get(room.roomId); + + const parents = getStateEvents(room, StateEvent.SpaceParent) + .map((event) => event.getStateKey()) + .filter((parentId) => typeof parentId === 'string') + .filter((parentId) => roomParents?.has(parentId)); + + if (parents.length === 0 && space && roomParents) { + // if no m.space.parent found + // find parent in current space + const selectedParents = subspaces.filter((rId) => roomParents.has(rId)); + if (roomParents.has(space.roomId)) { + selectedParents.push(space.roomId); + } + selectedParents.forEach((pId) => parents.push(pId)); + } parents.forEach((parentRoomId) => { if (!parentRoomId) return; allow.push({ @@ -92,7 +116,7 @@ export function RoomJoinRules({ powerLevels }: RoomJoinRulesProps) { if (allow.length > 0) c.allow = allow; await mx.sendStateEvent(room.roomId, StateEvent.RoomJoinRules as any, c); }, - [mx, room] + [mx, room, space, subspaces, roomIdToParents] ) ); From 3ed826087727c01074302b5663c78f856f6968a8 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Tue, 10 Jun 2025 19:18:55 +0530 Subject: [PATCH 3/3] Release v4.8.1 (#2360) --- package-lock.json | 4 ++-- package.json | 2 +- src/app/pages/auth/AuthFooter.tsx | 2 +- src/app/pages/client/WelcomePage.tsx | 2 +- src/client/state/cons.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d82c219..7dd2bf0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cinny", - "version": "4.8.0", + "version": "4.8.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cinny", - "version": "4.8.0", + "version": "4.8.1", "license": "AGPL-3.0-only", "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "1.1.6", diff --git a/package.json b/package.json index b1b14e55..3c1cef8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cinny", - "version": "4.8.0", + "version": "4.8.1", "description": "Yet another matrix client", "main": "index.js", "type": "module", diff --git a/src/app/pages/auth/AuthFooter.tsx b/src/app/pages/auth/AuthFooter.tsx index 30f4b3ca..ff2fdb9b 100644 --- a/src/app/pages/auth/AuthFooter.tsx +++ b/src/app/pages/auth/AuthFooter.tsx @@ -15,7 +15,7 @@ export function AuthFooter() { target="_blank" rel="noreferrer" > - v4.8.0 + v4.8.1 Twitter diff --git a/src/app/pages/client/WelcomePage.tsx b/src/app/pages/client/WelcomePage.tsx index 88d38981..645753ff 100644 --- a/src/app/pages/client/WelcomePage.tsx +++ b/src/app/pages/client/WelcomePage.tsx @@ -24,7 +24,7 @@ export function WelcomePage() { target="_blank" rel="noreferrer noopener" > - v4.8.0 + v4.8.1 } diff --git a/src/client/state/cons.js b/src/client/state/cons.js index 3e9306b2..1cb8b102 100644 --- a/src/client/state/cons.js +++ b/src/client/state/cons.js @@ -1,5 +1,5 @@ const cons = { - version: '4.8.0', + version: '4.8.1', secretKey: { ACCESS_TOKEN: 'cinny_access_token', DEVICE_ID: 'cinny_device_id',