style: use shared notes browsing and actions panels

This commit is contained in:
Valdemar Lorenzen
2026-10-19 10:51:45 +02:00
parent 0b5d27752c
commit 9ce2997c19
8 changed files with 487 additions and 128 deletions
+74 -30
View File
@@ -4,9 +4,9 @@ import Highlighter
@MainActor
final class NotesController: NSViewController, NSTextViewDelegate {
private let createButton = NSButton(title: "New Note", target: nil, action: nil)
private let browseButton = NSButton(title: "Browse Notes", target: nil, action: nil)
private let actionsButton = NSButton(title: "Actions", target: nil, action: nil)
private let noteMenu = NSMenu(title: "Notes")
private let actions = ApplicationActionsController()
private let editor = NSTextView()
private let status = NSTextField(labelWithString: "")
private let defaults: UserDefaults
@@ -29,16 +29,22 @@ final class NotesController: NSViewController, NSTextViewDelegate {
required init?(coder: NSCoder) { fatalError("Conjure creates Notes programmatically") }
override func loadView() {
view = NSView()
createButton.target = self
createButton.action = #selector(newNote)
createButton.toolTip = "Create a note (Command-N)"
let content = NotesView()
content.commandShortcut = { [weak self] in self?.handleCommandKey($0) ?? false }
view = content
browseButton.target = self
browseButton.action = #selector(browseNotes)
browseButton.setAccessibilityLabel("Browse notes")
browseButton.toolTip = "Browse notes (Command-P)"
actionsButton.target = self
actionsButton.action = #selector(showActions)
actionsButton.setAccessibilityLabel("Choose a note")
actionsButton.toolTip = "Choose a note (Command-K)"
actionsButton.menu = noteMenu
let pill = ActionPill(primaryButton: createButton, actionsButton: actionsButton, primaryCommand: "n")
actionsButton.setAccessibilityLabel("Notes actions")
actionsButton.toolTip = "Notes actions (Command-K)"
actions.closed = { [weak self] in self?.focusEditor() }
actions.selectNote = { [weak self] in self?.switchNote($0) }
actions.newNote = { [weak self] in self?.newNote() }
actions.browseNotes = { [weak self] in self?.browseNotes() }
let pill = ActionPill(primaryButton: browseButton, actionsButton: actionsButton, primaryCommand: "p")
let footer = NSStackView(views: [status, NSView(), pill])
footer.orientation = .horizontal
footer.alignment = .centerY
@@ -91,7 +97,7 @@ final class NotesController: NSViewController, NSTextViewDelegate {
])
guard let store else {
editor.isEditable = false
for control in [createButton, actionsButton] { control.isEnabled = false }
for control in [browseButton, actionsButton] { control.isEnabled = false }
showError(loadError!)
return
}
@@ -119,7 +125,6 @@ final class NotesController: NSViewController, NSTextViewDelegate {
do {
try store.update(id: selectedID, contents: editor.attributedString())
isDirty = false
updateActions()
status.stringValue = "Saved locally · Markdown · Paste or drop images"
status.textColor = SunsetPalette.secondaryText
return true
@@ -129,20 +134,50 @@ final class NotesController: NSViewController, NSTextViewDelegate {
}
}
func goBack() {
guard save() else { return }
func handle(_ selector: Selector) -> Bool {
actions.isShown && actions.handle(selector)
}
func handleControlKey(_ key: String) -> Bool {
guard actions.isShown, key == "j" || key == "k" else { return false }
actions.moveSelection(by: key == "j" ? 1 : -1)
return true
}
func handleCommandKey(_ key: String) -> Bool {
guard store != nil else { return false }
switch key {
case "p": browseNotes()
case "k": showActions()
case "n": newNote()
default: return false
}
return true
}
@discardableResult
func close() -> Bool {
guard save() else { return false }
actions.close(animated: false)
closed?()
return true
}
func goBack() {
if actions.isShown { actions.close() }
else { _ = close() }
}
@objc private func newNote() {
actions.close(animated: false)
guard save(), let store else { return }
do { select(try store.create()); focusEditor() }
catch { showError(error) }
}
@objc private func switchNote(_ sender: NSMenuItem) {
guard let value = sender.representedObject as? UUID, save() else { return }
select(value)
private func switchNote(_ id: UUID) {
guard save() else { return }
select(id)
focusEditor()
}
@@ -155,20 +190,10 @@ final class NotesController: NSViewController, NSTextViewDelegate {
editor.isEditable = true
editor.setSelectedRange(NSRange(location: editor.string.utf16.count, length: 0))
MarkdownHighlighting.apply(to: editor)
updateActions()
status.stringValue = "Saved locally · Markdown · Paste or drop images"
status.textColor = SunsetPalette.secondaryText
}
private func updateActions() {
noteMenu.removeAllItems()
for note in store?.notes ?? [] {
let item = noteMenu.addItem(withTitle: note.title, action: #selector(switchNote(_:)), keyEquivalent: "")
item.target = self
item.representedObject = note.id
item.state = note.id == selectedID ? .on : .off
}
}
private func showError(_ error: Error) {
status.stringValue = "Couldn’t save or load notes: \(error.localizedDescription)"
@@ -176,9 +201,28 @@ final class NotesController: NSViewController, NSTextViewDelegate {
status.textColor = .systemRed
}
@objc private func showActions() {
noteMenu.popUp(positioning: nil, at: NSPoint(x: 0, y: actionsButton.bounds.maxY + 4), in: actionsButton)
focusEditor()
@objc func browseNotes() {
guard let store else { return }
actions.showNotes(store.notes.map { (id: $0.id, title: $0.title) },
selected: selectedID, relativeTo: browseButton)
}
@objc func showActions() {
guard store != nil else { return }
actions.showNotesActions(relativeTo: actionsButton)
}
}
@MainActor
private final class NotesView: NSView {
var commandShortcut: ((String) -> Bool)?
override func performKeyEquivalent(with event: NSEvent) -> Bool {
if event.modifierFlags.intersection([.command, .control, .option, .shift]) == .command,
commandShortcut?(event.charactersIgnoringModifiers?.lowercased() ?? "") == true {
return true
}
return super.performKeyEquivalent(with: event)
}
}