From 13083700275a6086b2c796ac87db21f21b2963e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20I=C3=9Fbr=C3=BCcker?= Date: Sun, 14 Aug 2022 11:28:36 +0200 Subject: [PATCH] Add bookmark list keyboard navigation (#320) * Add bookmark list keyboard navigation * Fix focus outline for title link * Combine bookmark list scripts --- bookmarks/static/bookmark_list.js | 127 ++++++++++++++++++ bookmarks/static/bulk_edit.js | 86 ------------ bookmarks/styles/bookmarks.scss | 9 ++ bookmarks/templates/bookmarks/archive.html | 2 +- .../templates/bookmarks/bookmark_list.html | 7 +- bookmarks/templates/bookmarks/index.html | 2 +- 6 files changed, 143 insertions(+), 90 deletions(-) create mode 100644 bookmarks/static/bookmark_list.js delete mode 100644 bookmarks/static/bulk_edit.js diff --git a/bookmarks/static/bookmark_list.js b/bookmarks/static/bookmark_list.js new file mode 100644 index 0000000..aa5637d --- /dev/null +++ b/bookmarks/static/bookmark_list.js @@ -0,0 +1,127 @@ +(function () { + function setupBulkEdit() { + const bulkEditToggle = document.getElementById('bulk-edit-mode') + const bulkEditBar = document.querySelector('.bulk-edit-bar') + const singleToggles = document.querySelectorAll('.bulk-edit-toggle input') + const allToggle = document.querySelector('.bulk-edit-all-toggle input') + + function isAllSelected() { + let result = true + + singleToggles.forEach(function (toggle) { + result = result && toggle.checked + }) + + return result + } + + function selectAll() { + singleToggles.forEach(function (toggle) { + toggle.checked = true + }) + } + + function deselectAll() { + singleToggles.forEach(function (toggle) { + toggle.checked = false + }) + } + + // Toggle all + allToggle.addEventListener('change', function (e) { + if (e.target.checked) { + selectAll() + } else { + deselectAll() + } + }) + + // Toggle single + singleToggles.forEach(function (toggle) { + toggle.addEventListener('change', function () { + allToggle.checked = isAllSelected() + }) + }) + + // Allow overflow when bulk edit mode is active to be able to display tag auto complete menu + let bulkEditToggleTimeout + if (bulkEditToggle.checked) { + bulkEditBar.style.overflow = 'visible'; + } + bulkEditToggle.addEventListener('change', function (e) { + if (bulkEditToggleTimeout) { + clearTimeout(bulkEditToggleTimeout); + bulkEditToggleTimeout = null; + } + if (e.target.checked) { + bulkEditToggleTimeout = setTimeout(function () { + bulkEditBar.style.overflow = 'visible'; + }, 500); + } else { + bulkEditBar.style.overflow = 'hidden'; + } + }); + } + + function setupBulkEditTagAutoComplete() { + const wrapper = document.createElement('div'); + const tagInput = document.getElementById('bulk-edit-tags-input'); + const apiBaseUrl = document.documentElement.dataset.apiBaseUrl || ''; + const apiClient = new linkding.ApiClient(apiBaseUrl) + + new linkding.TagAutoComplete({ + target: wrapper, + props: { + id: 'bulk-edit-tags-input', + name: tagInput.name, + value: tagInput.value, + apiClient: apiClient, + variant: 'small' + } + }); + + tagInput.parentElement.replaceChild(wrapper, tagInput); + } + + function setupListNavigation() { + // Add logic for navigating bookmarks with arrow keys + document.addEventListener('keydown', event => { + // Skip if event occurred within an input element + // or does not use arrow keys + const targetNodeName = event.target.nodeName; + const isInputTarget = targetNodeName === 'INPUT' + || targetNodeName === 'SELECT' + || targetNodeName === 'TEXTAREA'; + const isArrowUp = event.key === 'ArrowUp'; + const isArrowDown = event.key === 'ArrowDown'; + + if (isInputTarget || !(isArrowUp || isArrowDown)) { + return; + } + event.preventDefault(); + + // Detect current bookmark list item + const path = event.composedPath(); + const currentItem = path.find(item => item.hasAttribute && item.hasAttribute('data-is-bookmark-item')); + + // Find next item + let nextItem; + if (currentItem) { + nextItem = isArrowUp + ? currentItem.previousElementSibling + : currentItem.nextElementSibling; + } else { + // Select first item + nextItem = document.querySelector('li[data-is-bookmark-item]'); + } + // Focus first link + if (nextItem) { + nextItem.querySelector('a').focus(); + } + }); + } + + setupBulkEdit(); + setupBulkEditTagAutoComplete(); + setupListNavigation(); +})() diff --git a/bookmarks/static/bulk_edit.js b/bookmarks/static/bulk_edit.js deleted file mode 100644 index 851c969..0000000 --- a/bookmarks/static/bulk_edit.js +++ /dev/null @@ -1,86 +0,0 @@ -(function () { - const bulkEditToggle = document.getElementById('bulk-edit-mode') - const bulkEditBar = document.querySelector('.bulk-edit-bar') - const singleToggles = document.querySelectorAll('.bulk-edit-toggle input') - const allToggle = document.querySelector('.bulk-edit-all-toggle input') - - function isAllSelected() { - let result = true - - singleToggles.forEach(function (toggle) { - result = result && toggle.checked - }) - - return result - } - - function selectAll() { - singleToggles.forEach(function (toggle) { - toggle.checked = true - }) - } - - function deselectAll() { - singleToggles.forEach(function (toggle) { - toggle.checked = false - }) - } - - // Toggle all - allToggle.addEventListener('change', function (e) { - if (e.target.checked) { - selectAll() - } else { - deselectAll() - } - }) - - // Toggle single - singleToggles.forEach(function (toggle) { - toggle.addEventListener('change', function () { - allToggle.checked = isAllSelected() - }) - }) - - // Allow overflow when bulk edit mode is active to be able to display tag auto complete menu - let bulkEditToggleTimeout - if (bulkEditToggle.checked) { - bulkEditBar.style.overflow = 'visible'; - } - bulkEditToggle.addEventListener('change', function (e) { - if (bulkEditToggleTimeout) { - clearTimeout(bulkEditToggleTimeout); - bulkEditToggleTimeout = null; - } - if (e.target.checked) { - bulkEditToggleTimeout = setTimeout(function () { - bulkEditBar.style.overflow = 'visible'; - }, 500); - } else { - bulkEditBar.style.overflow = 'hidden'; - } - }); - - // Init tag auto-complete - function initTagAutoComplete() { - const wrapper = document.createElement('div'); - const tagInput = document.getElementById('bulk-edit-tags-input'); - const apiBaseUrl = document.documentElement.dataset.apiBaseUrl || ''; - const apiClient = new linkding.ApiClient(apiBaseUrl) - - new linkding.TagAutoComplete({ - target: wrapper, - props: { - id: 'bulk-edit-tags-input', - name: tagInput.name, - value: tagInput.value, - apiClient: apiClient, - variant: 'small' - } - }); - - tagInput.parentElement.replaceChild(wrapper, tagInput); - } - - initTagAutoComplete(); -})() diff --git a/bookmarks/styles/bookmarks.scss b/bookmarks/styles/bookmarks.scss index 8574d49..8ed4bc0 100644 --- a/bookmarks/styles/bookmarks.scss +++ b/bookmarks/styles/bookmarks.scss @@ -49,6 +49,15 @@ ul.bookmark-list { margin: 0; padding: 0; + .title a { + display: inline-block; + vertical-align: top; + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .description { color: $gray-color-dark; diff --git a/bookmarks/templates/bookmarks/archive.html b/bookmarks/templates/bookmarks/archive.html index 0c8b359..bd8d1c1 100644 --- a/bookmarks/templates/bookmarks/archive.html +++ b/bookmarks/templates/bookmarks/archive.html @@ -42,5 +42,5 @@ - + {% endblock %} diff --git a/bookmarks/templates/bookmarks/bookmark_list.html b/bookmarks/templates/bookmarks/bookmark_list.html index 261a3e2..68aded0 100644 --- a/bookmarks/templates/bookmarks/bookmark_list.html +++ b/bookmarks/templates/bookmarks/bookmark_list.html @@ -8,8 +8,11 @@ -
- {{ bookmark.resolved_title }} +
{% if bookmark.tag_names %} diff --git a/bookmarks/templates/bookmarks/index.html b/bookmarks/templates/bookmarks/index.html index 60696b5..248f2e7 100644 --- a/bookmarks/templates/bookmarks/index.html +++ b/bookmarks/templates/bookmarks/index.html @@ -42,5 +42,5 @@ - + {% endblock %}