mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-13 10:30:16 +00:00
tags: js: Minor fixes and refactoring
- Drop changing the history as even without it, back/forward work just fine. - Drop debouncing as there was a bug that prevented it from working. Since we have a small number of tags, running the operations immediately seems to work fine. - Update incorrect docstring. - Flatten and isolate the event handlers code further for readability. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
parent
5ce7385f60
commit
00a5377d9e
119
static/tags.js
119
static/tags.js
@ -24,23 +24,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the URL path and history based on the selected tags.
|
* Update the URL path based on the selected tags.
|
||||||
*
|
*
|
||||||
* If no tags are selected, redirects to the base apps path. Otherwise,
|
* If no tags are selected, redirects to the base apps path. Otherwise,
|
||||||
* constructs a new URL with query parameters for each tag and updates
|
* constructs a new URL with query parameters for each tag.
|
||||||
* the browser history.
|
|
||||||
*
|
*
|
||||||
* @param {string[]} tags - An array of selected tag names.
|
* @param {string[]} tags - An array of selected tag names.
|
||||||
*/
|
*/
|
||||||
function updatePath(tags) {
|
function updatePathWithTags(tags) {
|
||||||
const appsPath = window.location.pathname;
|
const appsPath = window.location.pathname;
|
||||||
if (tags.length === 0) {
|
if (tags.length === 0) {
|
||||||
this.location.assign(appsPath);
|
this.location.assign(appsPath);
|
||||||
} else {
|
} else {
|
||||||
let queryParams = tags.map(tag => `tag=${tag}`).join('&');
|
const urlParams = new URLSearchParams();
|
||||||
let newPath = `${appsPath}?${queryParams}`;
|
tags.forEach(tag => urlParams.append('tag', tag));
|
||||||
this.history.pushState({ tags: tags }, '', newPath);
|
this.location.search = urlParams;
|
||||||
this.location.assign(newPath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,8 +54,8 @@ function updatePath(tags) {
|
|||||||
function getTags(tagToRemove) {
|
function getTags(tagToRemove) {
|
||||||
const tagBadges = document.querySelectorAll('#selected-tags .tag-badge');
|
const tagBadges = document.querySelectorAll('#selected-tags .tag-badge');
|
||||||
return Array.from(tagBadges)
|
return Array.from(tagBadges)
|
||||||
.map(tag => tag.dataset.tag)
|
.map(tagBadge => tagBadge.dataset.tag)
|
||||||
.filter(tagName => tagName !== tagToRemove);
|
.filter(tag => tag !== tagToRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,9 +65,10 @@ function getTags(tagToRemove) {
|
|||||||
* to match the user's input in the search box. It determines the best
|
* to match the user's input in the search box. It determines the best
|
||||||
* matching item and marks it as active.
|
* matching item and marks it as active.
|
||||||
*
|
*
|
||||||
* @param {KeyboardEvent} event - The keyboard event that triggered the search.
|
* @param {ElementList} [dropdownItems] - List of items in the tags dropdown.
|
||||||
*/
|
*/
|
||||||
function findMatchingTag(addTagInput, dropdownItems) {
|
function findMatchingTag(dropdownItems) {
|
||||||
|
const addTagInput = document.getElementById('add-tag-input');
|
||||||
const searchTerm = addTagInput.value.toLowerCase().trim();
|
const searchTerm = addTagInput.value.toLowerCase().trim();
|
||||||
|
|
||||||
// Remove highlighting from all items
|
// Remove highlighting from all items
|
||||||
@ -81,7 +80,8 @@ function findMatchingTag(addTagInput, dropdownItems) {
|
|||||||
if (text.includes(searchTerm)) {
|
if (text.includes(searchTerm)) {
|
||||||
item.style.display = 'block';
|
item.style.display = 'block';
|
||||||
function matchesEarly () {
|
function matchesEarly () {
|
||||||
return text.indexOf(searchTerm) < bestMatch.textContent.toLowerCase().indexOf(searchTerm);
|
let bestMatchText = bestMatch.textContent.toLowerCase();
|
||||||
|
return text.indexOf(searchTerm) < bestMatchText.indexOf(searchTerm);
|
||||||
};
|
};
|
||||||
if (bestMatch === null || matchesEarly()) {
|
if (bestMatch === null || matchesEarly()) {
|
||||||
bestMatch = item;
|
bestMatch = item;
|
||||||
@ -98,38 +98,18 @@ function findMatchingTag(addTagInput, dropdownItems) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage tag-related UI interactions for filtering and displaying apps.
|
* Handle a key press event on that tag input field.
|
||||||
*
|
|
||||||
* This script manages the user interface for filtering apps based on
|
|
||||||
* selected tags. It provides functionality for adding and removing tags,
|
|
||||||
* updating the URL based on selected tags, and displaying a set of
|
|
||||||
* available tags in a searchable dropdown.
|
|
||||||
*/
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
|
|
||||||
// Remove Tag handler.
|
|
||||||
document.querySelectorAll('.remove-tag').forEach(button => {
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
let tag = button.parentElement.dataset.tag;
|
|
||||||
let tags = getTags(tag);
|
|
||||||
updatePath(tags);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searchable dropdown for selecting tags.
|
|
||||||
*
|
*
|
||||||
* As the user types in the input field, the dropdown list is filtered
|
* As the user types in the input field, the dropdown list is filtered
|
||||||
* to show only matching items. The best matching item (first match if
|
* to show only matching items. The best matching item (first match if
|
||||||
* multiple match) is highlighted. Pressing Enter selects the
|
* multiple match) is highlighted. Pressing Enter selects the
|
||||||
* highlighted item and adds it as a tag.
|
* highlighted item and adds it as a tag.
|
||||||
|
*
|
||||||
|
* @param {KeyboardEvent} [event] - The key press event.
|
||||||
*/
|
*/
|
||||||
const addTagInput = document.getElementById('add-tag-input');
|
function onTagInputKeyUp(event) {
|
||||||
const dropdownItems = document.querySelectorAll('li.dropdown-item');
|
const dropdownItems = document.querySelectorAll('.tag-input li.dropdown-item');
|
||||||
|
|
||||||
var timeoutId;
|
|
||||||
addTagInput.addEventListener('keyup', (event) => {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
// Select the active tag if the user pressed Enter
|
// Select the active tag if the user pressed Enter
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
dropdownItems.forEach(item => {
|
dropdownItems.forEach(item => {
|
||||||
@ -138,33 +118,68 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Debounce the user input for search with 300ms delay.
|
findMatchingTag(dropdownItems);
|
||||||
timeoutId = setTimeout(findMatchingTag(addTagInput, dropdownItems), 300);
|
}
|
||||||
});
|
|
||||||
|
|
||||||
dropdownItems.forEach(item => {
|
/**
|
||||||
item.addEventListener('click', () => {
|
* When an item in the tags dropdown is clicked, navigate to a new URL with the
|
||||||
|
* added Tag.
|
||||||
|
*
|
||||||
|
* @param {PointerEvent} [event] - The click event.
|
||||||
|
*/
|
||||||
|
function onTagInputDropdownItemClicked(event) {
|
||||||
|
const item = event.currentTarget;
|
||||||
const selectedTag = item.dataset.value;
|
const selectedTag = item.dataset.value;
|
||||||
|
|
||||||
// Add the selected tag and update the path.
|
// Add the selected tag and update the path.
|
||||||
let tags = getTags('');
|
let tags = getTags('');
|
||||||
tags.push(selectedTag);
|
tags.push(selectedTag);
|
||||||
updatePath(tags);
|
updatePathWithTags(tags);
|
||||||
|
|
||||||
// Reset the input field and dropdown.
|
// Reset the input field
|
||||||
|
const addTagInput = document.getElementById('add-tag-input');
|
||||||
addTagInput.value = '';
|
addTagInput.value = '';
|
||||||
|
|
||||||
|
// Reset the dropdown
|
||||||
|
const dropdownItems = document.querySelectorAll('.tag-input li.dropdown-item');
|
||||||
dropdownItems.forEach(item => {
|
dropdownItems.forEach(item => {
|
||||||
item.style.display = '';
|
item.style.display = 'none';
|
||||||
item.classList.remove('active');
|
item.classList.remove('active');
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* when an remove button next to a tag is clicked, navigate to a new URL without
|
||||||
|
* that Tag.
|
||||||
|
*
|
||||||
|
* @param {PointerEvent} [event] - The click event.
|
||||||
|
*/
|
||||||
|
function onRemoveTagClicked(event) {
|
||||||
|
const button = event.currentTarget;
|
||||||
|
const tag = button.parentElement.dataset.tag;
|
||||||
|
const tags = getTags(tag);
|
||||||
|
updatePathWithTags(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage tag-related UI interactions for filtering and displaying apps.
|
||||||
|
*
|
||||||
|
* This script manages the user interface for filtering apps based on
|
||||||
|
* selected tags. It provides functionality for adding and removing tags,
|
||||||
|
* updating the URL based on selected tags, and displaying a set of
|
||||||
|
* available tags in a searchable dropdown.
|
||||||
|
*/
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
document.querySelectorAll('.remove-tag').forEach(button => {
|
||||||
|
button.addEventListener('click', onRemoveTagClicked);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle browser back/forward navigation.
|
const addTagInput = document.getElementById('add-tag-input');
|
||||||
window.addEventListener('popstate', function (event) {
|
addTagInput.addEventListener('keyup', onTagInputKeyUp);
|
||||||
if (event.state && event.state.tags) {
|
|
||||||
updatePath(event.state.tags);
|
const dropdownItems = document.querySelectorAll('.tag-input li.dropdown-item');
|
||||||
}
|
dropdownItems.forEach(item => {
|
||||||
|
item.addEventListener('click', onTagInputDropdownItemClicked);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user