Dropping EasyMDE entirely (previous commit) fixed the Korean IME jamo split but also removed the live markdown syntax highlighting everywhere, even on platforms that never had the bug. Only iOS WebKit breaks CJK IME composition under CodeMirror, so journal-editor.js now detects iOS (including the desktop-UA-masquerading iPad, via MacIntel + multi-touch) and only skips EasyMDE there, falling back to a native <textarea>. Everywhere else keeps the full EasyMDE/CodeMirror editing experience as before. window.JournalEditor now dispatches to the right engine per textarea (checking t._easymde) so the toolbar buttons and the category-template autofill in journal.html work unchanged regardless of which engine backs a given textarea. Restores the easymde vendor files, base.html includes, and service worker precache entries removed in the previous commit (cache bumped to v8). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
110 lines
3.4 KiB
JavaScript
110 lines
3.4 KiB
JavaScript
const CACHE_NAME = "habit-tracker-v8";
|
|
const APP_SHELL = [
|
|
"/static/css/style.css",
|
|
"/static/css/vendor/easymde.min.css",
|
|
"/static/js/app.js",
|
|
"/static/js/push-register.js",
|
|
"/static/js/habit-reorder.js",
|
|
"/static/js/journal-category-reorder.js",
|
|
"/static/js/journal-editor.js",
|
|
"/static/js/vendor/htmx.min.js",
|
|
"/static/js/vendor/alpine.min.js",
|
|
"/static/js/vendor/sortable.min.js",
|
|
"/static/js/vendor/easymde.min.js",
|
|
"/static/icons/icon-192.png",
|
|
"/static/icons/icon-512.png",
|
|
"/static/icons/icon-apple-180.png",
|
|
"/static/manifest.json",
|
|
"/static/offline.html",
|
|
];
|
|
|
|
self.addEventListener("install", (event) => {
|
|
event.waitUntil(
|
|
caches
|
|
.open(CACHE_NAME)
|
|
.then((cache) => cache.addAll(APP_SHELL))
|
|
.then(() => self.skipWaiting())
|
|
);
|
|
});
|
|
|
|
self.addEventListener("activate", (event) => {
|
|
event.waitUntil(
|
|
caches
|
|
.keys()
|
|
.then((keys) => Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))))
|
|
.then(() => self.clients.claim())
|
|
);
|
|
});
|
|
|
|
self.addEventListener("fetch", (event) => {
|
|
const { request } = event;
|
|
if (request.method !== "GET") return;
|
|
|
|
const url = new URL(request.url);
|
|
if (url.pathname.startsWith("/api/")) return;
|
|
|
|
// 페이지 탐색(/today, /habits, /history 등)은 습관 추가·수정 직후 리다이렉트되는 화면이라
|
|
// 캐시된 옛 내용이 먼저 보이면 "저장한 게 사라졌다"처럼 보인다. 네트워크를 우선 시도하고
|
|
// 오프라인일 때만 캐시로 폴백한다.
|
|
if (request.mode === "navigate") {
|
|
event.respondWith(
|
|
fetch(request)
|
|
.then((response) => {
|
|
if (response.ok) {
|
|
const clone = response.clone();
|
|
caches.open(CACHE_NAME).then((cache) => cache.put(request, clone));
|
|
}
|
|
return response;
|
|
})
|
|
.catch(() => caches.match(request).then((cached) => cached || caches.match("/static/offline.html")))
|
|
);
|
|
return;
|
|
}
|
|
|
|
// 정적 자산은 캐시 우선 응답 후 백그라운드로 갱신(stale-while-revalidate) — 자주 안 바뀌므로 속도 우선.
|
|
event.respondWith(
|
|
caches.match(request).then((cached) => {
|
|
const network = fetch(request)
|
|
.then((response) => {
|
|
if (response.ok) {
|
|
const clone = response.clone();
|
|
caches.open(CACHE_NAME).then((cache) => cache.put(request, clone));
|
|
}
|
|
return response;
|
|
})
|
|
.catch(() => cached);
|
|
return cached || network;
|
|
})
|
|
);
|
|
});
|
|
|
|
self.addEventListener("push", (event) => {
|
|
if (!event.data) return;
|
|
const data = event.data.json();
|
|
event.waitUntil(
|
|
self.registration.showNotification(data.title || "해빗랩", {
|
|
body: data.body || "",
|
|
icon: "/static/icons/icon-192.png",
|
|
badge: "/static/icons/icon-192.png",
|
|
data: { url: data.url || "/today" },
|
|
})
|
|
);
|
|
});
|
|
|
|
self.addEventListener("notificationclick", (event) => {
|
|
event.notification.close();
|
|
const targetUrl = (event.notification.data && event.notification.data.url) || "/today";
|
|
event.waitUntil(
|
|
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientsList) => {
|
|
for (const client of clientsList) {
|
|
if (client.url.includes(targetUrl) && "focus" in client) {
|
|
return client.focus();
|
|
}
|
|
}
|
|
if (self.clients.openWindow) {
|
|
return self.clients.openWindow(targetUrl);
|
|
}
|
|
})
|
|
);
|
|
});
|