'..' parent row in the file list
Synthetic cursor-navigable first row (hidden at /) — Enter/double-click goes to the parent like the toolbar button. Excluded from selection, rename, delete, chmod, and transfers; shown with an up-arrow icon. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,12 +16,12 @@ export class FileActionsService {
|
|||||||
private readonly toast = inject(ToastService);
|
private readonly toast = inject(ToastService);
|
||||||
private readonly dialogs = inject(DialogsService);
|
private readonly dialogs = inject(DialogsService);
|
||||||
|
|
||||||
/** Selection if non-empty, else the cursor entry. */
|
/** Selection if non-empty, else the cursor entry. Never the ".." parent row. */
|
||||||
targets(panel: PanelState): FileEntry[] {
|
targets(panel: PanelState): FileEntry[] {
|
||||||
const selected = panel.selectedEntries();
|
const selected = panel.selectedEntries();
|
||||||
if (selected.length) return selected;
|
if (selected.length) return selected;
|
||||||
const atCursor = panel.sorted()[panel.cursor()];
|
const atCursor = panel.sorted()[panel.cursor()];
|
||||||
return atCursor ? [atCursor] : [];
|
return atCursor && atCursor.name !== '..' ? [atCursor] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
mkdir(panel: PanelState): void {
|
mkdir(panel: PanelState): void {
|
||||||
@@ -58,7 +58,7 @@ export class FileActionsService {
|
|||||||
|
|
||||||
rename(panel: PanelState): void {
|
rename(panel: PanelState): void {
|
||||||
const entry = panel.sorted()[panel.cursor()];
|
const entry = panel.sorted()[panel.cursor()];
|
||||||
if (!entry) return;
|
if (!entry || entry.name === '..') return;
|
||||||
this.dialogs.prompt.set({
|
this.dialogs.prompt.set({
|
||||||
title: `Rename "${entry.name}"`,
|
title: `Rename "${entry.name}"`,
|
||||||
initial: entry.name,
|
initial: entry.name,
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ export class FilePanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iconFor(entry: FileEntry): string {
|
iconFor(entry: FileEntry): string {
|
||||||
|
if (entry.name === '..') return 'mynaArrowUp';
|
||||||
return entry.type === 'dir' ? 'mynaFolder' : 'mynaFile';
|
return entry.type === 'dir' ? 'mynaFolder' : 'mynaFile';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export class PanelState {
|
|||||||
readonly cursor = signal(0);
|
readonly cursor = signal(0);
|
||||||
readonly selected = signal<ReadonlySet<string>>(new Set());
|
readonly selected = signal<ReadonlySet<string>>(new Set());
|
||||||
|
|
||||||
/** Directories first, then the active sort column. */
|
/** Directories first, then the active sort column. ".." (parent) is pinned on top. */
|
||||||
readonly sorted = computed(() => {
|
readonly sorted = computed(() => {
|
||||||
const list = [...this.entries()];
|
const list = [...this.entries()];
|
||||||
const key = this.sortKey();
|
const key = this.sortKey();
|
||||||
@@ -41,6 +41,10 @@ export class PanelState {
|
|||||||
}
|
}
|
||||||
return cmp * dir;
|
return cmp * dir;
|
||||||
});
|
});
|
||||||
|
const cwd = this.cwd();
|
||||||
|
if (cwd && cwd !== '/') {
|
||||||
|
list.unshift({ name: '..', path: `${cwd}/..`, type: 'dir', size: 0, mtime: 0, mode: 0 });
|
||||||
|
}
|
||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -120,6 +124,7 @@ export class PanelState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleSelect(name: string): void {
|
toggleSelect(name: string): void {
|
||||||
|
if (name === '..') return;
|
||||||
this.selected.update((prev) => {
|
this.selected.update((prev) => {
|
||||||
const next = new Set(prev);
|
const next = new Set(prev);
|
||||||
if (next.has(name)) next.delete(name);
|
if (next.has(name)) next.delete(name);
|
||||||
@@ -132,12 +137,13 @@ export class PanelState {
|
|||||||
const [lo, hi] = fromIndex <= toIndex ? [fromIndex, toIndex] : [toIndex, fromIndex];
|
const [lo, hi] = fromIndex <= toIndex ? [fromIndex, toIndex] : [toIndex, fromIndex];
|
||||||
const names = this.sorted()
|
const names = this.sorted()
|
||||||
.slice(lo, hi + 1)
|
.slice(lo, hi + 1)
|
||||||
.map((e) => e.name);
|
.map((e) => e.name)
|
||||||
|
.filter((n) => n !== '..');
|
||||||
this.selected.update((prev) => new Set([...prev, ...names]));
|
this.selected.update((prev) => new Set([...prev, ...names]));
|
||||||
}
|
}
|
||||||
|
|
||||||
selectAll(): void {
|
selectAll(): void {
|
||||||
this.selected.set(new Set(this.sorted().map((e) => e.name)));
|
this.selected.set(new Set(this.sorted().map((e) => e.name).filter((n) => n !== '..')));
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelection(): void {
|
clearSelection(): void {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export class TransfersService {
|
|||||||
const selected = src.selectedEntries();
|
const selected = src.selectedEntries();
|
||||||
const atCursor = src.sorted()[src.cursor()];
|
const atCursor = src.sorted()[src.cursor()];
|
||||||
const targets = (selected.length ? selected : atCursor ? [atCursor] : []).filter(
|
const targets = (selected.length ? selected : atCursor ? [atCursor] : []).filter(
|
||||||
(e) => e.type !== 'symlink',
|
(e) => e.type !== 'symlink' && e.name !== '..',
|
||||||
);
|
);
|
||||||
if (!targets.length) {
|
if (!targets.length) {
|
||||||
this.toast.info('Nothing to copy (symlinks are skipped)');
|
this.toast.info('Nothing to copy (symlinks are skipped)');
|
||||||
|
|||||||
Reference in New Issue
Block a user