fix: offline-content renamed to downloads
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"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
|
* 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
|
* 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.
|
* 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 {
|
import {
|
||||||
BookOpen,
|
BookOpen,
|
||||||
FileText,
|
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 [items, setItems] = useState<OfflineItem[] | null>(null);
|
||||||
const [usage, setUsage] = useState<number | 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 () => {
|
const refresh = useCallback(async () => {
|
||||||
setItems(await listOffline());
|
setItems(await listOffline());
|
||||||
setUsage(await usageBytes());
|
setUsage(await usageBytes());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Load the library once mounted. The setState calls happen after an await
|
||||||
|
// (asynchronously), not synchronously in the effect body.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!offlineSupported()) {
|
if (!offlineSupported()) return;
|
||||||
setSupported(false);
|
|
||||||
setItems([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setOnline(navigator.onLine);
|
|
||||||
void refresh();
|
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]);
|
}, [refresh]);
|
||||||
|
|
||||||
async function remove(key: string) {
|
async function remove(key: string) {
|
||||||
@@ -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."
|
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">
|
<ul className="divide-y divide-border">
|
||||||
<LegalLink href="/offline-content" label="Manage offline library" />
|
<LegalLink href="/downloads" label="Manage offline downloads" />
|
||||||
</ul>
|
</ul>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const SECONDARY: NavItem[] = [
|
|||||||
{ href: "/journals", label: "Journals" },
|
{ href: "/journals", label: "Journals" },
|
||||||
{ href: "/magazines", label: "Magazines" },
|
{ href: "/magazines", label: "Magazines" },
|
||||||
{ href: "/other-documents", label: "Other Documents" },
|
{ href: "/other-documents", label: "Other Documents" },
|
||||||
{ href: "/offline-content", label: "Offline library" },
|
{ href: "/downloads", label: "Downloads" },
|
||||||
{ href: "/about", label: "About" },
|
{ href: "/about", label: "About" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ const TOP_ITEMS: NavItem[] = [
|
|||||||
icon: <AtSign className="size-5" />,
|
icon: <AtSign className="size-5" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: "/offline-content",
|
href: "/downloads",
|
||||||
label: "Offline",
|
label: "Downloads",
|
||||||
icon: <DownloadCloud className="size-5" />,
|
icon: <DownloadCloud className="size-5" />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
+3
-3
@@ -9,7 +9,7 @@
|
|||||||
* - **Transcript audio** → a Blob in IndexedDB, played through a pre-created
|
* - **Transcript audio** → a Blob in IndexedDB, played through a pre-created
|
||||||
* object URL (full native seeking; see `primeOfflineAudio`).
|
* 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.
|
* list it and the header button can show saved state.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -212,13 +212,13 @@ async function cachePageAndAssets(
|
|||||||
return { htmlLength: html.length, assetCount: assetUrls.length };
|
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. */
|
* navigation fallback even if the user never opened it online. Best-effort. */
|
||||||
export async function precacheOfflineLibrary(): Promise<void> {
|
export async function precacheOfflineLibrary(): Promise<void> {
|
||||||
if (!offlineSupported()) return;
|
if (!offlineSupported()) return;
|
||||||
try {
|
try {
|
||||||
await cachePageAndAssets(
|
await cachePageAndAssets(
|
||||||
new URL("/offline-content", window.location.origin).href,
|
new URL("/downloads", window.location.origin).href,
|
||||||
);
|
);
|
||||||
} catch {
|
} catch {
|
||||||
/* best-effort */
|
/* best-effort */
|
||||||
|
|||||||
+1
-1
@@ -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 ASSETS_CACHE = `offline-assets-${CACHE_VERSION}`; // /_next/* + static files
|
||||||
const OFFLINE_CACHES = [PAGES_CACHE, ASSETS_CACHE];
|
const OFFLINE_CACHES = [PAGES_CACHE, ASSETS_CACHE];
|
||||||
// The library page doubles as the navigation fallback when offline.
|
// 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("install", () => self.skipWaiting());
|
||||||
self.addEventListener("activate", (event) =>
|
self.addEventListener("activate", (event) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user