Commit 11cc2db16d17
tarball1 file changed+49-4
@@ -157,6 +157,7 @@
157157
let spaceChildrenLoading = false;158158
let spaceChildrenError: string | null = null;159159
let avatarByUser: Record<string, string | null> = {};160
+ let restoringRoute = false;160161
161162
// account data is read from the local cache, which is empty on a fresh device until the first sync lands162163
$: {@@ -291,9 +292,47 @@
291292
return r.unread_messages + r.unread_mentions;292293
}293294
295
+ function recordRoute(spaceId: string | null, roomId: string | null): void {296
+ if (restoringRoute) return;297
+ const url = new URL(window.location.href);298
+ url.searchParams.delete('space');299
+ url.searchParams.delete('room');300
+ if (spaceId) url.searchParams.set('space', spaceId);301
+ if (roomId) url.searchParams.set('room', roomId);302
+ const next = `${url.pathname}${url.search}${url.hash}`;303
+ const current = `${window.location.pathname}${window.location.search}${window.location.hash}`;304
+ if (next !== current) window.history.pushState({ spaceId, roomId }, '', next);305
+ }306
+307
+ async function restoreRoute(): Promise<void> {308
+ const url = new URL(window.location.href);309
+ const roomId = url.searchParams.get('room');310
+ const spaceId = url.searchParams.get('space');311
+ restoringRoute = true;312
+ try {313
+ if (roomId && $rooms.some((room) => room.room_id === roomId && !room.space)) {314
+ selectedSpaceId = spaceId && $rooms.some((room) => room.room_id === spaceId && room.space) ? spaceId : null;315
+ spaceChildrenError = null;316
+ spaceChildIds = selectedSpaceId ? (await getSpaceChildren(selectedSpaceId)).room_ids ?? [] : null;317
+ await selectRoom(roomId);318
+ } else if (spaceId && $rooms.some((room) => room.room_id === spaceId && room.space)) {319
+ await selectSpace(spaceId);320
+ } else {321
+ await selectHome();322
+ }323
+ } catch (e) {324
+ pushBanner(e instanceof Error ? e.message : 'Could not restore navigation');325
+ } finally {326
+ restoringRoute = false;327
+ }328
+ }329
+294330
onMount(() => {331
+ const onPopState = () => void restoreRoute();332
+ window.addEventListener('popstate', onPopState);295333
void boot();296334
return () => {335
+ window.removeEventListener('popstate', onPopState);297336
disconnectEvents();298337
stopTyping();299338
if (readTimer !== null) window.clearTimeout(readTimer);@@ -308,6 +347,7 @@
308347
if (session.user_id) {309348
softLogout.set(null);310349
await refreshRooms();350
+ await restoreRoute();311351
connectEvents();312352
void loadProfile();313353
void loadEncryption();@@ -451,6 +491,7 @@
451491
spaceChildrenError = null;452492
if (window.innerWidth > 640) navFolded.set(false);453493
sidebarOpen = true;494
+ recordRoute(null, null);454495
}455496
456497
function toggleChannelsPane(): void {@@ -504,6 +545,7 @@
504545
const spaceId = selectedSpaceId;505546
if (spaceId) lastSpaceRooms.update((all) => ({ ...all, [spaceId]: roomId }));506547
selectedRoomId.set(roomId);548
+ recordRoute(selectedSpaceId, roomId);507549
composer = drafts[roomId] ?? '';508550
replyingTo = null;509551
editing = null;@@ -563,6 +605,7 @@
563605
564606
let userScrollUntil = 0;565607
let draggingScrollbar = false;608
+ let paginationAnchor: { roomId: string; top: number; height: number } | null = null;566609
567610
function noteUserScroll(): void {568611
userScrollUntil = performance.now() + 300;@@ -581,6 +624,7 @@
581624
const gap = timelineEl.scrollHeight - timelineEl.scrollTop - timelineEl.clientHeight;582625
if (gap < 80) stickToBottom = true;583626
else if (draggingScrollbar || performance.now() < userScrollUntil) stickToBottom = false;627
+ if (timelineEl.scrollTop < 80 && !hasStart && !paginateBusy) void paginate();584628
}585629
586630
function pinTimeline(node: HTMLElement) {@@ -597,7 +641,10 @@
597641
if (!roomId || list.length === 0) return;598642
if (roomId !== $selectedRoomId) return;599643
await tick();600
- if (stickToBottom) scrollToBottom();644
+ if (paginationAnchor?.roomId === roomId && timelineEl && !stickToBottom) {645
+ timelineEl.scrollTop = paginationAnchor.top + timelineEl.scrollHeight - paginationAnchor.height;646
+ paginationAnchor = null;647
+ } else if (stickToBottom) scrollToBottom();601648
queueReadReceipt();602649
}603650
@@ -1227,6 +1274,7 @@
12271274
async function paginate(): Promise<void> {12281275
const roomId = $selectedRoomId;12291276
if (!roomId || paginateBusy) return;1277
+ if (timelineEl && !stickToBottom) paginationAnchor = { roomId, top: timelineEl.scrollTop, height: timelineEl.scrollHeight };12301278
paginateBusy = true;12311279
try {12321280
await paginateRoom(roomId);@@ -1870,9 +1918,6 @@
18701918
</button>18711919
{#if roomMenuOpen}18721920
<div class="room-menu" role="menu">1873
- <button type="button" role="menuitem" on:click={() => roomMenuAction(() => (dialog = 'security'))}>1874
- Security1875
- </button>18761921
{#if selectedRoom.membership === 'Joined'}18771922
<button18781923
type="button"