- 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>
91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
import { ApplicationMenu, type ApplicationMenuItemConfig } from "electrobun/bun";
|
|
import type { SavedConnection } from "../shared/types";
|
|
import type { ConnectionStore } from "./store/connections";
|
|
|
|
/**
|
|
* macOS application menu (electrobun menus are macOS-only). Standard roles plus
|
|
* app actions; custom actions are forwarded to the webview as menuAction
|
|
* messages. Rebuilt whenever saved connections change so the Connect submenu
|
|
* stays current.
|
|
*/
|
|
export async function applyApplicationMenu(store: ConnectionStore): Promise<void> {
|
|
if (process.platform !== "darwin") return;
|
|
|
|
const { folders, connections } = await store.load();
|
|
const item = (c: SavedConnection): ApplicationMenuItemConfig => ({
|
|
label: c.name,
|
|
action: `connect:${c.id}`,
|
|
});
|
|
const rootItems = connections.filter((c) => !c.folderId).map(item);
|
|
const folderMenus: ApplicationMenuItemConfig[] = folders.map((folder) => ({
|
|
label: folder.name,
|
|
submenu: connections.filter((c) => c.folderId === folder.id).map(item),
|
|
}));
|
|
|
|
const connectSubmenu: ApplicationMenuItemConfig[] = [
|
|
...rootItems,
|
|
...folderMenus,
|
|
...(rootItems.length || folderMenus.length ? [{ type: "separator" } as const] : []),
|
|
{ label: "Connection Manager…", action: "manage-connections" },
|
|
];
|
|
|
|
ApplicationMenu.setApplicationMenu([
|
|
{
|
|
submenu: [
|
|
{ role: "hide" },
|
|
{ role: "hideOthers" },
|
|
{ role: "showAll" },
|
|
{ type: "separator" },
|
|
{ label: "Quit", role: "quit" },
|
|
],
|
|
},
|
|
{
|
|
label: "File",
|
|
submenu: [
|
|
{ label: "New Tab", action: "new-tab", accelerator: "t" },
|
|
{ label: "Close Tab", action: "close-tab", accelerator: "w" },
|
|
{ type: "separator" },
|
|
{ label: "New Folder", action: "new-folder" },
|
|
{ label: "New File", action: "new-file" },
|
|
{ type: "separator" },
|
|
{ label: "Edit File", action: "edit-file", accelerator: "e" },
|
|
{ type: "separator" },
|
|
{ label: "Connect", submenu: connectSubmenu },
|
|
],
|
|
},
|
|
{
|
|
label: "Edit",
|
|
submenu: [
|
|
{ role: "undo" },
|
|
{ role: "redo" },
|
|
{ type: "separator" },
|
|
{ role: "cut" },
|
|
{ role: "copy" },
|
|
{ role: "paste" },
|
|
{ role: "pasteAndMatchStyle" },
|
|
{ role: "delete" },
|
|
{ role: "selectAll" },
|
|
],
|
|
},
|
|
{
|
|
label: "View",
|
|
submenu: [
|
|
{ label: "Refresh", action: "refresh-active", accelerator: "r" },
|
|
{ type: "separator" },
|
|
{ role: "toggleFullScreen" },
|
|
],
|
|
},
|
|
{
|
|
label: "Window",
|
|
submenu: [
|
|
{ role: "minimize" },
|
|
{ role: "zoom" },
|
|
{ type: "separator" },
|
|
{ role: "cycleThroughWindows" },
|
|
{ role: "bringAllToFront" },
|
|
],
|
|
},
|
|
]);
|
|
console.log(`[menu] applied (${connections.length} connections)`);
|
|
}
|