diff --git a/articles/index.html b/articles/index.html index 08aca40..4c14435 100644 --- a/articles/index.html +++ b/articles/index.html @@ -101,6 +101,7 @@ article h1{margin:.25rem 0 .5rem;font-size:1.6rem} const saveBtn=$('#saveBtn'), cancelBtn=$('#cancelBtn'); const KEY='articles14k'; let selId=null; let API=null; let preList; // cache first list to avoid duplicate initial fetches + const AC={}; // per-article cache by id function setLoading(on,msg){ if(!loading) return; if(msg) loading.querySelector('.msg').textContent=msg; loading.classList.toggle('hidden',!on); } const supportsUpload = true; // try server upload; fallback uses data URLs @@ -210,11 +211,11 @@ article h1{margin:.25rem 0 .5rem;font-size:1.6rem} cancelBtn.onclick=()=>{selId=null; clearEditor(); show(listV)}; // List click -> read (ensure full focus by switching view) - listV.addEventListener('click',async e=>{const card=e.target.closest('.card'); if(!card) return; const id=card.getAttribute('data-id'); const a=await API.get(id); if(!a) return; openRead(a)}); + listV.addEventListener('click',async e=>{const card=e.target.closest('.card'); if(!card) return; const id=card.getAttribute('data-id'); const c=AC[id]; if(c){ openRead(c); return; } setLoading(true,'Loading article…'); try{ const a=await API.get(id); if(!a) return; AC[id]=a; openRead(a); } finally { setLoading(false); }}); function openRead(a){ selId=a.id; rTh.loading='lazy'; rTh.decoding='async'; rTh.src=a.thumb||''; rT.textContent=a.title||''; rB.innerHTML=a.body||''; rB.querySelectorAll('img').forEach(im=>{ im.loading='lazy'; im.decoding='async'; }); dt.textContent=new Date(a.createdAt||Date.now()).toLocaleString(); show(readV) } // Delete - del.onclick=async()=>{ if(!selId) return; setLoading(true,'Deleting…'); await API.remove(selId); selId=null; await renderList(); setLoading(false); show(listV) }; + del.onclick=async()=>{ if(!selId) return; setLoading(true,'Deleting…'); await API.remove(selId); delete AC[selId]; selId=null; await renderList(); setLoading(false); show(listV) }; // Nav toList.onclick=async()=>{ setLoading(true,'Loading articles…'); await renderList(); setLoading(false); show(listV)}; diff --git a/articles/sw.js b/articles/sw.js index 7842990..81b95c9 100644 --- a/articles/sw.js +++ b/articles/sw.js @@ -1,18 +1,31 @@ // Minimal image cache-first service worker -const C = 'articles-img-v1'; +const C = 'articles-img-v2'; +const AC = 'articles-json-v1'; self.addEventListener('install', e => self.skipWaiting()); self.addEventListener('activate', e => e.waitUntil(self.clients.claim())); self.addEventListener('fetch', e => { const req = e.request; + if (req.method !== 'GET') return; const u = new URL(req.url); const isImg = req.destination === 'image' || u.pathname.startsWith('/uploads/'); - if (!isImg || req.method !== 'GET') return; - e.respondWith((async () => { - const cache = await caches.open(C); - const hit = await cache.match(req, { ignoreVary: true, ignoreSearch: false }); - if (hit) return hit; - const res = await fetch(req); - if (res && res.ok) cache.put(req, res.clone()); - return res; - })()); + const isArticle = u.pathname.startsWith('/api/articles/') && u.pathname.length > '/api/articles/'.length; + if (isImg) { + e.respondWith((async () => { + const cache = await caches.open(C); + const hit = await cache.match(req, { ignoreVary: true, ignoreSearch: false }); + if (hit) return hit; + const res = await fetch(req); + if (res && res.ok) cache.put(req, res.clone()); + return res; + })()); + } else if (isArticle) { + e.respondWith((async () => { + const cache = await caches.open(AC); + const hit = await cache.match(req); + if (hit) return hit; + const res = await fetch(req); + if (res && res.ok) cache.put(req, res.clone()); + return res; + })()); + } });