feat: bookmark and share favorite sessions (#160, #95)#176
Conversation
Dokploy Preview Deployment
|
878b511 to
489ea92
Compare
There was a problem hiding this comment.
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
useFavoritescomposable (provide/inject +useStorage) plusencodeFavorites/decodeFavoriteshelpers for the share link. - New
CpSessionViewToggle,CpSessionShareButton, andCpFavoriteImportBanner;CpSessionItem/CpSessionTableextended withfavorite/readonly/previewprops. pages/session/index.vuewires up filtering, shared-list preview, auto day selection, and import/dismiss;app.vueprovides the store;uno.config.tsadds acp-orangepalette.
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.
6d35746 to
e2e7d78
Compare
b3127d0 to
9724abc
Compare
9724abc to
ea938e4
Compare
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
There was a problem hiding this comment.
💡 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".
| const displayedSessions = computed(() => { | ||
| if (isSharing.value) { | ||
| const shared = new Set(sharedSessionIds.value) | ||
| return filteredSessions.value.filter((session) => shared.has(session.id)) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 👍 / 👎.
e5ef6de to
21500cd
Compare
ea938e4 to
c198a98
Compare
There was a problem hiding this comment.
💡 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".
|
|
||
| function importShared() { | ||
| setFavorites(sharedSessionIds.value) | ||
| clearShare() |
There was a problem hiding this comment.
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 👍 / 👎.
在議程頁(
/session、/en/session)加入收藏(#160)與分享收藏連結(#95),建構在 #172(議程搜尋/篩選) 的架構之上。變更內容
收藏(#160)
議程/收藏切換,於兩種視圖間切換。useStorage,原生序列化Set),重整後保留。cp-orange主題色盤。分享收藏(#95)
收藏視圖且有收藏時出現分享收藏鈕,複製帶?filter=<codes>的連結(逗號分隔、排序的 Pretalx 投稿碼,與 2025 站一致)。?filter=連結會唯讀預覽分享的議程(橘色實心書籤、不可切換),並自動跳到第一個含分享議程的日期。匯入會取代目前收藏、清掉?filter=並切到收藏視圖;忽略僅清掉參數。忽略、沒有匯入,既有收藏不會被清空。與 #172 的整合(複用既有元件,不重複造輪子)
session.vue、全computed:在 feat: add session search filters (#94) #172 的useSessionFilter→filteredSessions之上,再以displayedSessions疊一層(all/favorite/shared 視圖),往下傳給CpSessionTable/CpSessionList,元件只負責渲染。整段疊在 feat: add session search filters (#94) #172 的<ClientOnly>骨架/order-lastDaySelector/--viewport-width版面之上。CpButton:CpSessionViewToggle(basic + active)、CpSessionShareButton(secondary + icon slot)皆組合既有的CpButton,不另造按鈕元件。CpSessionEmptyBanner加variant(filter/favorite/shared),沿用 feat: add session search filters (#94) #172 既有的空狀態元件,而非新增第二個。CpSessionFilterBar加#controlsslot:視圖切換與分享鈕透過既有 FilterBar 的 slot 置於搜尋框旁,未複製一份篩選列。CpSessionItem維持純展示元件:扁平 props,書籤aria-label由父層解析後以favoriteLabel傳入。useFavoriteLabel():原本CpSessionTable與CpSessionList各自重複一份favoriteLabel函式+<i18n>字串,合併進useFavorites.ts的單一 composable(local i18n scope),兩個版面共用。pnpm build預渲染 session 詳情頁時會透過<NuxtPage>重繪整張議程表。若給每張CpSessionItem各建一個 vue-i18n scope,數百卡片 × 683 頁會累積到 heap OOM。因此書籤 label 一律由父層(Table/List,各一個實例)解析後傳入,CpSessionItem不持有 i18n scope;useFavoriteLabel也只在父層各呼叫一次。useFavorites的useStorage另加initOnMounted守衛,避免 SSR 每頁累積 storage 副作用。測試計畫(Test Plan)
以 Claude-in-Chrome 對 dev server 實測,桌機(1280px)與手機(414px)、中文與英文各跑過;下列已勾選項目皆通過。
1. 收藏切換(#160)
aria-label在「加入收藏」↔「取消收藏」(en:Add to favorites↔Remove from favorites)。2. 收藏視圖
議程/收藏切換存在,視覺與篩選鈕一致。收藏只顯示已收藏議程;無收藏顯示空狀態(favorite變體)。議程顯示全部,收藏狀態保留。3. 與篩選列整合(#172 共存)
收藏視圖中搜尋/教室/標籤仍可作用,且只在收藏範圍內過濾(搜尋「Welcome」→ 只剩對應收藏)。4. 持久化
localStorage保留,橘色/實心書籤還原。5. 分享連結(#95)
收藏視圖且有收藏時才顯示分享收藏。…/session?filter=<codes>(碼排序、逗號分隔,與收藏一致)。6. 開啟分享連結(預覽 + 匯入)
匯入→ 取代本機收藏、清掉?filter=、切到收藏視圖。忽略→ 僅清掉?filter=,原有收藏不變。7. 無效分享連結(防呆)
?filter=ZZZZZZ(格式合法、無對應議程)顯示錯誤橫幅「連結無效……」。忽略、沒有匯入。忽略後清掉參數、回到一般議程。8. i18n / 版面
分享收藏(含文字)在切換器左側、切換器在搜尋框左側,三者等高對齊。議程/收藏置中於搜尋框下方;分享鈕僅圖示、置於切換器右側。9. 自動化
pnpm lint、pnpm typecheck通過。pnpm build通過(1970 routes,無 OOM)。Closes #160
Closes #95