feat(client): add shift-click selection to book dock

Allow users to select a range of files in the book dock by holding the shift key while clicking checkboxes, matching standard multi-select behavior.

Closes #136
This commit is contained in:
neon
2026-05-29 04:57:12 +00:00
parent fc6d4fec83
commit 59511b0856
3 changed files with 39 additions and 9 deletions
@@ -37,13 +37,17 @@ const props = withDefaults(
},
)
defineEmits<{
select: [number]
const emit = defineEmits<{
select: [number, boolean]
selectAll: []
open: [BookDockFile]
applyFetched: [number]
}>()
function onCheckboxClick(event: MouseEvent, id: number) {
emit('select', id, event.shiftKey)
}
function backendCoverUrl(file: BookDockFile): string {
return `/api/v1/book-dock/files/${file.id}/cover?v=${new Date(file.updatedAt).getTime()}`
}
@@ -234,8 +238,7 @@ function targetSummary(file: BookDockFile): string {
type="checkbox"
:checked="isSelected(file.id)"
class="size-3.5 rounded border-input accent-primary shrink-0 self-center"
@click.stop
@change.stop="$emit('select', file.id)"
@click.stop="onCheckboxClick($event, file.id)"
/>
<HoverCardRoot :open-delay="250" :close-delay="100">
@@ -23,6 +23,7 @@ export function useBookDockFiles() {
const selectedIds = ref<Set<number>>(new Set())
const selectAll = ref(false)
const excludedIds = ref<Set<number>>(new Set())
const lastToggledId = ref<number | null>(null)
let fetchReqSeq = 0
const pageCount = computed(() => Math.ceil(total.value / filters.limit) || 1)
@@ -85,16 +86,37 @@ export function useBookDockFiles() {
fetchFiles()
}
function toggleSelect(id: number) {
function setSelected(id: number, selected: boolean) {
if (selectAll.value) {
if (excludedIds.value.has(id)) excludedIds.value.delete(id)
if (selected) excludedIds.value.delete(id)
else excludedIds.value.add(id)
} else {
if (selectedIds.value.has(id)) selectedIds.value.delete(id)
else selectedIds.value.add(id)
if (selected) selectedIds.value.add(id)
else selectedIds.value.delete(id)
}
}
function toggleSelect(id: number, options: { range?: boolean } = {}) {
const willBeSelected = !isSelected(id)
if (options.range && lastToggledId.value !== null && lastToggledId.value !== id) {
const anchorIdx = items.value.findIndex((f) => f.id === lastToggledId.value)
const targetIdx = items.value.findIndex((f) => f.id === id)
if (anchorIdx !== -1 && targetIdx !== -1) {
const [start, end] = anchorIdx < targetIdx ? [anchorIdx, targetIdx] : [targetIdx, anchorIdx]
for (let i = start; i <= end; i++) {
const item = items.value[i]
if (item) setSelected(item.id, willBeSelected)
}
lastToggledId.value = id
return
}
}
setSelected(id, willBeSelected)
lastToggledId.value = id
}
function toggleSelectAll() {
if (selectAll.value) {
selectAll.value = false
@@ -111,6 +133,7 @@ export function useBookDockFiles() {
selectAll.value = false
selectedIds.value.clear()
excludedIds.value.clear()
lastToggledId.value = null
}
function isSelected(id: number): boolean {
+5 -1
View File
@@ -84,6 +84,10 @@ function openSheet(file: BookDockFile) {
selectedFile.value = file
}
function handleSelect(id: number, shiftKey: boolean) {
toggleSelect(id, { range: shiftKey })
}
function closeSheet() {
selectedFile.value = null
}
@@ -469,7 +473,7 @@ onUnmounted(() => {
:select-all="selectAll"
:name-preview-by-file-id="namePreviewByFileId"
:empty-message="emptyMessage"
@select="toggleSelect"
@select="handleSelect"
@select-all="toggleSelectAll"
@open="openSheet"
@apply-fetched="handleInlineApplyFetched"