📅 2026年4月 — 站務變更(至 4/11)
對應資料夾
2604/;與 git_hub 總覽 併讀。
時間範圍
約 2026/04/01~2026/04/11(含當日前後相關 commit)。
重點條目(依主題)
- TOEIC 單字/閱讀
- POS(詞性) 標註:先導入單字與 Dashboard 顯示切換,再擴及 閱讀篇、Shane L2/TEPI06/TEPI07。
- 樣式微調:灰斜體 ↔ 橘色 badge 等迭代。
- Kid Daily / Diary 體驗
- 標籤瀏覽(
diary-tag-cloud)與 diary 版型、側欄一併更新。 - SPA 換頁後腳本可執行:主內容以 搬移 DOM 節點 取代純
innerHTML,避免<script>/資料島遺失。 - 標籤資料改以
<textarea hidden>承載 JSON,降低換頁後解析失敗風險。
- 標籤瀏覽(
- Secret Garden / 成績
- Daily overview:Secret Garden 與 schedule 並列;transcript 樞紐表,
kid/lib/transcript-pivot.js/.css,成績列_includes/kid_transcript_grades.txt。
- Daily overview:Secret Garden 與 schedule 並列;transcript 樞紐表,
- Utility
- 多工具頁小改(ascii、boshiamy、emoji、translator、繁簡轉換
tc_sc、world_clock 等)。 base_converter等與側欄同步。
- 多工具頁小改(ascii、boshiamy、emoji、translator、繁簡轉換
- 文件與流程
- SKILL(
github-io、mobile-web-fix)、ai.md、_config.yml等不時 Auto-update。 - 投資日報
daily_report_gemini_20260409、20260410等入庫。
- SKILL(
2026/04/11 — Claude AI 輔助三項重構
以下三項均由 Claude Sonnet 在單次對話完成,對話記憶涵蓋整個 repo 脈絡。
1. 條件式側邊欄(Conditional Sidebar)
需求:進入 /kid/ 時只顯示 Kid 選單,大人的 SSD/TOEIC/Investment 全部隱藏;不拆 repo、不分站。
解法:_includes/sidebar.html 頭尾加 Liquid 條件判斷:
{% if page.url contains '/kid/' %}
<!-- Kid's Corner nav(只有 kid 連結) -->
{% else %}
<!-- Rex's Hub 完整 nav -->
{% endif %}
為什麼這樣就夠:Jekyll 在伺服器端靜態產生 HTML,sidebar 只在完整頁面載入時渲染一次。因為 Kid sidebar 裡完全沒有主站連結,小孩透過 SPA 點任何連結都只在 /kid/ 內移動,自然看不到大人內容。只要小孩書籤直接指向 /kid/,效果完美。
效率做法(5 min):
- 只改
_includes/sidebar.html一個檔 - 把原本整個
<nav>包進{% else %}...{% endif %} {% if %}區塊內貼 Kid 版 nav(從 else 區塊的 kid<details>群組複製,升為頂層 group)_config.yml加defaults: kid/ → layout: default(新頁面不用手動設 layout)
2. SPA 主內容連結攔截(Content-link SPA Fix)
Bug:SPA 只攔截 .sidebar-nav 的點擊,頁面 .main-content 內的連結(如 ↩ Sha Dashboard)點下去觸發全頁重載 → Jekyll 重新渲染 → sidebar 切成 Kid’s Corner → 回不去 Rex’s Hub。
解法:default.html 的 SPA init block,補一個 .main-content 的事件委派 listener:
// DOMContentLoaded 內,緊接 sidebar listener 之後
var mainEl = document.querySelector('.main-content');
if (mainEl) {
mainEl.addEventListener('click', function (e) {
var a = e.target.closest('a[href]');
if (!a || !isLocal(a.getAttribute('href'))) return;
e.preventDefault();
navigate(a.getAttribute('href'));
});
}
關鍵細節:listener 掛在 .main-content 容器(而非個別 <a>)→ 事件委派,SPA 換頁後 innerHTML 被替換也不需重新綁定,新插入的連結全部自動繼承攔截。
效率做法(3 min):未來建站第一天就把這段加進去,和 sidebar 的 listener 並排,一次到位。
3. Service Worker — MP3 零延遲 / 零重複流量 / 離線播放
需求:539 個短 mp3(TOEIC 100 + Kid 439,均約 9 KB,合計 4.6 MB),手機重播時省流量、首播零延遲(快取後)、支援離線。
實作:新增 sw.js(repo 根目錄),default.html <head> 一次性註冊(全站生效)。
策略
| 資源 | 策略 | 說明 |
|——|——|——|
| .mp3 | Cache First + Range 切片 + LRU | 命中直接回傳,零流量 |
| HTML / JS / CSS | Stale-While-Revalidate | 有快取先回傳,背景更新 |
踩過的兩個坑(最重要)
坑 1:全部 ❌ — Range Request
瀏覽器播放 <audio> 時幾乎必定發出 Range: bytes=X-Y(HTTP 範圍請求)。SW 若直接從快取回傳完整 200 OK,瀏覽器期望 206 Partial Content,格式不符 → 全部播放失敗。
修法:偵測 Range header → 手動 ArrayBuffer.slice() 切片 → 回傳正確 206:
function makeRangeResponse(buffer, contentType, rangeHeader) {
const total = buffer.byteLength;
const m = /bytes=(\d+)-(\d*)/.exec(rangeHeader);
const start = parseInt(m[1], 10);
const end = m[2] ? parseInt(m[2], 10) : total - 1;
return new Response(buffer.slice(start, end + 1), {
status: 206,
headers: {
'Content-Type' : contentType,
'Content-Range' : `bytes ${start}-${end}/${total}`,
'Content-Length': String(end - start + 1),
'Accept-Ranges' : 'bytes',
}
});
}
坑 2:部分 ❌ — response.clone() 雙重使用
cache.put(key, response.clone()) 之後,再在 serveRange() 裡對同一個 response 呼叫第二次 .clone(),在部分瀏覽器的 Streams 實作中,body stream 進入 disturbed 狀態 → arrayBuffer() 失敗。
修法:body 只讀一次,fork 成兩份用途:
const buffer = await fullResp.arrayBuffer(); // 讀一次
cache.put(key, new Response(buffer.slice(0), {...})); // cache 用獨立 copy
return makeRangeResponse(buffer, contentType, rangeHdr); // serve 用原 buffer
各頁面加 clear cache checkbox
6 個單字版型(toeic_words、kid_words、tepi06_words、tepi07_words、toeic_phrase、toeic_reading)的 .pa-bar 都加:
<label class="pa-chk-label pa-chk-cache">
<input type="checkbox" id="clearcache-chk"> clear cache
</label>
勾起 → 清除 mp3-* 快取桶 → 自動取消勾選(一次性)。Mac 上幾乎瞬間完成,看起來像「勾不上去」,屬正常。
未來維護
- 更新音檔:只改
sw.js頂部的CACHE_VERSION('v2'→'v3'),deploy 後舊快取自動清除 - MP3_MAX:現設 600,覆蓋全部 539 個;未來新增單元要同步調高
效率做法(未來建新站 30 min)
- 直接複製本站
sw.js,只改CACHE_VERSION default.html<head>貼入 SW 註冊(8 行)- 各版型
.pa-bar貼 clear cache label(1 行) play-lib.html貼 clear cache JS + CSS(約 25 行)- 絕對不能省略
makeRangeResponse,這是音訊 SW 的最大坑