diff --git a/src/app/src/components/shared/item/ItemCardForm.vue b/src/app/src/components/shared/item/ItemCardForm.vue index f9e0800..19e0e57 100644 --- a/src/app/src/components/shared/item/ItemCardForm.vue +++ b/src/app/src/components/shared/item/ItemCardForm.vue @@ -65,7 +65,21 @@ const schema = computed(() => z.object({ extension: z.enum([...CONTENT_EXTENSIONS, ...MEDIA_EXTENSIONS] as [string, ...string[]]).nullish(), prefix: z.preprocess( val => val === '' ? null : val, - z.number().int().positive().nullish(), + z.string() + .regex(/^\d+$/, 'Prefix must be a string containing only digits') + .refine( + (prefix: string | null | undefined) => { + if (prefix === null || prefix === undefined) { + return true + } + + const num = Number(prefix) + + return Number.isInteger(num) && num >= 0 + }, + 'Prefix must be a non-negative integer', + ) + .nullish(), ), }).refine(() => { const siblings = props.parentItem.children?.filter(child => !child.hide) || [] @@ -87,7 +101,7 @@ const schema = computed(() => z.object({ type Schema = { name: string extension: string | null | undefined - prefix: number | null | undefined + prefix: string | null | undefined } const state = reactive({ name: originalName.value, @@ -267,11 +281,11 @@ async function onSubmit() { ({ ...acc, [ext]: 'i-lucide-file-audio' }), {}), } -export function parseName(name: string): { name: string, prefix: number | null, extension: string | null } { +export function parseName(name: string): { name: string, prefix: string | null, extension: string | null } { const prefixMatch = name.match(/^(\d+)\./) const extensionMatch = name.match(/\.(\w+)$/) return { - prefix: prefixMatch ? Number.parseInt(prefixMatch[1], 10) : null, + prefix: prefixMatch ? prefixMatch[1] : null, extension: extensionMatch ? extensionMatch[1] : null, name: name.replace(/^\d+\./, ''), } diff --git a/src/app/test/mocks/tree.ts b/src/app/test/mocks/tree.ts index 1b305e0..3376c05 100644 --- a/src/app/test/mocks/tree.ts +++ b/src/app/test/mocks/tree.ts @@ -12,34 +12,34 @@ export const tree: TreeItem[] = [ name: 'getting-started', fsPath: '1.getting-started', type: 'directory', - prefix: 1, + prefix: '1', children: [ { name: 'introduction', fsPath: '1.getting-started/2.introduction.md', type: 'file', routePath: '/getting-started/introduction', - prefix: 2, + prefix: '2', }, { name: 'installation', fsPath: '1.getting-started/3.installation.md', type: 'file', routePath: '/getting-started/installation', - prefix: 3, + prefix: '3', }, { name: 'advanced', fsPath: '1.getting-started/1.advanced', type: 'directory', - prefix: 1, + prefix: '1', children: [ { name: 'studio', fsPath: '1.getting-started/1.advanced/1.studio.md', type: 'file', routePath: '/getting-started/installation/advanced/studio', - prefix: 1, + prefix: '1', }, ], }, diff --git a/src/app/test/unit/utils/tree.test.ts b/src/app/test/unit/utils/tree.test.ts index b923ce3..dcd313f 100644 --- a/src/app/test/unit/utils/tree.test.ts +++ b/src/app/test/unit/utils/tree.test.ts @@ -25,21 +25,21 @@ describe('buildTree of documents with one level of depth', () => { name: 'getting-started', fsPath: '1.getting-started', type: 'directory', - prefix: 1, + prefix: '1', children: [ { name: 'introduction', fsPath: '1.getting-started/2.introduction.md', type: 'file', routePath: '/getting-started/introduction', - prefix: 2, + prefix: '2', }, { name: 'installation', fsPath: '1.getting-started/3.installation.md', type: 'file', routePath: '/getting-started/installation', - prefix: 3, + prefix: '3', }, ], }, @@ -97,7 +97,7 @@ describe('buildTree of documents with one level of depth', () => { type: 'file', routePath: deletedDbItem.path, status: TreeStatus.Deleted, - prefix: 2, + prefix: '2', }, ], }, @@ -131,7 +131,7 @@ describe('buildTree of documents with one level of depth', () => { type: 'file', routePath: deletedDbItem.path, status: TreeStatus.Deleted, - prefix: 3, + prefix: '3', }, ], }, @@ -282,7 +282,7 @@ describe('buildTree of documents with one level of depth', () => { name: createdDbItem.path!.split('/').pop()!, type: 'file', status: TreeStatus.Renamed, - prefix: 2, + prefix: '2', }, ], }, @@ -296,27 +296,27 @@ describe('buildTree of documents with two levels of depth', () => { name: 'essentials', fsPath: '1.essentials', type: 'directory', - prefix: 1, + prefix: '1', children: [ { name: 'configuration', fsPath: '1.essentials/2.configuration.md', type: 'file', routePath: '/essentials/configuration', - prefix: 2, + prefix: '2', }, { name: 'nested', fsPath: '1.essentials/1.nested', type: 'directory', - prefix: 1, + prefix: '1', children: [ { name: 'advanced', fsPath: '1.essentials/1.nested/2.advanced.md', type: 'file', routePath: '/essentials/nested/advanced', - prefix: 2, + prefix: '2', }, ], }, @@ -424,7 +424,7 @@ describe('buildTree of documents with two levels of depth', () => { routePath: deletedDbItem.path, type: 'file', status: TreeStatus.Deleted, - prefix: 2, + prefix: '2', }, ], }, @@ -452,21 +452,21 @@ describe('buildTree of documents with language prefixed', () => { name: 'getting-started', fsPath: 'en/1.getting-started', type: 'directory', - prefix: 1, + prefix: '1', children: [ { name: 'introduction', fsPath: 'en/1.getting-started/2.introduction.md', type: 'file', routePath: '/en/getting-started/introduction', - prefix: 2, + prefix: '2', }, { name: 'installation', fsPath: 'en/1.getting-started/3.installation.md', type: 'file', routePath: '/en/getting-started/installation', - prefix: 3, + prefix: '3', }, ], },