Move save functionality to File menu

- Remove save button from application toolbar
- Add Save (⌘S) and Save As (⌘⇧S) menu items to File menu
- Implement notification-based communication between menu and document views
- Add unsaved changes indicator in window titles
- Support both saving existing files and creating new files

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
0x8664b2
2025-06-20 11:03:18 -07:00
parent a3e8b09a8f
commit 68d6dd906f
3 changed files with 102 additions and 11 deletions

View File

@@ -1,5 +1,6 @@
import SwiftUI
import AppKit
import UniformTypeIdentifiers
struct ContentView: View {
var body: some View {
@@ -23,14 +24,18 @@ struct ContentView: View {
}
struct MarkdownDocumentView: View {
let content: String
let originalContent: String
let fileName: String
let fileURL: URL?
@State private var isEditMode = false
@State private var editableContent: String
@State private var hasUnsavedChanges = false
@State private var isSaving = false
init(content: String, fileName: String) {
self.content = content
init(content: String, fileName: String, fileURL: URL? = nil) {
self.originalContent = content
self.fileName = fileName
self.fileURL = fileURL
self._editableContent = State(initialValue: content)
print("MarkdownDocumentView: Initialized with content length: \(content.count)")
}
@@ -49,12 +54,6 @@ struct MarkdownDocumentView: View {
Spacer()
if isEditMode {
Button("Save") {
// TODO: Implement save functionality
}
.buttonStyle(.borderedProminent)
}
}
.padding()
.background(Color(NSColor.controlBackgroundColor))
@@ -91,7 +90,80 @@ struct MarkdownDocumentView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
.navigationTitle(fileName)
.navigationTitle(hasUnsavedChanges ? "\(fileName) • Edited" : fileName)
.onChange(of: editableContent) { newValue in
hasUnsavedChanges = (newValue != originalContent)
}
.onReceive(NotificationCenter.default.publisher(for: .saveDocument)) { _ in
saveFile()
}
.onReceive(NotificationCenter.default.publisher(for: .saveDocumentAs)) { _ in
saveAsNewFile()
}
}
func saveFile() {
guard let fileURL = fileURL else {
// If no URL, show save dialog
saveAsNewFile()
return
}
guard hasUnsavedChanges else { return }
isSaving = true
Task {
do {
try editableContent.write(to: fileURL, atomically: true, encoding: .utf8)
await MainActor.run {
hasUnsavedChanges = false
isSaving = false
}
} catch {
await MainActor.run {
isSaving = false
showSaveError(error)
}
}
}
}
func saveAsNewFile() {
let panel = NSSavePanel()
if let mdType = UTType(filenameExtension: "md") {
panel.allowedContentTypes = [mdType]
} else {
panel.allowedContentTypes = [.plainText]
}
panel.nameFieldStringValue = fileName
if panel.runModal() == .OK, let url = panel.url {
isSaving = true
Task {
do {
try editableContent.write(to: url, atomically: true, encoding: .utf8)
await MainActor.run {
hasUnsavedChanges = false
isSaving = false
}
} catch {
await MainActor.run {
isSaving = false
showSaveError(error)
}
}
}
}
}
private func showSaveError(_ error: Error) {
// For now, just print the error
// In a full implementation, we'd show an alert dialog
print("Save error: \(error.localizedDescription)")
}
}