86fbe8f396
- Liste als echte Dateitabelle und Raster mit Vorschaubildern; Typkacheln je Dateifamilie (OKLCH-Tokens, Kontrast hell und dunkel per Test geprüft), Ordner im gedämpften Mosaik-Gelb - Ordnerpfad mit Ablagezielen, Auswahlleiste (klebt beim Scrollen), Tastatur (Enter, Rücktaste/Alt+Pfeil hoch, Entf, F2, Strg+A, Escape), Zeilen- und Rechtsklickmenü - Dialoge für neuen Ordner, Umbenennen, Verschieben (eigene Ordnerwahl) und Löschen in den Papierkorb - Ziehen und Ablegen mit Streifen-Markierung und Ordnerzeilen als Ziel, auch ganze Ordner; Übertragungsleiste mit Fortschritt, Abbrechen, Klartextfehlern und Rückfrage bei vergebenem Namen - Speicheranzeige zählt im Unterordner den Speicher des ganzen Kontos; Ansicht je Benutzer gemerkt Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
85 lines
3.0 KiB
TypeScript
85 lines
3.0 KiB
TypeScript
/**
|
|
* Pfade der Dateiansicht (quick-261008-mzu). Pfade beginnen immer mit "/";
|
|
* die Wurzel ist "/". Die API prueft jeden Pfad noch einmal selbst (D-H) —
|
|
* diese Pruefungen dienen nur der verstaendlichen Rueckmeldung im Dialog.
|
|
*/
|
|
|
|
export const ROOT = '/';
|
|
const MAX_NAME_LENGTH = 250;
|
|
|
|
export function join(dir: string, name: string): string {
|
|
const base = dir.endsWith('/') ? dir.slice(0, -1) : dir;
|
|
return `${base}/${name}`;
|
|
}
|
|
|
|
export function parent(path: string): string {
|
|
if (path === ROOT || path === '') return ROOT;
|
|
const trimmed = path.endsWith('/') ? path.slice(0, -1) : path;
|
|
const i = trimmed.lastIndexOf('/');
|
|
return i <= 0 ? ROOT : trimmed.slice(0, i);
|
|
}
|
|
|
|
export function basename(path: string): string {
|
|
if (path === ROOT) return '';
|
|
const trimmed = path.endsWith('/') ? path.slice(0, -1) : path;
|
|
return trimmed.slice(trimmed.lastIndexOf('/') + 1);
|
|
}
|
|
|
|
/** Bestandteile eines Pfads: "/a/b" → ["a", "b"]; Wurzel → []. */
|
|
export function segments(path: string): string[] {
|
|
return path.split('/').filter((s) => s !== '');
|
|
}
|
|
|
|
/** Pfad aus den ersten `count` Bestandteilen. */
|
|
export function pathOf(parts: readonly string[], count = parts.length): string {
|
|
return count <= 0 ? ROOT : `/${parts.slice(0, count).join('/')}`;
|
|
}
|
|
|
|
export type NameProblem = 'empty' | 'dots' | 'slash' | 'part' | 'tooLong';
|
|
|
|
/** null = gueltiger Name, sonst der Grund. */
|
|
export function nameProblem(name: string): NameProblem | null {
|
|
if (name.trim() === '') return 'empty';
|
|
if (name === '.' || name === '..') return 'dots';
|
|
if (name.includes('/') || name.includes('\\')) return 'slash';
|
|
if (name.toLowerCase().endsWith('.part')) return 'part';
|
|
if (name.length > MAX_NAME_LENGTH) return 'tooLong';
|
|
return null;
|
|
}
|
|
|
|
export function validName(name: string): boolean {
|
|
return nameProblem(name) === null;
|
|
}
|
|
|
|
const DOUBLE_EXTENSIONS = ['.tar.gz', '.tar.bz2', '.tar.xz', '.tar.zst'];
|
|
|
|
/** Name ohne und mit Endung trennen; "Archiv.tar.gz" → ["Archiv", ".tar.gz"]. */
|
|
export function splitExtension(name: string): [string, string] {
|
|
const lower = name.toLowerCase();
|
|
for (const ext of DOUBLE_EXTENSIONS) {
|
|
if (lower.endsWith(ext) && name.length > ext.length) {
|
|
return [name.slice(0, -ext.length), name.slice(-ext.length)];
|
|
}
|
|
}
|
|
const dot = name.lastIndexOf('.');
|
|
if (dot <= 0) return [name, ''];
|
|
return [name.slice(0, dot), name.slice(dot)];
|
|
}
|
|
|
|
/** Freier Name nach dem Muster "Bericht (2).pdf", "Bericht (3).pdf" … */
|
|
export function freeName(name: string, taken: readonly string[]): string {
|
|
const used = new Set(taken.map((n) => n.toLowerCase()));
|
|
if (!used.has(name.toLowerCase())) return name;
|
|
const [base, ext] = splitExtension(name);
|
|
for (let i = 2; ; i++) {
|
|
const candidate = `${base} (${i})${ext}`;
|
|
if (!used.has(candidate.toLowerCase())) return candidate;
|
|
}
|
|
}
|
|
|
|
/** Liegt `path` innerhalb von `folder` (echt darunter)? "/a" und "/ab" gelten nicht. */
|
|
export function isInside(folder: string, path: string): boolean {
|
|
if (folder === ROOT) return path !== ROOT;
|
|
return path.startsWith(`${folder}/`);
|
|
}
|