diff --git a/src/frontend/clientJS/tag-picker.ts b/src/frontend/clientJS/tag-picker.ts index 817d460..5202353 100644 --- a/src/frontend/clientJS/tag-picker.ts +++ b/src/frontend/clientJS/tag-picker.ts @@ -1,100 +1,119 @@ -// // Client-side tag picker toggle functionality -// class TagPickerManager { -// constructor() { -// this.init(); -// } +// Add function to 'Clear all filters' +document.querySelector('.clear-tags-btn') + ?.addEventListener('click', (e) => { + const activeTags = document.querySelectorAll('a.tag-pill.active') + activeTags.forEach((activeTag) => { + activeTag.classList.remove('active') + }) + updateTagUrls(); + }) -// init() { -// this.attachTagClickListeners(); -// } +// Add function to toggle tag filters +document.querySelectorAll('a.tag-pill') +.forEach((tagPill) => { + tagPill.addEventListener('click', (e) => toggleTagPill(e)) +}) -// // Parse current URL to get active tags -// getActiveTags(): string[] { -// const urlParams = new URLSearchParams(window.location.search); -// return urlParams.getAll('tag').map(tag => decodeURIComponent(tag).replace(/-/g, ' ')); -// } +function toggleTagPill(e: Event) { + const target = e.currentTarget as HTMLElement; + const searchParams = new URLSearchParams(window.location.search) -// // Generate query string from tags array -// generateTagQueryString(tags: string[]): string { -// if (tags.length === 0) return ''; -// const params = new URLSearchParams(); -// tags.forEach(tag => { -// params.append('tag', tag.toLowerCase().replace(/\s+/g, '-')); -// }); -// return params.toString(); -// } + if (target.classList.contains('active')) { + target.classList.remove('active') + searchParams.delete('tag', getURLSafeTagName(target.innerText)) + } else { + target.classList.add('active') + searchParams.append('tag', getURLSafeTagName(target.innerText)) + } -// // Toggle a tag and update the page -// toggleTag(tagName: string) { -// const currentTags = this.getActiveTags(); + // Update tag urls after the loader in onLoad completes + setTimeout(() => updateTagUrls(), 0) +} -// // Toggle logic: if tag is active, remove it; otherwise add it -// const newTags = currentTags.includes(tagName) -// ? currentTags.filter(t => t !== tagName) -// : [...currentTags, tagName]; +function updateTagUrls() { + const activeTags = document.querySelectorAll('a.tag-pill.active') + const inactiveTags = document.querySelectorAll('a.tag-pill:not(.active)') -// // Navigate to new URL -// const queryString = this.generateTagQueryString(newTags); -// const newUrl = queryString ? `/?${queryString}` : '/'; -// window.location.href = newUrl; -// } + let baseTagParams = ''; + activeTags.forEach((val) => { + baseTagParams += `&tag=${getURLSafeTagName(val.innerHTML)}` + }) -// // Attach click listeners to tag links -// attachTagClickListeners() { -// const tagLinks = document.querySelectorAll('[data-taglink], .post-tag'); + inactiveTags.forEach((val) => { + val.setAttribute('href', `/?tag=${getURLSafeTagName(val.innerHTML)}${baseTagParams}`) + }) -// tagLinks.forEach(link => { -// link.addEventListener('click', (e) => { -// e.preventDefault(); -// const tagName = link.textContent?.trim(); -// if (tagName) { -// this.toggleTag(tagName); -// } -// }); -// }); -// } + activeTags.forEach((val) => { + const tagName = getURLSafeTagName(val.innerHTML) -// // Update visual state of tags based on current URL -// updateTagVisualState() { -// const activeTags = this.getActiveTags(); + const activeTagLink = baseTagParams.split(`&tag=${getURLSafeTagName(val.innerHTML)}`).join('') -// // Update tag pills in sidebar -// document.querySelectorAll('.tag-pill').forEach(link => { -// const tagName = link.textContent?.trim(); -// if (tagName && activeTags.includes(tagName)) { -// link.classList.add('active'); -// } else { -// link.classList.remove('active'); -// } -// }); + if (activeTagLink.length > 1) { + val.setAttribute('href', `/?${activeTagLink.substring(1)}`) + } else { + val.setAttribute('href', '/') + } + }) +} -// // Update post tags -// document.querySelectorAll('.post-tag').forEach(link => { -// const tagName = link.textContent?.trim(); -// if (tagName && activeTags.includes(tagName)) { -// link.classList.add('active'); -// } else { -// link.classList.remove('active'); -// } -// }); +// Helper function normalizing the user facing tag name into +// the url format for the tag name. +function getURLSafeTagName(tag: string) { + return tag.toLowerCase().split(" ").join("-") +} -// // Update clear filters button visibility -// const tagActions = document.querySelector('.tag-actions'); -// if (tagActions) { -// tagActions.style.display = activeTags.length > 0 ? 'block' : 'none'; -// } -// } -// } +// Function to sync the UI state with the current URL +function syncStateFromUrl() { + const searchParams = new URLSearchParams(window.location.search); + const currentTags = searchParams.getAll('tag'); -// // Initialize on page load -// const tagPickerManager = new TagPickerManager(); + // Reset all tags to inactive + document.querySelectorAll('a.tag-pill').forEach(tagPill => { + tagPill.classList.remove('active'); + }); -// // Update visual state after page loads -// document.addEventListener('DOMContentLoaded', () => { -// tagPickerManager.updateTagVisualState(); -// }); + // Set active tags based on current URL + let activeTagCount = 0 + currentTags.forEach(tagUrl => { + document.querySelectorAll('a.tag-pill').forEach(tagPill => { + const pill = tagPill as HTMLElement; + if (getURLSafeTagName(pill.innerText) === tagUrl) { + pill.classList.add('active'); + activeTagCount++ + } + }); + }); -// // Also call immediately if DOM is already loaded -// if (document.readyState === 'interactive' || document.readyState === 'complete') { -// tagPickerManager.updateTagVisualState(); -// } + // If there is an active tag, show the clear filters button + const tagActions = document.querySelector('.tag-actions') as HTMLElement + if (activeTagCount > 0) { + // Show the clear tags button + tagActions.style.display = 'block'; + } else { + // Hide the clear tags button + tagActions.style.display = 'none'; + } + + updateTagUrls(); +} + +// Hook into the popstate event to sync state when navigating back/forward +window.addEventListener('popstate', () => { + syncStateFromUrl(); +}); + +// Hook into the navigation system to sync state after content loads +// We'll observe the main element for changes +const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'childList' && document.querySelector('main')) { + syncStateFromUrl(); + } + }); +}); + +// Start observing the document body for changes +observer.observe(document.body, { + childList: true, + subtree: true +}); diff --git a/src/frontend/components/tag-picker.tsx b/src/frontend/components/tag-picker.tsx index bd90ad0..8934fe7 100644 --- a/src/frontend/components/tag-picker.tsx +++ b/src/frontend/components/tag-picker.tsx @@ -52,13 +52,11 @@ export function TagPicker({ searchParams }: { searchParams?: URLSearchParams }) {/* Show clear tags button if there are selected tags */} - {selectedTags.length > 0 && ( -
- )} +