254 lines
6.2 KiB
Vue
254 lines
6.2 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, watch } from 'vue'
|
|
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
|
|
import { CollectionPropertiesObject } from '@MailManager/models/collection'
|
|
import type { CollectionObject } from '@MailManager/models/collection'
|
|
import type { ServiceObject } from '@MailManager/models'
|
|
|
|
// Props
|
|
interface Props {
|
|
modelValue: boolean
|
|
service: ServiceObject
|
|
parentFolder?: CollectionObject | null
|
|
allFolders?: CollectionObject[]
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
parentFolder: null,
|
|
allFolders: () => []
|
|
})
|
|
|
|
// Emits
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: boolean]
|
|
'created': [folder: CollectionObject]
|
|
}>()
|
|
|
|
// Store
|
|
const collectionsStore = useCollectionsStore()
|
|
|
|
// Form state
|
|
const folderName = ref('')
|
|
const loading = ref(false)
|
|
const errorMessage = ref('')
|
|
const validationErrors = ref<string[]>([])
|
|
|
|
// Computed
|
|
const dialogValue = computed({
|
|
get: () => props.modelValue,
|
|
set: (value: boolean) => emit('update:modelValue', value)
|
|
})
|
|
|
|
const parentFolderLabel = computed(() => {
|
|
if (!props.parentFolder) return 'Root'
|
|
return props.parentFolder.properties.label
|
|
})
|
|
|
|
const isValid = computed(() => {
|
|
return folderName.value.trim().length > 0 && validationErrors.value.length === 0
|
|
})
|
|
|
|
// Validation functions
|
|
const validateFolderName = (name: string): string[] => {
|
|
const errors: string[] = []
|
|
|
|
if (!name || name.trim().length === 0) {
|
|
errors.push('Folder name is required')
|
|
return errors
|
|
}
|
|
|
|
if (name.length > 255) {
|
|
errors.push('Folder name too long (max 255 characters)')
|
|
}
|
|
|
|
// No special characters that might cause issues
|
|
if (/[<>:"|?*\x00-\x1F]/.test(name)) {
|
|
errors.push('Folder name contains invalid characters')
|
|
}
|
|
|
|
// Provider-specific rules
|
|
if (props.service.provider === 'imap' && /[\/\\]/.test(name)) {
|
|
errors.push('IMAP folder names cannot contain / or \\')
|
|
}
|
|
|
|
// Leading/trailing spaces
|
|
if (name !== name.trim()) {
|
|
errors.push('Folder name cannot have leading or trailing spaces')
|
|
}
|
|
|
|
return errors
|
|
}
|
|
|
|
const checkDuplicateName = (name: string): boolean => {
|
|
const parentId = props.parentFolder?.identifier ?? null
|
|
|
|
return props.allFolders.some(f =>
|
|
f.properties.label === name &&
|
|
String(f.collection) === String(parentId) &&
|
|
String(f.service) === String(props.service.identifier)
|
|
)
|
|
}
|
|
|
|
// Watch folder name for validation
|
|
watch(folderName, (newName) => {
|
|
errorMessage.value = ''
|
|
validationErrors.value = validateFolderName(newName)
|
|
|
|
// Check for duplicates only if no other validation errors
|
|
if (validationErrors.value.length === 0 && newName.trim().length > 0) {
|
|
if (checkDuplicateName(newName)) {
|
|
validationErrors.value.push('A folder with this name already exists in this location')
|
|
}
|
|
}
|
|
})
|
|
|
|
// Reset form when dialog opens/closes
|
|
watch(dialogValue, (isOpen) => {
|
|
if (isOpen) {
|
|
resetForm()
|
|
}
|
|
})
|
|
|
|
const resetForm = () => {
|
|
folderName.value = ''
|
|
errorMessage.value = ''
|
|
validationErrors.value = []
|
|
loading.value = false
|
|
}
|
|
|
|
const handleCreate = async () => {
|
|
// Final validation
|
|
const errors = validateFolderName(folderName.value)
|
|
if (errors.length > 0) {
|
|
validationErrors.value = errors
|
|
return
|
|
}
|
|
|
|
if (checkDuplicateName(folderName.value)) {
|
|
validationErrors.value = ['A folder with this name already exists in this location']
|
|
return
|
|
}
|
|
|
|
loading.value = true
|
|
errorMessage.value = ''
|
|
|
|
try {
|
|
// Create properties object
|
|
const properties = new CollectionPropertiesObject()
|
|
properties.label = folderName.value.trim()
|
|
properties.rank = 0
|
|
properties.subscribed = true
|
|
|
|
// Create the collection
|
|
const newFolder = await collectionsStore.create(
|
|
props.service.provider,
|
|
props.service.identifier as string | number,
|
|
props.parentFolder?.identifier ?? null,
|
|
properties
|
|
)
|
|
|
|
// Success!
|
|
emit('created', newFolder)
|
|
dialogValue.value = false
|
|
resetForm()
|
|
|
|
} catch (error: any) {
|
|
console.error('[CreateFolderDialog] Failed to create folder:', error)
|
|
errorMessage.value = error.message || 'Failed to create folder. Please try again.'
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const handleCancel = () => {
|
|
dialogValue.value = false
|
|
resetForm()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<v-dialog
|
|
v-model="dialogValue"
|
|
max-width="500"
|
|
persistent
|
|
>
|
|
<v-card>
|
|
<v-card-title class="text-h5">
|
|
Create New Folder
|
|
</v-card-title>
|
|
|
|
<v-card-text>
|
|
<!-- Service info -->
|
|
<div class="mb-4">
|
|
<div class="text-caption text-medium-emphasis">Account</div>
|
|
<div class="text-body-2">
|
|
{{ service.label || service.primaryAddress || 'Mail Account' }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Parent folder info -->
|
|
<div class="mb-4">
|
|
<div class="text-caption text-medium-emphasis">Location</div>
|
|
<div class="text-body-2">
|
|
{{ parentFolderLabel }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Folder name input -->
|
|
<v-text-field
|
|
v-model="folderName"
|
|
label="Folder Name"
|
|
placeholder="Enter folder name"
|
|
variant="outlined"
|
|
autofocus
|
|
:error-messages="validationErrors"
|
|
:disabled="loading"
|
|
@keyup.enter="isValid && handleCreate()"
|
|
/>
|
|
|
|
<!-- Error message -->
|
|
<v-alert
|
|
v-if="errorMessage"
|
|
type="error"
|
|
variant="tonal"
|
|
density="compact"
|
|
class="mt-2"
|
|
>
|
|
{{ errorMessage }}
|
|
</v-alert>
|
|
</v-card-text>
|
|
|
|
<v-card-actions>
|
|
<v-spacer />
|
|
|
|
<v-btn
|
|
variant="text"
|
|
:disabled="loading"
|
|
@click="handleCancel"
|
|
>
|
|
Cancel
|
|
</v-btn>
|
|
|
|
<v-btn
|
|
color="primary"
|
|
variant="elevated"
|
|
:loading="loading"
|
|
:disabled="!isValid"
|
|
@click="handleCreate"
|
|
>
|
|
Create Folder
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-dialog>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.text-caption {
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.0333em;
|
|
}
|
|
</style>
|