mirror of
https://github.com/sissbruecker/linkding.git
synced 2025-08-14 05:59:29 +02:00
Add bookmark list keyboard navigation (#320)
* Add bookmark list keyboard navigation * Fix focus outline for title link * Combine bookmark list scripts
This commit is contained in:
127
bookmarks/static/bookmark_list.js
Normal file
127
bookmarks/static/bookmark_list.js
Normal file
@@ -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();
|
||||||
|
})()
|
@@ -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();
|
|
||||||
})()
|
|
@@ -49,6 +49,15 @@ ul.bookmark-list {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
|
.title a {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
color: $gray-color-dark;
|
color: $gray-color-dark;
|
||||||
|
|
||||||
|
@@ -42,5 +42,5 @@
|
|||||||
|
|
||||||
<script src="{% static "bundle.js" %}"></script>
|
<script src="{% static "bundle.js" %}"></script>
|
||||||
<script src="{% static "shared.js" %}"></script>
|
<script src="{% static "shared.js" %}"></script>
|
||||||
<script src="{% static "bulk_edit.js" %}"></script>
|
<script src="{% static "bookmark_list.js" %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -8,8 +8,11 @@
|
|||||||
<input type="checkbox" name="bookmark_id" value="{{ bookmark.id }}">
|
<input type="checkbox" name="bookmark_id" value="{{ bookmark.id }}">
|
||||||
<i class="form-icon"></i>
|
<i class="form-icon"></i>
|
||||||
</label>
|
</label>
|
||||||
<div class="title truncate">
|
<div class="title">
|
||||||
<a href="{{ bookmark.url }}" target="{{ link_target }}" rel="noopener" class="{% if bookmark.unread %}text-italic{% endif %}">{{ bookmark.resolved_title }}</a>
|
<a href="{{ bookmark.url }}" target="{{ link_target }}" rel="noopener"
|
||||||
|
class="{% if bookmark.unread %}text-italic{% endif %}">
|
||||||
|
{{ bookmark.resolved_title }}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="description truncate">
|
<div class="description truncate">
|
||||||
{% if bookmark.tag_names %}
|
{% if bookmark.tag_names %}
|
||||||
|
@@ -42,5 +42,5 @@
|
|||||||
|
|
||||||
<script src="{% static "bundle.js" %}"></script>
|
<script src="{% static "bundle.js" %}"></script>
|
||||||
<script src="{% static "shared.js" %}"></script>
|
<script src="{% static "shared.js" %}"></script>
|
||||||
<script src="{% static "bulk_edit.js" %}"></script>
|
<script src="{% static "bookmark_list.js" %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Reference in New Issue
Block a user