import powershell from 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.5.1/build/es/languages/powershell.min.js';
hljs.registerLanguage('powershell', powershell);
+/* Set up logic for stale-while-revalidate cache strategy */
navigator.serviceWorker.register('/sw.js');
+const cacheChannel = new BroadcastChannel('cache');
+cacheChannel.addEventListener('message', e => {
+ // Ignore static assets like logo.svg, style.css, script.js, etc.
+ if (new URL(e.data.resource).pathname === '/api' && e.data.isUpdated) {
+ document.body.append(toast);
+ }
+});
/* Global data store for posts */
const posts = new Map();
const homeMain = components.get('home-main');
const postLoader = components.get('post-loader');
const postMain = components.get('post-main');
+const toast = components.get('toast');
+toast.querySelector('button').addEventListener('click', _ => location.reload());
/* Fetch important <head> elements for convenience */
const head = {
.post p {
line-height: 1.5;
}
+
+dialog {
+ position: fixed;
+ inset-block-end: 1rem;
+ background: hsl(50deg 72% 80%);
+ padding: 1rem;
+ border: solid hsl(50deg 72% 55% / 0.1);
+ border-radius: 1rem;
+ box-shadow:
+ 0px 1px 1.1px hsl(50deg 72% 69% / 0.17),
+ 0px 2.3px 2.6px -0.6px hsl(50deg 72% 69% / 0.17),
+ 0px 4.3px 4.8px -1.2px hsl(50deg 72% 69% / 0.17),
+ 0px 8.4px 9.5px -1.9px hsl(50deg 72% 69% / 0.17),
+ 0.1px 16px 18px -2.5px hsl(50deg 72% 69% / 0.17);
+}
+
+dialog * {
+ display: inline;
+}
+
+dialog > button {
+ cursor: pointer;
+ text-transform: uppercase;
+ font-weight: bold;
+ background: hsl(216deg 100% 80%);
+ padding: 0.25rem;
+ border: 1px hsl(216deg 100% 70%) solid;
+ border-radius: 0.5rem;
+ transition: 0.15s;
+}
+
+dialog > button:hover, dialog > button:focus {
+ background-color: hsl(216deg 100% 70%);
+}
-const cacheName = 'v3';
+const cacheName = 'v4';
+const cacheChannel = new BroadcastChannel('cache');
-self.addEventListener('install', event => event.waitUntil(
- caches.open(cacheName).then(cache => cache.addAll(
- [
- 'index.html',
- 'favicon.svg',
- 'apple-touch-icon.png',
- 'style.css',
- 'print.css',
- 'script.js'
- ]
- ))
-));
+async function revalidate(request) {
+ const cachePromise = caches.match(request);
+ const originPromise = fetch(request);
+ const [cacheResponse, originResponse] = await Promise.all([cachePromise, originPromise]);
-self.addEventListener('fetch', event => event.respondWith(
- caches.match(event.request, { ignoreVary: true }).then(response => {
- if (response !== undefined) {
- return response;
- }
- else {
- return fetch(event.request).then(response => {
- const responseClone = response.clone();
- caches.open(cacheName).then(cache => cache.put(event.request, responseClone));
- return response;
- })
- }
- })
-));
+ // Must make clones before calling .text()
+ const originClone = originResponse.clone(); // For updating the cache
+ const originClone2 = originResponse.clone(); // For the function to return
+
+ const [cacheText, responseText] = await Promise.all([cacheResponse?.text(), originResponse?.text()]);
+
+ if (originResponse.ok && cacheText !== responseText) {
+ caches.open(cacheName)
+ .then(cache => cache.put(request, originClone))
+ .then(() => cacheChannel.postMessage({
+ resource: request.url,
+ isCreated: cacheText === undefined,
+ isUpdated: cacheText !== undefined
+ }));
+ }
+
+ return originClone2;
+}
+
+// Implements stale-while-revalidate strategy: if there's anything in the cache, return that immediately.
+// Wait for the origin server's response in parallel and notify cacheChannel if the cache changed.
+globalThis.addEventListener('fetch', event => {
+ event.respondWith(caches.match(event.request).then(cacheResponse => {
+ const originResponse = revalidate(event.request);
+ return cacheResponse === undefined ? originResponse : cacheResponse;
+ }));
+});