feat: add persistent Markdown notes
This commit is contained in:
@@ -35,7 +35,9 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
private var launcherFrameBeforeReview: NSRect?
|
||||
private var translation: TranslationController?
|
||||
private var emojiPicker: EmojiPickerController?
|
||||
private var isCommandOpen: Bool { translation != nil || emojiPicker != nil }
|
||||
private var notes: NotesController?
|
||||
private let notesDirectory: URL?
|
||||
private var isCommandOpen: Bool { activeCommand != nil }
|
||||
private var activeCommand: Application.Command?
|
||||
private var pendingPresentation: (() -> Void)?
|
||||
private static let emojiIcon: NSImage = {
|
||||
@@ -52,11 +54,12 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
}()
|
||||
|
||||
init(history: LaunchHistory = LaunchHistory(), uninstaller: ApplicationUninstaller = ApplicationUninstaller(),
|
||||
defaults: UserDefaults = .standard, pasteboard: NSPasteboard = .general) {
|
||||
defaults: UserDefaults = .standard, pasteboard: NSPasteboard = .general, notesDirectory: URL? = nil) {
|
||||
self.history = history
|
||||
self.uninstaller = uninstaller
|
||||
self.defaults = defaults
|
||||
self.pasteboard = pasteboard
|
||||
self.notesDirectory = notesDirectory
|
||||
let window = LauncherPanel(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 750, height: 474),
|
||||
styleMask: [.borderless],
|
||||
@@ -85,6 +88,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
else { self.moveSelection(by: offset) }
|
||||
}
|
||||
window.commandNavigation = { [weak self] key in self?.emojiPicker?.handleControlKey(key) ?? false }
|
||||
window.usesTextNavigation = { [weak self] in self?.notes != nil }
|
||||
window.dismiss = { [weak self] in self?.dismiss() }
|
||||
window.canUseNumberShortcuts = { [weak self] in
|
||||
guard let self else { return false }
|
||||
@@ -122,7 +126,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
uninstallReview?.goBack()
|
||||
return
|
||||
}
|
||||
closeCommand()
|
||||
guard closeCommand() else { return }
|
||||
actions.close(animated: false)
|
||||
(window as? LauncherPanel)?.beginNumberHintSession()
|
||||
let frontmost = NSWorkspace.shared.frontmostApplication
|
||||
@@ -140,7 +144,8 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
showWindow(nil)
|
||||
NSApp.activate()
|
||||
window?.makeKeyAndOrderFront(nil)
|
||||
window?.makeFirstResponder(searchField)
|
||||
if let notes { notes.focusEditor() }
|
||||
else { window?.makeFirstResponder(searchField) }
|
||||
(window as? LauncherPanel)?.sampleNumberHints()
|
||||
(searchField.currentEditor() as? NSTextView)?.insertionPointColor = SunsetPalette.accent
|
||||
refreshApplications()
|
||||
@@ -164,7 +169,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
uninstallReview?.goBack()
|
||||
return
|
||||
}
|
||||
closeCommand()
|
||||
guard closeCommand() else { return }
|
||||
actions.close(animated: false)
|
||||
alignmentGuides?.end()
|
||||
let previous = previousApplication
|
||||
@@ -567,10 +572,18 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
cell.locationLabel.isHidden = true
|
||||
cell.kindLabel.attributedStringValue = history.isFavorite(entry)
|
||||
? ApplicationCell.favoriteCommandKind : ApplicationCell.commandKind
|
||||
cell.imageView?.image = entry.command == .emoji ? Self.emojiIcon
|
||||
: NSImage(systemSymbolName: "character.bubble", accessibilityDescription: nil)
|
||||
switch entry.command {
|
||||
case .emoji:
|
||||
cell.imageView?.image = Self.emojiIcon
|
||||
cell.toolTip = "Search and copy emoji"
|
||||
case .notes:
|
||||
cell.imageView?.image = NSImage(systemSymbolName: "note.text", accessibilityDescription: nil)
|
||||
cell.toolTip = "Write notes with Markdown and images"
|
||||
default:
|
||||
cell.imageView?.image = NSImage(systemSymbolName: "character.bubble", accessibilityDescription: nil)
|
||||
cell.toolTip = "Translate English to Japanese"
|
||||
}
|
||||
cell.imageView?.contentTintColor = entry.command == .emoji ? nil : SunsetPalette.accent
|
||||
cell.toolTip = entry.command == .emoji ? "Search and copy emoji" : "Translate English to Japanese"
|
||||
} else {
|
||||
let application = results[row]
|
||||
cell.textField?.stringValue = application.name
|
||||
@@ -794,11 +807,18 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
@objc private func goBackCommand() {
|
||||
translation?.goBack()
|
||||
emojiPicker?.goBack()
|
||||
notes?.goBack()
|
||||
}
|
||||
|
||||
private func closeCommand() {
|
||||
func saveNotes() -> Bool { notes?.save() ?? true }
|
||||
|
||||
@discardableResult
|
||||
private func closeCommand() -> Bool {
|
||||
guard saveNotes() else { return false }
|
||||
notes?.closed?()
|
||||
translation?.goBack()
|
||||
emojiPicker?.close()
|
||||
return true
|
||||
}
|
||||
|
||||
private func showCommand(_ command: Application.Command) {
|
||||
@@ -825,6 +845,12 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
controller = tool
|
||||
placeholder = "Search emoji by name or :shortcode:…"
|
||||
accessibilityLabel = "Search emoji"
|
||||
case .notes:
|
||||
let tool = NotesController(defaults: defaults, directory: notesDirectory)
|
||||
notes = tool
|
||||
controller = tool
|
||||
placeholder = "Conjure Notes"
|
||||
accessibilityLabel = "Conjure Notes"
|
||||
}
|
||||
let previousQuery = searchField.stringValue
|
||||
let previousPlaceholder = searchField.placeholderAttributedString
|
||||
@@ -833,6 +859,8 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
controller.view.removeFromSuperview()
|
||||
self.translation = nil
|
||||
self.emojiPicker = nil
|
||||
self.notes = nil
|
||||
self.searchField.isEnabled = true
|
||||
self.activeCommand = nil
|
||||
self.commandBack.isHidden = true
|
||||
self.commandBack.target = nil
|
||||
@@ -851,6 +879,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
}
|
||||
translation?.closed = close
|
||||
emojiPicker?.closed = close
|
||||
notes?.closed = close
|
||||
actions.close(animated: false)
|
||||
(window as? LauncherPanel)?.resetNumberHints()
|
||||
openButton.keyEquivalent = ""
|
||||
@@ -864,6 +893,10 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
attributes: previousPlaceholder?.attributes(at: 0, effectiveRange: nil) ?? [:]
|
||||
)
|
||||
searchField.setAccessibilityLabel(accessibilityLabel)
|
||||
if command == .notes {
|
||||
searchField.stringValue = "Conjure Notes"
|
||||
searchField.isEnabled = false
|
||||
}
|
||||
for view in launcherViews.dropFirst() {
|
||||
view.isHidden = true
|
||||
view.setAccessibilityHidden(true)
|
||||
@@ -876,7 +909,8 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
controller.view.topAnchor.constraint(equalTo: searchField.bottomAnchor, constant: 16),
|
||||
controller.view.bottomAnchor.constraint(equalTo: content.bottomAnchor),
|
||||
])
|
||||
window?.makeFirstResponder(searchField)
|
||||
if let notes { notes.focusEditor() }
|
||||
else { window?.makeFirstResponder(searchField) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1115,6 +1149,7 @@ final class LauncherPanel: NSPanel {
|
||||
var numberHintsChanged: (() -> Void)?
|
||||
var launchNumber: ((Int) -> Void)?
|
||||
var commandNavigation: ((String) -> Bool)?
|
||||
var usesTextNavigation: (() -> Bool)?
|
||||
private var numberShortcuts = CommandNumberShortcuts()
|
||||
private var commandHoldTask: Task<Void, Never>?
|
||||
var numberHintsVisible: Bool { numberShortcuts.isVisible }
|
||||
@@ -1218,7 +1253,7 @@ final class LauncherPanel: NSPanel {
|
||||
}
|
||||
resetNumberHints(modifierFlags: event.modifierFlags)
|
||||
}
|
||||
if event.type == .keyDown, attachedSheet == nil,
|
||||
if event.type == .keyDown, attachedSheet == nil, usesTextNavigation?() != true,
|
||||
event.modifierFlags.intersection([.control, .command, .option, .shift]) == .control {
|
||||
if commandNavigation?(event.charactersIgnoringModifiers?.lowercased() ?? "") == true { return }
|
||||
switch event.charactersIgnoringModifiers?.lowercased() {
|
||||
|
||||
Reference in New Issue
Block a user