diff --git a/README.md b/README.md index 9ee8fc7..ae7cf0c 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,8 @@ Action and selection popups paint a popup-sized background using the launcher wi 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. +Notes keeps its 36-point footer pill 8 points from the bottom and right window edges, matching the other views even at the minimum window size. + 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. ### Global shortcuts and launch at login diff --git a/Sources/Conjure/NotesController.swift b/Sources/Conjure/NotesController.swift index 0a2271f..5b65d16 100644 --- a/Sources/Conjure/NotesController.swift +++ b/Sources/Conjure/NotesController.swift @@ -163,7 +163,7 @@ final class NotesController: NSViewController, NSTextViewDelegate { body.topAnchor.constraint(equalTo: header.bottomAnchor), body.leadingAnchor.constraint(equalTo: view.leadingAnchor), body.trailingAnchor.constraint(equalTo: view.trailingAnchor), - body.bottomAnchor.constraint(equalTo: view.bottomAnchor), + body.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8), scroll.widthAnchor.constraint(equalTo: body.widthAnchor), footer.leadingAnchor.constraint(equalTo: body.leadingAnchor, constant: 24), footer.trailingAnchor.constraint(equalTo: body.trailingAnchor, constant: -8), diff --git a/TODO.md b/TODO.md index 86a8837..e14b3e6 100644 --- a/TODO.md +++ b/TODO.md @@ -16,4 +16,10 @@ - [x] add whole-note Markdown export actions to the Notes Actions menu: - Copy as Markdown: replace images with inline-code placeholders such as ``. - Export as Markdown…: save a single `.md` file without images; with images, create a folder containing the Markdown and image files, using relative links. - - exports leave the original note and RTFD autosaves unchanged. \ No newline at end of file + - exports leave the original note and RTFD autosaves unchanged. + - [ ] bug: pressing backspace at the start of a heading will move it back a line (as expected), but a subsequent return will cause the heading `#`s to appear on the first and the heading text to move down a line. + - [ ] improve: + - add cmd+shift+s for strikethrough and cmd+shift+u for underline + - don't hide the backticks surrounding a codeblock + - give inline codeblocks (e.g. `some code snippet`) an inline container and an orange text color matching the color scheme of the conjure application + - [ ] feature: vim motions \ No newline at end of file diff --git a/Tests/ConjureKitTests/NotesEditingTests.swift b/Tests/ConjureKitTests/NotesEditingTests.swift index a36dff1..a192382 100644 --- a/Tests/ConjureKitTests/NotesEditingTests.swift +++ b/Tests/ConjureKitTests/NotesEditingTests.swift @@ -3,6 +3,33 @@ import ConjureKit import Testing @testable import Conjure +@Test @MainActor +func notesActionPillKeepsWindowInsetsWhenResized() throws { + _ = NSApplication.shared + let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + let suite = "NotesLayout.\(UUID().uuidString)" + let defaults = try #require(UserDefaults(suiteName: suite)) + defer { + try? FileManager.default.removeItem(at: directory) + defaults.removePersistentDomain(forName: suite) + } + let controller = NotesController(defaults: defaults, directory: directory) + let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 750, height: 390), + styleMask: [.borderless], backing: .buffered, defer: false) + window.contentViewController = controller + defer { _ = controller.close(); window.orderOut(nil) } + func descendants(_ view: NSView) -> [NSView] { [view] + view.subviews.flatMap(descendants) } + let pill = try #require(descendants(controller.view).first { $0 is ActionPill }) + for size in [NSSize(width: 750, height: 390), NSSize(width: 570, height: 260)] { + window.setContentSize(size) + controller.view.layoutSubtreeIfNeeded() + let frame = pill.convert(pill.bounds, to: controller.view) + #expect(abs(frame.minY - controller.view.bounds.minY - 8) < 0.5) + #expect(abs(controller.view.bounds.maxX - frame.maxX - 8) < 0.5) + #expect(abs(frame.height - 36) < 0.5) + } +} + @Test @MainActor func notesSwitchWithoutLeakingEditsUndoOrAttachmentsAndRestoreSelection() throws { _ = NSApplication.shared