nandi/gleanpublic Fork 0
d8c42c5
Commits
Clone
git clone https://git.rickub.com/nandi/glean.git
git clone ssh://git@rickub.com/nandi/glean.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

fix: refresh page on new articles banner clickUnverified

Julien Robert committed 2026-07-03T10:25:25+02:00 Browse files
d8c42c5 parent: 5493a74
modified web/src/lib/components/NewArticlesBanner.svelte +22 -9
@@ -1,6 +1,5 @@
11 <script lang="ts">
22 import { onMount } from "svelte";
3- import { invalidateAll } from "$app/navigation";
43 import { endpoints } from "$lib/api";
54 import Icon from "./Icon.svelte";
65
@@ -9,10 +8,16 @@
98 since: number;
109 /** Polling interval in milliseconds. */
1110 interval?: number;
11+ /**
12+ * Reload handler invoked when the user clicks "Show". Should perform
13+ * a full route reload so the new articles render.
14+ */
15+ onrefresh: () => Promise<unknown>;
1216 }
13- let { since, interval = 60_000 }: Props = $props();
17+ let { since, interval = 60_000, onrefresh }: Props = $props();
1418
1519 let count = $state(0);
20+ let refreshing = $state(false);
1621 let timer: ReturnType<typeof setInterval>;
1722
1823 onMount(() => {
@@ -22,7 +27,7 @@
2227
2328 async function poll() {
2429 // Hide the banner for inactive tabs; the next visibility change polls.
25- if (document.hidden) return;
30+ if (document.hidden || refreshing) return;
2631 try {
2732 const res = await endpoints.newArticleCount(since);
2833 count = res.count;
@@ -32,25 +37,33 @@
3237 }
3338
3439 async function refresh() {
35- since = Math.floor(Date.now() / 1000);
36- count = 0;
37- await invalidateAll();
40+ if (refreshing) return;
41+ refreshing = true;
42+ try {
43+ await onrefresh();
44+ count = 0;
45+ } finally {
46+ refreshing = false;
47+ }
3848 }
3949 </script>
4050
41-{#if count > 0}
51+{#if count > 0 || refreshing}
4252 <div
4353 class="mb-4 flex items-center justify-between gap-3 border-2 border-[var(--border)] bg-[var(--accent)] px-4 py-2.5 text-[var(--accent-ink)] shadow-[4px_4px_0_0_var(--border)] dark:text-white"
4454 role="status"
4555 >
4656 <span class="flex items-center gap-2 text-sm font-bold">
4757 <Icon name="sparkles" class="h-4 w-4" />
48- {count} new article{count === 1 ? "" : "s"}
58+ {refreshing
59+ ? "Loading…"
60+ : `${count} new article${count === 1 ? "" : "s"}`}
4961 </span>
5062 <button
5163 type="button"
5264 onclick={refresh}
53- class="border-2 border-[var(--border)] bg-[var(--bg)] px-3 py-1 text-xs font-bold uppercase tracking-wide text-[var(--fg)] shadow-[2px_2px_0_0_var(--border)] transition-transform hover:-translate-x-px hover:-translate-y-px"
65+ disabled={refreshing}
66+ class="border-2 border-[var(--border)] bg-[var(--bg)] px-3 py-1 text-xs font-bold uppercase tracking-wide text-[var(--fg)] shadow-[2px_2px_0_0_var(--border)] transition-transform hover:-translate-x-px hover:-translate-y-px disabled:cursor-wait disabled:opacity-60"
5467 >
5568 Show
5669 </button>
@@ -1,6 +1,5 @@
1 <script lang="ts">1 <script lang="ts">
2 import { onMount } from "svelte";2 import { onMount } from "svelte";
3- import { invalidateAll } from "$app/navigation";
4 import { endpoints } from "$lib/api";3 import { endpoints } from "$lib/api";
5 import Icon from "./Icon.svelte";4 import Icon from "./Icon.svelte";
6 5
@@ -9,10 +8,16 @@
9 since: number;8 since: number;
10 /** Polling interval in milliseconds. */9 /** Polling interval in milliseconds. */
11 interval?: number;10 interval?: number;
11+ /**
12+ * Reload handler invoked when the user clicks "Show". Should perform
13+ * a full route reload so the new articles render.
14+ */
15+ onrefresh: () => Promise<unknown>;
12 }16 }
13- let { since, interval = 60_000 }: Props = $props();17+ let { since, interval = 60_000, onrefresh }: Props = $props();
14 18
15 let count = $state(0);19 let count = $state(0);
20+ let refreshing = $state(false);
16 let timer: ReturnType<typeof setInterval>;21 let timer: ReturnType<typeof setInterval>;
17 22
18 onMount(() => {23 onMount(() => {
@@ -22,7 +27,7 @@
22 27
23 async function poll() {28 async function poll() {
24 // Hide the banner for inactive tabs; the next visibility change polls.29 // Hide the banner for inactive tabs; the next visibility change polls.
25- if (document.hidden) return;30+ if (document.hidden || refreshing) return;
26 try {31 try {
27 const res = await endpoints.newArticleCount(since);32 const res = await endpoints.newArticleCount(since);
28 count = res.count;33 count = res.count;
@@ -32,25 +37,33 @@
32 }37 }
33 38
34 async function refresh() {39 async function refresh() {
35- since = Math.floor(Date.now() / 1000);40+ if (refreshing) return;
36- count = 0;41+ refreshing = true;
37- await invalidateAll();42+ try {
43+ await onrefresh();
44+ count = 0;
45+ } finally {
46+ refreshing = false;
47+ }
38 }48 }
39 </script>49 </script>
40 50
41-{#if count > 0}51+{#if count > 0 || refreshing}
42 <div52 <div
43 class="mb-4 flex items-center justify-between gap-3 border-2 border-[var(--border)] bg-[var(--accent)] px-4 py-2.5 text-[var(--accent-ink)] shadow-[4px_4px_0_0_var(--border)] dark:text-white"53 class="mb-4 flex items-center justify-between gap-3 border-2 border-[var(--border)] bg-[var(--accent)] px-4 py-2.5 text-[var(--accent-ink)] shadow-[4px_4px_0_0_var(--border)] dark:text-white"
44 role="status"54 role="status"
45 >55 >
46 <span class="flex items-center gap-2 text-sm font-bold">56 <span class="flex items-center gap-2 text-sm font-bold">
47 <Icon name="sparkles" class="h-4 w-4" />57 <Icon name="sparkles" class="h-4 w-4" />
48- {count} new article{count === 1 ? "" : "s"}58+ {refreshing
59+ ? "Loading…"
60+ : `${count} new article${count === 1 ? "" : "s"}`}
49 </span>61 </span>
50 <button62 <button
51 type="button"63 type="button"
52 onclick={refresh}64 onclick={refresh}
53- class="border-2 border-[var(--border)] bg-[var(--bg)] px-3 py-1 text-xs font-bold uppercase tracking-wide text-[var(--fg)] shadow-[2px_2px_0_0_var(--border)] transition-transform hover:-translate-x-px hover:-translate-y-px"65+ disabled={refreshing}
66+ class="border-2 border-[var(--border)] bg-[var(--bg)] px-3 py-1 text-xs font-bold uppercase tracking-wide text-[var(--fg)] shadow-[2px_2px_0_0_var(--border)] transition-transform hover:-translate-x-px hover:-translate-y-px disabled:cursor-wait disabled:opacity-60"
54 >67 >
55 Show68 Show
56 </button>69 </button>
modified web/src/routes/articles/+page.svelte +7 -1
@@ -54,6 +54,12 @@
5454 .then(() => goto("/articles", { invalidateAll: true }));
5555 }
5656
57+ // Reload the current route (preserving filters) so newly-fetched articles
58+ // appear without a manual hard refresh.
59+ function onrefresh() {
60+ return goto(buildURL({}), { invalidateAll: true, noScroll: false });
61+ }
62+
5763 // Expanded view: mark articles read on scroll.
5864 function readOnScroll(node: HTMLElement, id: number) {
5965 if (!data.expanded_view) return;
@@ -283,7 +289,7 @@
283289 {/if}
284290
285291 <!-- Live banner: surfaces newly-fetched articles since the page loaded -->
286-<NewArticlesBanner since={data.now} />
292+<NewArticlesBanner since={data.now} {onrefresh} />
287293
288294 <!-- List -->
289295 <div class="space-y-3">
@@ -54,6 +54,12 @@
54 .then(() => goto("/articles", { invalidateAll: true }));54 .then(() => goto("/articles", { invalidateAll: true }));
55 }55 }
56 56
57+ // Reload the current route (preserving filters) so newly-fetched articles
58+ // appear without a manual hard refresh.
59+ function onrefresh() {
60+ return goto(buildURL({}), { invalidateAll: true, noScroll: false });
61+ }
62+
57 // Expanded view: mark articles read on scroll.63 // Expanded view: mark articles read on scroll.
58 function readOnScroll(node: HTMLElement, id: number) {64 function readOnScroll(node: HTMLElement, id: number) {
59 if (!data.expanded_view) return;65 if (!data.expanded_view) return;
@@ -283,7 +289,7 @@
283 {/if}289 {/if}
284 290
285 <!-- Live banner: surfaces newly-fetched articles since the page loaded -->291 <!-- Live banner: surfaces newly-fetched articles since the page loaded -->
286-<NewArticlesBanner since={data.now} />292+<NewArticlesBanner since={data.now} {onrefresh} />
287 293
288 <!-- List -->294 <!-- List -->
289 <div class="space-y-3">295 <div class="space-y-3">
modified web/src/routes/dashboard/+page.svelte +7 -1
@@ -7,6 +7,7 @@
77 import EmptyState from "$lib/components/EmptyState.svelte";
88 import Icon from "$lib/components/Icon.svelte";
99 import NewArticlesBanner from "$lib/components/NewArticlesBanner.svelte";
10+ import { goto } from "$app/navigation";
1011 import { endpoints } from "$lib/api";
1112 import type {
1213 Article,
@@ -66,6 +67,11 @@
6667 discover = discover.filter((p) => p.did !== did);
6768 }
6869
70+ // Full route reload so newly-fetched articles render without a hard refresh.
71+ function onrefresh() {
72+ return goto("/dashboard", { invalidateAll: true });
73+ }
74+
6975 let digestOpen = $state(false);
7076 async function markDigestRead() {
7177 if (!digest) return;
@@ -106,7 +112,7 @@
106112 </div>
107113
108114 {#if data.subscription_count > 0}
109- <NewArticlesBanner since={data.now} />
115+ <NewArticlesBanner since={data.now} {onrefresh} />
110116 {/if}
111117
112118 {#if data.subscription_count === 0}
@@ -7,6 +7,7 @@
7 import EmptyState from "$lib/components/EmptyState.svelte";7 import EmptyState from "$lib/components/EmptyState.svelte";
8 import Icon from "$lib/components/Icon.svelte";8 import Icon from "$lib/components/Icon.svelte";
9 import NewArticlesBanner from "$lib/components/NewArticlesBanner.svelte";9 import NewArticlesBanner from "$lib/components/NewArticlesBanner.svelte";
10+ import { goto } from "$app/navigation";
10 import { endpoints } from "$lib/api";11 import { endpoints } from "$lib/api";
11 import type {12 import type {
12 Article,13 Article,
@@ -66,6 +67,11 @@
66 discover = discover.filter((p) => p.did !== did);67 discover = discover.filter((p) => p.did !== did);
67 }68 }
68 69
70+ // Full route reload so newly-fetched articles render without a hard refresh.
71+ function onrefresh() {
72+ return goto("/dashboard", { invalidateAll: true });
73+ }
74+
69 let digestOpen = $state(false);75 let digestOpen = $state(false);
70 async function markDigestRead() {76 async function markDigestRead() {
71 if (!digest) return;77 if (!digest) return;
@@ -106,7 +112,7 @@
106 </div>112 </div>
107 113
108 {#if data.subscription_count > 0}114 {#if data.subscription_count > 0}
109- <NewArticlesBanner since={data.now} />115+ <NewArticlesBanner since={data.now} {onrefresh} />
110 {/if}116 {/if}
111 117
112 {#if data.subscription_count === 0}118 {#if data.subscription_count === 0}