diora-web/static/js/sw.js
mrwnslz 429b875faf
Some checks failed
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 11m30s
Test / e2e (push) Failing after 1m30s
PDF: öffnet an der gespeicherten Seite, Leserfarben, Seitenzahlen wie im Buch (sw CACHE v166)
Der Lesestand wird gleich beim Öffnen erfragt (parallel zur Datei), ein PDF zeichnet zuerst
die Seite, an der es weitergeht, statt Seite 1; Titel, Inhaltsverzeichnis und Seitenzahlen
kommen nebenher und halten das Öffnen nicht mehr auf. Eine Zeichnung, die schon läuft, wird
abgewartet statt neu begonnen.

„Leserfarben“ (neben Originalfarben und Invertieren): die Seite in den Farben des Lesers
(Dunkel, Sepia) per SVG-Filter, Bilder darüber wie gedruckt (ihre Lage aus den
Zeichenbefehlen); ein Bild, das die halbe Seite füllt, ist ein Scan und wird mit umgefärbt.
pdf.js' eigene pageColors färben die Bilder mit um. Ein anderes Design ändert nur die Farben
des Filters.

Seitenzahlen des Buchs (PageLabels: „iv“, „A-3“) im Kopf mit der Seite der Datei daneben,
eingeben lässt sich beides; Lesezeichen und der Zurück-Knopf nennen sie. Das Test-PDF hat
dafür Seitenzahlen und ein farbiges Bild bekommen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:42:26 +01:00

152 lines
5 KiB
JavaScript

/**
* diora service worker — caches the app shell for offline use.
*/
const CACHE = 'diora-v166';
const SHELL = [
'/static/css/app.css',
'/static/js/app/01-core.js',
'/static/js/app/10-radio.js',
'/static/js/app/14-focus.js',
'/static/js/app/16-stations.js',
'/static/js/app/20-tabs.js',
'/static/js/app/24-ambient.js',
'/static/js/app/26-sidebar.js',
'/static/js/app/28-serviceworker.js',
'/static/js/app/29-m3u.js',
'/static/js/app/30-contrast.js',
'/static/js/app/32-crypto.js',
'/static/js/app/33-unlock.js',
'/static/js/app/34-wallpaper.js',
'/static/js/app/40-epub.js',
'/static/js/app/44-books.js',
'/static/js/app/46-readpos.js',
'/static/js/app/48-book-cache.js',
'/static/js/app/50-reader-state.js',
'/static/js/app/52-book-list.js',
'/static/js/app/53-library.js',
'/static/js/app/54-issues.js',
'/static/js/app/55-library-tools.js',
'/static/js/app/56-cloud-import.js',
'/static/js/app/57-cloud-inbox.js',
'/static/js/app/58-pdf.js',
'/static/js/app/60-tts.js',
'/static/js/app/62-reader.js',
'/static/js/app/63-pages.js',
'/static/js/app/65-article.js',
'/static/js/app/64-reader-settings.js',
'/static/js/app/66-pdf-paginated.js',
'/static/js/app/68-bookmarks.js',
'/static/js/app/70-search.js',
'/static/js/app/72-highlights.js',
'/static/js/app/74-margin.js',
'/static/js/app/76-export.js',
'/static/js/app/78-footnotes.js',
'/static/js/app/80-radio-sidebar.js',
'/static/js/app/99-init.js',
'/static/js/podcasts-web.js',
'/static/js/home.js',
'/static/js/feeds.js',
'/static/js/audiobooks.js',
'/static/js/nav.js',
'/static/js/folders.js',
'/static/js/covers.js',
'/static/js/gencover.js',
'/static/js/sources.js',
'/static/js/search.js',
'/static/js/projects.js',
'/static/podcasts/native-podcast.js',
'/static/podcasts/podcast-ui.css',
'/static/podcasts/podcasts-web.css',
'/static/manifest.json',
'/static/ambient/index.html',
'/static/fonts/SourceSerif4-normal-latin.woff2',
'/static/fonts/SourceSerif4-normal-latin-ext.woff2',
'/static/fonts/SourceSerif4-italic-latin.woff2',
'/static/fonts/SourceSerif4-italic-latin-ext.woff2',
];
// Install: pre-cache the app shell
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open(CACHE).then(function (cache) {
return cache.addAll(SHELL);
})
);
// Activate immediately without waiting for old tabs to close
self.skipWaiting();
});
// Activate: remove stale caches from previous versions (also the old
// 'diora-podcast-v1' of episodes the web app used to download)
self.addEventListener('activate', function (event) {
event.waitUntil(
caches.keys().then(function (keys) {
return Promise.all(
keys.filter(function (key) { return key !== CACHE; })
.map(function (key) { return caches.delete(key); })
);
}).then(function () {
return self.clients.claim();
}).then(function () {
// Tell all open tabs to reload so they get the latest cached assets
return self.clients.matchAll({type: 'window'}).then(function (clients) {
clients.forEach(function (client) {
client.postMessage({type: 'SW_ACTIVATED'});
});
});
})
);
});
// Fetch: serve from cache, fall back to network
self.addEventListener('fetch', function (event) {
// Only handle GET requests; let POST/SSE etc. pass through
if (event.request.method !== 'GET') return;
const url = new URL(event.request.url);
// Bypass for API/mutation endpoints
if (url.pathname.startsWith('/radio/sse/') ||
url.pathname.startsWith('/radio/record/') ||
url.pathname.startsWith('/radio/affiliate/') ||
url.pathname.startsWith('/tts/') ||
url.pathname.startsWith('/admin/') ||
url.pathname.startsWith('/podcasts/progress/') ||
url.pathname.startsWith('/podcasts/queue/') ||
url.pathname.startsWith('/podcasts/player/') ||
url.pathname === '/podcasts/feeds/add' ||
url.pathname.startsWith('/podcasts/feeds/add') ||
url.pathname.includes('/remove') ||
url.pathname.startsWith('/podcasts/feeds/refresh')) {
return;
}
// Cache-first for pre-defined shell assets
const isShell = SHELL.some(function (s) { return url.pathname === s; });
if (isShell) {
event.respondWith(
// ignoreSearch: the Ambient frame loads with ?embed=1&bg=…
caches.match(event.request, {ignoreSearch: true}).then(function (cached) {
return cached || fetch(event.request);
})
);
return;
}
// Navigation requests (the HTML page itself): network-first, fall back to
// last cached version so the app opens offline after being visited once.
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).then(function (response) {
if (response.ok) {
var clone = response.clone();
caches.open(CACHE).then(function (cache) { cache.put(event.request, clone); });
}
return response;
}).catch(function () {
return caches.match(event.request);
})
);
}
});