This commit is contained in:
Tilman 2025-09-30 21:34:12 +02:00
parent dbb91a9c1a
commit 56090e15a9
8 changed files with 55 additions and 47 deletions

View file

@ -9,13 +9,13 @@ interface FilesBreadcrumbProps {
sortOrder?: SortOrder;
}
export default function FilesBreadcrumb({
path,
isShowingNotes,
isShowingPhotos,
fileShareId,
export default function FilesBreadcrumb({
path,
isShowingNotes,
isShowingPhotos,
fileShareId,
sortBy = 'name',
sortOrder = 'asc'
sortOrder = 'asc',
}: FilesBreadcrumbProps) {
let routePath = fileShareId ? `file-share/${fileShareId}` : 'files';
let rootPath = '/';

View file

@ -75,15 +75,17 @@ export default function ListFiles(
): ComponentChildren {
const isActive = sortBy === column;
const iconClass = isActive ? 'text-blue-400' : 'text-slate-400';
if (!onClickSort) {
return <th scope='col' class={`px-6 py-4 font-medium text-white ${className || ''}`}>{label}</th>;
}
return (
<th scope='col' class={`px-6 py-4 font-medium text-white ${className || ''}`}>
<button
class={`flex items-center justify-between w-full text-left hover:text-blue-300 ${isActive ? 'text-blue-400' : ''}`}
class={`flex items-center justify-between w-full text-left hover:text-blue-300 ${
isActive ? 'text-blue-400' : ''
}`}
onClick={() => onClickSort(column)}
type='button'
>
@ -147,9 +149,7 @@ export default function ListFiles(
)}
{renderSortableHeader('Name', 'name')}
{renderSortableHeader('Last update', 'updated_at', 'w-64')}
{isShowingNotes || isShowingPhotos
? null
: renderSortableHeader('Size', 'size_in_bytes', 'w-32')}
{isShowingNotes || isShowingPhotos ? null : renderSortableHeader('Size', 'size_in_bytes', 'w-32')}
{isShowingPhotos || fileShareId
? null
: <th scope='col' class='px-6 py-4 font-medium text-white w-24'></th>}

View file

@ -100,7 +100,7 @@ export default function MainFiles(
if (typeof window === 'undefined') {
return { sortBy: initialSortBy, sortOrder: initialSortOrder };
}
try {
const saved = localStorage.getItem(getSortingKey(path));
if (saved) {
@ -122,7 +122,7 @@ export default function MainFiles(
function saveSortingPreference(path: string, sortBy: SortColumn, sortOrder: SortOrder) {
if (typeof window === 'undefined') return;
try {
localStorage.setItem(getSortingKey(path), JSON.stringify({ sortBy, sortOrder }));
} catch (error) {
@ -132,7 +132,7 @@ export default function MainFiles(
function onClickSort(column: SortColumn) {
let newSortOrder: SortOrder = 'asc';
if (sortBy.value === column) {
// Toggle sort order if clicking the same column
newSortOrder = sortOrder.value === 'asc' ? 'desc' : 'asc';
@ -140,10 +140,10 @@ export default function MainFiles(
// Default to ascending for new columns
newSortOrder = 'asc';
}
// Save to localStorage
saveSortingPreference(path.value, column, newSortOrder);
// Update URL and navigate to trigger re-render with new sorting
const url = new URL(window.location.href);
url.searchParams.set('sortBy', column);
@ -826,8 +826,8 @@ export default function MainFiles(
</section>
<section class='flex items-center justify-end'>
<FilesBreadcrumb
path={path.value}
<FilesBreadcrumb
path={path.value}
fileShareId={fileShareId}
sortBy={sortBy.value}
sortOrder={sortOrder.value}

View file

@ -4,7 +4,15 @@ import { Cookie, getCookies, setCookie } from '@std/http';
import { AppConfig } from '/lib/config.ts';
import { Directory, DirectoryFile, FileShare } from '/lib/types.ts';
import { sortDirectoriesByName, sortEntriesByName, sortFilesByName, sortDirectories, sortFiles, SortOptions, TRASH_PATH } from '/lib/utils/files.ts';
import {
sortDirectories,
sortDirectoriesByName,
sortEntriesByName,
sortFiles,
sortFilesByName,
SortOptions,
TRASH_PATH,
} from '/lib/utils/files.ts';
import Database, { sql } from '/lib/interfaces/database.ts';
import {
COOKIE_NAME as AUTH_COOKIE_NAME,
@ -605,11 +613,11 @@ export async function ensureUserPathIsValidAndSecurelyAccessible(userId: string,
const resolvedFullPath = `${resolve(fullPath)}/`;
console.log({ userRootPath, fullPath, resolvedFullPath });
// Normalize path separators for consistent comparison on Windows
const normalizedUserRootPath = userRootPath.replace(/\\/g, '/');
const normalizedResolvedFullPath = resolvedFullPath.replace(/\\/g, '/');
if (!normalizedResolvedFullPath.startsWith(normalizedUserRootPath)) {
throw new Error('Invalid file path');
}

View file

@ -31,7 +31,7 @@ export interface SortOptions {
export function sortEntriesByName(entryA: Deno.DirEntry, entryB: Deno.DirEntry) {
const nameA = entryA.name.toLocaleLowerCase();
const nameB = entryB.name.toLocaleLowerCase();
if (nameA > nameB) {
return 1;
}
@ -47,17 +47,17 @@ export function sortDirectoriesByName(directoryA: Directory, directoryB: Directo
// Always put .Trash directory first
const fullPathA = `${directoryA.parent_path}${directoryA.directory_name}/`;
const fullPathB = `${directoryB.parent_path}${directoryB.directory_name}/`;
if (fullPathA === TRASH_PATH || directoryA.directory_name === '.Trash') {
return -1;
}
if (fullPathB === TRASH_PATH || directoryB.directory_name === '.Trash') {
return 1;
}
const nameA = directoryA.directory_name.toLocaleLowerCase();
const nameB = directoryB.directory_name.toLocaleLowerCase();
if (nameA > nameB) {
return 1;
}
@ -77,10 +77,10 @@ export function sortFilesByName(fileA: DirectoryFile, fileB: DirectoryFile) {
if (fileB.file_name === '.Trash') {
return 1;
}
const nameA = fileA.file_name.toLocaleLowerCase();
const nameB = fileB.file_name.toLocaleLowerCase();
if (nameA > nameB) {
return 1;
}
@ -94,19 +94,19 @@ export function sortFilesByName(fileA: DirectoryFile, fileB: DirectoryFile) {
export function sortDirectories(directories: Directory[], options: SortOptions): Directory[] {
// Separate .Trash folder from other directories
const trashDir = directories.find(dir =>
`${dir.parent_path}${dir.directory_name}/` === TRASH_PATH ||
const trashDir = directories.find((dir) =>
`${dir.parent_path}${dir.directory_name}/` === TRASH_PATH ||
dir.directory_name === '.Trash'
);
const otherDirs = directories.filter(dir =>
`${dir.parent_path}${dir.directory_name}/` !== TRASH_PATH &&
const otherDirs = directories.filter((dir) =>
`${dir.parent_path}${dir.directory_name}/` !== TRASH_PATH &&
dir.directory_name !== '.Trash'
);
// Sort the non-trash directories
const sorted = [...otherDirs].sort((a, b) => {
let result = 0;
switch (options.sortBy) {
case 'name':
result = a.directory_name.toLowerCase().localeCompare(b.directory_name.toLowerCase());
@ -118,23 +118,23 @@ export function sortDirectories(directories: Directory[], options: SortOptions):
result = a.size_in_bytes - b.size_in_bytes;
break;
}
return options.sortOrder === 'desc' ? -result : result;
});
// Return with .Trash folder first (if it exists), followed by sorted directories
return trashDir ? [trashDir, ...sorted] : sorted;
}
export function sortFiles(files: DirectoryFile[], options: SortOptions): DirectoryFile[] {
// Separate .Trash file from other files (if it exists)
const trashFile = files.find(file => file.file_name === '.Trash');
const otherFiles = files.filter(file => file.file_name !== '.Trash');
const trashFile = files.find((file) => file.file_name === '.Trash');
const otherFiles = files.filter((file) => file.file_name !== '.Trash');
// Sort the non-trash files
const sorted = [...otherFiles].sort((a, b) => {
let result = 0;
switch (options.sortBy) {
case 'name':
result = a.file_name.toLowerCase().localeCompare(b.file_name.toLowerCase());
@ -146,10 +146,10 @@ export function sortFiles(files: DirectoryFile[], options: SortOptions): Directo
result = a.size_in_bytes - b.size_in_bytes;
break;
}
return options.sortOrder === 'desc' ? -result : result;
});
// Return with .Trash file first (if it exists), followed by sorted files
return trashFile ? [trashFile, ...sorted] : sorted;
}

View file

@ -32,7 +32,7 @@ export const handler: Handlers<Data, FreshContextState> = {
return new Response('Bad Request', { status: 400 });
}
const sortOptions = (requestBody.sortBy && requestBody.sortOrder)
const sortOptions = (requestBody.sortBy && requestBody.sortOrder)
? { sortBy: requestBody.sortBy, sortOrder: requestBody.sortOrder }
: undefined;

View file

@ -31,7 +31,7 @@ export const handler: Handlers<Data, FreshContextState> = {
return new Response('Bad Request', { status: 400 });
}
const sortOptions = (requestBody.sortBy && requestBody.sortOrder)
const sortOptions = (requestBody.sortBy && requestBody.sortOrder)
? { sortBy: requestBody.sortBy, sortOrder: requestBody.sortOrder }
: undefined;

View file

@ -41,14 +41,14 @@ export const handler: Handlers<Data, FreshContextState> = {
// Get sort parameters
const sortBy = (searchParams.get('sortBy') as SortColumn) || 'name';
const sortOrder = (searchParams.get('sortOrder') as SortOrder) || 'asc';
// Validate sort parameters
const validSortColumns: SortColumn[] = ['name', 'updated_at', 'size_in_bytes'];
const validSortOrders: SortOrder[] = ['asc', 'desc'];
const finalSortBy = validSortColumns.includes(sortBy) ? sortBy : 'name';
const finalSortOrder = validSortOrders.includes(sortOrder) ? sortOrder : 'asc';
const sortOptions = { sortBy: finalSortBy, sortOrder: finalSortOrder };
const userDirectories = await DirectoryModel.list(context.state.user.id, currentPath, sortOptions);