Compare commits

..

6 Commits

4 changed files with 41 additions and 23 deletions
+4 -2
View File
@@ -68,7 +68,7 @@ Right-click the launcher background for Settings and Quit. Actions include favor
Action and selection popups paint a popup-sized background using the launcher window’s gradient coordinates, keeping the glows continuous without showing the results underneath. Each opening rebuilds the complete native view and layer tree, rather than resizing a retained presentation between the footer and header. Callbacks and selection behavior stay in the controller. Layout finishes before the opening fade, including when switching popup types.
The launcher, emoji picker, and Translate share `ActionPill` for footer styling, alignment, spacing, and keyboard indicators: primary action first, then Actions with separate Command and K keycaps. Notes instead uses a compact icon-only title bar and formatting strip. Controllers supply the buttons and retain their targets, enabled state, and popup behavior.
The launcher, emoji picker, Translate, and Notes share `ActionPill` for footer styling, alignment, spacing, and keyboard indicators: primary action first, then Actions with separate Command and K keycaps. Notes places Browse Notes (Command-P) and Actions (Command-K) at the bottom right, with both popups opening upwards; New Note remains in Actions and on Command-N. Its formatting controls scroll horizontally when the remaining footer space is too narrow. Controllers supply the buttons and retain their targets, enabled state, and popup behavior.
The header’s trailing edge is constrained to the footer’s trailing edge. The translation provider pill and footer pill therefore share the same 8-point right inset and 36-point height.
@@ -137,7 +137,7 @@ Commands explicitly declare `Application.Command.presentsView`. The shared dispa
## Notes
Search for **Conjure Notes**, identified by its centered 📔 icon, or assign it a global shortcut in Settings. Notes starts with a compact 570 × 404 layout inspired by Raycast Notes, with rounded corners and a centered muted note title, while using Conjure’s shared warm gradient background, cream text, and amber list markers and caret. Drag a window edge or corner to resize it; Notes remembers its size across reopening and app restarts, independently of other launcher views. The minimum size is 570 × 260, and restored dimensions fit the current screen’s visible area. The title bar offers Close, **Actions** (⌘), **Browse Notes** (note icon), and **New Note** (+). Browse Notes (Command-P) opens Conjure’s styled, scrollable searchable picker below the title bar, shared with translation language selection—not a native menu. Fuzzy-search note titles, then select with Up/Down, Control-J/K, and Return or by clicking a note. A checkmark marks the current note, which is selected and scrolled into view on opening. Reopening Notes restores the last selection. Each note’s first nonempty line becomes its title; duplicate titles remain independently selectable.
Search for **Conjure Notes**, identified by its centered 📔 icon, or assign it a global shortcut in Settings. Notes starts with a compact 570 × 404 layout inspired by Raycast Notes, with rounded corners and a centered muted note title, while using Conjure’s shared warm gradient background, cream text, and amber list markers and caret. Drag a window edge or corner to resize it; Notes remembers its size across reopening and app restarts, independently of other launcher views. The minimum size is 570 × 260, and restored dimensions fit the current screen’s visible area. The title bar offers Close; the bottom-right action pill offers **Browse Notes** and **Actions**, with **New Note** in Actions and on Command-N. Browse Notes (Command-P) opens Conjure’s styled, scrollable searchable picker upwards from the footer, shared with translation language selection—not a native menu. Fuzzy-search note titles, then select with Up/Down, Control-J/K, and Return or by clicking a note. A checkmark marks the current note, which is selected and scrolled into view on opening. Reopening Notes restores the last selection. Each note’s first nonempty line becomes its title; duplicate titles remain independently selectable.
Notes uses a standard macOS window with system-inherited appearance and only the native Close button visible; minimize and zoom are hidden. The editor uses 15-point monospaced body text, including bold, italic, and code, while retaining Conjure’s warm palette. Heading sizes decrease from H1 through H6 (26, 23, 21, 19, 17, and 15 points); inline emphasis retains the heading’s size, and fenced code is not treated as headings. Closing still saves first and keeps the window open if saving fails. Other launcher views remain borderless with their existing dark appearance.
@@ -145,6 +145,8 @@ Actions (Command-K) opens the same searchable popup style used throughout Conjur
The bottom strip applies Markdown headings, bold, italic, strikethrough, inline code, links, fenced code blocks, quotes, numbered lists, bullet lists, and checklists. Formatting preserves embedded images and supports Undo. List buttons convert existing list types; clicking the current type removes its marker. The formatting strip is always visible. Successful autosaves stay unobtrusive; only save, load, or deletion errors appear below the editor.
Note text fades over 16 points at the editor’s scroll edges rather than clipping sharply above the formatting strip. Matching 16-point scroll insets keep the first and last lines clear of the fade when scrolled to either end. The 36-point title header and footer meet the editor without extra vertical gaps.
Formatting shortcuts use the same actions as the bottom strip. They apply to the editor’s current selection or caret, never to text in an open picker. Hover over a formatting button to see its shortcut. Command-K remains Actions.
| Formatting | Shortcut |
@@ -252,7 +252,7 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
selectionAccessibilityLabel = "Notes"
currentChoiceAccessibilityLabel = "Current note"
show(title: "Browse Notes", relativeTo: anchor, available: [],
searchAccessibilityLabel: "Search notes", belowAnchor: true,
searchAccessibilityLabel: "Search notes",
selectionChoices: notes.map {
SelectionChoice(id: .note($0.id), title: $0.title, searchText: FuzzySearchText($0.title))
}, selectionName: "notes")
@@ -260,7 +260,7 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
func showNotesActions(relativeTo anchor: NSView) {
show(title: "Notes", relativeTo: anchor, available: [12, 13, 14],
searchAccessibilityLabel: "Search notes actions", belowAnchor: true)
searchAccessibilityLabel: "Search notes actions")
}
private func show(title: String, relativeTo anchor: NSView, available: [Int], emoji: Bool = false,
@@ -1184,6 +1184,9 @@ final class ApplicationCell: NSTableCellView {
@MainActor
final class LauncherScrollView: NSScrollView {
static let fadeHeight: CGFloat = 32
var edgeFadeHeight: CGFloat = LauncherScrollView.fadeHeight {
didSet { needsLayout = true }
}
private let edgeFade = CAGradientLayer()
override init(frame frameRect: NSRect) {
@@ -1200,7 +1203,7 @@ final class LauncherScrollView: NSScrollView {
override func layout() {
super.layout()
let fade = min(0.5, Self.fadeHeight / max(bounds.height, 1))
let fade = min(0.5, edgeFadeHeight / max(bounds.height, 1))
CATransaction.begin()
CATransaction.setDisableActions(true)
edgeFade.frame = bounds
+31 -18
View File
@@ -53,30 +53,24 @@ final class NotesController: NSViewController, NSTextViewDelegate {
view = content
browseButton.target = self
browseButton.action = #selector(browseNotes)
configureIcon(browseButton, symbol: "note.text", label: "Browse notes (⌘P)")
browseButton.keyEquivalent = "p"
actionsButton.target = self
actionsButton.action = #selector(showActions)
configureIcon(actionsButton, symbol: "command", label: "Notes actions (⌘K)")
let create = iconButton("plus", label: "New note (⌘N)", action: #selector(newNote))
let controls = NSStackView(views: [actionsButton, browseButton, create])
controls.spacing = 2
let controls = ActionPill(primaryButton: browseButton, actionsButton: actionsButton, primaryCommand: "p")
noteTitle.font = .systemFont(ofSize: 13)
noteTitle.textColor = SunsetPalette.secondaryText
noteTitle.alignment = .center
noteTitle.lineBreakMode = .byTruncatingTail
noteTitle.setAccessibilityLabel("Note title")
let header = NSView()
for control in [noteTitle, controls] {
for control in [noteTitle] {
control.translatesAutoresizingMaskIntoConstraints = false
header.addSubview(control)
control.centerYAnchor.constraint(equalTo: header.centerYAnchor).isActive = true
}
NSLayoutConstraint.activate([
controls.trailingAnchor.constraint(equalTo: header.trailingAnchor, constant: -8),
noteTitle.centerXAnchor.constraint(equalTo: header.centerXAnchor),
noteTitle.leadingAnchor.constraint(greaterThanOrEqualTo: header.leadingAnchor, constant: 44),
noteTitle.trailingAnchor.constraint(lessThanOrEqualTo: controls.leadingAnchor, constant: -16),
noteTitle.trailingAnchor.constraint(lessThanOrEqualTo: header.trailingAnchor, constant: -44),
])
actions.closed = { [weak self] in self?.focusEditor() }
actions.selectNote = { [weak self] in self?.switchNote($0) }
@@ -97,7 +91,22 @@ final class NotesController: NSViewController, NSTextViewDelegate {
formatting.addArrangedSubview(button)
}
formatting.spacing = 8
let footer = NSStackView(views: [formatting, NSView()])
let formattingScroll = NSScrollView()
formattingScroll.drawsBackground = false
formattingScroll.contentView.drawsBackground = false
formattingScroll.hasHorizontalScroller = true
formattingScroll.autohidesScrollers = true
formattingScroll.scrollerStyle = .overlay
formattingScroll.verticalScrollElasticity = .none
formattingScroll.documentView = formatting
formatting.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
formatting.widthAnchor.constraint(equalToConstant: CGFloat(tools.count * 28 + (tools.count - 1) * 8)),
formatting.heightAnchor.constraint(equalToConstant: 28),
formattingScroll.widthAnchor.constraint(greaterThanOrEqualToConstant: 28),
formattingScroll.heightAnchor.constraint(equalToConstant: 36),
])
let footer = NSStackView(views: [formattingScroll, controls])
footer.alignment = .centerY
footer.spacing = 8
editor.delegate = self
@@ -119,12 +128,16 @@ final class NotesController: NSViewController, NSTextViewDelegate {
editor.textContainer?.widthTracksTextView = true
editor.textContainerInset = NSSize(width: 22, height: 8)
editor.setAccessibilityLabel("Note editor")
let scroll = NSScrollView()
let scroll = LauncherScrollView()
scroll.edgeFadeHeight = 16
scroll.documentView = editor
scroll.hasVerticalScroller = true
scroll.autohidesScrollers = true
scroll.drawsBackground = false
scroll.scrollerStyle = .overlay
scroll.automaticallyAdjustsContentInsets = false
scroll.contentInsets = NSEdgeInsets(top: scroll.edgeFadeHeight, left: 0,
bottom: scroll.edgeFadeHeight, right: 0)
status.font = .systemFont(ofSize: 12)
status.textColor = SunsetPalette.secondaryText
status.setAccessibilityLabel("Notes save status")
@@ -134,7 +147,7 @@ final class NotesController: NSViewController, NSTextViewDelegate {
let body = NSStackView(views: [scroll, status, footer])
body.orientation = .vertical
body.alignment = .leading
body.spacing = 4
body.spacing = 0
for control in [header, body] {
control.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(control)
@@ -143,22 +156,22 @@ final class NotesController: NSViewController, NSTextViewDelegate {
header.topAnchor.constraint(equalTo: view.topAnchor),
header.leadingAnchor.constraint(equalTo: view.leadingAnchor),
header.trailingAnchor.constraint(equalTo: view.trailingAnchor),
header.heightAnchor.constraint(equalToConstant: 38),
body.topAnchor.constraint(equalTo: header.bottomAnchor, constant: 4),
header.heightAnchor.constraint(equalToConstant: 36),
body.topAnchor.constraint(equalTo: header.bottomAnchor),
body.leadingAnchor.constraint(equalTo: view.leadingAnchor),
body.trailingAnchor.constraint(equalTo: view.trailingAnchor),
body.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -4),
body.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scroll.widthAnchor.constraint(equalTo: body.widthAnchor),
footer.leadingAnchor.constraint(equalTo: body.leadingAnchor, constant: 24),
footer.trailingAnchor.constraint(equalTo: body.trailingAnchor, constant: -6),
footer.trailingAnchor.constraint(equalTo: body.trailingAnchor, constant: -8),
status.leadingAnchor.constraint(equalTo: body.leadingAnchor, constant: 28),
status.trailingAnchor.constraint(equalTo: body.trailingAnchor, constant: -12),
scroll.heightAnchor.constraint(greaterThanOrEqualToConstant: 80),
footer.heightAnchor.constraint(equalToConstant: 32),
footer.heightAnchor.constraint(equalToConstant: 36),
])
guard let store else {
editor.isEditable = false
for control in [browseButton, actionsButton, create] { control.isEnabled = false }
for control in [browseButton, actionsButton] { control.isEnabled = false }
formatting.subviews.compactMap { $0 as? NSButton }.forEach { $0.isEnabled = false }
showError(loadError!)
return