Make the row checkbox select items instead of ticking them off

The checkbox was marking items done, but the intent was to gather items
and act on them: tick several, then right-click to copy, copy as a list,
move, merge or delete them all at once. Done is still available from the
same menu, it is just no longer what the checkbox does.
This commit is contained in:
2026-10-09 17:54:54 +08:00
parent 9f0c0f8113
commit c7b4ed32f2
4 changed files with 96 additions and 44 deletions
+39 -27
View File
@@ -193,20 +193,20 @@ struct PanelView: View {
.padding(.vertical, 48)
}
/// Rows are checklists by default: a checkbox to tick an item off, with the
/// rest of the row copying on click. The row cannot be a Button because the
/// checkbox is one, and nested buttons do not receive clicks.
/// Rows carry a checkbox that *selects* the item, so a selection can be
/// copied or deleted from the context menu. The row cannot be a Button
/// because the checkbox is one, and nested buttons do not receive clicks.
private func row(for item: PromptItem, sectionName: String) -> some View {
HStack(alignment: .top, spacing: 8) {
Button {
store.toggleDone(item.id)
toggleSelection(item.id)
} label: {
Image(systemName: item.done ? "checkmark.circle.fill" : "circle")
Image(systemName: selection.contains(item.id) ? "checkmark.circle.fill" : "circle")
.font(.system(size: 15))
.foregroundStyle(item.done ? Color.accentColor : Color.secondary.opacity(0.55))
.foregroundStyle(selection.contains(item.id) ? Color.accentColor : Color.secondary.opacity(0.55))
}
.buttonStyle(.plain)
.help(item.done ? "Mark as not done" : "Mark as done")
.help(selection.contains(item.id) ? "Deselect" : "Select for copy or delete")
VStack(alignment: .leading, spacing: 6) {
if !sectionName.isEmpty {
@@ -278,43 +278,51 @@ struct PanelView: View {
}
private func contextMenu(for item: PromptItem) -> some View {
Group {
let ids = effectiveIDs(for: item)
let multiple = ids.count > 1
return Group {
Button("Copy") { copy(item) }
Button("Copy as List") {
store.copyAsList(ids: effectiveIDs(for: item))
Button(multiple ? "Copy \(ids.count) as List" : "Copy as List") {
store.copyAsList(ids: ids)
flashCopied(item.id)
}
Button("Copy as Lines") {
store.copyAsLines(ids: effectiveIDs(for: item))
Button(multiple ? "Copy \(ids.count) as Lines" : "Copy as Lines") {
store.copyAsLines(ids: ids)
flashCopied(item.id)
}
Divider()
Button(item.done ? "Mark as Not Done" : "Mark as Done") { store.toggleDone(item.id) }
if case .text(let text) = item.content {
Button(item.done ? "Mark as Not Done" : "Mark as Done") {
store.setDone(!item.done, ids: ids)
}
if case .text(let text) = item.content, !multiple {
Button("Edit…") {
PanelController.shared.editController.edit(itemID: item.id, text: text)
}
}
if case .image(let filename) = item.content {
if case .image(let filename) = item.content, !multiple {
Button("Open") {
NSWorkspace.shared.open(store.imageURL(for: filename))
}
}
if selection.count > 1, selection.contains(item.id) {
if multiple {
Button("Merge Notes") {
store.merge(ids: selection)
store.merge(ids: ids)
clearSelection()
}
}
Menu("Move To") {
ForEach(store.sections.filter { $0.id != store.sectionID(of: item.id) }) { section in
Button(section.name) { store.move(item.id, to: section.id) }
Button(section.name) {
store.move(ids: ids, to: section.id)
if multiple { clearSelection() }
}
}
}
Divider()
Button("Delete", role: .destructive) {
selection.remove(item.id)
store.delete(item.id)
Button(multiple ? "Delete \(ids.count) Items" : "Delete", role: .destructive) {
store.delete(ids: ids)
selection.subtract(ids)
lastSelectedID = nil
}
}
}
@@ -664,12 +672,7 @@ struct PanelView: View {
private func handleClick(_ item: PromptItem) {
let modifiers = NSApp.currentEvent?.modifierFlags ?? []
if modifiers.contains(.command) {
if selection.contains(item.id) {
selection.remove(item.id)
} else {
selection.insert(item.id)
}
lastSelectedID = item.id
toggleSelection(item.id)
return
}
if modifiers.contains(.shift), let anchor = lastSelectedID, anchor != item.id {
@@ -679,6 +682,15 @@ struct PanelView: View {
copy(item)
}
private func toggleSelection(_ id: UUID) {
if selection.contains(id) {
selection.remove(id)
} else {
selection.insert(id)
}
lastSelectedID = id
}
private func selectRange(from anchor: UUID, to target: UUID) {
let ids = displayedItems.map(\.item.id)
guard let start = ids.firstIndex(of: anchor), let end = ids.firstIndex(of: target) else { return }