atagen

malice

public
Star0
Watch0

Commit 7efc4c6d41ab

tarball

atagen <boss@atagen.co>2026-10-24 11:40 UTC

2 files changed+67-20
web: refine responsive panes and pin previews
Modified file: web/src/App.svelte+42 additions−4 deletions
@@ -449,11 +449,12 @@
449449 selectedSpaceId = null;
450450 spaceChildIds = null;
451451 spaceChildrenError = null;
452+ if (window.innerWidth > 640) navFolded.set(false);
452453 sidebarOpen = true;
453454 }
454455
455456 function toggleChannelsPane(): void {
456- if (window.innerWidth <= 1100) sidebarOpen = !sidebarOpen;
457+ if (window.innerWidth <= 640) sidebarOpen = !sidebarOpen;
457458 else navFolded.update((v) => !v);
458459 }
459460
@@ -469,6 +470,7 @@
469470 if (resume) {
470471 void selectRoom(resume);
471472 } else {
473+ if (window.innerWidth > 640) navFolded.set(false);
472474 sidebarOpen = true;
473475 // A space is navigation, not a message timeline.
474476 selectedRoomId.set(null);
@@ -478,6 +480,7 @@
478480 if (selectedSpaceId === spaceId) {
479481 spaceChildIds = res.room_ids ?? [];
480482 if (resume && !spaceChildIds.includes(resume) && $selectedRoomId === resume) {
483+ if (window.innerWidth > 640) navFolded.set(false);
481484 sidebarOpen = true;
482485 selectedRoomId.set(null);
483486 }
@@ -924,6 +927,8 @@
924927
925928 let pinnedOpenFor: string | null = null;
926929 let pinsExpanded = false;
930+ let pinPreview: TimelineEvent | null = null;
931+ let pinJumpBusy = false;
927932 $: syncPinned(selectedRoom?.room_id ?? null, (selectedRoom?.pinned_events.length ?? 0) > 0);
928933 function syncPinned(roomId: string | null, hasPins: boolean): void {
929934 const want = hasPins ? roomId : null;
@@ -951,9 +956,29 @@
951956 );
952957 }
953958
954- function jumpToPin(eventId: string): void {
959+ async function jumpToPin(eventId: string): Promise<void> {
960+ const roomId = $selectedRoomId;
961+ if (!roomId || !eventId || pinJumpBusy) return;
955962 pinsExpanded = false;
956- jumpTo(eventId);
963+ pinPreview = pins.find((event) => event.event_id === eventId) ?? null;
964+ pinJumpBusy = true;
965+ try {
966+ for (let attempt = 0; attempt < 24; attempt += 1) {
967+ if (($timelines[roomId] ?? []).some((item) => item.kind === 'event' && item.event.event_id === eventId)) {
968+ pinPreview = null;
969+ await tick();
970+ jumpTo(eventId);
971+ return;
972+ }
973+ await paginateRoom(roomId);
974+ await new Promise((resolve) => window.setTimeout(resolve, 180));
975+ }
976+ pushBanner('Could not load that pinned message');
977+ } catch (e) {
978+ pushBanner(e instanceof Error ? e.message : 'Could not load that pinned message');
979+ } finally {
980+ pinJumpBusy = false;
981+ }
957982 }
958983
959984 function jumpTo(eventId: string): void {
@@ -1716,7 +1741,7 @@
17161741 {:else}
17171742 <div
17181743 class="app"
1719- class:nav-folded={$navFolded && (selectedRoom || sidebarOpen)}
1744+ class:nav-folded={$navFolded}
17201745 class:rail-folded={$railFolded && selectedRoom}
17211746 class:members-open={selectedRoom && membersOpen}
17221747 class:sidebar-open={sidebarOpen}
@@ -2257,6 +2282,19 @@
22572282 <p class="conn-banner error" role="alert">{openFailed[$selectedRoomId]}</p>
22582283 {/if}
22592284
2285+ {#if pinPreview}
2286+ <div class="pin-preview" role="dialog" aria-label="Pinned message preview">
2287+ <div class="pin-preview-head">
2288+ <strong>{people[pinPreview.sender.user_id]?.name ?? senderLabel(pinPreview.sender)}</strong>
2289+ <button type="button" class="icon-btn" aria-label="Close pinned message preview" on:click={() => (pinPreview = null)}>✕</button>
2290+ </div>
2291+ <p>{contentPreview(pinPreview.content)}</p>
2292+ <button type="button" class="primary-btn" disabled={pinJumpBusy} on:click={() => void jumpToPin(pinPreview?.event_id ?? '')}>
2293+ {pinJumpBusy ? 'Loading message…' : 'Jump to message'}
2294+ </button>
2295+ </div>
2296+ {/if}
2297+
22602298 <!-- svelte-ignore a11y_no_static_element_interactions -->
22612299 <div
22622300 class="timeline"
Modified file: web/src/app.css+25 additions−16 deletions
@@ -282,10 +282,11 @@ input, textarea { font: inherit; }
282282 min-height: 0;
283283 }
284284 .side-search {
285+ order: 2;
285286 display: flex;
286287 gap: 4px;
287288 padding: 8px;
288- border-bottom: 1px solid var(--border);
289+ border-top: 1px solid var(--border);
289290 }
290291 .side-search input {
291292 flex: 1;
@@ -298,7 +299,7 @@ input, textarea { font: inherit; }
298299 padding: 0 10px;
299300 }
300301 .side-search input:focus { outline: none; border-color: var(--accent); }
301-.side-scroll { flex: 1; overflow-y: auto; padding: 6px; min-height: 0; }
302+.side-scroll { order: 1; flex: 1; overflow-y: auto; padding: 6px; min-height: 0; }
302303 .side-h {
303304 font-size: 10.5px;
304305 font-weight: 650;
@@ -463,7 +464,7 @@ input, textarea { font: inherit; }
463464 .scrim { display: none; }
464465
465466 /* ——— Chat ——— */
466-.chat { display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--bg); }
467+.chat { position: relative; display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--bg); }
467468 .empty {
468469 margin: auto;
469470 text-align: center;
@@ -583,7 +584,7 @@ input, textarea { font: inherit; }
583584 .ghost-btn.danger { color: var(--danger); }
584585 .ghost-btn.danger:hover:not(:disabled) { border-color: var(--danger); }
585586
586-.banners { display: flex; flex-direction: column; gap: 6px; padding: 8px 12px 0; }
587+.banners { position: absolute; top: 8px; left: 12px; right: 12px; z-index: 12; display: flex; flex-direction: column; gap: 6px; pointer-events: none; }
587588 .banner {
588589 display: flex;
589590 align-items: center;
@@ -595,6 +596,7 @@ input, textarea { font: inherit; }
595596 padding: 6px 10px;
596597 font-size: 13px;
597598 animation: pop-in var(--pop);
599+ pointer-events: auto;
598600 }
599601 .banner span { flex: 1; }
600602 .banner button {
@@ -613,6 +615,10 @@ input, textarea { font: inherit; }
613615 color: var(--warn-text);
614616 font-size: 12.5px;
615617 }
618+.pin-preview { position: absolute; top: 56px; right: 16px; z-index: 11; width: min(360px, calc(100% - 32px)); padding: 12px; border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--panel); box-shadow: 0 12px 36px rgb(0 0 0 / 0.35); animation: pop-in var(--pop); }
619+.pin-preview-head { display: flex; align-items: center; gap: 8px; }
620+.pin-preview-head strong { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
621+.pin-preview p { margin: 8px 0 12px; color: var(--muted); overflow-wrap: anywhere; }
616622
617623 .timeline {
618624 flex: 1;
@@ -946,19 +952,22 @@ input, textarea { font: inherit; }
946952 .only-mobile { display: none; }
947953 .only-desktop { display: inline-block; }
948954 .app { transition: grid-template-columns var(--pane-ease); }
949- .app > .sidebar { left: var(--rail-w); transform: translateX(calc(-100% - var(--rail-w))); }
950- .app > .sidebar.open { transform: none; }
951- .scrim { left: var(--rail-w); }
952- .app > .account {
953- position: fixed;
954- left: var(--rail-w);
955- bottom: 0;
956- z-index: 41;
957- width: var(--side-open, 260px);
958- transform: translateX(calc(-100% - var(--rail-w)));
959- transition: transform var(--pane-ease);
955+ .app { --side-w: var(--side-open, 260px); }
956+ .app.rail-folded { --rail-w: 0px; }
957+ .app.rail-folded > .rail { visibility: hidden !important; }
958+ .app > .sidebar {
959+ position: relative;
960+ left: auto;
961+ top: auto;
962+ bottom: auto;
963+ width: auto;
964+ transform: none;
965+ box-shadow: none;
966+ transition: visibility 0s linear 0s;
960967 }
961- .app.sidebar-open > .account { transform: none; }
968+ .app > .sidebar > * { min-width: var(--side-open, 260px); }
969+ .app.nav-folded > .sidebar { visibility: hidden !important; transition: visibility 0s linear 0.22s; }
970+ .scrim { display: none; }
962971 .app > .members {
963972 position: relative;
964973 top: auto;