diff --git a/apps/desktop/src/lib/api/collection.ts b/apps/desktop/src/lib/api/collection.ts index cec8e1e..d282e5e 100644 --- a/apps/desktop/src/lib/api/collection.ts +++ b/apps/desktop/src/lib/api/collection.ts @@ -1,5 +1,5 @@ import { activeFile, collection, noteHistory } from '@/store'; -import { hideDotFiles, validateHapticFolder } from '@/utils'; +import { hideDotFiles, validateHapticFolder, sortFileEntry } from '@/utils'; import { readDir } from '@tauri-apps/api/fs'; import { get } from 'svelte/store'; import { open } from '@tauri-apps/api/dialog'; @@ -19,7 +19,7 @@ export const fetchCollectionEntries = async ( let files = await readDir(dirPath, { recursive: true }); if (sort === 'name') { - files.sort((a, b) => a.name!.localeCompare(b.name!)); + files.sort((a, b) => sortFileEntry(a, b)); } // Hide dotfiles diff --git a/apps/desktop/src/lib/utils.ts b/apps/desktop/src/lib/utils.ts index 547108d..1c11527 100644 --- a/apps/desktop/src/lib/utils.ts +++ b/apps/desktop/src/lib/utils.ts @@ -303,3 +303,17 @@ export const getNextUntitledName = (files: FileEntry[], prefix: string, extensio // This should never happen, but just in case return `${prefix} ${maxNumber + 1}${extension}`; }; + +export const sortFileEntry = (a: FileEntry, b: FileEntry): number => { + const isDirectory = (file: FileEntry) => file.children != null; + if (isDirectory(a) && isDirectory(b)) { + return naturalSort(a.name!, b.name!); + } + if (isDirectory(a)) return -1; + if (isDirectory(b)) return 1; + return naturalSort(a.name!, b.name!); +}; + +const naturalSort = (a: string, b: string): number => { + return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }); +}; diff --git a/apps/web/src/lib/api/collection.ts b/apps/web/src/lib/api/collection.ts index bd51ba8..0de4d30 100644 --- a/apps/web/src/lib/api/collection.ts +++ b/apps/web/src/lib/api/collection.ts @@ -2,7 +2,7 @@ import { db } from '@/database/client'; import { collection as collectionTable, entry as entryTable } from '@/database/schema'; import { activeFile, collection, collectionEntries, noteHistory } from '@/store'; import type { FileEntry } from '@/types'; -import { buildFileTree } from '@/utils'; +import { buildFileTree, sortFileEntry } from '@/utils'; import { and, eq } from 'drizzle-orm'; import { get } from 'svelte/store'; @@ -41,7 +41,7 @@ export const fetchCollectionEntries = async ( const sortEntries = (entries: FileEntry[]) => { entries.sort((a, b) => { if (sort === 'name' && a.name && b.name) { - return a.name.localeCompare(b.name); + return sortFileEntry(a, b); } else if (sort === 'date') { console.warn('Sorting by date is not implemented yet'); } diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts index de0120f..9cae40d 100644 --- a/apps/web/src/lib/utils.ts +++ b/apps/web/src/lib/utils.ts @@ -417,3 +417,19 @@ export function createDeviceDetector() { } ); } + +export const sortFileEntry = (a: FileEntry, b: FileEntry): number => { + const isDirectory = (file: FileEntry) => file.children != null; + + if (isDirectory(a) && isDirectory(b)) { + return naturalSort(a.name!, b.name!); + } + if (isDirectory(a)) return -1; + if (isDirectory(b)) return 1; + + return naturalSort(a.name!, b.name!); +}; + +const naturalSort = (a: string, b: string): number => { + return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }); +};