Skip to content

feat: add favorite math templates - #161

Open
jamliaoo wants to merge 7 commits into
mainfrom
feat/favorite-math-templates
Open

jamliaoo wants to merge 7 commits into
mainfrom
feat/favorite-math-templates

Conversation

@jamliaoo

@jamliaoo jamliaoo commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

背景描述 (Why)

編輯數學內容時,常在特定單元反覆使用少數幾個模版,例如幾何單元的弧、角、平行、垂直,多項式單元的分數和根號。目前每次都要在 14 個分類、141 個模版裡找,用鍵盤或螢幕閱讀器操作的人更費力。這個 PR 讓使用者把常用模版收進「常用符號」分類,之後點一下就能插入。

需求來自常用快捷功能的 PRD、EDD 和 Figma 設計稿。三者衝突的兩處依設計稿實作:

  • 排序和移除在「編輯常用」modal 裡完成,不採用 EDD 提的常用項目右鍵選單。
  • 重複加入時跳 dialog,選單項目不停用,並且先檢查上限再檢查重複。

實作方法 (How)

資料層

  • localStorage 只存模版 id 陣列,key 是 access8math.favoriteMathTemplateIds。畫面需要時再用 mathTabList 建出的 mathTemplateIndex 查回完整模版。模版物件裡有 React 元件,無法序列化,只存 id 也不會留下過期的複本。
  • 狀態放在模組層級的 store,沿用既有的 createStore,元件透過以 useSyncExternalStore 實作的 useFavoriteMathTemplates 訂閱。EDD 原本建議把狀態放在 EditIconsTab 裡,但設計稿把「編輯常用」放在首頁工具列,兩邊必須共用同一份資料。
  • 讀取時清掉壞資料,包括非陣列、非字串、未知 id、重複和超過 10 個的部分。寫入時先寫 localStorage,成功後才更新狀態,所以失敗時畫面和儲存都維持原樣。
  • store 監聽 storage 事件,讓多個分頁的列表保持同步。

介面

  • EditIconsTab 在數學分類最前面加上「常用符號」分頁。列表不是空的時候預設開這個分頁,否則開「常見」。
  • 新的 ContextMenu 核心元件照 WAI-ARIA menu pattern 實作,支援右鍵、快顯鍵和 Shift+F10。選取項目時先把焦點還給模版按鈕再執行,之後 dialog 關閉時焦點才回得去。
  • 新的 AlertModal 負責上限、重複、列表為空三種提示,role 是 alertdialog。
  • EditFavoritesModal 在草稿上編輯,按「完成」才呼叫 saveFavorites。首頁每次開啟它都換一個 key,讓草稿從已儲存的列表重新開始。

無障礙

  • modal 裡的移動和刪除透過 dialog 內的 live region 報讀,焦點跟著被移動的那一列。↑↓ 用 aria-disabled,移到頂端或底部時按鈕不會失焦。
  • Headless UI Dialog 開著時會把頁面其他部分設為 inert,toast 因此不會被報讀,所以存檔成功的 toast 等 dialog 關閉後才發出。為此 BasicModal 新增可選的 initialFocus 和 afterLeave,既有呼叫端行為不變。

實際變更

  • 新增 src/lib/favorite-math-templates/,包含 store、hook 和模版 index
  • EditIconsTab 加上常用符號分類、右鍵選單,以及加入後的 toast 和 dialog
  • 新增核心元件 ContextMenu 與 AlertModal
  • 新增 EditFavoritesModal,首頁工具列加上「編輯常用」按鈕
  • BasicModal 新增可選 prop initialFocus 與 afterLeave
  • 首頁工具列空間不夠時改為換行,避免在 1024 到 1200px 寬時切掉 LaTeX 分隔符切換
  • 新增 zh-TW 與 en 文案,改動 tabs.json、home.json,並新增 edit-favorites-modal.json
  • setupTests.js 加上 ResizeObserver stub,讓 Headless UI Dialog 能在 jsdom 裡測試

截圖或錄影參考

待補:右鍵選單、上限與重複 dialog、編輯 modal、完整操作錄影

測試驗證

  • 116 個單元與元件測試通過,原本只有 8 個。涵蓋壞資料、寫入失敗、上限與重複的檢查順序、跨分頁同步、選單鍵盤操作、焦點回歸,以及草稿的取消與儲存
  • npm run lint、npm run format:check、npm run build 通過
  • 在 Chrome 手動驗證右鍵與 Shift+F10 加入、重複與上限 dialog、重新整理後保留、從常用分類插入,以及編輯 modal 移動、刪除後的取消與完成
  • 以 ?locale=en 確認英文介面文案
  • Windows + NVDA 與 Menu 鍵
  • Firefox、Safari
  • Electron 正式版重開後保留常用列表
  • 用螢幕閱讀器實際確認 toast 與 dialog 的報讀

補充說明

  • 兩個分頁同時編輯時,以最後一次存檔為準。如果一個分頁開著編輯 modal,另一個分頁改了列表,前者調整過列表再按「完成」,會蓋掉後者的修改;沒有調整就按「完成」則只會關閉,不會寫入。
  • toast 位置維持在右下角。設計稿把 toast 畫在上方,但 Toaster 是全站共用的元件,這個 PR 沒有改動它。
  • 英文介面在 1280 到 1350px 寬時,工具列會換成兩行。
  • 設計稿上限 dialog 標題的「以超過上限」改成「已超過上限」,空狀態文案也加上鍵盤操作的說明。
  • 每個 commit 對應一個可以獨立驗證的階段,可以逐個 commit 審閱。

相關連結

jamliaoo and others added 7 commits October 20, 2026 15:30
Persist favorite math template ids in localStorage (max 10) behind a
shared store and a useFavoriteMathTemplates hook. Stored data is
sanitized on read, rejected writes leave state and storage untouched,
and the list stays in sync across tabs via the storage event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Show the saved favorite templates as the first math category, insert
them on click, and open on it by default when the list is not empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Right-clicking a math template, or pressing the context menu key or
Shift+F10 on it, opens an accessible menu to add it to favorites.
Success is announced with a toast; reaching the limit or adding a
duplicate shows an alert dialog, and focus returns to the template.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new "Edit favorites" toolbar button opens a modal that edits a draft
of the favorites list with move up, move down and remove buttons; it is
saved only on Done. Changes are announced inside the dialog, the success
toast waits until the dialog has closed, and an empty list shows how to
add favorites instead. The toolbar now wraps on narrow screens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop the minimum width and inner padding so a single item reads as one
dark label with even padding, matching the design. The item highlights
on hover and keyboard focus instead of on any focus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove comments that restate the code or a test name, and rewrite the
rest in plain, active sentences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pressing Done without any change saved the list the modal opened with,
which silently reverted changes another tab made in the meantime. Done
now just closes the modal when the draft still matches the list at
opening, and skips the "saved" toast because nothing was saved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jamliaoo
jamliaoo marked this pull request as ready for review November 6, 2026 15:22

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant