WIP: preview from empty search with space

This commit is contained in:
Akshay Kolli
2026-06-30 04:20:46 -07:00
parent bc7be9ea59
commit 1ceb3c5a4e
5 changed files with 43 additions and 14 deletions

View File

@@ -14,6 +14,7 @@ The project is intentionally dependency-light: Swift Package Manager, AppKit, Ca
- `Command + 1` through `Command + 9` paste the numbered visible card; add `Shift` to paste that card as plain text - `Command + 1` through `Command + 9` paste the numbered visible card; add `Shift` to paste that card as plain text
- `Command + G` shows a filtered result back in the full clipboard history - `Command + G` shows a filtered result back in the full clipboard history
- `Shift + Command + N` creates a new collection - `Shift + Command + N` creates a new collection
- `Space` previews the selected card when the focused search field is empty
- Clipboard history for text, URLs with local preview thumbnails when available, images, audio, RTF/HTML rich text, PDFs, and file references - Clipboard history for text, URLs with local preview thumbnails when available, images, audio, RTF/HTML rich text, PDFs, and file references
- SQLite persistence with bounded history, pinned-item retention, and encrypted app-managed payloads - SQLite persistence with bounded history, pinned-item retention, and encrypted app-managed payloads
- Search with independent token matching, structured filters such as `app:Safari`, `type:image,pdf`, `pinboard:"Client Work","Read Later"`, `date:2026-06-30`, result jump-back to full history, and optional local OCR for copied images - Search with independent token matching, structured filters such as `app:Safari`, `type:image,pdf`, `pinboard:"Client Work","Read Later"`, `date:2026-06-30`, result jump-back to full history, and optional local OCR for copied images

View File

@@ -34,20 +34,21 @@ Use this checklist before a release or after changes to panel, pasteboard, setti
1. Open the panel and confirm the search field is focused. 1. Open the panel and confirm the search field is focused.
2. Type a query and confirm results filter immediately. 2. Type a query and confirm results filter immediately.
3. Type a structured query such as `pinboard:"Client Work","Read Later" type:image,pdf` and confirm only clips from those collections and content types remain. 3. Type a structured query such as `pinboard:"Client Work","Read Later" type:image,pdf` and confirm only clips from those collections and content types remain.
4. Use arrow keys to move selection while the search field is focused. 4. Clear the search field, press `Space`, and confirm the selected previewable clip opens in Quick Look instead of inserting a blank query.
5. Right-click a filtered result and choose Show in Clipboard, or press `Command + G`, and confirm search clears while the same card stays selected in Most Recent. 5. Use arrow keys to move selection while the search field is focused.
6. Press `Esc` once with a non-empty search field and confirm search clears. 6. Right-click a filtered result and choose Show in Clipboard, or press `Command + G`, and confirm search clears while the same card stays selected in Most Recent.
7. Press `Esc` again and confirm the panel closes. 7. Press `Esc` once with a non-empty search field and confirm search clears.
8. Reopen the panel, change sort segments, and confirm each segment updates results. 8. Press `Esc` again and confirm the panel closes.
9. Press `Shift + Command + N` or the collection rail `+`, enter `Client Work`, choose a color, and confirm a Client Work chip appears with 0 clips and an empty collection view. 9. Reopen the panel, change sort segments, and confirm each segment updates results.
10. Return to Clipboard, select a card, use its Collect button to choose Client Work, and confirm the Client Work chip count increases. 10. Press `Shift + Command + N` or the collection rail `+`, enter `Client Work`, choose a color, and confirm a Client Work chip appears with 0 clips and an empty collection view.
11. Select the Client Work chip and confirm the rail filters to assigned items, cards use the Client Work name/color in their headers, and the collection/color/assignment persists after quitting and reopening ClipBored. 11. Return to Clipboard, select a card, use its Collect button to choose Client Work, and confirm the Client Work chip count increases.
12. Right-click the Client Work chip, choose Edit Collection..., rename it, change its color, and confirm the chip and assigned card headers update. 12. Select the Client Work chip and confirm the rail filters to assigned items, cards use the Client Work name/color in their headers, and the collection/color/assignment persists after quitting and reopening ClipBored.
13. Right-click a media, file, link, PDF, audio, or text card, choose Rename..., give it a title, and confirm the card title and search results use the custom title while paste/copy still uses the original payload. 13. Right-click the Client Work chip, choose Edit Collection..., rename it, change its color, and confirm the chip and assigned card headers update.
14. Double-click an item and confirm it attempts to paste or falls back to copy without creating a duplicate history entry. 14. Right-click a media, file, link, PDF, audio, or text card, choose Rename..., give it a title, and confirm the card title and search results use the custom title while paste/copy still uses the original payload.
15. Right-click a card, use Capture Rules to ignore its source app, copy from that app again, and confirm the new item is skipped. 15. Double-click an item and confirm it attempts to paste or falls back to copy without creating a duplicate history entry.
16. Drag an unassigned card onto the renamed collection chip and confirm the chip count increases and the card appears when that collection is selected. 16. Right-click a card, use Capture Rules to ignore its source app, copy from that app again, and confirm the new item is skipped.
17. Resize or test on a narrow display and confirm the bottom shelf switches to compact cards that still show two recent clips cleanly. 17. Drag an unassigned card onto the renamed collection chip and confirm the chip count increases and the card appears when that collection is selected.
18. Resize or test on a narrow display and confirm the bottom shelf switches to compact cards that still show two recent clips cleanly.
## Copy And Paste ## Copy And Paste

View File

@@ -339,6 +339,16 @@ final class ClipboardPanelController: NSObject, NSWindowDelegate, QLPreviewPanel
removeKeyMonitor() removeKeyMonitor()
keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
guard let self else { return event } guard let self else { return event }
if self.shouldHandlePanelKeyEvent(event, allowSearchFieldEditing: true),
self.panelView.isSearchFieldEditing,
Self.searchFieldPreviewShortcut(
forKeyCode: event.keyCode,
modifiers: event.modifierFlags,
searchText: self.panelView.searchTextForKeyboardShortcut
) {
self.previewSelected()
return nil
}
if self.shouldHandlePanelKeyEvent(event, allowSearchFieldEditing: true), if self.shouldHandlePanelKeyEvent(event, allowSearchFieldEditing: true),
let index = Self.quickPasteIndex(forKeyCode: event.keyCode, modifiers: event.modifierFlags) { let index = Self.quickPasteIndex(forKeyCode: event.keyCode, modifiers: event.modifierFlags) {
self.viewModel.pasteItem(at: index) self.viewModel.pasteItem(at: index)
@@ -469,6 +479,11 @@ final class ClipboardPanelController: NSObject, NSWindowDelegate, QLPreviewPanel
return collectionShortcuts[keyCode] return collectionShortcuts[keyCode]
} }
static func searchFieldPreviewShortcut(forKeyCode keyCode: UInt16, modifiers: NSEvent.ModifierFlags, searchText: String) -> Bool {
let relevantModifiers = modifiers.intersection(.deviceIndependentFlagsMask)
return keyCode == 49 && relevantModifiers.isEmpty && searchText.clipboardTrimmed.isEmpty
}
static func commandShortcutAction(forKeyCode keyCode: UInt16, modifiers: NSEvent.ModifierFlags) -> ClipboardPanelShortcutAction? { static func commandShortcutAction(forKeyCode keyCode: UInt16, modifiers: NSEvent.ModifierFlags) -> ClipboardPanelShortcutAction? {
let relevantModifiers = modifiers.intersection(.deviceIndependentFlagsMask) let relevantModifiers = modifiers.intersection(.deviceIndependentFlagsMask)
guard relevantModifiers == .command else { return nil } guard relevantModifiers == .command else { return nil }

View File

@@ -1102,6 +1102,10 @@ final class ClipboardPanelView: NSVisualEffectView, NSSearchFieldDelegate {
return false return false
} }
var searchTextForKeyboardShortcut: String {
searchField.stringValue
}
override func acceptsFirstMouse(for event: NSEvent?) -> Bool { override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
return true return true
} }

View File

@@ -164,6 +164,14 @@ final class ClipboardPanelControllerTests: XCTestCase {
XCTAssertNil(ClipboardPanelController.collectionShortcutMode(forKeyCode: 29, modifiers: .command)) XCTAssertNil(ClipboardPanelController.collectionShortcutMode(forKeyCode: 29, modifiers: .command))
} }
func testSearchFieldSpacePreviewShortcutRequiresEmptySearchAndNoModifiers() {
XCTAssertTrue(ClipboardPanelController.searchFieldPreviewShortcut(forKeyCode: 49, modifiers: [], searchText: ""))
XCTAssertTrue(ClipboardPanelController.searchFieldPreviewShortcut(forKeyCode: 49, modifiers: [], searchText: " "))
XCTAssertFalse(ClipboardPanelController.searchFieldPreviewShortcut(forKeyCode: 49, modifiers: [], searchText: "release note"))
XCTAssertFalse(ClipboardPanelController.searchFieldPreviewShortcut(forKeyCode: 49, modifiers: .command, searchText: ""))
XCTAssertFalse(ClipboardPanelController.searchFieldPreviewShortcut(forKeyCode: 36, modifiers: [], searchText: ""))
}
func testCommandActionShortcutsMapToSelectedClipActions() { func testCommandActionShortcutsMapToSelectedClipActions() {
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 8, modifiers: .command), .copy) XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 8, modifiers: .command), .copy)
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 5, modifiers: .command), .showInClipboard) XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 5, modifiers: .command), .showInClipboard)