vue · 5300 bytes Raw Blame History
1 <template>
2 <Modal @close="$emit('close')">
3 <template #title>Create New Folder</template>
4
5 <form @submit.prevent="handleCreate" class="space-y-4">
6 <!-- Folder name input -->
7 <div>
8 <label for="folderName" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
9 Folder Name
10 </label>
11 <input
12 id="folderName"
13 ref="nameInput"
14 v-model="form.name"
15 type="text"
16 required
17 class="input"
18 :class="{ 'border-red-500': errors.name }"
19 placeholder="Enter folder name"
20 @input="validateName"
21 />
22 <p v-if="errors.name" class="mt-1 text-sm text-red-600 dark:text-red-400">
23 {{ errors.name }}
24 </p>
25 </div>
26
27 <!-- Current path display -->
28 <div class="p-3 bg-gray-50 dark:bg-gray-700 rounded-md">
29 <p class="text-sm text-gray-600 dark:text-gray-400">
30 Create in: <span class="font-mono">{{ displayPath }}</span>
31 </p>
32 </div>
33
34 <!-- Options -->
35 <div class="space-y-3">
36 <label class="flex items-center">
37 <input
38 v-model="form.encrypted"
39 type="checkbox"
40 class="mr-2 rounded border-gray-300 focus:ring-primary-500"
41 />
42 <span class="text-sm text-gray-700 dark:text-gray-300">
43 Create encrypted folder
44 </span>
45 </label>
46
47 <div v-if="form.encrypted" class="ml-6 text-xs text-gray-500 dark:text-gray-400">
48 All files uploaded to this folder will be automatically encrypted
49 </div>
50 </div>
51
52 <!-- Error display -->
53 <div v-if="error" class="p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md">
54 <p class="text-sm text-red-600 dark:text-red-400">{{ error }}</p>
55 </div>
56 </form>
57
58 <template #footer>
59 <button
60 type="button"
61 @click="$emit('close')"
62 class="btn btn-outline mr-3"
63 >
64 Cancel
65 </button>
66 <button
67 @click="handleCreate"
68 :disabled="loading || !isValid"
69 class="btn btn-primary"
70 :class="{ 'opacity-50 cursor-not-allowed': loading || !isValid }"
71 >
72 <div v-if="loading" class="spinner w-4 h-4 mr-2"></div>
73 {{ loading ? 'Creating...' : 'Create Folder' }}
74 </button>
75 </template>
76 </Modal>
77 </template>
78
79 <script setup lang="ts">
80 import { ref, computed, onMounted, nextTick } from 'vue'
81 import { apiClient } from '@/services/api'
82 import Modal from './Modal.vue'
83
84 interface Props {
85 currentPath?: string
86 }
87
88 const props = withDefaults(defineProps<Props>(), {
89 currentPath: '/',
90 })
91
92 const emit = defineEmits<{
93 close: []
94 created: [path: string]
95 }>()
96
97 // Form state
98 const form = ref({
99 name: '',
100 encrypted: false,
101 })
102
103 const nameInput = ref<HTMLInputElement>()
104 const loading = ref(false)
105 const error = ref('')
106 const errors = ref<Record<string, string>>({})
107
108 // Computed
109 const displayPath = computed(() => {
110 return props.currentPath === '/' ? '/' : props.currentPath
111 })
112
113 const isValid = computed(() => {
114 return form.value.name.trim().length > 0 && !errors.value.name
115 })
116
117 // Methods
118 function validateName() {
119 errors.value.name = ''
120
121 const name = form.value.name.trim()
122
123 if (!name) {
124 errors.value.name = 'Folder name is required'
125 return
126 }
127
128 if (name.length > 255) {
129 errors.value.name = 'Folder name must be less than 255 characters'
130 return
131 }
132
133 // Check for invalid characters
134 const invalidChars = /[<>:"/\\|?*\x00-\x1f]/
135 if (invalidChars.test(name)) {
136 errors.value.name = 'Folder name contains invalid characters'
137 return
138 }
139
140 // Check for reserved names
141 const reservedNames = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']
142 if (reservedNames.includes(name.toUpperCase())) {
143 errors.value.name = 'This folder name is reserved'
144 return
145 }
146
147 // Check for names starting/ending with spaces or dots
148 if (name.startsWith(' ') || name.endsWith(' ') || name.startsWith('.') || name.endsWith('.')) {
149 errors.value.name = 'Folder name cannot start or end with spaces or dots'
150 return
151 }
152 }
153
154 async function handleCreate() {
155 if (!isValid.value || loading.value) return
156
157 loading.value = true
158 error.value = ''
159
160 try {
161 const folderName = form.value.name.trim()
162
163 // Create folder via API
164 await apiClient.request({
165 method: 'POST',
166 url: '/files/folder',
167 data: {
168 name: folderName,
169 path: props.currentPath,
170 encrypted: form.value.encrypted,
171 },
172 })
173
174 const newPath = props.currentPath === '/' ? `/${folderName}` : `${props.currentPath}/${folderName}`
175
176 emit('created', newPath)
177 emit('close')
178
179 // Show success notification
180 if (window.$notify) {
181 window.$notify.success(`Folder "${folderName}" created successfully`)
182 }
183 } catch (err: any) {
184 error.value = err.response?.data?.message || 'Failed to create folder'
185 console.error('Create folder failed:', err)
186 } finally {
187 loading.value = false
188 }
189 }
190
191 // Focus input on mount
192 onMounted(async () => {
193 await nextTick()
194 nameInput.value?.focus()
195 })
196 </script>