Files
shinalokandClaude Sonnet 5 6000de166f keep EasyMDE on desktop/Android, fall back to plain textarea only on iOS
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>
2026-08-27 08:16:06 +09:00

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