Add recent files menu functionality

Implemented comprehensive recent files tracking system:
- Created RecentFilesManager singleton with UserDefaults persistence
- Added Recent Files submenu to File menu with up to 10 recent files
- Automatic file validation that removes deleted files from list
- Smart file tracking that adds files when opened and moves to top
- Clear Recent Files option for user control
- User-friendly display showing filename and relative path
- Error handling for missing files with user notification

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
0x8664b2
2025-06-20 10:48:11 -07:00
parent 732f633086
commit a3e8b09a8f
4 changed files with 149 additions and 26 deletions

View File

@@ -4,6 +4,8 @@ import AppKit
@main
struct MarkdownViewerApp: App {
@StateObject private var recentFilesManager = RecentFilesManager.shared
var body: some Scene {
Settings {
EmptyView()
@@ -14,6 +16,34 @@ struct MarkdownViewerApp: App {
openMarkdownFile()
}
.keyboardShortcut("o", modifiers: .command)
Divider()
Menu("Recent Files") {
if recentFilesManager.recentFiles.isEmpty {
Text("No Recent Files")
.disabled(true)
} else {
ForEach(recentFilesManager.recentFiles, id: \.self) { url in
Button(action: {
openRecentFile(url)
}) {
VStack(alignment: .leading) {
Text(recentFilesManager.getDisplayName(for: url))
Text(recentFilesManager.getRelativePath(for: url))
.font(.caption)
.foregroundColor(.secondary)
}
}
}
Divider()
Button("Clear Recent Files") {
recentFilesManager.clearRecentFiles()
}
}
}
}
}
}
@@ -32,9 +62,24 @@ struct MarkdownViewerApp: App {
}
}
private func openRecentFile(_ url: URL) {
// Check if file still exists
guard FileManager.default.fileExists(atPath: url.path) else {
recentFilesManager.removeRecentFile(url)
showErrorWindow(message: "File no longer exists", details: url.path)
return
}
openNewWindow(for: url)
}
private func openNewWindow(for url: URL) {
do {
let content = try String(contentsOf: url, encoding: .utf8)
// Add to recent files
recentFilesManager.addRecentFile(url)
let newWindow = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 800, height: 600),
styleMask: [.titled, .closable, .resizable, .miniaturizable],
@@ -54,31 +99,34 @@ struct MarkdownViewerApp: App {
} catch {
print("Error reading file: \(error)")
// Show error in a new window
let newWindow = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 400, height: 200),
styleMask: [.titled, .closable],
backing: .buffered,
defer: false
)
newWindow.title = "Error"
newWindow.contentView = NSHostingView(
rootView: VStack {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 40))
.foregroundColor(.red)
Text("Error loading file")
.font(.headline)
Text(error.localizedDescription)
.font(.caption)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
}
.padding()
)
newWindow.center()
newWindow.makeKeyAndOrderFront(nil)
showErrorWindow(message: "Error loading file", details: error.localizedDescription)
}
}
private func showErrorWindow(message: String, details: String) {
let newWindow = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 400, height: 200),
styleMask: [.titled, .closable],
backing: .buffered,
defer: false
)
newWindow.title = "Error"
newWindow.contentView = NSHostingView(
rootView: VStack {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 40))
.foregroundColor(.red)
Text(message)
.font(.headline)
Text(details)
.font(.caption)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
}
.padding()
)
newWindow.center()
newWindow.makeKeyAndOrderFront(nil)
}
}