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 + G` shows a filtered result back in the full clipboard history
- `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
- 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

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.
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.
4. Use arrow keys to move selection while the search field is focused.
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.
6. Press `Esc` once with a non-empty search field and confirm search clears.
7. Press `Esc` again and confirm the panel closes.
8. Reopen the panel, change sort segments, and confirm each segment updates results.
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.
10. Return to Clipboard, select a card, use its Collect button to choose Client Work, and confirm the Client Work chip count increases.
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.
12. Right-click the Client Work chip, choose Edit Collection..., rename it, change its color, and confirm the chip and assigned card headers update.
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.
14. Double-click an item and confirm it attempts to paste or falls back to copy without creating a duplicate history entry.
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.
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.
17. Resize or test on a narrow display and confirm the bottom shelf switches to compact cards that still show two recent clips cleanly.
4. Clear the search field, press `Space`, and confirm the selected previewable clip opens in Quick Look instead of inserting a blank query.
5. Use arrow keys to move selection while the search field is focused.
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` once with a non-empty search field and confirm search clears.
8. Press `Esc` again and confirm the panel closes.
9. Reopen the panel, change sort segments, and confirm each segment updates results.
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. Return to Clipboard, select a card, use its Collect button to choose Client Work, and confirm the Client Work chip count increases.
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 the Client Work chip, choose Edit Collection..., rename it, change its color, and confirm the chip and assigned card headers update.
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. Double-click an item and confirm it attempts to paste or falls back to copy without creating a duplicate history entry.
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. 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

View File

@@ -339,6 +339,16 @@ final class ClipboardPanelController: NSObject, NSWindowDelegate, QLPreviewPanel
removeKeyMonitor()
keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
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),
let index = Self.quickPasteIndex(forKeyCode: event.keyCode, modifiers: event.modifierFlags) {
self.viewModel.pasteItem(at: index)
@@ -469,6 +479,11 @@ final class ClipboardPanelController: NSObject, NSWindowDelegate, QLPreviewPanel
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? {
let relevantModifiers = modifiers.intersection(.deviceIndependentFlagsMask)
guard relevantModifiers == .command else { return nil }

View File

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

View File

@@ -164,6 +164,14 @@ final class ClipboardPanelControllerTests: XCTestCase {
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() {
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 8, modifiers: .command), .copy)
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 5, modifiers: .command), .showInClipboard)