-
Notifications
You must be signed in to change notification settings - Fork 8
小畫面的議程表 #294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
小畫面的議程表 #294
Changes from all commits
3466700
3ec901d
c1a6067
67348a7
b8c3e11
cd49185
baadc49
72a8007
0b3eb25
59a7d01
6e24d1f
f5e3c54
d05cfb0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| <script setup lang="ts"> | ||
| import type { Item } from '~/components/shared/CpGroupButton.vue' | ||
| import { useI18n } from 'vue-i18n' | ||
| import CpButton from '~/components/shared/CpButton.vue' | ||
| import CpGroupButton from '~/components/shared/CpGroupButton.vue' | ||
|
|
||
| type SessionViewItemKey = 'all' | 'favorite' | ||
|
|
||
| const model = defineModel<SessionViewItemKey>({ required: true }) | ||
|
|
||
| const { t } = useI18n() | ||
|
|
||
| const viewItems = computed<Item<SessionViewItemKey>[]>(() => [ | ||
| { key: 'all', label: t('view.all') }, | ||
| { key: 'favorite', label: t('view.favorite'), icon: 'tabler:bookmark' }, | ||
| ]) | ||
|
|
||
| function switchView() { | ||
| model.value = model.value === 'all' ? 'favorite' : 'all' | ||
| } | ||
| </script> | ||
|
|
||
| <template> | ||
| <!-- Flush segments so the toggle's height matches the adjacent search field. --> | ||
| <CpGroupButton | ||
| v-model="model" | ||
| class="hidden md:inline-flex" | ||
| :items="viewItems" | ||
| /> | ||
| <div class="border border-gray-200 rounded-md bg-white inline-flex overflow-hidden md:hidden"> | ||
| <CpButton | ||
| :aria-label="model === 'favorite' ? t('view.favorite') : t('view.all')" | ||
| :aria-pressed="model === 'favorite'" | ||
| :class="model === 'favorite' ? '!bg-yellow-400 !text-white' : '!bg-white !text-black'" | ||
| variant="basic" | ||
| @click="switchView" | ||
| > | ||
| <Icon | ||
| :name="model === 'favorite' ? 'tabler:star-filled' : 'tabler:star'" | ||
| size="18" | ||
| /> | ||
| </CpButton> | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| </div> | ||
| </template> | ||
|
|
||
| <i18n lang="yaml"> | ||
| en: | ||
| view: | ||
| all: 'Sessions' | ||
| favorite: 'Favorites' | ||
| zh: | ||
| view: | ||
| all: '議程' | ||
| favorite: '收藏' | ||
| </i18n> | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,4 @@ | ||||||||||||||||||||||||||||
| <script setup lang="ts"> | ||||||||||||||||||||||||||||
| import CpBadge from '~/components/shared/CpBadge.vue' | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| defineProps<{ | ||||||||||||||||||||||||||||
| // Session link target, rendered as an overlay so the bookmark isn't nested in the anchor. | ||||||||||||||||||||||||||||
| to: string | ||||||||||||||||||||||||||||
|
|
@@ -11,6 +9,11 @@ defineProps<{ | |||||||||||||||||||||||||||
| room?: string | ||||||||||||||||||||||||||||
| tags: string[] | ||||||||||||||||||||||||||||
| favorite?: boolean | ||||||||||||||||||||||||||||
| track: { | ||||||||||||||||||||||||||||
| id?: number | ||||||||||||||||||||||||||||
| name: string | ||||||||||||||||||||||||||||
| color: string | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| // Resolved by the parent to avoid a per-card i18n scope (see CpSessionTable). | ||||||||||||||||||||||||||||
| favoriteLabel?: string | ||||||||||||||||||||||||||||
| // Shared-list preview: show the filled bookmark as a static, non-toggleable indicator. | ||||||||||||||||||||||||||||
|
|
@@ -24,10 +27,8 @@ defineEmits<{ | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| <template> | ||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||
| class="p-2 border rounded transition-colors relative" | ||||||||||||||||||||||||||||
| :class="favorite | ||||||||||||||||||||||||||||
| ? 'text-cp-orange-700 border-cp-orange-300 bg-cp-orange-50' | ||||||||||||||||||||||||||||
| : 'text-primary-600 border-primary-100 bg-primary-50'" | ||||||||||||||||||||||||||||
| class="p-2 border border-l-4 border-gray-300 rounded transition-colors relative" | ||||||||||||||||||||||||||||
| :style="{ 'border-left-color': track.color }" | ||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||
| <!-- Overlay link, so the bookmark is a sibling rather than nested in the anchor. | ||||||||||||||||||||||||||||
| Both are absolute; the bookmark follows in DOM order, so it paints on top. --> | ||||||||||||||||||||||||||||
|
|
@@ -42,42 +43,50 @@ defineEmits<{ | |||||||||||||||||||||||||||
| aria-hidden="true" | ||||||||||||||||||||||||||||
| class="text-xl text-cp-orange-600 leading-none p-1 pointer-events-none right-1 top-1 absolute" | ||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||
| <Icon name="tabler:bookmark-filled" /> | ||||||||||||||||||||||||||||
| <Icon name="tabler:star-filled" /> | ||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||
| v-else | ||||||||||||||||||||||||||||
| :aria-label="favoriteLabel" | ||||||||||||||||||||||||||||
| :aria-pressed="favorite" | ||||||||||||||||||||||||||||
| class="text-xl leading-none p-1 rounded cursor-pointer transition-colors right-1 top-1 absolute" | ||||||||||||||||||||||||||||
| :class="favorite | ||||||||||||||||||||||||||||
| ? 'text-cp-orange-600 hover:text-cp-orange-700' | ||||||||||||||||||||||||||||
| : 'text-primary-300 hover:text-primary-500'" | ||||||||||||||||||||||||||||
| ? 'text-yellow-400' | ||||||||||||||||||||||||||||
| : 'text-gray-600'" | ||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||
| @click.prevent.stop="$emit('toggleFavorite')" | ||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||
| <Icon :name="favorite ? 'tabler:bookmark-filled' : 'tabler:bookmark'" /> | ||||||||||||||||||||||||||||
| <Icon :name="favorite ? 'tabler:star-filled' : 'tabler:star'" /> | ||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||
| <div class="pr-6 flex flex-col"> | ||||||||||||||||||||||||||||
| <h3 class="text-base text-inherit font-normal my-1"> | ||||||||||||||||||||||||||||
| <span | ||||||||||||||||||||||||||||
| class="text-xs text-white font-medium px-3 py-1 rounded-full flex gap-0.5 w-max items-center" | ||||||||||||||||||||||||||||
| :style="{ backgroundColor: track.color }" | ||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||
| {{ track.name }} | ||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||
|
Comment on lines
+62
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Guard the track pill against an empty Unlike 🛡️ Proposed fix <span
+ v-if="track.name"
class="text-xs text-white font-medium px-3 py-1 rounded-full flex gap-0.5 w-max items-center"
:style="{ backgroundColor: track.color }"
>
{{ track.name }}
</span>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| <h3 class="text-base text-inherit font-bold my-1"> | ||||||||||||||||||||||||||||
| {{ title }} | ||||||||||||||||||||||||||||
| </h3> | ||||||||||||||||||||||||||||
| <p class="text-sm my-1"> | ||||||||||||||||||||||||||||
| {{ speaker }} | ||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||
| <div class="my-1 flex gap-2 items-center"> | ||||||||||||||||||||||||||||
| <template v-if="room && room.trim() !== ''"> | ||||||||||||||||||||||||||||
| <span>{{ room }}</span> | ||||||||||||||||||||||||||||
| <span>•</span> | ||||||||||||||||||||||||||||
| </template> | ||||||||||||||||||||||||||||
| <time class="text-base opacity-50">{{ start }} ~ {{ end }}</time> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| <p class="text-sm my-1"> | ||||||||||||||||||||||||||||
| {{ speaker }} | ||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| <p class="flex gap-1"> | ||||||||||||||||||||||||||||
| <CpBadge | ||||||||||||||||||||||||||||
| <span | ||||||||||||||||||||||||||||
| v-for="tag in tags" | ||||||||||||||||||||||||||||
| :key="tag" | ||||||||||||||||||||||||||||
| class="text-xs text-gray-600 font-medium px-3 py-1 rounded-full bg-stone-100" | ||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||
| {{ tag }} | ||||||||||||||||||||||||||||
| </CpBadge> | ||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,13 @@ | ||
| <script lang="ts" setup> | ||
| import type { SessionSummary } from '#shared/types/session' | ||
| import type { SessionSummary, SessionTrack } from '#shared/types/session' | ||
| import { useI18n } from '#imports' | ||
| import { useFavoriteLabel, useFavorites } from '~/composables/useFavorites' | ||
| import { DEFAULT_TRACK_COLOR, trackKey } from '~/utils/tracks' | ||
| import CpSessionItem from './CpSessionItem.vue' | ||
|
|
||
| const { sessions: _sessions, preview = false } = defineProps<{ | ||
| const { sessions: _sessions, trackColors, preview = false } = defineProps<{ | ||
| sessions: SessionSummary[] | ||
| trackColors: Map<string, string> | ||
| // Shared-list preview: render every session as a read-only favorite. | ||
| preview?: boolean | ||
| }>() | ||
|
|
@@ -15,6 +17,11 @@ const localePath = useLocalePath() | |
| const { isFavorite, toggleFavorite } = useFavorites() | ||
| const favoriteLabel = useFavoriteLabel(t) | ||
|
|
||
| function localeName(name?: SessionTrack['name']) { | ||
| const { en = '', 'zh-hant': zh = '' } = name ?? {} | ||
| return locale.value === 'zh' ? zh || en : en || zh | ||
| } | ||
|
|
||
| const sessions = computed(() => { | ||
| if (!_sessions) { | ||
| return {} | ||
|
|
@@ -31,7 +38,12 @@ const sessions = computed(() => { | |
| room: locale.value === 'zh' | ||
| ? (session.room?.['zh-hant'] || session.room?.en || '') | ||
| : (session.room?.en || session.room?.['zh-hant'] || ''), | ||
| tags: [], | ||
| tags: session.tags, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For sessions with a normalized difficulty, passing AGENTS.md reference: AGENTS.md:L5-L5 Useful? React with 👍 / 👎. |
||
| track: { | ||
| id: session.track?.id, | ||
| name: localeName(session.track?.name), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Pretalx supplies a null or unknown track—which AGENTS.md reference: AGENTS.md:L5-L5 Useful? React with 👍 / 👎. |
||
| color: trackColors.get(trackKey(session)) ?? DEFAULT_TRACK_COLOR, | ||
| }, | ||
| })), | ||
| (session) => session.start, | ||
| ) | ||
|
|
@@ -41,30 +53,60 @@ const times = computed(() => Object.keys(sessions.value).sort()) | |
| </script> | ||
|
|
||
| <template> | ||
| <div class="flex flex-col gap-6 w-[var(--viewport-width,100vw)] isolate"> | ||
| <div class="p-3 flex flex-col gap-6 w-[var(--viewport-width,100vw)] isolate"> | ||
| <section | ||
| v-for="time in times" | ||
| :key="time" | ||
| class="group/section" | ||
| > | ||
| <h3 class="text-lg text-primary-400 font-medium mb-2 py-1 bg-white top-0 sticky z-content"> | ||
| {{ time }} | ||
| <input | ||
| :id="`session-time-${time}`" | ||
| checked | ||
| class="sr-only" | ||
| type="checkbox" | ||
| > | ||
| <h3 class="text-sm font-medium mb-2 bg-white top-0 sticky z-content"> | ||
| <label | ||
| class="text-primary-400 py-1 flex gap-1 w-full cursor-pointer select-none items-center justify-between" | ||
| :for="`session-time-${time}`" | ||
| > | ||
| <div class="flex gap-x-2 items-center"> | ||
| <span class="w-[5ch] block">{{ time }}</span> | ||
| <Icon | ||
| class="text-md text-primary-400" | ||
| name="tabler:circle" | ||
| /> | ||
| <span v-if="sessions?.[time] && sessions[time].length > 1"> | ||
| {{ t('concurrent', { count: sessions[time].length }) }} | ||
| </span> | ||
|
mirumodapon marked this conversation as resolved.
mirumodapon marked this conversation as resolved.
|
||
| </div> | ||
| <Icon | ||
| class="text-sm transition-transform duration-300 group-has-[input:not(:checked)]/section:-rotate-180" | ||
| name="tabler:chevron-up" | ||
| /> | ||
| </label> | ||
| </h3> | ||
| <div class="flex flex-col gap-2"> | ||
| <CpSessionItem | ||
| v-for="session in sessions[time]" | ||
| :key="session.id" | ||
| :end="session.end" | ||
| :favorite="preview || isFavorite(session.id)" | ||
| :favorite-label="favoriteLabel(session.id, preview)" | ||
| :readonly="preview" | ||
| :room="session.room" | ||
| :speaker="session.speakers" | ||
| :start="session.start" | ||
| :tags="session.tags" | ||
| :title="session.title" | ||
| :to="localePath(`/session/${session.id}`)" | ||
| @toggle-favorite="toggleFavorite(session.id)" | ||
| /> | ||
| <div class="grid grid-rows-[1fr] transition-[grid-template-rows] duration-300 ease-in-out group-has-[input:not(:checked)]/section:grid-rows-[0fr]"> | ||
| <div class="overflow-hidden"> | ||
| <div class="pb-2 flex flex-col gap-2"> | ||
| <CpSessionItem | ||
| v-for="session in sessions[time]" | ||
| :key="session.id" | ||
| :end="session.end" | ||
| :favorite="preview || isFavorite(session.id)" | ||
| :favorite-label="favoriteLabel(session.id, preview)" | ||
| :readonly="preview" | ||
| :room="session.room" | ||
| :speaker="session.speakers" | ||
| :start="session.start" | ||
| :tags="session.tags" | ||
| :title="session.title" | ||
| :to="localePath(`/session/${session.id}`)" | ||
| :track="session.track" | ||
| @toggle-favorite="toggleFavorite(session.id)" | ||
| /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </section> | ||
| </div> | ||
|
|
@@ -74,7 +116,9 @@ const times = computed(() => Object.keys(sessions.value).sort()) | |
| en: | ||
| add: 'Add to favorites' | ||
| remove: 'Remove from favorites' | ||
| concurrent: '{count} sessions starting at the same time' | ||
| zh: | ||
| add: '加入收藏' | ||
| remove: '取消收藏' | ||
| concurrent: '{count} 場同時開始' | ||
| </i18n> | ||
Uh oh!
There was an error while loading. Please reload this page.