style: use shared notes browsing and actions panels
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user