Skip to content

feat: bookmark and share favorite sessions (#160, #95)#176

Open
pan93412 wants to merge 1 commit into
add-session-search-filtersfrom
claude/github-issue-160-1dmFt
Open

feat: bookmark and share favorite sessions (#160, #95)#176
pan93412 wants to merge 1 commit into
add-session-search-filtersfrom
claude/github-issue-160-1dmFt

Conversation

@pan93412

@pan93412 pan93412 commented May 30, 2026

Copy link
Copy Markdown
Contributor

在議程頁(/session/en/session)加入收藏(#160)與分享收藏連結(#95,建構在 #172(議程搜尋/篩選) 的架構之上。

Stacked on #172:本 PR 的 base 已設為 add-session-search-filters#172)。本分支只含收藏/分享的增量單一 commit;#172 併入 main 後,GitHub 會自動把 base 轉回 main,diff 不變。請先合併 #172,再合併本 PR。


變更內容

收藏(#160

  • 每張議程卡右上角加書籤鈕:收藏後卡片由紫翻橘、書籤由外框變實心。
  • 議程頁加 議程 / 收藏 切換,於兩種視圖間切換。
  • 收藏以 local storage 持久化(VueUse useStorage,原生序列化 Set),重整後保留。
  • 沿用 2025 站的書籤/卡片樣式,以 UnoCSS 重寫;新增 cp-orange 主題色盤。

分享收藏(#95

  • 收藏 視圖且有收藏時出現 分享收藏 鈕,複製帶 ?filter=<codes> 的連結(逗號分隔、排序的 Pretalx 投稿碼,與 2025 站一致)。
  • 開啟 ?filter= 連結會唯讀預覽分享的議程(橘色實心書籤、不可切換),並自動跳到第一個含分享議程的日期。
  • 頂部匯入橫幅匯入取代目前收藏、清掉 ?filter= 並切到 收藏 視圖;忽略 僅清掉參數。
  • 無效連結(碼格式合法但對不到任何議程)顯示錯誤橫幅、只有 忽略、沒有 匯入,既有收藏不會被清空。

#172 的整合(複用既有元件,不重複造輪子)

  • 狀態集中在 session.vue、全 computed:在 feat: add session search filters (#94) #172useSessionFilterfilteredSessions 之上,再以 displayedSessions 疊一層(all/favorite/shared 視圖),往下傳給 CpSessionTable / CpSessionList,元件只負責渲染。整段疊在 feat: add session search filters (#94) #172<ClientOnly> 骨架/order-last DaySelector/--viewport-width 版面之上。
  • 複用 feat: add session search filters (#94) #172 的 shared CpButtonCpSessionViewToggle(basic + active)、CpSessionShareButton(secondary + icon slot)皆組合既有的 CpButton,不另造按鈕元件。
  • CpSessionEmptyBannervariantfilter / favorite / shared),沿用 feat: add session search filters (#94) #172 既有的空狀態元件,而非新增第二個。
  • CpSessionFilterBar#controls slot:視圖切換與分享鈕透過既有 FilterBar 的 slot 置於搜尋框旁,未複製一份篩選列。
  • CpSessionItem 維持純展示元件:扁平 props,書籤 aria-label 由父層解析後以 favoriteLabel 傳入。
  • 抽出共用的 useFavoriteLabel():原本 CpSessionTableCpSessionList 各自重複一份 favoriteLabel 函式+ <i18n> 字串,合併進 useFavorites.ts 的單一 composable(local i18n scope),兩個版面共用。

⚠️ 預渲染 OOM 的防護

pnpm build 預渲染 session 詳情頁時會透過 <NuxtPage> 重繪整張議程表。若給每張 CpSessionItem 各建一個 vue-i18n scope,數百卡片 × 683 頁會累積到 heap OOM。因此書籤 label 一律由父層(Table/List,各一個實例)解析後傳入,CpSessionItem 不持有 i18n scope;useFavoriteLabel 也只在父層各呼叫一次。useFavoritesuseStorage 另加 initOnMounted 守衛,避免 SSR 每頁累積 storage 副作用。


測試計畫(Test Plan)

Claude-in-Chrome 對 dev server 實測,桌機(1280px)與手機(414px)、中文與英文各跑過;下列已勾選項目皆通過。

1. 收藏切換(#160

  • 1.1 卡片書籤鈕存在;未收藏為紫色/外框書籤。
  • 1.2 點書籤 → 卡片翻橘、書籤變實心;aria-label 在「加入收藏」↔「取消收藏」(en:Add to favoritesRemove from favorites)。
  • 1.3 再點還原;書籤不會誤觸進入詳情頁。
  • 1.4 桌機表格與手機清單兩種版面皆正確。

2. 收藏視圖

  • 2.1 議程 / 收藏 切換存在,視覺與篩選鈕一致。
  • 2.2 切到 收藏 只顯示已收藏議程;無收藏顯示空狀態(favorite 變體)。
  • 2.3 切回 議程 顯示全部,收藏狀態保留。

3. 與篩選列整合(#172 共存)

  • 3.1 收藏 視圖中搜尋/教室/標籤仍可作用,且只在收藏範圍內過濾(搜尋「Welcome」→ 只剩對應收藏)。

4. 持久化

  • 4.1 收藏後重整(F5)→ localStorage 保留,橘色/實心書籤還原。
  • 4.2 載入無 console error,無 hydration mismatch。

5. 分享連結(#95

  • 5.1 收藏 視圖且有收藏時才顯示 分享收藏
  • 5.2 點擊複製 …/session?filter=<codes>(碼排序、逗號分隔,與收藏一致)。

6. 開啟分享連結(預覽 + 匯入)

  • 6.1 顯示匯入橫幅「有人分享了 N 個收藏議程……」。
  • 6.2 自動跳到第一個含分享議程的日期;預覽為唯讀橘色收藏。
  • 6.3 預覽模式下篩選列隱藏。
  • 6.4 預覽張數 = 分享連結內議程數。
  • 6.5 匯入 → 取代本機收藏、清掉 ?filter=、切到 收藏 視圖。
  • 6.6 匯入後重整 → 收藏保留。
  • 6.7 忽略 → 僅清掉 ?filter=,原有收藏不變。

7. 無效分享連結(防呆)

  • 7.1 ?filter=ZZZZZZ(格式合法、無對應議程)顯示錯誤橫幅「連結無效……」。
  • 7.2 只有 忽略、沒有 匯入
  • 7.3 既有收藏完全不被覆蓋;忽略 後清掉參數、回到一般議程。

8. i18n / 版面

  • 8.1 中英文各跑一輪,切換鈕、分享鈕、匯入/錯誤橫幅、空狀態、書籤 aria-label 皆正確翻譯。
  • 8.2 桌機:分享收藏(含文字)在切換器左側、切換器在搜尋框左側,三者等高對齊。
  • 8.3 手機:議程/收藏 置中於搜尋框下方;分享鈕僅圖示、置於切換器右側。

9. 自動化

  • pnpm lintpnpm typecheck 通過。
  • pnpm build 通過(1970 routes,無 OOM)。

Closes #160
Closes #95

@rileychh-dokploy-coscup

rileychh-dokploy-coscup Bot commented May 30, 2026

Copy link
Copy Markdown

Dokploy Preview Deployment

Name Status Preview Updated (UTC)
Nuxt ❌ Failed Preview URL 2026-06-01T15:35:28.617Z

@pan93412 pan93412 force-pushed the claude/github-issue-160-1dmFt branch from 878b511 to 489ea92 Compare May 30, 2026 14:31
@pan93412 pan93412 changed the title feat: bookmark sessions with a favorites view (#160) feat: bookmark and share favorite sessions (#160, #95) May 30, 2026
@pan93412 pan93412 marked this pull request as ready for review May 30, 2026 18:34
Copilot AI review requested due to automatic review settings May 30, 2026 18:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds session bookmarking persisted to local storage, a 議程 / 收藏 view toggle on the schedule, and the ability to share a favorites list via a ?filter= query that previews and can be imported by another user.

Changes:

  • New useFavorites composable (provide/inject + useStorage) plus encodeFavorites / decodeFavorites helpers for the share link.
  • New CpSessionViewToggle, CpSessionShareButton, and CpFavoriteImportBanner; CpSessionItem / CpSessionTable extended with favorite / readonly / preview props.
  • pages/session/index.vue wires up filtering, shared-list preview, auto day selection, and import/dismiss; app.vue provides the store; uno.config.ts adds a cp-orange palette.

Reviewed changes

Copilot reviewed 9 out of 27 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
app/composables/useFavorites.ts New favorites store + share encode/decode helpers.
app/app.vue Provides favorites store at root.
app/pages/session/index.vue View toggle, shared preview, import/dismiss, empty states.
app/components/feature/CpSessionTable.vue Reads favorites and forwards preview/readonly to cards.
app/components/feature/CpSessionItem.vue Bookmark button, orange favorited state, readonly indicator.
app/components/feature/CpSessionViewToggle.vue Segmented 議程 / 收藏 control.
app/components/feature/CpSessionShareButton.vue Copies share link via clipboard.
app/components/feature/CpFavoriteImportBanner.vue Import / dismiss banner for shared favorites.
uno.config.ts Adds cp-orange-* palette.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/components/feature/CpFavoriteImportBanner.vue Outdated
Comment thread app/pages/session.vue
@pan93412 pan93412 requested a review from a team May 30, 2026 19:36
@pan93412 pan93412 force-pushed the claude/github-issue-160-1dmFt branch from 6d35746 to e2e7d78 Compare June 1, 2026 15:32
@pan93412 pan93412 marked this pull request as draft June 1, 2026 16:22
@pan93412 pan93412 force-pushed the claude/github-issue-160-1dmFt branch 4 times, most recently from b3127d0 to 9724abc Compare June 3, 2026 15:06
@pan93412 pan93412 force-pushed the claude/github-issue-160-1dmFt branch from 9724abc to ea938e4 Compare June 17, 2026 12:53
@pan93412 pan93412 changed the base branch from main to add-session-search-filters June 17, 2026 12:54
@pan93412 pan93412 marked this pull request as ready for review June 17, 2026 13:04
Rebuild the favorites (#160) and share (#95) features on the
session-filter
architecture from #172: page-level state computed into the displayed
list,
shared base components, and feature components that compose them.

- CpSessionItem gains favorite/readonly bookmark state (orange filled
  icon).
- CpSessionTable/CpSessionList forward favorites + a preview (read-only)
  mode.
- session.vue layers the all/favorite/shared view on top of
  filteredSessions
  from useSessionFilter, keeping the filter bar and empty banner.
- CpSessionViewToggle and CpSessionShareButton compose the shared
  CpButton.
- CpSessionEmptyBanner gains filter/favorite/shared variants.
- useFavorites store (provide/inject + useStorage) and cp-orange
  palette.

Closes #160
Closes #95

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ea938e47be

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/pages/session.vue
const displayedSessions = computed(() => {
if (isSharing.value) {
const shared = new Set(sharedSessionIds.value)
return filteredSessions.value.filter((session) => shared.has(session.id))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore stale filters when previewing shared favorites

When a shared ?filter= URL is opened in the same tab after the user has selected a search, room, or tag filter, this still intersects the shared IDs with filteredSessions even though the filter bar is hidden by v-if="!isSharing". Valid shared sessions can therefore disappear, or the preview can look empty while the banner reports imported sessions, with no visible control to clear the stale filters until the link is dismissed; build the shared preview from the unfiltered sessions for the selected day or reset filter state when entering sharing mode.

Useful? React with 👍 / 👎.

>
<Icon name="tabler:bookmark-filled" />
</span>
<button

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid nesting the bookmark button in session links

When this component is rendered by CpSessionList or CpSessionTable, the whole card is already wrapped in a NuxtLink, so adding a real <button> here creates nested interactive controls. For keyboard and assistive-technology users on session cards, focus and activation of the bookmark can conflict with the enclosing session link; make the bookmark and link siblings, or use an overlay link pattern, so the favorite control is not inside the anchor.

Useful? React with 👍 / 👎.

@pan93412 pan93412 force-pushed the add-session-search-filters branch from e5ef6de to 21500cd Compare June 17, 2026 13:11
@pan93412 pan93412 force-pushed the claude/github-issue-160-1dmFt branch from ea938e4 to c198a98 Compare June 17, 2026 13:11

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c198a987ba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/pages/session.vue

function importShared() {
setFavorites(sharedSessionIds.value)
clearShare()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep imported favorites on the shared day

When importing a valid share whose first shared session is not on the first conference day, clearShare() removes the query and resets manualSelectedDay to null; after the route update, firstSharedDay is also null, so selectedDay falls back to days[0]. The user is then switched to the Favorites view on a day that may contain none of the imported sessions, making the import look empty immediately after it succeeds; preserve firstSharedDay as the selected day before clearing the share query.

Useful? React with 👍 / 👎.

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.

可以收藏議程 收藏夾分享功能

2 participants