Compare commits
6 Commits
c22881b9d1
...
47f8f62b80
| Author | SHA1 | Date | |
|---|---|---|---|
| 47f8f62b80 | |||
| 1804f679fc | |||
| bb640867b7 | |||
| 0ec5e87e0b | |||
| 3a638ba9d1 | |||
| 931eb0ffc3 |
@@ -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.
|
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.
|
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
|
## 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.
|
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.
|
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 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 |
|
| Formatting | Shortcut |
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
selectionAccessibilityLabel = "Notes"
|
selectionAccessibilityLabel = "Notes"
|
||||||
currentChoiceAccessibilityLabel = "Current note"
|
currentChoiceAccessibilityLabel = "Current note"
|
||||||
show(title: "Browse Notes", relativeTo: anchor, available: [],
|
show(title: "Browse Notes", relativeTo: anchor, available: [],
|
||||||
searchAccessibilityLabel: "Search notes", belowAnchor: true,
|
searchAccessibilityLabel: "Search notes",
|
||||||
selectionChoices: notes.map {
|
selectionChoices: notes.map {
|
||||||
SelectionChoice(id: .note($0.id), title: $0.title, searchText: FuzzySearchText($0.title))
|
SelectionChoice(id: .note($0.id), title: $0.title, searchText: FuzzySearchText($0.title))
|
||||||
}, selectionName: "notes")
|
}, selectionName: "notes")
|
||||||
@@ -260,7 +260,7 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
|
|
||||||
func showNotesActions(relativeTo anchor: NSView) {
|
func showNotesActions(relativeTo anchor: NSView) {
|
||||||
show(title: "Notes", relativeTo: anchor, available: [12, 13, 14],
|
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,
|
private func show(title: String, relativeTo anchor: NSView, available: [Int], emoji: Bool = false,
|
||||||
|
|||||||
@@ -1184,6 +1184,9 @@ final class ApplicationCell: NSTableCellView {
|
|||||||
@MainActor
|
@MainActor
|
||||||
final class LauncherScrollView: NSScrollView {
|
final class LauncherScrollView: NSScrollView {
|
||||||
static let fadeHeight: CGFloat = 32
|
static let fadeHeight: CGFloat = 32
|
||||||
|
var edgeFadeHeight: CGFloat = LauncherScrollView.fadeHeight {
|
||||||
|
didSet { needsLayout = true }
|
||||||
|
}
|
||||||
private let edgeFade = CAGradientLayer()
|
private let edgeFade = CAGradientLayer()
|
||||||
|
|
||||||
override init(frame frameRect: NSRect) {
|
override init(frame frameRect: NSRect) {
|
||||||
@@ -1200,7 +1203,7 @@ final class LauncherScrollView: NSScrollView {
|
|||||||
|
|
||||||
override func layout() {
|
override func layout() {
|
||||||
super.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.begin()
|
||||||
CATransaction.setDisableActions(true)
|
CATransaction.setDisableActions(true)
|
||||||
edgeFade.frame = bounds
|
edgeFade.frame = bounds
|
||||||
|
|||||||
@@ -53,30 +53,24 @@ final class NotesController: NSViewController, NSTextViewDelegate {
|
|||||||
view = content
|
view = content
|
||||||
browseButton.target = self
|
browseButton.target = self
|
||||||
browseButton.action = #selector(browseNotes)
|
browseButton.action = #selector(browseNotes)
|
||||||
configureIcon(browseButton, symbol: "note.text", label: "Browse notes (⌘P)")
|
|
||||||
browseButton.keyEquivalent = "p"
|
|
||||||
actionsButton.target = self
|
actionsButton.target = self
|
||||||
actionsButton.action = #selector(showActions)
|
actionsButton.action = #selector(showActions)
|
||||||
configureIcon(actionsButton, symbol: "command", label: "Notes actions (⌘K)")
|
let controls = ActionPill(primaryButton: browseButton, actionsButton: actionsButton, primaryCommand: "p")
|
||||||
let create = iconButton("plus", label: "New note (⌘N)", action: #selector(newNote))
|
|
||||||
let controls = NSStackView(views: [actionsButton, browseButton, create])
|
|
||||||
controls.spacing = 2
|
|
||||||
noteTitle.font = .systemFont(ofSize: 13)
|
noteTitle.font = .systemFont(ofSize: 13)
|
||||||
noteTitle.textColor = SunsetPalette.secondaryText
|
noteTitle.textColor = SunsetPalette.secondaryText
|
||||||
noteTitle.alignment = .center
|
noteTitle.alignment = .center
|
||||||
noteTitle.lineBreakMode = .byTruncatingTail
|
noteTitle.lineBreakMode = .byTruncatingTail
|
||||||
noteTitle.setAccessibilityLabel("Note title")
|
noteTitle.setAccessibilityLabel("Note title")
|
||||||
let header = NSView()
|
let header = NSView()
|
||||||
for control in [noteTitle, controls] {
|
for control in [noteTitle] {
|
||||||
control.translatesAutoresizingMaskIntoConstraints = false
|
control.translatesAutoresizingMaskIntoConstraints = false
|
||||||
header.addSubview(control)
|
header.addSubview(control)
|
||||||
control.centerYAnchor.constraint(equalTo: header.centerYAnchor).isActive = true
|
control.centerYAnchor.constraint(equalTo: header.centerYAnchor).isActive = true
|
||||||
}
|
}
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
controls.trailingAnchor.constraint(equalTo: header.trailingAnchor, constant: -8),
|
|
||||||
noteTitle.centerXAnchor.constraint(equalTo: header.centerXAnchor),
|
noteTitle.centerXAnchor.constraint(equalTo: header.centerXAnchor),
|
||||||
noteTitle.leadingAnchor.constraint(greaterThanOrEqualTo: header.leadingAnchor, constant: 44),
|
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.closed = { [weak self] in self?.focusEditor() }
|
||||||
actions.selectNote = { [weak self] in self?.switchNote($0) }
|
actions.selectNote = { [weak self] in self?.switchNote($0) }
|
||||||
@@ -97,7 +91,22 @@ final class NotesController: NSViewController, NSTextViewDelegate {
|
|||||||
formatting.addArrangedSubview(button)
|
formatting.addArrangedSubview(button)
|
||||||
}
|
}
|
||||||
formatting.spacing = 8
|
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.alignment = .centerY
|
||||||
footer.spacing = 8
|
footer.spacing = 8
|
||||||
editor.delegate = self
|
editor.delegate = self
|
||||||
@@ -119,12 +128,16 @@ final class NotesController: NSViewController, NSTextViewDelegate {
|
|||||||
editor.textContainer?.widthTracksTextView = true
|
editor.textContainer?.widthTracksTextView = true
|
||||||
editor.textContainerInset = NSSize(width: 22, height: 8)
|
editor.textContainerInset = NSSize(width: 22, height: 8)
|
||||||
editor.setAccessibilityLabel("Note editor")
|
editor.setAccessibilityLabel("Note editor")
|
||||||
let scroll = NSScrollView()
|
let scroll = LauncherScrollView()
|
||||||
|
scroll.edgeFadeHeight = 16
|
||||||
scroll.documentView = editor
|
scroll.documentView = editor
|
||||||
scroll.hasVerticalScroller = true
|
scroll.hasVerticalScroller = true
|
||||||
scroll.autohidesScrollers = true
|
scroll.autohidesScrollers = true
|
||||||
scroll.drawsBackground = false
|
scroll.drawsBackground = false
|
||||||
scroll.scrollerStyle = .overlay
|
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.font = .systemFont(ofSize: 12)
|
||||||
status.textColor = SunsetPalette.secondaryText
|
status.textColor = SunsetPalette.secondaryText
|
||||||
status.setAccessibilityLabel("Notes save status")
|
status.setAccessibilityLabel("Notes save status")
|
||||||
@@ -134,7 +147,7 @@ final class NotesController: NSViewController, NSTextViewDelegate {
|
|||||||
let body = NSStackView(views: [scroll, status, footer])
|
let body = NSStackView(views: [scroll, status, footer])
|
||||||
body.orientation = .vertical
|
body.orientation = .vertical
|
||||||
body.alignment = .leading
|
body.alignment = .leading
|
||||||
body.spacing = 4
|
body.spacing = 0
|
||||||
for control in [header, body] {
|
for control in [header, body] {
|
||||||
control.translatesAutoresizingMaskIntoConstraints = false
|
control.translatesAutoresizingMaskIntoConstraints = false
|
||||||
view.addSubview(control)
|
view.addSubview(control)
|
||||||
@@ -143,22 +156,22 @@ final class NotesController: NSViewController, NSTextViewDelegate {
|
|||||||
header.topAnchor.constraint(equalTo: view.topAnchor),
|
header.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
header.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
header.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
header.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
header.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
header.heightAnchor.constraint(equalToConstant: 38),
|
header.heightAnchor.constraint(equalToConstant: 36),
|
||||||
body.topAnchor.constraint(equalTo: header.bottomAnchor, constant: 4),
|
body.topAnchor.constraint(equalTo: header.bottomAnchor),
|
||||||
body.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
body.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
body.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
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),
|
scroll.widthAnchor.constraint(equalTo: body.widthAnchor),
|
||||||
footer.leadingAnchor.constraint(equalTo: body.leadingAnchor, constant: 24),
|
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.leadingAnchor.constraint(equalTo: body.leadingAnchor, constant: 28),
|
||||||
status.trailingAnchor.constraint(equalTo: body.trailingAnchor, constant: -12),
|
status.trailingAnchor.constraint(equalTo: body.trailingAnchor, constant: -12),
|
||||||
scroll.heightAnchor.constraint(greaterThanOrEqualToConstant: 80),
|
scroll.heightAnchor.constraint(greaterThanOrEqualToConstant: 80),
|
||||||
footer.heightAnchor.constraint(equalToConstant: 32),
|
footer.heightAnchor.constraint(equalToConstant: 36),
|
||||||
])
|
])
|
||||||
guard let store else {
|
guard let store else {
|
||||||
editor.isEditable = false
|
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 }
|
formatting.subviews.compactMap { $0 as? NSButton }.forEach { $0.isEnabled = false }
|
||||||
showError(loadError!)
|
showError(loadError!)
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user