fix: offline-content renamed to downloads

This commit is contained in:
gosangam
2026-07-14 16:51:17 +05:30
parent 12a5c15121
commit 1683abc2e9
6 changed files with 39 additions and 27 deletions
@@ -1,7 +1,7 @@
"use client";
/**
* /offline-content — the reader's device-local offline library.
* /downloads — the reader's device-local offline library.
*
* Lists everything saved via the header's "Save offline" button and lets them
* open (offline-capable) or remove each item. Items link with a plain <a> (a
@@ -10,7 +10,7 @@
* fail. This page also doubles as the SW's offline navigation fallback.
*/
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useState, useSyncExternalStore } from "react";
import {
BookOpen,
FileText,
@@ -67,33 +67,45 @@ function formatDate(ms: number): string {
}
}
export default function OfflineContentPage() {
// External-store readers for the two client-only, non-React values. Using
// useSyncExternalStore (instead of setState-in-an-effect) is SSR-safe and
// avoids the "setState synchronously within an effect" cascade warning.
function subscribeOnline(cb: () => void) {
window.addEventListener("online", cb);
window.addEventListener("offline", cb);
return () => {
window.removeEventListener("online", cb);
window.removeEventListener("offline", cb);
};
}
const noopSubscribe = () => () => {};
export default function DownloadsPage() {
const [items, setItems] = useState<OfflineItem[] | null>(null);
const [usage, setUsage] = useState<number | null>(null);
const [online, setOnline] = useState(true);
const [supported, setSupported] = useState(true);
// Client-only reads via external stores (SSR snapshot → server-safe default).
const online = useSyncExternalStore(
subscribeOnline,
() => navigator.onLine,
() => true,
);
const supported = useSyncExternalStore(
noopSubscribe,
() => offlineSupported(),
() => false,
);
const refresh = useCallback(async () => {
setItems(await listOffline());
setUsage(await usageBytes());
}, []);
// Load the library once mounted. The setState calls happen after an await
// (asynchronously), not synchronously in the effect body.
useEffect(() => {
if (!offlineSupported()) {
setSupported(false);
setItems([]);
return;
}
setOnline(navigator.onLine);
if (!offlineSupported()) return;
void refresh();
const on = () => setOnline(true);
const off = () => setOnline(false);
window.addEventListener("online", on);
window.addEventListener("offline", off);
return () => {
window.removeEventListener("online", on);
window.removeEventListener("offline", off);
};
}, [refresh]);
async function remove(key: string) {
+1 -1
View File
@@ -71,7 +71,7 @@ export default function SettingsPage() {
description="Pages and lectures you've saved to read or listen to without a connection. Saved on this device — open the library to remove downloads and free up space."
>
<ul className="divide-y divide-border">
<LegalLink href="/offline-content" label="Manage offline library" />
<LegalLink href="/downloads" label="Manage offline downloads" />
</ul>
</Section>
+1 -1
View File
@@ -33,7 +33,7 @@ const SECONDARY: NavItem[] = [
{ href: "/journals", label: "Journals" },
{ href: "/magazines", label: "Magazines" },
{ href: "/other-documents", label: "Other Documents" },
{ href: "/offline-content", label: "Offline library" },
{ href: "/downloads", label: "Downloads" },
{ href: "/about", label: "About" },
];
+2 -2
View File
@@ -97,8 +97,8 @@ const TOP_ITEMS: NavItem[] = [
icon: <AtSign className="size-5" />,
},
{
href: "/offline-content",
label: "Offline",
href: "/downloads",
label: "Downloads",
icon: <DownloadCloud className="size-5" />,
},
];
+3 -3
View File
@@ -9,7 +9,7 @@
* - **Transcript audio** → a Blob in IndexedDB, played through a pre-created
* object URL (full native seeking; see `primeOfflineAudio`).
*
* An `items` index (IndexedDB) records what's saved so `/offline-content` can
* An `items` index (IndexedDB) records what's saved so `/downloads` can
* list it and the header button can show saved state.
*/
@@ -212,13 +212,13 @@ async function cachePageAndAssets(
return { htmlLength: html.length, assetCount: assetUrls.length };
}
/** Precache the /offline-content library page so it works as the SW's offline
/** Precache the /downloads library page so it works as the SW's offline
* navigation fallback even if the user never opened it online. Best-effort. */
export async function precacheOfflineLibrary(): Promise<void> {
if (!offlineSupported()) return;
try {
await cachePageAndAssets(
new URL("/offline-content", window.location.origin).href,
new URL("/downloads", window.location.origin).href,
);
} catch {
/* best-effort */
+1 -1
View File
@@ -13,7 +13,7 @@ const PAGES_CACHE = `offline-pages-${CACHE_VERSION}`; // saved page HTML documen
const ASSETS_CACHE = `offline-assets-${CACHE_VERSION}`; // /_next/* + static files
const OFFLINE_CACHES = [PAGES_CACHE, ASSETS_CACHE];
// The library page doubles as the navigation fallback when offline.
const OFFLINE_FALLBACK = "/offline-content";
const OFFLINE_FALLBACK = "/downloads";
self.addEventListener("install", () => self.skipWaiting());
self.addEventListener("activate", (event) =>