style: match Raycast notes window
This commit is contained in:
@@ -2,6 +2,21 @@ import AppKit
|
||||
import ConjureKit
|
||||
import Highlighter
|
||||
|
||||
@MainActor
|
||||
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 paragraph: NSParagraphStyle = {
|
||||
let style = NSMutableParagraphStyle()
|
||||
style.lineSpacing = 5
|
||||
style.paragraphSpacing = 8
|
||||
return style
|
||||
}()
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class NotesController: NSViewController, NSTextViewDelegate {
|
||||
private let browseButton = NSButton(title: "Browse Notes", target: nil, action: nil)
|
||||
@@ -9,6 +24,8 @@ final class NotesController: NSViewController, NSTextViewDelegate {
|
||||
private let actions = ApplicationActionsController()
|
||||
private let editor = NotesTextView()
|
||||
private let status = NSTextField(labelWithString: "")
|
||||
private let noteTitle = NSTextField(labelWithString: "Untitled Note")
|
||||
private let formatting = NSStackView()
|
||||
private let defaults: UserDefaults
|
||||
private var store: NotesStore?
|
||||
private var selectedID: UUID?
|
||||
@@ -32,32 +49,84 @@ final class NotesController: NSViewController, NSTextViewDelegate {
|
||||
let content = NotesView()
|
||||
content.commandShortcut = { [weak self] in self?.handleCommandKey($0) ?? false }
|
||||
view = content
|
||||
content.wantsLayer = true
|
||||
content.layer?.backgroundColor = NotesAppearance.background.cgColor
|
||||
let close = iconButton("circle.fill", label: "Close notes", action: #selector(closeWindow))
|
||||
close.contentTintColor = NotesAppearance.accent
|
||||
close.isBordered = false
|
||||
close.image = close.image?.withSymbolConfiguration(.init(paletteColors: [NotesAppearance.accent]))
|
||||
let lights = NSStackView(views: [close])
|
||||
lights.spacing = 8
|
||||
for _ in 0..<2 {
|
||||
let dot = NSView()
|
||||
dot.wantsLayer = true
|
||||
dot.layer?.backgroundColor = NSColor(white: 0.15, alpha: 1).cgColor
|
||||
dot.layer?.cornerRadius = 6
|
||||
dot.setAccessibilityElement(false)
|
||||
NSLayoutConstraint.activate([dot.widthAnchor.constraint(equalToConstant: 12), dot.heightAnchor.constraint(equalToConstant: 12)])
|
||||
lights.addArrangedSubview(dot)
|
||||
}
|
||||
browseButton.target = self
|
||||
browseButton.action = #selector(browseNotes)
|
||||
browseButton.setAccessibilityLabel("Browse notes")
|
||||
browseButton.toolTip = "Browse notes (Command-P)"
|
||||
configureIcon(browseButton, symbol: "note.text", label: "Browse notes (⌘P)")
|
||||
browseButton.keyEquivalent = "p"
|
||||
actionsButton.target = self
|
||||
actionsButton.action = #selector(showActions)
|
||||
actionsButton.setAccessibilityLabel("Notes actions")
|
||||
actionsButton.toolTip = "Notes actions (Command-K)"
|
||||
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
|
||||
noteTitle.font = .systemFont(ofSize: 13)
|
||||
noteTitle.textColor = NotesAppearance.muted
|
||||
noteTitle.alignment = .center
|
||||
noteTitle.lineBreakMode = .byTruncatingTail
|
||||
noteTitle.setAccessibilityLabel("Note title")
|
||||
let header = NSView()
|
||||
for control in [lights, noteTitle, controls] {
|
||||
control.translatesAutoresizingMaskIntoConstraints = false
|
||||
header.addSubview(control)
|
||||
control.centerYAnchor.constraint(equalTo: header.centerYAnchor).isActive = true
|
||||
}
|
||||
NSLayoutConstraint.activate([
|
||||
lights.leadingAnchor.constraint(equalTo: header.leadingAnchor, constant: 10),
|
||||
controls.trailingAnchor.constraint(equalTo: header.trailingAnchor, constant: -8),
|
||||
noteTitle.centerXAnchor.constraint(equalTo: header.centerXAnchor),
|
||||
noteTitle.leadingAnchor.constraint(greaterThanOrEqualTo: lights.trailingAnchor, constant: 16),
|
||||
noteTitle.trailingAnchor.constraint(lessThanOrEqualTo: controls.leadingAnchor, constant: -16),
|
||||
])
|
||||
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
|
||||
let tools = [
|
||||
("textformat.size", "Heading"), ("bold", "Bold"), ("italic", "Italic"),
|
||||
("strikethrough", "Strikethrough"), ("chevron.left.forwardslash.chevron.right", "Inline code"),
|
||||
("link", "Link"), ("curlybraces.square", "Code block"), ("text.quote", "Quote"),
|
||||
("list.number", "Ordered list"), ("list.bullet", "Bullet list"), ("checklist", "Checklist"),
|
||||
]
|
||||
for (index, tool) in tools.enumerated() {
|
||||
let button = iconButton(tool.0, label: tool.1, action: #selector(formatNote(_:)))
|
||||
button.tag = index
|
||||
formatting.addArrangedSubview(button)
|
||||
}
|
||||
formatting.spacing = 8
|
||||
let toggle = iconButton("xmark.circle.fill", label: "Hide formatting", action: #selector(toggleFormatting(_:)))
|
||||
let separator = NSBox()
|
||||
separator.boxType = .separator
|
||||
let footer = NSStackView(views: [formatting, NSView(), separator, toggle])
|
||||
footer.alignment = .centerY
|
||||
footer.spacing = 12
|
||||
|
||||
footer.spacing = 8
|
||||
separator.heightAnchor.constraint(equalToConstant: 22).isActive = true
|
||||
editor.delegate = self
|
||||
editor.isRichText = true
|
||||
editor.importsGraphics = true
|
||||
editor.allowsUndo = true
|
||||
editor.drawsBackground = false
|
||||
editor.textColor = SunsetPalette.text
|
||||
editor.insertionPointColor = SunsetPalette.accent
|
||||
editor.font = .systemFont(ofSize: 14)
|
||||
editor.textColor = NotesAppearance.text
|
||||
editor.insertionPointColor = NotesAppearance.accent
|
||||
editor.selectedTextAttributes = [.backgroundColor: NotesAppearance.accent.withAlphaComponent(0.25)]
|
||||
editor.font = NotesAppearance.font
|
||||
editor.defaultParagraphStyle = NotesAppearance.paragraph
|
||||
editor.isAutomaticQuoteSubstitutionEnabled = false
|
||||
editor.isAutomaticDashSubstitutionEnabled = false
|
||||
editor.isAutomaticTextReplacementEnabled = false
|
||||
@@ -65,39 +134,49 @@ final class NotesController: NSViewController, NSTextViewDelegate {
|
||||
editor.isHorizontallyResizable = false
|
||||
editor.autoresizingMask = [.width]
|
||||
editor.textContainer?.widthTracksTextView = true
|
||||
editor.textContainerInset = NSSize(width: 10, height: 10)
|
||||
editor.textContainerInset = NSSize(width: 22, height: 8)
|
||||
editor.setAccessibilityLabel("Note editor")
|
||||
let scroll = NSScrollView()
|
||||
scroll.documentView = editor
|
||||
scroll.hasVerticalScroller = true
|
||||
scroll.autohidesScrollers = true
|
||||
scroll.drawsBackground = false
|
||||
scroll.wantsLayer = true
|
||||
scroll.layer?.cornerRadius = 8
|
||||
scroll.layer?.borderWidth = 0.5
|
||||
scroll.layer?.borderColor = SunsetPalette.secondaryText.withAlphaComponent(0.22).cgColor
|
||||
scroll.scrollerStyle = .overlay
|
||||
status.font = .systemFont(ofSize: 12)
|
||||
status.textColor = SunsetPalette.secondaryText
|
||||
status.textColor = NotesAppearance.muted
|
||||
status.setAccessibilityLabel("Notes save status")
|
||||
status.lineBreakMode = .byTruncatingTail
|
||||
status.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
for control in [scroll, footer] {
|
||||
status.isHidden = true
|
||||
let body = NSStackView(views: [scroll, status, footer])
|
||||
body.orientation = .vertical
|
||||
body.alignment = .leading
|
||||
body.spacing = 4
|
||||
for control in [header, body] {
|
||||
control.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(control)
|
||||
}
|
||||
NSLayoutConstraint.activate([
|
||||
scroll.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
scroll.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
|
||||
scroll.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
|
||||
scroll.bottomAnchor.constraint(equalTo: footer.topAnchor, constant: -8),
|
||||
footer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
|
||||
footer.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8),
|
||||
footer.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8),
|
||||
footer.heightAnchor.constraint(equalToConstant: 36),
|
||||
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),
|
||||
body.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
body.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
body.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -4),
|
||||
scroll.widthAnchor.constraint(equalTo: body.widthAnchor),
|
||||
footer.leadingAnchor.constraint(equalTo: body.leadingAnchor, constant: 24),
|
||||
footer.trailingAnchor.constraint(equalTo: body.trailingAnchor, constant: -6),
|
||||
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),
|
||||
])
|
||||
guard let store else {
|
||||
editor.isEditable = false
|
||||
for control in [browseButton, actionsButton] { control.isEnabled = false }
|
||||
for control in [browseButton, actionsButton, create] { control.isEnabled = false }
|
||||
formatting.subviews.compactMap { $0 as? NSButton }.forEach { $0.isEnabled = false }
|
||||
showError(loadError!)
|
||||
return
|
||||
}
|
||||
@@ -111,6 +190,94 @@ final class NotesController: NSViewController, NSTextViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private func configureIcon(_ button: NSButton, symbol: String, label: String) {
|
||||
button.image = NSImage(systemSymbolName: symbol, accessibilityDescription: nil)?
|
||||
.withSymbolConfiguration(.init(pointSize: 16, weight: .regular))
|
||||
button.imagePosition = .imageOnly
|
||||
button.bezelStyle = .accessoryBarAction
|
||||
button.showsBorderOnlyWhileMouseInside = true
|
||||
button.contentTintColor = NotesAppearance.muted
|
||||
button.toolTip = label
|
||||
button.setAccessibilityLabel(label)
|
||||
NSLayoutConstraint.activate([button.widthAnchor.constraint(equalToConstant: 28), button.heightAnchor.constraint(equalToConstant: 28)])
|
||||
}
|
||||
|
||||
private func iconButton(_ symbol: String, label: String, action: Selector) -> NSButton {
|
||||
let button = NSButton(title: "", target: self, action: action)
|
||||
configureIcon(button, symbol: symbol, label: label)
|
||||
return button
|
||||
}
|
||||
|
||||
@objc private func closeWindow() {
|
||||
guard save() else { return }
|
||||
view.window?.performClose(nil)
|
||||
}
|
||||
|
||||
@objc private func toggleFormatting(_ sender: NSButton) {
|
||||
formatting.isHidden.toggle()
|
||||
sender.image = NSImage(systemSymbolName: formatting.isHidden ? "textformat" : "xmark.circle.fill", accessibilityDescription: nil)
|
||||
sender.toolTip = formatting.isHidden ? "Show formatting" : "Hide formatting"
|
||||
sender.setAccessibilityLabel(sender.toolTip)
|
||||
focusEditor()
|
||||
}
|
||||
|
||||
@objc private func formatNote(_ sender: NSButton) {
|
||||
guard editor.isEditable else { return }
|
||||
let contents = editor.markdownContents
|
||||
let source = contents.string as NSString
|
||||
var selection = editor.markdownInsertionRange(for: editor.selectedRange())
|
||||
if sender.tag == 6 {
|
||||
selection = source.lineRange(for: selection)
|
||||
if selection.length > 0, source.character(at: NSMaxRange(selection) - 1) == 10 { selection.length -= 1 }
|
||||
}
|
||||
let wrappers = [1: ("**", "**"), 2: ("*", "*"), 3: ("~~", "~~"), 4: ("`", "`"),
|
||||
5: ("[", "](url)"), 6: ("```\n", "\n```")]
|
||||
if let (open, close) = wrappers[sender.tag] {
|
||||
let before = selection.location - open.utf16.count
|
||||
let after = NSMaxRange(selection)
|
||||
if before >= 0, after + close.utf16.count <= source.length,
|
||||
source.substring(with: NSRange(location: before, length: open.utf16.count)) == open,
|
||||
source.substring(with: NSRange(location: after, length: close.utf16.count)) == close {
|
||||
let body = contents.attributedSubstring(from: selection)
|
||||
selection = NSRange(location: before, length: selection.length + open.utf16.count + close.utf16.count)
|
||||
replaceMarkdown(body, range: selection, selection: NSRange(location: before, length: body.length))
|
||||
} else {
|
||||
let body = NSMutableAttributedString(string: open, attributes: editor.typingAttributes)
|
||||
body.append(contents.attributedSubstring(from: selection))
|
||||
body.append(NSAttributedString(string: close, attributes: editor.typingAttributes))
|
||||
replaceMarkdown(body, range: selection,
|
||||
selection: NSRange(location: selection.location + open.utf16.count, length: selection.length))
|
||||
}
|
||||
} else {
|
||||
let prefixes = [0: "# ", 7: "> ", 8: "1. ", 9: "- ", 10: "- [ ] "]
|
||||
guard let prefix = prefixes[sender.tag] else { return }
|
||||
let length = selection.length > 0 && source.character(at: NSMaxRange(selection) - 1) == 10
|
||||
? selection.length - 1 : selection.length
|
||||
let lines = source.lineRange(for: NSRange(location: selection.location, length: length))
|
||||
let body = NSMutableAttributedString(attributedString: contents.attributedSubstring(from: lines))
|
||||
let rows = body.string.components(separatedBy: "\n")
|
||||
var offset = body.length, firstDelta = 0
|
||||
for (index, line) in rows.enumerated().reversed() {
|
||||
offset -= line.utf16.count + (index < rows.count - 1 ? 1 : 0)
|
||||
if index == rows.count - 1, index > 0, line.isEmpty { continue }
|
||||
let indent = String(line.prefix { $0 == " " || $0 == "\t" })
|
||||
let content = String(line.dropFirst(indent.count))
|
||||
let item = Self.listMatch(in: content as NSString)
|
||||
let kind = item.map { $0.range(at: 6).location != NSNotFound ? 10 : $0.range(at: 3).location != NSNotFound ? 8 : 9 }
|
||||
let removing = kind == sender.tag || (item == nil && content.hasPrefix(prefix))
|
||||
let oldPrefix = item?.range.length ?? (removing ? prefix.utf16.count : 0)
|
||||
let newPrefix = removing ? "" : sender.tag == 8 ? "\(index + 1). " : prefix
|
||||
body.replaceCharacters(in: NSRange(location: offset + indent.utf16.count, length: oldPrefix),
|
||||
with: NSAttributedString(string: newPrefix, attributes: editor.typingAttributes))
|
||||
if index == 0 { firstDelta = newPrefix.utf16.count - oldPrefix }
|
||||
}
|
||||
let start = max(lines.location, selection.location + firstDelta)
|
||||
let end = max(start, NSMaxRange(selection) + body.length - lines.length)
|
||||
replaceMarkdown(body, range: lines, selection: NSRange(location: start, length: end - start))
|
||||
}
|
||||
focusEditor()
|
||||
}
|
||||
|
||||
func focusEditor() { view.window?.makeFirstResponder(editor) }
|
||||
|
||||
func textDidChange(_ notification: Notification) {
|
||||
@@ -164,6 +331,10 @@ final class NotesController: NSViewController, NSTextViewDelegate {
|
||||
}
|
||||
|
||||
private func replaceListMarkdown(_ replacement: String, range: NSRange, selection: NSRange) {
|
||||
replaceMarkdown(NSAttributedString(string: replacement, attributes: editor.typingAttributes), range: range, selection: selection)
|
||||
}
|
||||
|
||||
private func replaceMarkdown(_ replacement: NSAttributedString, range: NSRange, selection: NSRange) {
|
||||
editor.undoManager?.beginUndoGrouping()
|
||||
// Native text undo restores its diff boundary, not the user's selection.
|
||||
setSelectionWithUndo(editor.selectedRange())
|
||||
@@ -246,8 +417,8 @@ final class NotesController: NSViewController, NSTextViewDelegate {
|
||||
do {
|
||||
try store.update(id: selectedID, contents: editor.markdownContents)
|
||||
isDirty = false
|
||||
status.stringValue = "Saved locally · Markdown · Paste or drop images"
|
||||
status.textColor = SunsetPalette.secondaryText
|
||||
noteTitle.stringValue = store.notes.first { $0.id == selectedID }?.title ?? "Untitled Note"
|
||||
status.isHidden = true
|
||||
return true
|
||||
} catch {
|
||||
showError(error)
|
||||
@@ -312,12 +483,13 @@ final class NotesController: NSViewController, NSTextViewDelegate {
|
||||
editor.setSelectedRange(NSRange(location: editor.string.utf16.count, length: 0))
|
||||
MarkdownHighlighting.apply(to: editor)
|
||||
editor.refreshTasks()
|
||||
status.stringValue = "Saved locally · Markdown · Paste or drop images"
|
||||
status.textColor = SunsetPalette.secondaryText
|
||||
noteTitle.stringValue = note.title
|
||||
status.isHidden = true
|
||||
}
|
||||
|
||||
|
||||
private func showError(_ error: Error) {
|
||||
status.isHidden = false
|
||||
status.stringValue = "Couldn’t save or load notes: \(error.localizedDescription)"
|
||||
status.toolTip = status.stringValue
|
||||
status.textColor = .systemRed
|
||||
@@ -353,16 +525,17 @@ private final class NotesView: NSView {
|
||||
enum MarkdownHighlighting {
|
||||
// ponytail: whole-note highlighting; incremental parsing if large notes make typing slow.
|
||||
private static let rules: [(NSRegularExpression, [NSAttributedString.Key: Any])] = [
|
||||
(try! NSRegularExpression(pattern: "(?m)^#{1,6} .+$"), [.font: NSFont.systemFont(ofSize: 17, weight: .semibold), .foregroundColor: SunsetPalette.accent]),
|
||||
(try! NSRegularExpression(pattern: "\\*\\*[^\\n]+?\\*\\*|__[^\\n]+?__"), [.font: NSFont.boldSystemFont(ofSize: 14)]),
|
||||
(try! NSRegularExpression(pattern: "(?<!\\*)\\*(?!\\*)[^*\\n]+\\*(?!\\*)|(?<!_)_(?!_)[^_\\n]+_(?!_)"), [.font: NSFontManager.shared.convert(.systemFont(ofSize: 14), toHaveTrait: .italicFontMask)]),
|
||||
(try! NSRegularExpression(pattern: "!?\\[[^\\]\\n]+\\]\\([^\\n)]+\\)|(?m)^\\s*(?:[-+*]|[0-9]+\\.)\\s"), [.foregroundColor: SunsetPalette.accent]),
|
||||
(try! NSRegularExpression(pattern: "`[^`\\n]+`"), [.font: NSFont.monospacedSystemFont(ofSize: 14, weight: .regular), .foregroundColor: NSColor.systemGreen]),
|
||||
(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: NSFontManager.shared.convert(NotesAppearance.font, toHaveTrait: .italicFontMask)]),
|
||||
(try! NSRegularExpression(pattern: "!?\\[[^\\]\\n]+\\]\\([^\\n)]+\\)|(?m)^\\s*(?:[-+*]|[0-9]+\\.)\\s"), [.foregroundColor: NotesAppearance.accent]),
|
||||
(try! NSRegularExpression(pattern: "~~[^\\n]+?~~"), [.strikethroughStyle: NSUnderlineStyle.single.rawValue]),
|
||||
(try! NSRegularExpression(pattern: "`[^`\\n]+`"), [.font: NSFont.monospacedSystemFont(ofSize: 17, weight: .regular), .foregroundColor: NSColor.systemGreen]),
|
||||
]
|
||||
|
||||
private static let highlighter: Highlighter = {
|
||||
guard let value = Highlighter() else { preconditionFailure("Missing bundled syntax highlighter") }
|
||||
value.setTheme("atom-one-dark", withFont: "Menlo-Regular", ofSize: 14)
|
||||
value.setTheme("atom-one-dark", withFont: "Menlo-Regular", ofSize: 17)
|
||||
return value
|
||||
}()
|
||||
private static let openingFence = try! NSRegularExpression(pattern: "(?m)^ {0,3}(`{3,}|~{3,})([^\\r\\n]*)\\r?\\n")
|
||||
@@ -401,7 +574,9 @@ enum MarkdownHighlighting {
|
||||
let scale = min(1, 460 / max(size.width, 1), 240 / max(size.height, 1))
|
||||
if scale < 1 { image.size = NSSize(width: size.width * scale, height: size.height * scale) }
|
||||
}
|
||||
storage.addAttributes([.font: NSFont.systemFont(ofSize: 14), .foregroundColor: SunsetPalette.text], range: range)
|
||||
storage.removeAttribute(.strikethroughStyle, range: range)
|
||||
storage.addAttributes([.font: NotesAppearance.font, .foregroundColor: NotesAppearance.text,
|
||||
.paragraphStyle: NotesAppearance.paragraph], range: range)
|
||||
let blocks = codeBlocks(in: text)
|
||||
for (expression, attributes) in rules {
|
||||
for match in expression.matches(in: text, range: range) {
|
||||
@@ -410,9 +585,9 @@ enum MarkdownHighlighting {
|
||||
}
|
||||
}
|
||||
for block in blocks {
|
||||
storage.addAttributes([.font: NSFont.monospacedSystemFont(ofSize: 14, weight: .regular),
|
||||
.foregroundColor: SunsetPalette.secondaryText], range: block.range)
|
||||
storage.addAttribute(.foregroundColor, value: SunsetPalette.text, range: block.body)
|
||||
storage.addAttributes([.font: NSFont.monospacedSystemFont(ofSize: 17, weight: .regular),
|
||||
.foregroundColor: NotesAppearance.muted], range: block.range)
|
||||
storage.addAttribute(.foregroundColor, value: NotesAppearance.text, range: block.body)
|
||||
let code = (text as NSString).substring(with: block.body)
|
||||
guard let highlighted = highlighter.highlight(code, as: block.language),
|
||||
highlighted.string == code else { continue }
|
||||
@@ -423,6 +598,7 @@ enum MarkdownHighlighting {
|
||||
}
|
||||
}
|
||||
}
|
||||
editor.typingAttributes = [.font: NSFont.systemFont(ofSize: 14), .foregroundColor: SunsetPalette.text]
|
||||
editor.typingAttributes = [.font: NotesAppearance.font, .foregroundColor: NotesAppearance.text,
|
||||
.paragraphStyle: NotesAppearance.paragraph]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user