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;
+ })());
+ }
});