Skip to content

Commit 8dd156d

Browse files
committed
Add shift-click and improved keys multiselect
1 parent ca4f038 commit 8dd156d

2 files changed

Lines changed: 62 additions & 12 deletions

File tree

assets/js/scripts.js

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -144,18 +144,67 @@ if (delete_all) {
144144
});
145145
}
146146

147-
const check_all = document.querySelector('.check-all');
148-
if (check_all) {
149-
check_all.addEventListener('change', () => {
150-
if (delete_selected) {
151-
delete_selected.disabled = check_all.checked !== true;
152-
}
147+
// Check all keys in a table or treeview
148+
document.addEventListener('change', (e) => {
149+
if (!e.target.matches('input[type="checkbox"].check-all')) {
150+
return;
151+
}
153152

154-
keys.forEach(key => {
155-
key.querySelector('[type="checkbox"]').checked = check_all.checked;
156-
});
153+
let scope;
154+
155+
if (e.target.closest('.tree-group')) {
156+
const tree_group = e.target.closest('.tree-group');
157+
const children = tree_group.querySelector(':scope > .tree-children');
158+
scope = children || tree_group;
159+
} else {
160+
scope = e.target.closest('table') || e.target.closest('.treeview');
161+
}
162+
163+
if (!scope) {
164+
return;
165+
}
166+
167+
const checkboxes = scope.querySelectorAll('input[type="checkbox"]:not(.check-all)');
168+
169+
checkboxes.forEach(cb => {
170+
cb.checked = e.target.checked;
171+
cb.dispatchEvent(new Event('change', {bubbles: true}));
157172
});
158-
}
173+
});
174+
175+
// Shift-click multi-select
176+
let last_checked = null;
177+
document.addEventListener('click', (e) => {
178+
if (!e.target.matches('input[type="checkbox"]') || e.target.classList.contains('check-all')) {
179+
return;
180+
}
181+
182+
const tree = e.target.closest('.treeview');
183+
const table = e.target.closest('table');
184+
let checkboxes;
185+
186+
if (tree) {
187+
checkboxes = Array.from(tree.querySelectorAll('.keywrapper input[type="checkbox"]:not(.check-all)'));
188+
} else if (table) {
189+
checkboxes = Array.from(table.querySelectorAll('input[type="checkbox"]:not(.check-all)'));
190+
} else {
191+
return;
192+
}
193+
194+
if (e.shiftKey && last_checked) {
195+
const start = checkboxes.indexOf(last_checked);
196+
const end = checkboxes.indexOf(e.target);
197+
198+
if (start !== -1 && end !== -1) {
199+
for (let i = Math.min(start, end); i <= Math.max(start, end); i++) {
200+
checkboxes[i].checked = e.target.checked;
201+
checkboxes[i].dispatchEvent(new Event('change', {bubbles: true}));
202+
}
203+
}
204+
}
205+
206+
last_checked = e.target;
207+
});
159208

160209
/**
161210
* Ajax panels

templates/partials/tree_view.twig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
{% macro render_tree(items, level=0, show_actions, view_key, components) %}
55
{% for key, item in items %}
66
{% if item.type == 'folder' %}
7-
<div class="border-t border-gray-200 dark:border-gray-700" style="--level: {{ level }}">
7+
<div class="tree-group border-t border-gray-200 dark:border-gray-700" style="--level: {{ level }}">
88
<div class="flex gap-1 items-center py-1 px-6 hover:bg-gray-50 dark:hover:bg-white/5" style="padding-left: calc({{ level }} * 20px + 1.5rem)">
9+
<div>{{ components.checkbox('check-all') }}</div>
910
<button class="flex gap-1 items-center tree-toggle" data-path="{{ item.path }}">
1011
{{ svg('down', 10, '-rotate-90') }}
1112
<span class="tree">{{ item.name }}{{ separator }}*</span>
1213
</button>
13-
<span class="text-xs text-gray-500 dark:text-gray-400 items-count">({{ item.count }})</span>
14+
<span class="text-xs text-gray-500 dark:text-gray-400 items-count">({{ item.count }} items)</span>
1415
</div>
1516
<div class="hidden tree-children" data-path="{{ item.path }}">
1617
{{ _self.render_tree(item.children, level + 1, show_actions, view_key, components) }}

0 commit comments

Comments
 (0)