refactor: unified EditorPanel for notes and files, SaveFileText backend

- Create EditorPanel.svelte: shared editor component for notes + files
  - Markdown: edit/preview/split modes via MarkdownEditor + MarkdownPreview
  - Plain text: monospace textarea
  - Header: title, mode switcher, external open button
  - Footer: save + close buttons, dirty indicator
- Rewrite NoteEditorPanel.svelte as thin wrapper over EditorPanel
- FilesTab: browser/editor modes (editor replaces file list, not appended)
- Add SaveFileText Wails binding: writes text files atomically (tmp+rename)
- Add WriteText to files.Service with SHA256 update
- Remove separate TextFileEditor.svelte (replaced by EditorPanel)
- Note context: SaveNote API, noteId/title props
- File context: SaveFileText API, fileId prop
- Linked .md (CheckFileAction=note) → note editor tab
- Unlinked .md / text files → EditorPanel in FilesTab
- Images/PDF/binary → FilePreviewModal only

Co-Authored-By: OWL (Hermes Agent) <hermes@nousresearch.com>
This commit is contained in:
2026-06-16 10:40:09 +08:00
co-authored by OWL
parent 383a9546df
commit 120ff7d6fe
9 changed files with 566 additions and 463 deletions
+40
View File
@@ -210,6 +210,46 @@ func (a *App) CheckFileAction(fileID string) (*PreflightFileAction, error) {
return &PreflightFileAction{Action: "preview", FileName: fileRec.Filename}, nil
}
// SaveFileText saves text content to a file record.
// Only works for vault-contained text files (not binary).
func (a *App) SaveFileText(fileID string, content string) error {
if err := a.requireVault(); err != nil {
return err
}
fileRec, err := a.files.Get(fileID)
if err != nil {
return fmt.Errorf("get file: %w", err)
}
// Safety: only save text-like files
name := strings.ToLower(fileRec.Filename)
isText := strings.HasPrefix(fileRec.MIME, "text/") ||
strings.HasSuffix(name, ".md") ||
strings.HasSuffix(name, ".txt") ||
strings.HasSuffix(name, ".json") ||
strings.HasSuffix(name, ".yaml") ||
strings.HasSuffix(name, ".yml") ||
strings.HasSuffix(name, ".csv") ||
strings.HasSuffix(name, ".xml") ||
strings.HasSuffix(name, ".ini") ||
strings.HasSuffix(name, ".conf") ||
strings.HasSuffix(name, ".sh") ||
strings.HasSuffix(name, ".py") ||
strings.HasSuffix(name, ".js") ||
strings.HasSuffix(name, ".ts") ||
strings.HasSuffix(name, ".css") ||
strings.HasSuffix(name, ".html") ||
strings.HasSuffix(name, ".log")
if !isText {
return fmt.Errorf("refusing to save binary file: %s", fileRec.Filename)
}
if err := a.files.WriteText(fileRec, content); err != nil {
return fmt.Errorf("write file: %w", err)
}
// Record activity
_ = a.activity.Record(fileRec.NodeID, activity.TargetFile, fileID, "", activity.TypeFileModified, fileRec.Filename, "")
return nil
}
func (a *App) PreviewImport(sourcePath string) (*files.ImportSummary, error) {
if err := a.requireVault(); err != nil {
return nil, err
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -19,8 +19,8 @@
background: #13131f;
}
</style>
<script type="module" crossorigin src="/assets/main-D23Sgy9Q.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-CoirOqGf.css">
<script type="module" crossorigin src="/assets/main-B99YW--H.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-CtgLvi_n.css">
</head>
<body>
<div id="app"></div>