@carlonicora/nextjs-jsonapi 6.12.1 → 6.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ApiData-DPKNfY-9.d.mts → ApiData-Be8yxCOX.d.mts} +2 -0
- package/dist/{ApiData-DPKNfY-9.d.ts → ApiData-Be8yxCOX.d.ts} +2 -0
- package/dist/{BlockNoteEditor-N2NBYCPC.mjs → BlockNoteEditor-22Q4DJBU.mjs} +20 -8
- package/dist/BlockNoteEditor-22Q4DJBU.mjs.map +1 -0
- package/dist/{BlockNoteEditor-CBYKEXAQ.js → BlockNoteEditor-2VLPSEKS.js} +34 -22
- package/dist/BlockNoteEditor-2VLPSEKS.js.map +1 -0
- package/dist/{BlockNoteViewer-WGCDB7IJ.js → BlockNoteViewer-2XYASA5Y.js} +8 -5
- package/dist/BlockNoteViewer-2XYASA5Y.js.map +1 -0
- package/dist/{BlockNoteViewer-MTGS6YJV.mjs → BlockNoteViewer-NBW2H6LC.mjs} +7 -4
- package/dist/{BlockNoteViewer-MTGS6YJV.mjs.map → BlockNoteViewer-NBW2H6LC.mjs.map} +1 -1
- package/dist/{JsonApiRequest-A66XK5WT.mjs → JsonApiRequest-F6TMJ42G.mjs} +2 -2
- package/dist/JsonApiRequest-WISJZSDE.js +26 -0
- package/dist/{JsonApiRequest-FZGPLNUF.js.map → JsonApiRequest-WISJZSDE.js.map} +1 -1
- package/dist/billing/index.js +499 -494
- package/dist/billing/index.js.map +1 -1
- package/dist/billing/index.mjs +13 -8
- package/dist/billing/index.mjs.map +1 -1
- package/dist/chunk-27LQDGNP.mjs +317 -0
- package/dist/chunk-27LQDGNP.mjs.map +1 -0
- package/dist/{chunk-6NZKWIOR.js → chunk-2H342HNN.js} +956 -929
- package/dist/chunk-2H342HNN.js.map +1 -0
- package/dist/{chunk-KSYQEKJX.js → chunk-2SV4SS6N.js} +14 -14
- package/dist/{chunk-KSYQEKJX.js.map → chunk-2SV4SS6N.js.map} +1 -1
- package/dist/{chunk-4FNGWALA.mjs → chunk-47BBY5KA.mjs} +6 -4
- package/dist/{chunk-4FNGWALA.mjs.map → chunk-47BBY5KA.mjs.map} +1 -1
- package/dist/chunk-6JUCIWDC.mjs +570 -0
- package/dist/chunk-6JUCIWDC.mjs.map +1 -0
- package/dist/chunk-7DBJTNSU.mjs +1049 -0
- package/dist/chunk-7DBJTNSU.mjs.map +1 -0
- package/dist/chunk-7JZHPMGW.mjs +704 -0
- package/dist/chunk-7JZHPMGW.mjs.map +1 -0
- package/dist/{chunk-L6PYFQTO.js → chunk-7ZHTPFFT.js} +16 -14
- package/dist/chunk-7ZHTPFFT.js.map +1 -0
- package/dist/chunk-B6JGKDP2.js +214 -0
- package/dist/chunk-B6JGKDP2.js.map +1 -0
- package/dist/{chunk-3YW35YHQ.mjs → chunk-BH6JWIKI.mjs} +5 -5
- package/dist/chunk-BZ5J2FIV.js +570 -0
- package/dist/chunk-BZ5J2FIV.js.map +1 -0
- package/dist/{chunk-V5MI7RNB.mjs → chunk-DGWOVYEC.mjs} +2 -2
- package/dist/chunk-DOCIFE6A.mjs +212 -0
- package/dist/chunk-DOCIFE6A.mjs.map +1 -0
- package/dist/chunk-GB6DBFYY.js +25 -0
- package/dist/chunk-GB6DBFYY.js.map +1 -0
- package/dist/{chunk-6XJOQFCP.mjs → chunk-H4NLDX5G.mjs} +59 -32
- package/dist/chunk-H4NLDX5G.mjs.map +1 -0
- package/dist/{chunk-EWCY6QJA.mjs → chunk-JJMMT24D.mjs} +37 -3
- package/dist/chunk-JJMMT24D.mjs.map +1 -0
- package/dist/{chunk-JTPRJNT2.js → chunk-KMW7UNL2.js} +38 -3
- package/dist/chunk-KMW7UNL2.js.map +1 -0
- package/dist/chunk-MHB36MYE.js +317 -0
- package/dist/chunk-MHB36MYE.js.map +1 -0
- package/dist/{chunk-FF4EIQ6H.js → chunk-MOGEGWXW.js} +1039 -1680
- package/dist/chunk-MOGEGWXW.js.map +1 -0
- package/dist/{chunk-AA52ORW2.mjs → chunk-P445QGOC.mjs} +61 -702
- package/dist/chunk-P445QGOC.mjs.map +1 -0
- package/dist/chunk-PV4Z5W6V.js +212 -0
- package/dist/chunk-PV4Z5W6V.js.map +1 -0
- package/dist/chunk-PXDUJ47N.js +1049 -0
- package/dist/chunk-PXDUJ47N.js.map +1 -0
- package/dist/{chunk-GHE4VT2R.js → chunk-QKNGZDSQ.js} +2 -2
- package/dist/{chunk-GHE4VT2R.js.map → chunk-QKNGZDSQ.js.map} +1 -1
- package/dist/chunk-W62MHDWM.js +704 -0
- package/dist/chunk-W62MHDWM.js.map +1 -0
- package/dist/chunk-XXP3TQHF.mjs +25 -0
- package/dist/chunk-XXP3TQHF.mjs.map +1 -0
- package/dist/chunk-YRKIFYOE.mjs +214 -0
- package/dist/chunk-YRKIFYOE.mjs.map +1 -0
- package/dist/{chunk-O3PDL2CV.js → chunk-Z4POQWFG.js} +40 -6
- package/dist/chunk-Z4POQWFG.js.map +1 -0
- package/dist/{chunk-PI56A4DG.mjs → chunk-ZLTOUZN2.mjs} +37 -2
- package/dist/chunk-ZLTOUZN2.mjs.map +1 -0
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +14 -9
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +24 -19
- package/dist/components/index.d.mts +7 -1
- package/dist/components/index.d.ts +7 -1
- package/dist/components/index.js +11 -7
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +10 -6
- package/dist/contexts/index.d.mts +1 -1
- package/dist/contexts/index.d.ts +1 -1
- package/dist/contexts/index.js +11 -7
- package/dist/contexts/index.js.map +1 -1
- package/dist/contexts/index.mjs +10 -6
- package/dist/core/index.d.mts +128 -2
- package/dist/core/index.d.ts +128 -2
- package/dist/core/index.js +17 -4
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +22 -9
- package/dist/email-automations/index.js +101 -96
- package/dist/email-automations/index.js.map +1 -1
- package/dist/email-automations/index.mjs +13 -8
- package/dist/email-automations/index.mjs.map +1 -1
- package/dist/features/analytics/index.js +128 -123
- package/dist/features/analytics/index.js.map +1 -1
- package/dist/features/analytics/index.mjs +13 -8
- package/dist/features/analytics/index.mjs.map +1 -1
- package/dist/features/analytics/server.js +11 -8
- package/dist/features/analytics/server.js.map +1 -1
- package/dist/features/analytics/server.mjs +9 -6
- package/dist/features/analytics/server.mjs.map +1 -1
- package/dist/features/blog/index.js +158 -153
- package/dist/features/blog/index.js.map +1 -1
- package/dist/features/blog/index.mjs +14 -9
- package/dist/features/blog/index.mjs.map +1 -1
- package/dist/features/blog/server.js +30 -26
- package/dist/features/blog/server.js.map +1 -1
- package/dist/features/blog/server.mjs +10 -6
- package/dist/features/blog/server.mjs.map +1 -1
- package/dist/features/help/index.js +44 -40
- package/dist/features/help/index.js.map +1 -1
- package/dist/features/help/index.mjs +10 -6
- package/dist/features/help/index.mjs.map +1 -1
- package/dist/features/tokenusage/index.js +134 -129
- package/dist/features/tokenusage/index.js.map +1 -1
- package/dist/features/tokenusage/index.mjs +13 -8
- package/dist/features/tokenusage/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +18 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -11
- package/dist/lifecycle-4GUOTB46.js +23 -0
- package/dist/lifecycle-4GUOTB46.js.map +1 -0
- package/dist/lifecycle-LKGPRBS7.mjs +23 -0
- package/dist/local-transport-7H3SPZSA.js +15 -0
- package/dist/local-transport-7H3SPZSA.js.map +1 -0
- package/dist/local-transport-QYFKA6AN.mjs +15 -0
- package/dist/local-transport-QYFKA6AN.mjs.map +1 -0
- package/dist/offline/index.d.mts +607 -0
- package/dist/offline/index.d.ts +607 -0
- package/dist/offline/index.js +1052 -0
- package/dist/offline/index.js.map +1 -0
- package/dist/offline/index.mjs +1052 -0
- package/dist/offline/index.mjs.map +1 -0
- package/dist/offline/sqlite/device-worker.mjs +62 -0
- package/dist/offline/sqlite/index.mjs +17392 -0
- package/dist/offline/sqlite/sqlite3-opfs-async-proxy.js +881 -0
- package/dist/offline/sqlite/sqlite3.wasm +0 -0
- package/dist/request-KTBV6HHH.js +10 -0
- package/dist/request-KTBV6HHH.js.map +1 -0
- package/dist/request-SQOUNWZ4.mjs +10 -0
- package/dist/request-SQOUNWZ4.mjs.map +1 -0
- package/dist/server/cachedGet.d.mts +1 -1
- package/dist/server/cachedGet.d.ts +1 -1
- package/dist/server/cachedGet.stub.d.mts +1 -1
- package/dist/server/cachedGet.stub.d.ts +1 -1
- package/dist/server/index.d.mts +28 -2
- package/dist/server/index.d.ts +28 -2
- package/dist/server/index.js +18 -8
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +18 -8
- package/dist/{useSocket-IRHtk2xc.d.ts → useSocket-Bo9M0t0J.d.ts} +3 -1
- package/dist/{useSocket-C8fqU4od.d.mts → useSocket-J5qanQPN.d.mts} +3 -1
- package/dist/write-through-FDQ7N4YI.js +15 -0
- package/dist/write-through-FDQ7N4YI.js.map +1 -0
- package/dist/write-through-R25VQMH6.mjs +15 -0
- package/dist/write-through-R25VQMH6.mjs.map +1 -0
- package/package.json +7 -1
- package/pwa/sw.js +277 -26
- package/src/client/request.ts +37 -0
- package/src/components/containers/RoundPageContainer.tsx +8 -8
- package/src/components/editors/BlockNoteEditor.tsx +14 -0
- package/src/components/editors/__tests__/BlockNoteEditor.empty-change.spec.tsx +205 -0
- package/src/contexts/SocketContext.tsx +7 -1
- package/src/contexts/__tests__/SocketContext.session.spec.tsx +52 -5
- package/src/core/factories/RehydrationFactory.ts +28 -8
- package/src/core/factories/__tests__/RehydrationFactory.spec.ts +33 -0
- package/src/core/index.ts +3 -0
- package/src/core/interfaces/ApiData.ts +2 -0
- package/src/core/registry/ModuleRegistry.ts +3 -0
- package/src/features/auth/components/forms/Logout.tsx +8 -1
- package/src/features/auth/components/forms/__tests__/Logout.spec.tsx +62 -0
- package/src/hooks/__tests__/useSocket.pause.spec.ts +39 -0
- package/src/hooks/useSocket.ts +20 -1
- package/src/offline/__tests__/answer-builders.spec.ts +176 -0
- package/src/offline/__tests__/device-switch.spec.ts +179 -0
- package/src/offline/__tests__/generic-answers.spec.ts +380 -0
- package/src/offline/__tests__/local-transport.spec.ts +150 -0
- package/src/offline/__tests__/logout.spec.ts +122 -0
- package/src/offline/__tests__/offline-fixtures.ts +89 -0
- package/src/offline/__tests__/router.spec.ts +56 -0
- package/src/offline/__tests__/state.spec.ts +62 -0
- package/src/offline/__tests__/write-through.spec.ts +137 -0
- package/src/offline/config.ts +229 -0
- package/src/offline/configure.ts +14 -0
- package/src/offline/device-switch.ts +75 -0
- package/src/offline/generic-answers.ts +706 -0
- package/src/offline/index.ts +67 -0
- package/src/offline/local-transport.ts +189 -0
- package/src/offline/logout.ts +41 -0
- package/src/offline/router.ts +41 -0
- package/src/offline/server/__tests__/build-offline-manifest.spec.ts +76 -0
- package/src/offline/server/build-offline-manifest.ts +49 -0
- package/src/offline/state-hooks.ts +10 -0
- package/src/offline/state.ts +143 -0
- package/src/offline/store/DeviceStore.ts +112 -0
- package/src/offline/store/__tests__/device-store-lifecycle.spec.ts +343 -0
- package/src/offline/store/__tests__/shared-database.spec.ts +151 -0
- package/src/offline/store/__tests__/sql-device-store.spec.ts +399 -0
- package/src/offline/store/lifecycle.ts +315 -0
- package/src/offline/store/schema.ts +101 -0
- package/src/offline/store/shared-database.ts +321 -0
- package/src/offline/store/sql-device-store.ts +469 -0
- package/src/offline/store/worker-client.ts +71 -0
- package/src/offline/store/worker.ts +87 -0
- package/src/offline/sync/__tests__/conflicts.spec.ts +185 -0
- package/src/offline/sync/__tests__/queue-drain.spec.ts +364 -0
- package/src/offline/sync/__tests__/runtime.spec.ts +337 -0
- package/src/offline/sync/__tests__/sync-fixtures.ts +103 -0
- package/src/offline/sync/__tests__/sync-root.spec.ts +727 -0
- package/src/offline/sync/artwork-cache.ts +67 -0
- package/src/offline/sync/conflicts.ts +211 -0
- package/src/offline/sync/counts.ts +39 -0
- package/src/offline/sync/drain-queue.ts +246 -0
- package/src/offline/sync/known-roots.ts +37 -0
- package/src/offline/sync/models/OfflineChangeSet.ts +34 -0
- package/src/offline/sync/models/OfflineChangeSetInterface.ts +54 -0
- package/src/offline/sync/models/RootSync.ts +68 -0
- package/src/offline/sync/models/RootSyncInterface.ts +20 -0
- package/src/offline/sync/models/__tests__/OfflineChangeSet.spec.ts +47 -0
- package/src/offline/sync/models/__tests__/RootSync.spec.ts +54 -0
- package/src/offline/sync/models/index.ts +5 -0
- package/src/offline/sync/models/offline.modules.ts +14 -0
- package/src/offline/sync/runtime.ts +226 -0
- package/src/offline/sync/session-refresh.ts +32 -0
- package/src/offline/sync/sync-root.ts +336 -0
- package/src/offline/sync/waiting.ts +40 -0
- package/src/offline/types.ts +115 -0
- package/src/offline/write-through.ts +189 -0
- package/src/pwa/__tests__/sw.spec.ts +358 -1
- package/src/server/index.ts +5 -0
- package/src/unified/__tests__/JsonApiRequest.offline.spec.ts +243 -0
- package/dist/BlockNoteEditor-CBYKEXAQ.js.map +0 -1
- package/dist/BlockNoteEditor-N2NBYCPC.mjs.map +0 -1
- package/dist/BlockNoteViewer-WGCDB7IJ.js.map +0 -1
- package/dist/JsonApiRequest-FZGPLNUF.js +0 -26
- package/dist/chunk-6NZKWIOR.js.map +0 -1
- package/dist/chunk-6XJOQFCP.mjs.map +0 -1
- package/dist/chunk-AA52ORW2.mjs.map +0 -1
- package/dist/chunk-EWCY6QJA.mjs.map +0 -1
- package/dist/chunk-FF4EIQ6H.js.map +0 -1
- package/dist/chunk-JTPRJNT2.js.map +0 -1
- package/dist/chunk-L6PYFQTO.js.map +0 -1
- package/dist/chunk-O3PDL2CV.js.map +0 -1
- package/dist/chunk-PI56A4DG.mjs.map +0 -1
- package/dist/request-5RLK2AM3.mjs +0 -9
- package/dist/request-EUGTI3BE.js +0 -9
- package/dist/request-EUGTI3BE.js.map +0 -1
- /package/dist/{JsonApiRequest-A66XK5WT.mjs.map → JsonApiRequest-F6TMJ42G.mjs.map} +0 -0
- /package/dist/{chunk-3YW35YHQ.mjs.map → chunk-BH6JWIKI.mjs.map} +0 -0
- /package/dist/{chunk-V5MI7RNB.mjs.map → chunk-DGWOVYEC.mjs.map} +0 -0
- /package/dist/{request-5RLK2AM3.mjs.map → lifecycle-LKGPRBS7.mjs.map} +0 -0
package/pwa/sw.js
CHANGED
|
@@ -6,12 +6,147 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Per-app values (appId, offlineRevision, notificationIcon) come from the app's
|
|
8
8
|
// own public/sw-config.js, which sets self.NJA_SW_CONFIG.
|
|
9
|
+
//
|
|
10
|
+
// Offline mode (opt-in; without these keys the worker behaves exactly as before):
|
|
11
|
+
// - offlineShell: { default: "/offline-shell", locales: { it: "/it/offline-shell" } }
|
|
12
|
+
// At install the worker reads /offline-manifest.json ({ buildId, assets }) and
|
|
13
|
+
// caches every asset plus each shell URL in `${appId}-shell-${buildId}`, then
|
|
14
|
+
// a completion marker last; one failed file fails the install and deletes the
|
|
15
|
+
// half-filled cache, so the previous worker stays in charge and no worker ever
|
|
16
|
+
// answers from a cache without the marker. A
|
|
17
|
+
// failed navigation answers the shell of its locale (the first path segment
|
|
18
|
+
// matched against `locales`, otherwise `default`), then offline.html. GETs
|
|
19
|
+
// under /_next/static/ are served cache-first from the shell cache; after an
|
|
20
|
+
// online navigation the worker re-reads the manifest (at most every few
|
|
21
|
+
// minutes) and precaches a new build's shell in the background, so a deploy
|
|
22
|
+
// refreshes the shell without a new worker; any other
|
|
23
|
+
// same-origin GET goes to the network first and, when it fails, is answered
|
|
24
|
+
// from the shell cache (the device worker and SQLite files). Activate deletes
|
|
25
|
+
// shell caches of other builds.
|
|
26
|
+
// - offlineImageHosts: ["api.example.com"] (a host or an origin)
|
|
27
|
+
// A GET to one of these hosts goes to the network; when the network fails it
|
|
28
|
+
// is answered from `${appId}-images`, where the app stores each artwork image
|
|
29
|
+
// under its exact URL. That cache is kept across activations.
|
|
9
30
|
|
|
10
31
|
importScripts("/sw-config.js");
|
|
11
32
|
|
|
12
33
|
const OFFLINE_URL = "/offline.html";
|
|
13
34
|
const CACHE_NAME = `${self.NJA_SW_CONFIG.appId}-offline-${self.NJA_SW_CONFIG.offlineRevision}`;
|
|
14
35
|
|
|
36
|
+
const OFFLINE_SHELL = self.NJA_SW_CONFIG.offlineShell || null;
|
|
37
|
+
const OFFLINE_IMAGE_HOSTS = self.NJA_SW_CONFIG.offlineImageHosts || [];
|
|
38
|
+
const MANIFEST_URL = "/offline-manifest.json";
|
|
39
|
+
const SHELL_CACHE_PREFIX = `${self.NJA_SW_CONFIG.appId}-shell-`;
|
|
40
|
+
const IMAGES_CACHE_NAME = `${self.NJA_SW_CONFIG.appId}-images`;
|
|
41
|
+
// Written last into a shell cache: a shell cache without it is incomplete and never used.
|
|
42
|
+
const SHELL_COMPLETE_URL = "/__nja-offline-shell-complete__";
|
|
43
|
+
|
|
44
|
+
// The shell cache this worker installed. A worker can be stopped and restarted
|
|
45
|
+
// between events, losing this value; it then falls back to the newest complete
|
|
46
|
+
// shell cache (caches.keys() lists caches in creation order).
|
|
47
|
+
let shellCacheName = null;
|
|
48
|
+
|
|
49
|
+
async function currentShellCacheName() {
|
|
50
|
+
if (shellCacheName) return shellCacheName;
|
|
51
|
+
const keys = await caches.keys();
|
|
52
|
+
const shells = keys.filter(function (key) {
|
|
53
|
+
return key.startsWith(SHELL_CACHE_PREFIX);
|
|
54
|
+
});
|
|
55
|
+
for (let index = shells.length - 1; index >= 0; index--) {
|
|
56
|
+
const cache = await caches.open(shells[index]);
|
|
57
|
+
if (await cache.match(SHELL_COMPLETE_URL)) return shells[index];
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function shellUrls() {
|
|
63
|
+
const urls = [OFFLINE_SHELL.default];
|
|
64
|
+
const locales = OFFLINE_SHELL.locales || {};
|
|
65
|
+
for (const locale of Object.keys(locales)) urls.push(locales[locale]);
|
|
66
|
+
return urls;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function shellUrlFor(url) {
|
|
70
|
+
const segment = new URL(url).pathname.split("/")[1] || "";
|
|
71
|
+
const locales = OFFLINE_SHELL.locales || {};
|
|
72
|
+
return Object.prototype.hasOwnProperty.call(locales, segment) ? locales[segment] : OFFLINE_SHELL.default;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function installShell() {
|
|
76
|
+
const response = await fetch(new Request(MANIFEST_URL, { cache: "no-store" }));
|
|
77
|
+
if (!response.ok) throw new Error(`${MANIFEST_URL} answered ${response.status}`);
|
|
78
|
+
const manifest = await response.json();
|
|
79
|
+
const name = `${SHELL_CACHE_PREFIX}${manifest.buildId}`;
|
|
80
|
+
try {
|
|
81
|
+
const cache = await caches.open(name);
|
|
82
|
+
// cache.addAll rejects when any file fails, so the install fails with it.
|
|
83
|
+
await cache.addAll(
|
|
84
|
+
manifest.assets.concat(shellUrls()).map(function (url) {
|
|
85
|
+
return new Request(url, { cache: "reload" });
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
await cache.put(SHELL_COMPLETE_URL, new Response("", { status: 200 }));
|
|
89
|
+
} catch (error) {
|
|
90
|
+
// Never leave a half-filled shell cache behind for any worker to pick.
|
|
91
|
+
await caches.delete(name);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
shellCacheName = name;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A deploy changes the app's build, not this file: sw.js is byte-identical across builds, so no
|
|
98
|
+
// new worker is installed. After an online navigation the worker reads the manifest and, when
|
|
99
|
+
// the server runs a build it has no complete shell cache for, precaches that build's shell in
|
|
100
|
+
// the background, then drops the other shell caches. At most one check per few minutes.
|
|
101
|
+
const SHELL_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
|
|
102
|
+
let lastShellRefresh = 0;
|
|
103
|
+
let shellRefresh = null;
|
|
104
|
+
|
|
105
|
+
async function refreshShellIfStale() {
|
|
106
|
+
const response = await fetch(new Request(MANIFEST_URL, { cache: "no-store" }));
|
|
107
|
+
if (!response.ok) return;
|
|
108
|
+
const manifest = await response.json();
|
|
109
|
+
const name = `${SHELL_CACHE_PREFIX}${manifest.buildId}`;
|
|
110
|
+
const keys = await caches.keys();
|
|
111
|
+
if (keys.indexOf(name) !== -1) {
|
|
112
|
+
const existing = await caches.open(name);
|
|
113
|
+
if (await existing.match(SHELL_COMPLETE_URL)) {
|
|
114
|
+
shellCacheName = name;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
await installShell();
|
|
119
|
+
const others = (await caches.keys()).filter(function (key) {
|
|
120
|
+
return key.startsWith(SHELL_CACHE_PREFIX) && key !== shellCacheName;
|
|
121
|
+
});
|
|
122
|
+
await Promise.all(
|
|
123
|
+
others.map(function (key) {
|
|
124
|
+
return caches.delete(key);
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function maybeRefreshShell() {
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
if (shellRefresh || now - lastShellRefresh < SHELL_REFRESH_INTERVAL_MS) return shellRefresh || Promise.resolve();
|
|
132
|
+
lastShellRefresh = now;
|
|
133
|
+
shellRefresh = refreshShellIfStale()
|
|
134
|
+
.catch(function (error) {
|
|
135
|
+
// The current shell stays in charge; the next navigation tries again later.
|
|
136
|
+
console.warn("[sw] refreshing the offline shell failed", error);
|
|
137
|
+
})
|
|
138
|
+
.finally(function () {
|
|
139
|
+
shellRefresh = null;
|
|
140
|
+
});
|
|
141
|
+
return shellRefresh;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isImageHost(url) {
|
|
145
|
+
if (!OFFLINE_IMAGE_HOSTS.length) return false;
|
|
146
|
+
const parsed = new URL(url);
|
|
147
|
+
return OFFLINE_IMAGE_HOSTS.indexOf(parsed.host) !== -1 || OFFLINE_IMAGE_HOSTS.indexOf(parsed.origin) !== -1;
|
|
148
|
+
}
|
|
149
|
+
|
|
15
150
|
// Served when a navigation fails and the cached offline page is missing too
|
|
16
151
|
// (evicted or cleared). Never answer a navigation with a bare network error.
|
|
17
152
|
const OFFLINE_FALLBACK_HTML = `<!doctype html>
|
|
@@ -58,11 +193,16 @@ const OFFLINE_FALLBACK_HTML = `<!doctype html>
|
|
|
58
193
|
|
|
59
194
|
self.addEventListener("install", function (event) {
|
|
60
195
|
event.waitUntil(
|
|
61
|
-
caches
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
196
|
+
caches
|
|
197
|
+
.open(CACHE_NAME)
|
|
198
|
+
.then(function (cache) {
|
|
199
|
+
// cache.add rejects on a network error or a non-2xx response, so the
|
|
200
|
+
// install fails and the previous worker stays in charge.
|
|
201
|
+
return cache.add(new Request(OFFLINE_URL, { cache: "reload" }));
|
|
202
|
+
})
|
|
203
|
+
.then(function () {
|
|
204
|
+
if (OFFLINE_SHELL) return installShell();
|
|
205
|
+
}),
|
|
66
206
|
);
|
|
67
207
|
});
|
|
68
208
|
|
|
@@ -70,10 +210,16 @@ self.addEventListener("activate", function (event) {
|
|
|
70
210
|
event.waitUntil(
|
|
71
211
|
(async function () {
|
|
72
212
|
const keys = await caches.keys();
|
|
213
|
+
const keep = [CACHE_NAME];
|
|
214
|
+
if (OFFLINE_SHELL) {
|
|
215
|
+
const shell = await currentShellCacheName();
|
|
216
|
+
if (shell) keep.push(shell);
|
|
217
|
+
}
|
|
218
|
+
if (OFFLINE_SHELL || OFFLINE_IMAGE_HOSTS.length) keep.push(IMAGES_CACHE_NAME);
|
|
73
219
|
await Promise.all(
|
|
74
220
|
keys
|
|
75
221
|
.filter(function (key) {
|
|
76
|
-
return key
|
|
222
|
+
return keep.indexOf(key) === -1;
|
|
77
223
|
})
|
|
78
224
|
.map(function (key) {
|
|
79
225
|
return caches.delete(key);
|
|
@@ -88,28 +234,133 @@ self.addEventListener("activate", function (event) {
|
|
|
88
234
|
});
|
|
89
235
|
|
|
90
236
|
self.addEventListener("fetch", function (event) {
|
|
91
|
-
// Only navigations are intercepted
|
|
92
|
-
// network without
|
|
93
|
-
|
|
237
|
+
// Only navigations are intercepted, plus (offline mode only) build assets and
|
|
238
|
+
// artwork images. Everything else goes straight to the network without
|
|
239
|
+
// respondWith.
|
|
240
|
+
if (event.request.mode !== "navigate") {
|
|
241
|
+
if (!OFFLINE_SHELL && !OFFLINE_IMAGE_HOSTS.length) return;
|
|
242
|
+
const method = event.request.method || "GET";
|
|
243
|
+
if (method !== "GET") return;
|
|
244
|
+
// A no-store request (the page's session refresh of the shell) is never answered from a
|
|
245
|
+
// cache: when the network fails it must fail, so the page knows the app is unreachable.
|
|
246
|
+
if (event.request.cache === "no-store") return;
|
|
247
|
+
const url = new URL(event.request.url);
|
|
94
248
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
249
|
+
if (OFFLINE_SHELL && url.origin === self.location.origin && url.pathname.startsWith("/_next/static/")) {
|
|
250
|
+
event.respondWith(
|
|
251
|
+
(async function () {
|
|
252
|
+
const name = await currentShellCacheName();
|
|
253
|
+
if (name) {
|
|
254
|
+
const cache = await caches.open(name);
|
|
255
|
+
const cached = await cache.match(event.request.url);
|
|
256
|
+
if (cached) return cached;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
return await fetch(event.request);
|
|
260
|
+
} catch {
|
|
261
|
+
// Offline and not in the shell: a plain network error for the page, no uncaught rejection here.
|
|
262
|
+
return Response.error();
|
|
263
|
+
}
|
|
264
|
+
})(),
|
|
265
|
+
);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// An image of an offline image host through Next's image optimizer (`/_next/image?url=...`):
|
|
270
|
+
// network first, then the downloaded original (the images cache keeps it under its own URL).
|
|
271
|
+
const optimizedSource = url.origin === self.location.origin && url.pathname === "/_next/image" ? url.searchParams.get("url") : null;
|
|
272
|
+
if (optimizedSource && /^https?:/.test(optimizedSource) && isImageHost(optimizedSource)) {
|
|
273
|
+
event.respondWith(
|
|
274
|
+
(async function () {
|
|
275
|
+
try {
|
|
276
|
+
return await fetch(event.request);
|
|
277
|
+
} catch {
|
|
278
|
+
const cache = await caches.open(IMAGES_CACHE_NAME);
|
|
279
|
+
const cached = await cache.match(optimizedSource);
|
|
280
|
+
if (cached) return cached;
|
|
281
|
+
return Response.error();
|
|
282
|
+
}
|
|
283
|
+
})(),
|
|
284
|
+
);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Any other same-origin file the offline manifest precached (the device worker and the
|
|
289
|
+
// SQLite files it loads): network first, the shell cache when the network fails.
|
|
290
|
+
if (OFFLINE_SHELL && url.origin === self.location.origin) {
|
|
291
|
+
event.respondWith(
|
|
292
|
+
(async function () {
|
|
293
|
+
try {
|
|
294
|
+
return await fetch(event.request);
|
|
295
|
+
} catch {
|
|
296
|
+
const name = await currentShellCacheName();
|
|
297
|
+
if (name) {
|
|
298
|
+
const cache = await caches.open(name);
|
|
299
|
+
const cached = await cache.match(event.request.url);
|
|
300
|
+
if (cached) return cached;
|
|
301
|
+
}
|
|
302
|
+
return Response.error();
|
|
303
|
+
}
|
|
304
|
+
})(),
|
|
305
|
+
);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (isImageHost(event.request.url)) {
|
|
310
|
+
event.respondWith(
|
|
311
|
+
(async function () {
|
|
312
|
+
try {
|
|
313
|
+
return await fetch(event.request);
|
|
314
|
+
} catch {
|
|
315
|
+
const cache = await caches.open(IMAGES_CACHE_NAME);
|
|
316
|
+
const cached = await cache.match(event.request.url);
|
|
317
|
+
if (cached) return cached;
|
|
318
|
+
return Response.error();
|
|
319
|
+
}
|
|
320
|
+
})(),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let reachedNetwork = false;
|
|
327
|
+
const answer = (async function () {
|
|
328
|
+
try {
|
|
329
|
+
const preload = await event.preloadResponse;
|
|
330
|
+
if (preload) {
|
|
331
|
+
reachedNetwork = true;
|
|
332
|
+
return preload;
|
|
110
333
|
}
|
|
111
|
-
|
|
112
|
-
|
|
334
|
+
// A 4xx/5xx or an opaque redirect is passed through as is.
|
|
335
|
+
const response = await fetch(event.request);
|
|
336
|
+
reachedNetwork = true;
|
|
337
|
+
return response;
|
|
338
|
+
} catch (error) {
|
|
339
|
+
if (OFFLINE_SHELL) {
|
|
340
|
+
const name = await currentShellCacheName();
|
|
341
|
+
if (name) {
|
|
342
|
+
const shellCache = await caches.open(name);
|
|
343
|
+
const shell = await shellCache.match(shellUrlFor(event.request.url));
|
|
344
|
+
if (shell) return shell;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const cache = await caches.open(CACHE_NAME);
|
|
348
|
+
const cached = await cache.match(OFFLINE_URL);
|
|
349
|
+
if (cached) return cached;
|
|
350
|
+
return new Response(OFFLINE_FALLBACK_HTML, {
|
|
351
|
+
status: 200,
|
|
352
|
+
headers: { "content-type": "text/html; charset=utf-8" },
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
})();
|
|
356
|
+
event.respondWith(answer);
|
|
357
|
+
if (OFFLINE_SHELL) {
|
|
358
|
+
event.waitUntil(
|
|
359
|
+
answer.then(function () {
|
|
360
|
+
if (reachedNetwork) return maybeRefreshShell();
|
|
361
|
+
}),
|
|
362
|
+
);
|
|
363
|
+
}
|
|
113
364
|
});
|
|
114
365
|
|
|
115
366
|
self.addEventListener("message", function (event) {
|
package/src/client/request.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { ApiData } from "../core/interfaces/ApiData";
|
|
4
|
+
import { isOfflineConfigured } from "../offline/config";
|
|
5
|
+
import { getOfflineState, reachesNetworkWhileOffline, setOfflineState } from "../offline/state";
|
|
6
|
+
import { OFFLINE_REPLAY_HEADER } from "../offline/types";
|
|
4
7
|
|
|
5
8
|
export interface DirectFetchParams {
|
|
6
9
|
method: string;
|
|
@@ -18,6 +21,39 @@ export interface DirectFetchParams {
|
|
|
18
21
|
* Use this for client-side API calls.
|
|
19
22
|
*/
|
|
20
23
|
export async function directFetch(params: DirectFetchParams): Promise<ApiData> {
|
|
24
|
+
// Offline mode is opt-in (`configureOffline`). Without it nothing below loads or runs. A request
|
|
25
|
+
// the server runs as an offline replay always goes to the network, and so do the reads and the
|
|
26
|
+
// change-set request of a reconnect attempt (the session stays offline until its sync succeeds).
|
|
27
|
+
const offline = isOfflineConfigured() && !hasReplayHeader(params.additionalHeaders);
|
|
28
|
+
if (offline && !getOfflineState().online && !reachesNetworkWhileOffline(params.method, params.url)) {
|
|
29
|
+
return answerLocally(params);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const response = await fetchFromApi(params);
|
|
33
|
+
|
|
34
|
+
if (offline && response.networkError) {
|
|
35
|
+
setOfflineState({ online: false });
|
|
36
|
+
return answerLocally(params);
|
|
37
|
+
}
|
|
38
|
+
if (offline && response.ok && params.method.toUpperCase() !== "GET") {
|
|
39
|
+
const { writeThrough } = await import("../offline/write-through");
|
|
40
|
+
await writeThrough({ method: params.method, url: params.url }, response);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return response;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function hasReplayHeader(headers?: Record<string, string>): boolean {
|
|
47
|
+
if (!headers) return false;
|
|
48
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === OFFLINE_REPLAY_HEADER);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function answerLocally(params: DirectFetchParams): Promise<ApiData> {
|
|
52
|
+
const { localTransport } = await import("../offline/local-transport");
|
|
53
|
+
return localTransport({ method: params.method, url: params.url, body: params.body, files: params.files });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function fetchFromApi(params: DirectFetchParams): Promise<ApiData> {
|
|
21
57
|
const response: ApiData = {
|
|
22
58
|
data: undefined,
|
|
23
59
|
ok: false,
|
|
@@ -95,6 +131,7 @@ export async function directFetch(params: DirectFetchParams): Promise<ApiData> {
|
|
|
95
131
|
response.status = 503;
|
|
96
132
|
response.statusText = "Service Unavailable";
|
|
97
133
|
response.data = undefined;
|
|
134
|
+
response.networkError = true;
|
|
98
135
|
}
|
|
99
136
|
|
|
100
137
|
return response;
|
|
@@ -486,7 +486,7 @@ export function RoundPageContainer({
|
|
|
486
486
|
of label length. */}
|
|
487
487
|
<TabsList className="flex h-auto w-full flex-col items-stretch gap-0.5 bg-transparent p-0">
|
|
488
488
|
{ungrouped.map((tab) => (
|
|
489
|
-
<TabsTrigger key={tab
|
|
489
|
+
<TabsTrigger key={tabValue(tab)} value={tabValue(tab)} className={railTriggerClass}>
|
|
490
490
|
{tab.contentLabel ?? tab.label}
|
|
491
491
|
</TabsTrigger>
|
|
492
492
|
))}
|
|
@@ -496,7 +496,7 @@ export function RoundPageContainer({
|
|
|
496
496
|
{group.label}
|
|
497
497
|
</MicroLabel>
|
|
498
498
|
{group.items.map((tab) => (
|
|
499
|
-
<TabsTrigger key={tab
|
|
499
|
+
<TabsTrigger key={tabValue(tab)} value={tabValue(tab)} className={railTriggerClass}>
|
|
500
500
|
{tab.contentLabel ?? tab.label}
|
|
501
501
|
</TabsTrigger>
|
|
502
502
|
))}
|
|
@@ -529,7 +529,7 @@ export function RoundPageContainer({
|
|
|
529
529
|
</SelectTrigger>
|
|
530
530
|
<SelectContent>
|
|
531
531
|
{ungrouped.map((tab) => (
|
|
532
|
-
<SelectItem key={tab
|
|
532
|
+
<SelectItem key={tabValue(tab)} value={tabValue(tab)}>
|
|
533
533
|
{tab.contentLabel ?? tab.label}
|
|
534
534
|
</SelectItem>
|
|
535
535
|
))}
|
|
@@ -537,7 +537,7 @@ export function RoundPageContainer({
|
|
|
537
537
|
<SelectGroup key={group.label}>
|
|
538
538
|
<SelectLabel>{group.label}</SelectLabel>
|
|
539
539
|
{group.items.map((tab) => (
|
|
540
|
-
<SelectItem key={tab
|
|
540
|
+
<SelectItem key={tabValue(tab)} value={tabValue(tab)}>
|
|
541
541
|
{tab.contentLabel ?? tab.label}
|
|
542
542
|
</SelectItem>
|
|
543
543
|
))}
|
|
@@ -565,7 +565,7 @@ export function RoundPageContainer({
|
|
|
565
565
|
{header}
|
|
566
566
|
{tabs.map((tab) => (
|
|
567
567
|
<TabsContent
|
|
568
|
-
key={tab
|
|
568
|
+
key={tabValue(tab)}
|
|
569
569
|
value={tabValue(tab)}
|
|
570
570
|
className={tab.fillHeight ? `flex min-h-0 w-full flex-1 flex-col` : ``}
|
|
571
571
|
>
|
|
@@ -615,7 +615,7 @@ export function RoundPageContainer({
|
|
|
615
615
|
</SelectTrigger>
|
|
616
616
|
<SelectContent>
|
|
617
617
|
{tabs.map((tab) => (
|
|
618
|
-
<SelectItem key={tab
|
|
618
|
+
<SelectItem key={tabValue(tab)} value={tabValue(tab)}>
|
|
619
619
|
{tab.contentLabel ?? tab.label}
|
|
620
620
|
</SelectItem>
|
|
621
621
|
))}
|
|
@@ -626,7 +626,7 @@ export function RoundPageContainer({
|
|
|
626
626
|
<div className="p-4">
|
|
627
627
|
<TabsList data-help="page.tabs">
|
|
628
628
|
{tabs.map((tab) => (
|
|
629
|
-
<TabsTrigger key={tab
|
|
629
|
+
<TabsTrigger key={tabValue(tab)} value={tabValue(tab)} className="px-4">
|
|
630
630
|
{tab.contentLabel ?? tab.label}
|
|
631
631
|
</TabsTrigger>
|
|
632
632
|
))}
|
|
@@ -642,7 +642,7 @@ export function RoundPageContainer({
|
|
|
642
642
|
>
|
|
643
643
|
{tabs.map((tab) => (
|
|
644
644
|
<TabsContent
|
|
645
|
-
key={tab
|
|
645
|
+
key={tabValue(tab)}
|
|
646
646
|
value={tabValue(tab)}
|
|
647
647
|
className={tab.fillHeight ? `flex flex-1 min-h-0 w-full flex-col` : `pb-20`}
|
|
648
648
|
>
|
|
@@ -821,11 +821,21 @@ export default function BlockNoteEditor({
|
|
|
821
821
|
// initialContent is just an echo of our own emission (form-controlled flow).
|
|
822
822
|
const lastEmittedHashRef = useRef<string | null>(null);
|
|
823
823
|
|
|
824
|
+
// Whether the parent's copy of the text is empty: the content it handed in, then each change
|
|
825
|
+
// reported to it. A change from empty to still empty is not reported: BlockNote emits one when an
|
|
826
|
+
// empty editor mounts (its trailing empty paragraph), and reported it became a save of nothing on
|
|
827
|
+
// every view of a page with an empty field.
|
|
828
|
+
const processedContentRef = useRef<any[]>([]);
|
|
829
|
+
const parentKnowsEmptyRef = useRef<boolean | null>(null);
|
|
830
|
+
|
|
824
831
|
const handleChange = useCallback(async () => {
|
|
825
832
|
if (!onChange) return;
|
|
826
833
|
const newBlocks = editor.document;
|
|
827
834
|
lastEmittedHashRef.current = JSON.stringify(newBlocks);
|
|
828
835
|
const isEmpty = isDocumentEmpty(newBlocks);
|
|
836
|
+
const wasEmpty = parentKnowsEmptyRef.current ?? isDocumentEmpty(processedContentRef.current);
|
|
837
|
+
if (isEmpty && wasEmpty) return;
|
|
838
|
+
parentKnowsEmptyRef.current = isEmpty;
|
|
829
839
|
|
|
830
840
|
function hasUnresolvedDiffsRecursive(block: any): boolean {
|
|
831
841
|
if (!block || typeof block !== "object") return false;
|
|
@@ -880,6 +890,8 @@ export default function BlockNoteEditor({
|
|
|
880
890
|
onChange(newBlocks, isEmpty, hasUnresolvedDiff);
|
|
881
891
|
}, [editor, onChange, id, acceptedChanges, rejectedChanges]);
|
|
882
892
|
|
|
893
|
+
processedContentRef.current = processedContent as any[];
|
|
894
|
+
|
|
883
895
|
// Utility: deep equality for arrays of blocks
|
|
884
896
|
const areBlocksEqual = (a: any[], b: any[]): boolean => {
|
|
885
897
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
@@ -912,6 +924,8 @@ export default function BlockNoteEditor({
|
|
|
912
924
|
if (collaboration) return;
|
|
913
925
|
const hash = JSON.stringify(processedContent);
|
|
914
926
|
if (previousContentHashRef.current === hash) return; // no changes
|
|
927
|
+
// New content from the parent: that is now the text it knows.
|
|
928
|
+
parentKnowsEmptyRef.current = isDocumentEmpty(processedContent as any[]);
|
|
915
929
|
// Skip replaceBlocks when the new initialContent is just an echo of what
|
|
916
930
|
// the editor itself emitted. Without this, a form-controlled parent
|
|
917
931
|
// (FormBlockNote) round-trips field.value back as initialContent on every
|