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:
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user