fix: address review findings — icons, a11y, quiz state, SW, scores
- C-1: move saveScore() out of resultScore getter into _onAllAnswered
to prevent repeated localStorage writes on every reactive access
- C-2: $watch('search') → createIcons() so sidebar Lucide icons are
re-converted after x-for re-render on search filter/clear
- T-8: replace x-show Lucide <i> chevrons with inline SVG in
.concept-chevron-wrap; Alpine x-show was orphaned after
createIcons() replaced <i> with <svg>, causing stale display
- H-1: remove quizState={} from stepGoBack() Step3 branch so going
back no longer silently destroys quiz answers
- H-2: add role=button tabindex=0 and Enter/Space keydown handlers to
sidebar-item divs for keyboard navigation
- M-3: move skipWaiting() inside Promise.all in install waitUntil
- M-4: return 503 Response in SW fetch catch when cache also unavailable
- M-6: call saveScore() in nextWeakDrillUnit() so weak-drill results
persist to localStorage the same as regular quiz
- UI: fix .unit-cat-badge vertical misalignment in flex badge-row
(was display:inline-block margin-bottom:6px, now inline-flex)
- SW: bump cache version to v19 to retire old worker
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7647d8137c
commit
2255ec55c8
16
index.html
16
index.html
|
|
@ -153,7 +153,7 @@ header{display:flex;align-items:center;justify-content:space-between;padding:0 1
|
|||
/* Unit header */
|
||||
.unit-header{margin-bottom:18px}
|
||||
.unit-meta{min-width:0}
|
||||
.unit-cat-badge{font-size:10px;font-weight:600;letter-spacing:.07em;text-transform:uppercase;color:var(--accent);background:var(--accent-dim);border:1px solid var(--accent-border);border-radius:20px;padding:3px 9px;display:inline-block;margin-bottom:6px}
|
||||
.unit-cat-badge{font-size:10px;font-weight:600;letter-spacing:.07em;text-transform:uppercase;color:var(--accent);background:var(--accent-dim);border:1px solid var(--accent-border);border-radius:20px;padding:3px 9px;display:inline-flex;align-items:center}
|
||||
.unit-title{font-size:19px;font-weight:600;letter-spacing:-.01em;line-height:1.3}
|
||||
|
||||
/* Concept text */
|
||||
|
|
@ -369,6 +369,7 @@ header{display:flex;align-items:center;justify-content:space-between;padding:0 1
|
|||
/* Concept expand */
|
||||
.concept-expand-btn{display:inline-flex;align-items:center;gap:5px;font-size:11px;color:var(--accent);background:none;border:none;cursor:pointer;padding:4px 0 0;font-family:'Geist',sans-serif;font-weight:500}
|
||||
.concept-expand-btn:hover{opacity:.75}
|
||||
.concept-chevron-wrap{display:inline-flex;min-width:11px;height:11px;align-items:center;justify-content:center;flex-shrink:0}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -425,8 +426,11 @@ header{display:flex;align-items:center;justify-content:space-between;padding:0 1
|
|||
</div>
|
||||
<template x-for="u in cat.units" :key="u.id">
|
||||
<div class="sidebar-item"
|
||||
role="button" tabindex="0"
|
||||
:class="{active: currentUnit && currentUnit.id===u.id, done: isDone(u.id)}"
|
||||
@click="openUnit(u); sidebarOpen=false">
|
||||
@click="openUnit(u); sidebarOpen=false"
|
||||
@keydown.enter.prevent="openUnit(u); sidebarOpen=false"
|
||||
@keydown.space.prevent="openUnit(u); sidebarOpen=false">
|
||||
<span class="item-num" x-text="u.num"></span>
|
||||
<span class="item-title" x-text="u.title"></span>
|
||||
<span class="score-chip" x-show="bestScore(u.id) && !hasWrong(u.id)" :class="scoreChipCls(u.id)" x-text="bestScore(u.id)"></span>
|
||||
|
|
@ -607,9 +611,13 @@ header{display:flex;align-items:center;justify-content:space-between;padding:0 1
|
|||
</div>
|
||||
<div class="concept-text" x-html="safeHtml(conceptPreview)"></div>
|
||||
<div class="concept-text" x-show="conceptExpanded" x-html="safeHtml(conceptRest)"></div>
|
||||
<button class="concept-expand-btn" x-show="conceptRest"
|
||||
<button type="button" class="concept-expand-btn" x-show="conceptRest"
|
||||
:aria-expanded="conceptExpanded ? 'true' : 'false'"
|
||||
@click="conceptExpanded = !conceptExpanded">
|
||||
<i :data-lucide="conceptExpanded ? 'chevron-up' : 'chevron-down'" style="width:11px;height:11px"></i>
|
||||
<span class="concept-chevron-wrap" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" x-show="!conceptExpanded"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" x-show="conceptExpanded"><polyline points="18 15 12 9 6 15"></polyline></svg>
|
||||
</span>
|
||||
<span x-text="conceptExpanded ? '閉じる' : 'もっと詳しく'"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -82,6 +82,9 @@ document.addEventListener('alpine:init', () => {
|
|||
}
|
||||
this.syncUnitToUrl();
|
||||
this.$nextTick(()=>{ if(window.lucide) lucide.createIcons(); });
|
||||
this.$watch('search', ()=>{
|
||||
this.$nextTick(()=>{ if(window.lucide) lucide.createIcons(); });
|
||||
});
|
||||
if('serviceWorker' in navigator){
|
||||
navigator.serviceWorker.register('/sw.js').catch(()=>{});
|
||||
}
|
||||
|
|
@ -202,6 +205,7 @@ document.addEventListener('alpine:init', () => {
|
|||
const uid=this.currentUnit.id;
|
||||
const total=this.currentUnit.quiz?.length||0;
|
||||
const correct=Object.values(this.quizState).filter(s=>s?.correct).length;
|
||||
this.saveScore(uid,correct,total);
|
||||
if(correct>=total){
|
||||
delete this.wrongUnits[uid];
|
||||
} else {
|
||||
|
|
@ -272,7 +276,6 @@ document.addEventListener('alpine:init', () => {
|
|||
get resultScore(){
|
||||
const correct=Object.values(this.quizState).filter(s=>s?.correct).length;
|
||||
const total=this.currentUnit?.quiz?.length||0;
|
||||
if(this.currentUnit) this.saveScore(this.currentUnit.id,correct,total);
|
||||
return correct+' / '+total;
|
||||
},
|
||||
get resultMsg(){
|
||||
|
|
@ -379,6 +382,7 @@ document.addEventListener('alpine:init', () => {
|
|||
if(!unit) return;
|
||||
const correct=Object.values(this.weakDrillQuizState).filter(s=>s?.correct).length;
|
||||
const total=unit.quiz?.length||0;
|
||||
this.saveScore(unit.id,correct,total);
|
||||
this.weakDrillResults.push({unitId:unit.id,num:unit.num,unitTitle:unit.title,correct,total});
|
||||
// Update wrong tracking
|
||||
if(correct>=total){
|
||||
|
|
@ -460,7 +464,6 @@ document.addEventListener('alpine:init', () => {
|
|||
this.resetFlashRevealState();
|
||||
}
|
||||
} else if(this.stepStep===3){
|
||||
this.quizState={};
|
||||
const drills=this.unitDrills;
|
||||
if(drills.length>0){
|
||||
this.stepStep=2;
|
||||
|
|
|
|||
10
sw.js
10
sw.js
|
|
@ -1,14 +1,16 @@
|
|||
// posimai-boki SW — stale-while-revalidate + skipWaiting
|
||||
const CACHE = 'posimai-boki-v18';
|
||||
const CACHE = 'posimai-boki-v19';
|
||||
const STATIC = ['/', '/index.html', '/manifest.json', '/logo.png', '/js/app.js', '/js/data/drills.js', '/js/data/categories.js'];
|
||||
|
||||
self.addEventListener('install', e => {
|
||||
e.waitUntil(
|
||||
Promise.all([
|
||||
caches.open(CACHE).then(c => c.addAll(STATIC.filter(u => {
|
||||
try { new URL(u, self.location.origin); return true; } catch { return false; }
|
||||
})))
|
||||
}))),
|
||||
self.skipWaiting()
|
||||
])
|
||||
);
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', e => {
|
||||
|
|
@ -29,7 +31,7 @@ self.addEventListener('fetch', e => {
|
|||
const network = fetch(e.request).then(res => {
|
||||
if (res.ok && res.type === 'basic') cache.put(e.request, res.clone());
|
||||
return res;
|
||||
}).catch(() => cached);
|
||||
}).catch(() => cached || new Response('Offline', { status: 503, statusText: 'Service Unavailable' }));
|
||||
return cached || network;
|
||||
})
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue