@@ -6,22 +6,18 @@ import { api } from 'backend-convex/convex/_generated/api'
66import { useConvexQuery } from ' convex-vue'
77import { Split } from ' lucide-vue-next'
88import { computed , ref } from ' vue'
9- import {
10- AlertDialog ,
11- AlertDialogAction ,
12- AlertDialogCancel ,
13- AlertDialogContent ,
14- AlertDialogDescription ,
15- AlertDialogFooter ,
16- AlertDialogHeader ,
17- AlertDialogTitle ,
18- AlertDialogTrigger ,
19- } from ' @/lib/shadcn/components/ui/alert-dialog'
9+
2010import {
2111 Avatar ,
2212 AvatarFallback ,
2313 AvatarImage ,
2414} from ' @/lib/shadcn/components/ui/avatar'
15+ import {
16+ ContextMenu ,
17+ ContextMenuContent ,
18+ ContextMenuItem ,
19+ ContextMenuTrigger ,
20+ } from ' @/lib/shadcn/components/ui/context-menu'
2521import {
2622 Sidebar ,
2723 SidebarContent ,
@@ -102,16 +98,6 @@ const filteredThreads = computed(() => {
10298 )
10399})
104100
105- const activeThread = computed <Doc <' threads' > | undefined >(() => {
106- if (! threadIdRef .value )
107- return undefined
108-
109- return threads .value .find (thread => thread ._id === threadIdRef .value )
110- })
111- useHead ({
112- title: computed (() => activeThread .value ?.title ?? ' > New Chat' ),
113- })
114-
115101function pinThread(thread : Doc <' threads' >) {
116102 pinnedThreadIds .value .push (thread ._id )
117103 nextTick (() => { document .getElementById (` li_thread_${thread ._id } ` )?.scrollIntoView ({ behavior: ' smooth' }) })
@@ -123,6 +109,9 @@ function unpinThread(thread: Doc<'threads'>) {
123109}
124110
125111async function _deleteThread(thread : Doc <' threads' >) {
112+ if (threadIdRef .value === thread ._id )
113+ threadIdRef .value = ' '
114+
126115 threads .value .splice (threads .value .indexOf (thread ), 1 )
127116 await deleteThread (convex , { threadId: thread ._id , lockerKey: $auth .loggedIn ? undefined : thread .lockerKey })
128117}
@@ -182,92 +171,104 @@ const [DefineThreadLiItem, ReuseThreadLiItem] = createReusableTemplate<{ thread:
182171 <!-- Define reusable items -->
183172 <div class =" hidden" >
184173 <DefineDeleteBtn v-slot =" { thread }" >
185- <AlertDialog >
186- <Tooltip :delay-duration =" 500" >
187- <AlertDialogTrigger v-show =" !thread.userId || (thread.userId === $auth?.user?.sub)" as-child >
188- <TooltipTrigger as-child >
189- <Button tabindex =" -1" variant =" ghost" size =" icon" class =" size-7 transition-none" @pointerdown.stop.prevent @click.shift.stop.prevent =" _deleteThread(thread)" >
190- <div class =" i-hugeicons:cancel-01" />
191- </Button >
192- </TooltipTrigger >
193- <TooltipContent side =" bottom" :side-offset =" 6" >
194- <p >{{ $t('chat.thread.delete') }}</p >
195- </TooltipContent >
196- </AlertDialogTrigger >
197- </Tooltip >
198- <AlertDialogContent >
199- <AlertDialogHeader >
200- <AlertDialogTitle >{{ $t('chat.sidebar.deleteThread.title') }}</AlertDialogTitle >
201- <AlertDialogDescription class =" whitespace-pre-line" >
202- {{ $t('chat.sidebar.deleteThread.description', { name: thread.title }) }}
203- </AlertDialogDescription >
204- </AlertDialogHeader >
205- <AlertDialogFooter >
206- <AlertDialogCancel >{{ $t('cancel') }}</AlertDialogCancel >
207- <AlertDialogAction @click =" _deleteThread(thread)" >
208- {{ $t('confirm') }}
209- </AlertDialogAction >
210- </AlertDialogFooter >
211- </AlertDialogContent >
212- </AlertDialog >
174+ <DeleteThreadAlertDialog :thread :callback =" () => { _deleteThread(thread) }" >
175+ <Button tabindex =" -1" variant =" ghost" size =" icon" class =" size-7 transition-none" >
176+ <div class =" i-hugeicons:cancel-01" />
177+ </Button >
178+ </DeleteThreadAlertDialog >
213179 </DefineDeleteBtn >
214180
215181 <DefineThreadLiItem v-slot =" { thread, pinned }" >
216182 <li :id =" `li_thread_${thread._id}`" >
217- <!-- Using [&.active] instead of :active-class because of reactivity bug -->
218- <NuxtLink
219- :to =" `/chat/${thread._id}`"
220- class =" group/thread relative block flex items-center overflow-hidden rounded-md p-2 px-3 [& .router-link-exact-active]:bg-primary/10 hover:bg-primary/20"
221- @pointerdown =" sidebarContext.setOpenMobile(false)"
222- >
223- <Tooltip v-if =" thread.parentThread" :delay-duration =" 500" >
224- <TooltipTrigger as-child >
225- <NuxtLink
226- :to =" `/chat/${thread.parentThread}`"
227- class =" mr-2 size-4"
228- >
229- <Button variant =" link" size =" icon" class =" size-full text-current opacity-40 transition-opacity hover:(text-primary opacity-100)" >
230- <Split />
231- </Button >
232- </NuxtLink >
233- </TooltipTrigger >
234- <TooltipContent side =" bottom" :side-offset =" 6" >
235- <p >{{ $t('chat.thread.branchedFrom', { title: threadsKeyed[thread.parentThread]?.title }) }}</p >
236- </TooltipContent >
237- </Tooltip >
238-
239- <Tooltip :delay-duration =" 500" >
240- <TooltipTrigger as-child >
241- <div class =" line-clamp-1" >
242- {{ thread.title }}
183+ <ContextMenu >
184+ <ContextMenuTrigger >
185+ <!-- Using [&.active] instead of :active-class because of reactivity bug -->
186+ <NuxtLink
187+ :to =" `/chat/${thread._id}`"
188+ class =" group/thread relative block flex items-center gap-2 overflow-hidden rounded-md p-2 px-3 text-sm [& .router-link-exact-active]:bg-primary/10 hover:bg-primary/20"
189+ @pointerdown =" sidebarContext.setOpenMobile(false)"
190+ >
191+ <div class =" h-4 flex items-center gap-1" >
192+ <Tooltip v-if =" thread.parentThread" :delay-duration =" 500" >
193+ <TooltipTrigger as-child >
194+ <NuxtLink
195+ :to =" `/chat/${thread.parentThread}`"
196+ class =" size-4"
197+ >
198+ <Button variant =" link" size =" icon" class =" size-4 text-current opacity-40 transition-opacity hover:(text-primary opacity-100)" >
199+ <Split />
200+ </Button >
201+ </NuxtLink >
202+ </TooltipTrigger >
203+ <TooltipContent side =" bottom" :side-offset =" 6" >
204+ <p >{{ $t('chat.thread.branchedFrom', { title: threadsKeyed[thread.parentThread]?.title }) }}</p >
205+ </TooltipContent >
206+ </Tooltip >
207+ <Tooltip v-if =" thread.frozen" :delay-duration =" 500" >
208+ <TooltipTrigger as-child >
209+ <Button variant =" link" size =" icon" class =" size-4 text-current opacity-40 transition-color hover:(text-primary opacity-100)" >
210+ <div class =" i-hugeicons:snow" />
211+ </Button >
212+ </TooltipTrigger >
213+ <TooltipContent side =" bottom" :side-offset =" 6" >
214+ <p >{{ $t('chat.thread.frozen') }}</p >
215+ </TooltipContent >
216+ </Tooltip >
243217 </div >
244- </TooltipTrigger >
245- <TooltipContent side =" bottom" :side-offset =" 6" >
246- <p >{{ thread.title }}</p >
247- </TooltipContent >
248- </Tooltip >
249218
250- <LiquidGlassDiv
251- class =" right-0 top-0 h-full flex translate-x-[calc(100%+1rem)] items-center gap-1 px-2 pr-1 transition-transform will-change-transform $c-radius=6px absolute! group-hover/thread:translate-x-0"
252- @click.stop.prevent
253- >
254- <Tooltip :delay-duration =" 500" >
255- <TooltipTrigger as-child >
256- <Button
257- tabindex =" -1" variant =" ghost" size =" icon" class =" size-7 transition-none hover:bg-surface-200/20!"
258- @pointerdown.stop.prevent @click.stop.prevent =" pinned ? unpinThread(thread) : pinThread(thread)"
259- >
260- <div :class =" [pinned ? 'i-hugeicons:pin-off' : 'i-hugeicons:pin']" />
261- </Button >
262- </TooltipTrigger >
263- <TooltipContent side =" bottom" :side-offset =" 6" >
264- <p >{{ pinned ? $t('chat.thread.unpin') : $t('chat.thread.pin') }}</p >
265- </TooltipContent >
266- </Tooltip >
219+ <Tooltip :delay-duration =" 500" >
220+ <TooltipTrigger as-child >
221+ <div class =" truncate" >
222+ {{ thread.title }}
223+ </div >
224+ </TooltipTrigger >
225+ <TooltipContent side =" bottom" :side-offset =" 6" >
226+ <p >{{ thread.title }}</p >
227+ </TooltipContent >
228+ </Tooltip >
229+
230+ <LiquidGlassDiv
231+ class =" right-0 top-0 h-full flex translate-x-[calc(100%+1rem)] items-center gap-1 px-2 pr-1 transition-transform will-change-transform $c-radius=6px absolute! group-hover/thread:translate-x-0"
232+ @click.stop.prevent
233+ >
234+ <Tooltip :delay-duration =" 500" >
235+ <TooltipTrigger as-child >
236+ <Button
237+ tabindex =" -1" variant =" ghost" size =" icon" class =" size-7 transition-none hover:bg-surface-200/20!"
238+ @pointerdown.stop.prevent @click.stop.prevent =" pinned ? unpinThread(thread) : pinThread(thread)"
239+ >
240+ <div :class =" [pinned ? 'i-hugeicons:pin-off' : 'i-hugeicons:pin']" />
241+ </Button >
242+ </TooltipTrigger >
243+ <TooltipContent side =" bottom" :side-offset =" 6" >
244+ <p >{{ pinned ? $t('chat.thread.unpin') : $t('chat.thread.pin') }}</p >
245+ </TooltipContent >
246+ </Tooltip >
267247
268- <ReuseDeleteBtn :thread />
269- </LiquidGlassDiv >
270- </NuxtLink >
248+ <ReuseDeleteBtn :thread />
249+ </LiquidGlassDiv >
250+ </NuxtLink >
251+ </ContextMenuTrigger >
252+ <ContextMenuContent >
253+ <ContextMenuItem
254+ @click =" pinned ? unpinThread(thread) : pinThread(thread)"
255+ >
256+ {{ pinned ? $t('chat.thread.unpin') : $t('chat.thread.pin') }}
257+ </ContextMenuItem >
258+ <ContextMenuItem @select.prevent >
259+ <DeleteThreadAlertDialog :thread :callback =" () => { _deleteThread(thread) }" :tip-only =" true" >
260+ <p >{{ $t('chat.thread.delete') }}</p >
261+ </DeleteThreadAlertDialog >
262+ </ContextMenuItem >
263+ <ContextMenuItem
264+ @click =" thread.frozen
265+ ? unfreezeThread(convex, { threadId: thread._id, lockerKey: thread.lockerKey })
266+ : freezeThread(convex, { threadId: thread._id, lockerKey: thread.lockerKey })"
267+ >
268+ {{ thread.frozen ? $t('chat.thread.unfreeze') : $t('chat.thread.freeze') }}
269+ </ContextMenuItem >
270+ </ContextMenuContent >
271+ </ContextMenu >
271272 </li >
272273 </DefineThreadLiItem >
273274 </div >
0 commit comments