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:
Jokin
2026-07-11 20:28:54 +02:00
co-authored by Claude Fable 5
parent 38ac454380
commit e4f7ca86f7
17 changed files with 606 additions and 5 deletions
+21
View File
@@ -5,6 +5,7 @@ import { ConnectionManager } from './connection-manager/connection-manager';
import { ConnectionsService } from './connections.service';
import { ChmodDialog } from './dialogs/chmod-dialog';
import { ConfirmDialog } from './dialogs/confirm-dialog';
import { EditorDialog } from './dialogs/editor-dialog';
import { OverwriteDialog } from './dialogs/overwrite-dialog';
import { PromptDialog } from './dialogs/prompt-dialog';
import { DialogsService } from './dialogs.service';
@@ -28,6 +29,7 @@ import { TransferBar } from './transfer-bar/transfer-bar';
ConfirmDialog,
ChmodDialog,
OverwriteDialog,
EditorDialog,
TransferBar,
ConnectionManager,
],
@@ -46,9 +48,25 @@ export class App {
document.title = BRANDING.appName;
if (this.rpc.available) {
this.rpc.onMenuAction((action) => this.handleMenuAction(action));
void this.promptForEditLeftovers();
}
}
/** Temp files from external edits in a previous run (crash / quit mid-edit). */
private async promptForEditLeftovers(): Promise<void> {
const res = await this.rpc.request.getEditLeftovers({});
if (!res.ok || !res.names.length) return;
const shown = res.names.slice(0, 5).join(', ');
const more = res.names.length > 5 ? `, +${res.names.length - 5} more` : '';
this.dialogs.confirm.set({
title: 'Leftover edited files',
body: `A previous session left ${res.names.length} temporary edited file(s) behind (${shown}${more}). Clear them?`,
confirmLabel: 'Clear',
destructive: true,
onConfirm: () => void this.rpc.request.clearEditLeftovers({}),
});
}
/** Native application-menu actions forwarded from the bun process. */
private handleMenuAction(action: string): void {
if (action.startsWith('connect:')) {
@@ -72,6 +90,9 @@ export class App {
case 'new-file':
this.actions.createFile(this.panels.active());
break;
case 'edit-file':
void this.actions.edit(this.panels.active());
break;
case 'refresh-active':
void this.panels.active().refresh();
break;