Edit file: built-in editor + external editor with watch & auto-upload
- Built-in editor (default): large modal with monospace textarea, Cmd+S save, dirty guard; reads/writes through the provider streams so it works on local and remote files identically (2MB cap with a friendly error) - External editor: remote files download to appDataDir/edits/<id>, open via macOS `open -a`, the temp DIR is watched (survives atomic rename-saves) and every save enqueues a normal upload job (progress/errors in the transfer list); watchers + temp dirs are cleaned when the session disconnects - Editor setting: Built-in | Custom with a NATIVE app picker (electrobun openFileDialog, /Applications, .app filter) and reset-to-default - Leftover temp files from a crash prompt for cleanup at boot - F4, toolbar button (mynaFileText), and File > Edit File (Cmd+E) menu entry - README: feature notes + Portability notes (open/start/xdg-open seam documented in code and README per user request) Verified headless against the box: sftp read/write roundtrip incl. UTF-8, size cap, watch->save->auto-upload->remote-updated, session cleanup, orphan listing and clearing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { DialogsService } from './dialogs.service';
|
||||
import { joinPath } from './format';
|
||||
import { type PanelState } from './panels.service';
|
||||
import { RpcService } from './rpc.service';
|
||||
import { SettingsService } from './settings.service';
|
||||
import { ToastService } from './toast.service';
|
||||
|
||||
/**
|
||||
@@ -15,6 +16,7 @@ export class FileActionsService {
|
||||
private readonly rpc = inject(RpcService);
|
||||
private readonly toast = inject(ToastService);
|
||||
private readonly dialogs = inject(DialogsService);
|
||||
private readonly settings = inject(SettingsService);
|
||||
|
||||
/** Selection if non-empty, else the cursor entry. Never the ".." parent row. */
|
||||
targets(panel: PanelState): FileEntry[] {
|
||||
@@ -56,6 +58,42 @@ export class FileActionsService {
|
||||
});
|
||||
}
|
||||
|
||||
/** Open the cursor file in the built-in editor or the configured external app. */
|
||||
async edit(panel: PanelState): Promise<void> {
|
||||
const entry = panel.sorted()[panel.cursor()];
|
||||
if (!entry || entry.type !== 'file' || entry.name === '..') return;
|
||||
|
||||
if (this.settings.editor() === 'custom') {
|
||||
const res = await this.rpc.request.editExternal({
|
||||
sessionId: panel.sessionId(),
|
||||
path: entry.path,
|
||||
});
|
||||
if (!res.ok) {
|
||||
this.toast.error(res.error);
|
||||
} else if (res.watching) {
|
||||
this.toast.info(`Editing ${entry.name} — saves upload automatically`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await this.rpc.request.readFile({
|
||||
sessionId: panel.sessionId(),
|
||||
path: entry.path,
|
||||
});
|
||||
if (!res.ok) {
|
||||
this.toast.error(res.error);
|
||||
return;
|
||||
}
|
||||
this.dialogs.editor.set({
|
||||
sessionId: panel.sessionId(),
|
||||
path: entry.path,
|
||||
name: entry.name,
|
||||
sessionTitle: panel.title(),
|
||||
content: res.content,
|
||||
panel,
|
||||
});
|
||||
}
|
||||
|
||||
rename(panel: PanelState): void {
|
||||
const entry = panel.sorted()[panel.cursor()];
|
||||
if (!entry || entry.name === '..') return;
|
||||
|
||||
Reference in New Issue
Block a user