11<script setup lang="ts">
22import type { Ref } from ' vue'
33import type { PerfectScrollbarExpose } from ' vue3-perfect-scrollbar'
4- import type { Node } from ' ./types'
4+ import type { Node , Position } from ' ./types'
55import { languages } from ' @/components/editor/grammars/languages'
66import * as ContextMenu from ' @/components/ui/shadcn/context-menu'
77import { useApp , useDialog , useFolders , useSnippets } from ' @/composables'
@@ -20,8 +20,11 @@ interface Props {
2020
2121interface Emits {
2222 (e : ' update:modelValue' , value : Node []): void
23- (e : ' clickNode' , value : number ): void
24- (e : ' dragNode' , value : { node: Node , target: Node , position: string }): void
23+ (e : ' clickNode' , value : { id: number , event? : MouseEvent }): void
24+ (
25+ e : ' dragNode' ,
26+ value : { nodes: Node [], target: Node , position: Position },
27+ ): void
2528 (e : ' toggleNode' , value : Node ): void
2629}
2730
@@ -39,6 +42,9 @@ const {
3942 updateFolder,
4043 getFolderByIdFromTree,
4144 getFolders,
45+ selectedFolderIds,
46+ clearFolderSelection,
47+ selectFolder,
4248} = useFolders ()
4349const { state } = useApp ()
4450const { clearSnippetsState } = useSnippets ()
@@ -49,13 +55,22 @@ const scrollRef = useTemplateRef<PerfectScrollbarExpose>('scrollRef')
4955const hoveredNodeId = ref (' ' )
5056const isHoveredByIdDisabled = ref (false )
5157const contextNode = ref <Node | null >(null )
58+ const isContextMultiSelection = computed (() => {
59+ if (! contextNode .value )
60+ return false
61+
62+ if (selectedFolderIds .value .length <= 1 )
63+ return false
64+
65+ return selectedFolderIds .value .includes (contextNode .value .id )
66+ })
5267
53- function clickNode(id : number ) {
54- return emit (' clickNode' , id )
68+ function clickNode(id : number , event ? : MouseEvent ) {
69+ return emit (' clickNode' , { id , event } )
5570}
5671
57- function dragNode(node : Node , target : Node , position : string ) {
58- return emit (' dragNode' , { node , target , position })
72+ function dragNode(nodes : Node [] , target : Node , position : Position ) {
73+ return emit (' dragNode' , { nodes , target , position })
5974}
6075
6176function toggleNode(node : Node ) {
@@ -84,38 +99,47 @@ async function onDeleteFolder() {
8499 return
85100
86101 const { confirm } = useDialog ()
87-
102+ const activeBeforeDelete = state .folderId
103+ const targetIds = selectedFolderIds .value .includes (contextNode .value .id )
104+ ? [... selectedFolderIds .value ]
105+ : [contextNode .value .id ]
88106 const folderName = getFolderByIdFromTree (
89107 folders .value ,
90108 contextNode .value .id ,
91109 )?.name
92110
93111 const isConfirmed = await confirm ({
94- title: i18n .t (' messages:confirm.delete' , { name: folderName }),
112+ title:
113+ targetIds .length > 1
114+ ? i18n .t (' messages:confirm.delete' , {
115+ name: i18n .t (' sidebar.folders' ),
116+ })
117+ : i18n .t (' messages:confirm.delete' , { name: folderName }),
95118 description: i18n .t (' messages:warning:allSnippetsMoveToTrash' ),
96119 })
97120
98- if (isConfirmed ) {
99- await deleteFolder ( contextNode . value . id )
121+ if (! isConfirmed )
122+ return
100123
101- if (contextNode .value .id === state .folderId ) {
102- state .folderId = undefined
103- clearSnippetsState ()
124+ await Promise .all (targetIds .map (id => deleteFolder (id , false )))
125+ await getFolders (false )
104126
105- const firstFolder = folders .value ?.[0 ]
127+ if (activeBeforeDelete && targetIds .includes (activeBeforeDelete )) {
128+ clearSnippetsState ()
129+ const fallbackId = selectedFolderIds .value [0 ]
106130
107- if (firstFolder ) {
108- state .folderId = firstFolder .id
109- scrollToElement (` [id="${state .folderId }"] ` )
110- }
131+ if (fallbackId ) {
132+ await selectFolder (fallbackId )
133+ scrollToElement (` [id="${fallbackId }"] ` )
134+ }
135+ else {
136+ clearFolderSelection ()
111137 }
112-
113- nextTick (() => {
114- if (scrollRef .value ) {
115- scrollRef .value .ps ?.update ()
116- }
117- })
118138 }
139+
140+ nextTick (() => {
141+ scrollRef .value ?.ps ?.update ()
142+ })
119143}
120144
121145function onRenameFolder() {
@@ -210,45 +234,52 @@ provide(treeKeys, {
210234 </div >
211235 </ContextMenu .Trigger >
212236 <ContextMenu .Content >
213- <ContextMenu .Item @click =" createFolderAndSelect" >
214- {{ i18n.t("action.new.folder") }}
215- </ContextMenu .Item >
216- <ContextMenu .Separator />
217- <ContextMenu .Item @click =" onRenameFolder" >
218- {{ i18n.t("action.rename") }}
219- </ContextMenu .Item >
220- <ContextMenu .Item @click =" onDeleteFolder" >
221- {{ i18n.t("action.delete.common") }}
222- </ContextMenu .Item >
223- <ContextMenu .Separator />
224- <ContextMenu .Item @click =" onSetCustomIcon" >
225- {{ i18n.t("action.setCustomIcon") }}
226- </ContextMenu .Item >
227- <ContextMenu .Item
228- v-if =" contextNode?.icon"
229- @click =" onRemoveCustomIcon"
230- >
231- {{ i18n.t("action.removeCustomIcon") }}
232- </ContextMenu .Item >
233- <ContextMenu .Separator />
234- <ContextMenu .Sub >
235- <ContextMenu .SubTrigger >
236- {{ i18n.t("action.defaultLanguage") }}
237- </ContextMenu .SubTrigger >
238- <ContextMenu .SubContent >
239- <PerfectScrollbar :options =" { minScrollbarLength: 20 }" >
240- <div class =" max-h-[250px]" >
241- <ContextMenu .Item
242- v-for =" language in languages"
243- :key =" language.value"
244- @click =" onSelectLanguage(language.value)"
245- >
246- {{ language.name }}
247- </ContextMenu .Item >
248- </div >
249- </PerfectScrollbar >
250- </ContextMenu .SubContent >
251- </ContextMenu .Sub >
237+ <template v-if =" isContextMultiSelection " >
238+ <ContextMenu .Item @click =" onDeleteFolder" >
239+ {{ i18n.t("action.delete.common") }}
240+ </ContextMenu .Item >
241+ </template >
242+ <template v-else >
243+ <ContextMenu .Item @click =" createFolderAndSelect" >
244+ {{ i18n.t("action.new.folder") }}
245+ </ContextMenu .Item >
246+ <ContextMenu .Separator />
247+ <ContextMenu .Item @click =" onRenameFolder" >
248+ {{ i18n.t("action.rename") }}
249+ </ContextMenu .Item >
250+ <ContextMenu .Item @click =" onDeleteFolder" >
251+ {{ i18n.t("action.delete.common") }}
252+ </ContextMenu .Item >
253+ <ContextMenu .Separator />
254+ <ContextMenu .Item @click =" onSetCustomIcon" >
255+ {{ i18n.t("action.setCustomIcon") }}
256+ </ContextMenu .Item >
257+ <ContextMenu .Item
258+ v-if =" contextNode?.icon"
259+ @click =" onRemoveCustomIcon"
260+ >
261+ {{ i18n.t("action.removeCustomIcon") }}
262+ </ContextMenu .Item >
263+ <ContextMenu .Separator />
264+ <ContextMenu .Sub >
265+ <ContextMenu .SubTrigger >
266+ {{ i18n.t("action.defaultLanguage") }}
267+ </ContextMenu .SubTrigger >
268+ <ContextMenu .SubContent >
269+ <PerfectScrollbar :options =" { minScrollbarLength: 20 }" >
270+ <div class =" max-h-[250px]" >
271+ <ContextMenu .Item
272+ v-for =" language in languages"
273+ :key =" language.value"
274+ @click =" onSelectLanguage(language.value)"
275+ >
276+ {{ language.name }}
277+ </ContextMenu .Item >
278+ </div >
279+ </PerfectScrollbar >
280+ </ContextMenu .SubContent >
281+ </ContextMenu .Sub >
282+ </template >
252283 </ContextMenu .Content >
253284 </ContextMenu .Root >
254285 </PerfectScrollbar >
0 commit comments