From 5bda809921c3f446b88bf3ec2b1d59d4cef8a4f5 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Thu, 22 Jan 2026 00:23:23 +0000 Subject: [PATCH] Cleaner folder headers in gallery mode search results. Multi folder results are always grouped by folder (FileExplorer and Gallery), but still otherwise respecting the chosen sort order. Overall this produces a much cleaner layout. --- frontend/src/components/Gallery.vue | 38 ++++++++++++++--------- frontend/src/utils/docsort.ts | 47 +++++++++++++++++++++++++++++ frontend/src/views/ExplorerView.vue | 6 ++-- 3 files changed, 73 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/Gallery.vue b/frontend/src/components/Gallery.vue index 0f89c62..e0b27af 100644 --- a/frontend/src/components/Gallery.vue +++ b/frontend/src/components/Gallery.vue @@ -2,12 +2,8 @@ @@ -55,10 +51,12 @@ const rename = (doc: Doc, newName: string) => { doc.name = newName // We should get an update from watch but this is quicker } const gallery = ref() -const columns = computed(() => { - if (!gallery.value) return 1 - return getComputedStyle(gallery.value).gridTemplateColumns.split(' ').length -}) +const columnCount = ref(1) +const updateColumns = () => { + if (!gallery.value) return + columnCount.value = getComputedStyle(gallery.value).gridTemplateColumns.split(' ').length +} +const columns = computed(() => columnCount.value) defineExpose({ newFolder() { const now = Math.floor(Date.now() / 1000) @@ -168,12 +166,21 @@ watchEffect(() => { focusBreadcrumb() } }) +let resizeObserver: ResizeObserver | null = null onMounted(() => { const active = document.querySelector('.cursor') as HTMLElement | null if (active) { active.scrollIntoView({ block: 'center', behavior: 'instant' }) active.focus() } + updateColumns() + if (gallery.value) { + resizeObserver = new ResizeObserver(updateColumns) + resizeObserver.observe(gallery.value) + } +}) +onUnmounted(() => { + resizeObserver?.disconnect() }) const mkdir = (doc: Doc, name: string) => { const control = connect(controlUrl, { @@ -205,6 +212,8 @@ const showFolderBreadcrumb = (i: number) => { const docloc = docs[i].loc return i === 0 ? docloc !== loc.value : docloc !== docs[i - 1].loc } + + const selectionIndeterminate = computed({ get: () => { return ( @@ -254,13 +263,12 @@ const contextMenu = (ev: MouseEvent, doc: Doc) => { display: grid; gap: .5em; grid-template-columns: repeat(auto-fill, minmax(15em, 1fr)); - grid-template-rows: repeat(minmax(auto, 15em)); align-items: end; } -.breadcrumb { - border-radius: .5em 0 0 .5em; +.folder-indicator { + grid-column: 1 / -1; } -.spacer { - flex: 0 1000000000 4rem; +.folder-start { + grid-column-start: 1; } diff --git a/frontend/src/utils/docsort.ts b/frontend/src/utils/docsort.ts index 016db06..6c67158 100644 --- a/frontend/src/utils/docsort.ts +++ b/frontend/src/utils/docsort.ts @@ -13,3 +13,50 @@ export const sorted = (documents: Doc[], order: SortOrder) => { sorted.sort(ordering[order]) return sorted } + +/** + * Sort documents while keeping files grouped by their folder. + * - name: folders sorted by folder path, items within by name + * - modified: folders sorted by newest item within results, items within by mtime + * - size: folders sorted by largest file within results, items within by size + */ +export const sortedGrouped = (documents: Doc[], order: SortOrder) => { + if (!order) return documents + + const compare = ordering[order] + + // Group documents by their folder location + const byFolder = new Map() + for (const doc of documents) { + const folder = doc.loc + if (!byFolder.has(folder)) byFolder.set(folder, []) + byFolder.get(folder)!.push(doc) + } + + // Sort items within each folder + for (const docs of byFolder.values()) { + docs.sort(compare) + } + + // Find the "best" item in each folder (first after sorting = best according to criteria) + const folderBest = new Map() + for (const [folder, docs] of byFolder) { + folderBest.set(folder, docs[0]) + } + + // Sort folders: by path for name sort, by best item for modified/size + const sortedFolders = [...byFolder.keys()].sort((a, b) => { + if (order === 'name') { + return collator.compare(a, b) + } + return compare(folderBest.get(a)!, folderBest.get(b)!) + }) + + // Flatten back into a single array with folder grouping preserved + const result: Doc[] = [] + for (const folder of sortedFolders) { + result.push(...byFolder.get(folder)!) + } + + return result +} diff --git a/frontend/src/views/ExplorerView.vue b/frontend/src/views/ExplorerView.vue index 8615168..f455f26 100644 --- a/frontend/src/views/ExplorerView.vue +++ b/frontend/src/views/ExplorerView.vue @@ -21,7 +21,7 @@ import { watchEffect, ref, computed, watch } from 'vue' import { useMainStore } from '@/stores/main' import Router from '@/router/index' import { needleFormat, localeIncludes, collator } from '@/utils' -import { sorted } from '@/utils/docsort' +import { sorted, sortedGrouped } from '@/utils/docsort' import FileExplorer from '@/components/FileExplorer.vue' const store = useMainStore() @@ -49,9 +49,9 @@ const documents = computed(() => { } } const locsub = loc + '/' - // Custom sort override in effect? + // Custom sort override in effect? Use grouped sorting to keep folders together const order = store.prefs.sortFiltered - if (order) return sorted(docs, order) + if (order) return sortedGrouped(docs, order) // Sort by relevance - current folder, then subfolders, then others docs.sort((a, b) => ( // @ts-ignore