style: restore Conjure colors in notes

This commit is contained in:
Valdemar Lorenzen
2026-10-19 16:06:28 +02:00
parent 999d44226d
commit adac0723a1
4 changed files with 22 additions and 25 deletions
+2 -2
View File
@@ -135,7 +135,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 opens in a compact 570 × 404 dark window inspired by Raycast Notes: rounded corners, a centered muted note title, white text, and coral-red list markers and caret. 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 keeps 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. 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.
Actions (Command-K) opens the same searchable popup style used throughout Conjure, with **Browse Notes** (Command-P) and **New Note** (Command-N). Command-N creates a note while the editor or either popup is focused. Command-P toggles Browse Notes even while its title-bar controls are covered. Escape or clicking outside closes an open popup and returns focus to the editor without leaving Notes; another Escape dismisses the launcher. Control-J/K navigates only while a popup is open, preserving normal editor commands when it is closed. Actions (Command-K) opens the same searchable popup style used throughout Conjure, with **Browse Notes** (Command-P) and **New Note** (Command-N). Command-N creates a note while the editor or either popup is focused. Command-P toggles Browse Notes even while its title-bar controls are covered. Escape or clicking outside closes an open popup and returns focus to the editor without leaving Notes; another Escape dismisses the launcher. Control-J/K navigates only while a popup is open, preserving normal editor commands when it is closed.
@@ -157,7 +157,7 @@ Typing, pasting, and Return at the visible start of an item use the position aft
Fenced code uses [HighlighterSwift](https://github.com/smittytone/HighlighterSwift) (Highlight.js) for language-aware token colors. Add a language after the opening backticks or tildes, for example `py`, `python`, `swift`, `js`, or `json`. Unlabeled fences use automatic detection; unknown languages remain plain monospace. Highlighting runs entirely offline. Fenced code uses [HighlighterSwift](https://github.com/smittytone/HighlighterSwift) (Highlight.js) for language-aware token colors. Add a language after the opening backticks or tildes, for example `py`, `python`, `swift`, `js`, or `json`. Unlabeled fences use automatic detection; unknown languages remain plain monospace. Highlighting runs entirely offline.
Notes autosave locally as RTFD packages, including embedded images, under `~/Library/Application Support/Conjure/Notes`. No account, network access, or cloud sync is involved. Save failures appear below the editor and prevent switching, closing, or quitting until saving succeeds, preserving the unsaved buffer. The red Close button and Command-W dismiss Conjure, including any open popup; leaving Notes or switching destinations removes its popup completely and restores the launcher’s original dimensions. Notes autosave locally as RTFD packages, including embedded images, under `~/Library/Application Support/Conjure/Notes`. No account, network access, or cloud sync is involved. Save failures appear below the editor and prevent switching, closing, or quitting until saving succeeds, preserving the unsaved buffer. The Close button and Command-W dismiss Conjure, including any open popup; leaving Notes or switching destinations removes its popup completely and restores the launcher’s original dimensions.
## Translation ## Translation
+14 -20
View File
@@ -4,10 +4,6 @@ import Highlighter
@MainActor @MainActor
enum NotesAppearance { enum NotesAppearance {
static let background = NSColor(white: 0.035, alpha: 1)
static let text = NSColor(white: 0.94, alpha: 1)
static let muted = NSColor(white: 0.58, alpha: 1)
static let accent = NSColor(srgbRed: 1, green: 0.36, blue: 0.39, alpha: 1)
static let font = NSFont.systemFont(ofSize: 17) static let font = NSFont.systemFont(ofSize: 17)
static let paragraph: NSParagraphStyle = { static let paragraph: NSParagraphStyle = {
let style = NSMutableParagraphStyle() let style = NSMutableParagraphStyle()
@@ -49,18 +45,16 @@ final class NotesController: NSViewController, NSTextViewDelegate {
let content = NotesView() let content = NotesView()
content.commandShortcut = { [weak self] in self?.handleCommandKey($0) ?? false } content.commandShortcut = { [weak self] in self?.handleCommandKey($0) ?? false }
view = content view = content
content.wantsLayer = true
content.layer?.backgroundColor = NotesAppearance.background.cgColor
let close = iconButton("circle.fill", label: "Close notes", action: #selector(closeWindow)) let close = iconButton("circle.fill", label: "Close notes", action: #selector(closeWindow))
close.contentTintColor = NotesAppearance.accent close.contentTintColor = SunsetPalette.accent
close.isBordered = false close.isBordered = false
close.image = close.image?.withSymbolConfiguration(.init(paletteColors: [NotesAppearance.accent])) close.image = close.image?.withSymbolConfiguration(.init(paletteColors: [SunsetPalette.accent]))
let lights = NSStackView(views: [close]) let lights = NSStackView(views: [close])
lights.spacing = 8 lights.spacing = 8
for _ in 0..<2 { for _ in 0..<2 {
let dot = NSView() let dot = NSView()
dot.wantsLayer = true dot.wantsLayer = true
dot.layer?.backgroundColor = NSColor(white: 0.15, alpha: 1).cgColor dot.layer?.backgroundColor = SunsetPalette.secondaryText.withAlphaComponent(0.18).cgColor
dot.layer?.cornerRadius = 6 dot.layer?.cornerRadius = 6
dot.setAccessibilityElement(false) dot.setAccessibilityElement(false)
NSLayoutConstraint.activate([dot.widthAnchor.constraint(equalToConstant: 12), dot.heightAnchor.constraint(equalToConstant: 12)]) NSLayoutConstraint.activate([dot.widthAnchor.constraint(equalToConstant: 12), dot.heightAnchor.constraint(equalToConstant: 12)])
@@ -77,7 +71,7 @@ final class NotesController: NSViewController, NSTextViewDelegate {
let controls = NSStackView(views: [actionsButton, browseButton, create]) let controls = NSStackView(views: [actionsButton, browseButton, create])
controls.spacing = 2 controls.spacing = 2
noteTitle.font = .systemFont(ofSize: 13) noteTitle.font = .systemFont(ofSize: 13)
noteTitle.textColor = NotesAppearance.muted noteTitle.textColor = SunsetPalette.secondaryText
noteTitle.alignment = .center noteTitle.alignment = .center
noteTitle.lineBreakMode = .byTruncatingTail noteTitle.lineBreakMode = .byTruncatingTail
noteTitle.setAccessibilityLabel("Note title") noteTitle.setAccessibilityLabel("Note title")
@@ -122,9 +116,9 @@ final class NotesController: NSViewController, NSTextViewDelegate {
editor.importsGraphics = true editor.importsGraphics = true
editor.allowsUndo = true editor.allowsUndo = true
editor.drawsBackground = false editor.drawsBackground = false
editor.textColor = NotesAppearance.text editor.textColor = SunsetPalette.text
editor.insertionPointColor = NotesAppearance.accent editor.insertionPointColor = SunsetPalette.accent
editor.selectedTextAttributes = [.backgroundColor: NotesAppearance.accent.withAlphaComponent(0.25)] editor.selectedTextAttributes = [.backgroundColor: SunsetPalette.selectionFill]
editor.font = NotesAppearance.font editor.font = NotesAppearance.font
editor.defaultParagraphStyle = NotesAppearance.paragraph editor.defaultParagraphStyle = NotesAppearance.paragraph
editor.isAutomaticQuoteSubstitutionEnabled = false editor.isAutomaticQuoteSubstitutionEnabled = false
@@ -143,7 +137,7 @@ final class NotesController: NSViewController, NSTextViewDelegate {
scroll.drawsBackground = false scroll.drawsBackground = false
scroll.scrollerStyle = .overlay scroll.scrollerStyle = .overlay
status.font = .systemFont(ofSize: 12) status.font = .systemFont(ofSize: 12)
status.textColor = NotesAppearance.muted status.textColor = SunsetPalette.secondaryText
status.setAccessibilityLabel("Notes save status") status.setAccessibilityLabel("Notes save status")
status.lineBreakMode = .byTruncatingTail status.lineBreakMode = .byTruncatingTail
status.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) status.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
@@ -196,7 +190,7 @@ final class NotesController: NSViewController, NSTextViewDelegate {
button.imagePosition = .imageOnly button.imagePosition = .imageOnly
button.bezelStyle = .accessoryBarAction button.bezelStyle = .accessoryBarAction
button.showsBorderOnlyWhileMouseInside = true button.showsBorderOnlyWhileMouseInside = true
button.contentTintColor = NotesAppearance.muted button.contentTintColor = SunsetPalette.secondaryText
button.toolTip = label button.toolTip = label
button.setAccessibilityLabel(label) button.setAccessibilityLabel(label)
NSLayoutConstraint.activate([button.widthAnchor.constraint(equalToConstant: 28), button.heightAnchor.constraint(equalToConstant: 28)]) NSLayoutConstraint.activate([button.widthAnchor.constraint(equalToConstant: 28), button.heightAnchor.constraint(equalToConstant: 28)])
@@ -528,7 +522,7 @@ enum MarkdownHighlighting {
(try! NSRegularExpression(pattern: "(?m)^#{1,6} .+$"), [.font: NSFont.systemFont(ofSize: 23, weight: .semibold)]), (try! NSRegularExpression(pattern: "(?m)^#{1,6} .+$"), [.font: NSFont.systemFont(ofSize: 23, weight: .semibold)]),
(try! NSRegularExpression(pattern: "\\*\\*[^\\n]+?\\*\\*|__[^\\n]+?__"), [.font: NSFont.boldSystemFont(ofSize: 17)]), (try! NSRegularExpression(pattern: "\\*\\*[^\\n]+?\\*\\*|__[^\\n]+?__"), [.font: NSFont.boldSystemFont(ofSize: 17)]),
(try! NSRegularExpression(pattern: "(?<!\\*)\\*(?!\\*)[^*\\n]+\\*(?!\\*)|(?<!_)_(?!_)[^_\\n]+_(?!_)"), [.font: NSFontManager.shared.convert(NotesAppearance.font, toHaveTrait: .italicFontMask)]), (try! NSRegularExpression(pattern: "(?<!\\*)\\*(?!\\*)[^*\\n]+\\*(?!\\*)|(?<!_)_(?!_)[^_\\n]+_(?!_)"), [.font: NSFontManager.shared.convert(NotesAppearance.font, toHaveTrait: .italicFontMask)]),
(try! NSRegularExpression(pattern: "!?\\[[^\\]\\n]+\\]\\([^\\n)]+\\)|(?m)^\\s*(?:[-+*]|[0-9]+\\.)\\s"), [.foregroundColor: NotesAppearance.accent]), (try! NSRegularExpression(pattern: "!?\\[[^\\]\\n]+\\]\\([^\\n)]+\\)|(?m)^\\s*(?:[-+*]|[0-9]+\\.)\\s"), [.foregroundColor: SunsetPalette.accent]),
(try! NSRegularExpression(pattern: "~~[^\\n]+?~~"), [.strikethroughStyle: NSUnderlineStyle.single.rawValue]), (try! NSRegularExpression(pattern: "~~[^\\n]+?~~"), [.strikethroughStyle: NSUnderlineStyle.single.rawValue]),
(try! NSRegularExpression(pattern: "`[^`\\n]+`"), [.font: NSFont.monospacedSystemFont(ofSize: 17, weight: .regular), .foregroundColor: NSColor.systemGreen]), (try! NSRegularExpression(pattern: "`[^`\\n]+`"), [.font: NSFont.monospacedSystemFont(ofSize: 17, weight: .regular), .foregroundColor: NSColor.systemGreen]),
] ]
@@ -575,7 +569,7 @@ enum MarkdownHighlighting {
if scale < 1 { image.size = NSSize(width: size.width * scale, height: size.height * scale) } if scale < 1 { image.size = NSSize(width: size.width * scale, height: size.height * scale) }
} }
storage.removeAttribute(.strikethroughStyle, range: range) storage.removeAttribute(.strikethroughStyle, range: range)
storage.addAttributes([.font: NotesAppearance.font, .foregroundColor: NotesAppearance.text, storage.addAttributes([.font: NotesAppearance.font, .foregroundColor: SunsetPalette.text,
.paragraphStyle: NotesAppearance.paragraph], range: range) .paragraphStyle: NotesAppearance.paragraph], range: range)
let blocks = codeBlocks(in: text) let blocks = codeBlocks(in: text)
for (expression, attributes) in rules { for (expression, attributes) in rules {
@@ -586,8 +580,8 @@ enum MarkdownHighlighting {
} }
for block in blocks { for block in blocks {
storage.addAttributes([.font: NSFont.monospacedSystemFont(ofSize: 17, weight: .regular), storage.addAttributes([.font: NSFont.monospacedSystemFont(ofSize: 17, weight: .regular),
.foregroundColor: NotesAppearance.muted], range: block.range) .foregroundColor: SunsetPalette.secondaryText], range: block.range)
storage.addAttribute(.foregroundColor, value: NotesAppearance.text, range: block.body) storage.addAttribute(.foregroundColor, value: SunsetPalette.text, range: block.body)
let code = (text as NSString).substring(with: block.body) let code = (text as NSString).substring(with: block.body)
guard let highlighted = highlighter.highlight(code, as: block.language), guard let highlighted = highlighter.highlight(code, as: block.language),
highlighted.string == code else { continue } highlighted.string == code else { continue }
@@ -598,7 +592,7 @@ enum MarkdownHighlighting {
} }
} }
} }
editor.typingAttributes = [.font: NotesAppearance.font, .foregroundColor: NotesAppearance.text, editor.typingAttributes = [.font: NotesAppearance.font, .foregroundColor: SunsetPalette.text,
.paragraphStyle: NotesAppearance.paragraph] .paragraphStyle: NotesAppearance.paragraph]
} }
} }
+2 -2
View File
@@ -18,7 +18,7 @@ private final class ListAttachment: NSTextAttachment {
super.init(data: nil, ofType: nil) super.init(data: nil, ofType: nil)
let cell = ListAttachmentCell(textCell: isTask ? "" : (source.length == 1 ? "•" : source.string)) let cell = ListAttachmentCell(textCell: isTask ? "" : (source.length == 1 ? "•" : source.string))
cell.attributedStringValue = NSAttributedString(string: cell.stringValue, cell.attributedStringValue = NSAttributedString(string: cell.stringValue,
attributes: [.font: NotesAppearance.font, .foregroundColor: NotesAppearance.accent]) attributes: [.font: NotesAppearance.font, .foregroundColor: SunsetPalette.accent])
cell.alignment = .right cell.alignment = .right
attachmentCell = cell attachmentCell = cell
bounds = NSRect(x: 0, y: -4, width: 22, height: 21) bounds = NSRect(x: 0, y: -4, width: 22, height: 21)
@@ -510,7 +510,7 @@ final class NotesTextView: NSTextView, @MainActor NSLayoutManagerDelegate {
while checkboxes.count < taskIndices.count { while checkboxes.count < taskIndices.count {
let checkbox = NSButton(checkboxWithTitle: "", target: self, action: #selector(toggleTask(_:))) let checkbox = NSButton(checkboxWithTitle: "", target: self, action: #selector(toggleTask(_:)))
checkbox.controlSize = .small checkbox.controlSize = .small
checkbox.contentTintColor = NotesAppearance.accent checkbox.contentTintColor = SunsetPalette.accent
checkbox.sizeToFit() checkbox.sizeToFit()
checkboxes.append(checkbox) checkboxes.append(checkbox)
addSubview(checkbox) addSubview(checkbox)
@@ -1301,6 +1301,9 @@ func notesFormattingPreservesAttachmentsMarkdownSelectionAndUndo() throws {
button.performClick(nil) button.performClick(nil)
} }
window.makeFirstResponder(editor) window.makeFirstResponder(editor)
let undo = try #require(editor.undoManager)
undo.groupsByEvent = false
undo.beginUndoGrouping()
editor.insertText("🦊 first\nsecond\nuntouched", replacementRange: editor.selectedRange()) editor.insertText("🦊 first\nsecond\nuntouched", replacementRange: editor.selectedRange())
let selection = NSRange(location: 0, length: "🦊 first\nsecond\n".utf16.count) let selection = NSRange(location: 0, length: "🦊 first\nsecond\n".utf16.count)
editor.setSelectedRange(selection) editor.setSelectedRange(selection)
@@ -1335,11 +1338,11 @@ func notesFormattingPreservesAttachmentsMarkdownSelectionAndUndo() throws {
editor.setSelectedRange(NSRange(location: 0, length: editor.string.utf16.count)) editor.setSelectedRange(NSRange(location: 0, length: editor.string.utf16.count))
let before = editor.markdownString let before = editor.markdownString
editor.breakUndoCoalescing() editor.breakUndoCoalescing()
undo.endUndoGrouping()
editor.undoManager?.removeAllActions() editor.undoManager?.removeAllActions()
try click("Quote") try click("Quote")
try #require(editor.markdownString == "> 🦊 first\n> second\n> untouched\u{fffc}") try #require(editor.markdownString == "> 🦊 first\n> second\n> untouched\u{fffc}")
#expect(editor.markdownContents.attribute(.attachment, at: editor.markdownContents.length - 1, effectiveRange: nil) is NSTextAttachment) #expect(editor.markdownContents.attribute(.attachment, at: editor.markdownContents.length - 1, effectiveRange: nil) is NSTextAttachment)
editor.undoManager?.endUndoGrouping()
editor.undoManager?.undo() editor.undoManager?.undo()
try #require(editor.markdownString == before) try #require(editor.markdownString == before)
#expect(editor.markdownContents.attribute(.attachment, at: editor.markdownContents.length - 1, effectiveRange: nil) is NSTextAttachment) #expect(editor.markdownContents.attribute(.attachment, at: editor.markdownContents.length - 1, effectiveRange: nil) is NSTextAttachment)