| New file |
| | |
| | | class FeedBlockOld { |
| | | constructor() { |
| | | this.container = document.querySelector('section.feed-block'); |
| | | if (!this.container) { |
| | | return; |
| | | } |
| | | |
| | | this.a11y = window.jvbA11y; |
| | | this.cache = new window.jvbCache('feed'); |
| | | this.error = window.jvbError; |
| | | |
| | | this.config = { |
| | | source: '', |
| | | context: '', |
| | | highlight: null, |
| | | gallery: false, |
| | | view: this.cache.get('feedView') || 'grid', |
| | | ... this.container.dataset |
| | | }; |
| | | this.initElements(); |
| | | this.initFilters(); |
| | | |
| | | |
| | | this.loadWhenAble(); |
| | | } |
| | | |
| | | loadWhenAble() { |
| | | if ('requestIdleCallback' in window) { |
| | | requestIdleCallback(() => { |
| | | this.initTaxonomies(); |
| | | this.initStore(); |
| | | this.initListeners(); |
| | | this.initGallery(); |
| | | }, { timeout: 2000 }); |
| | | } else { |
| | | setTimeout(() => { |
| | | this.initTaxonomies(); |
| | | this.initStore(); |
| | | this.initListeners(); |
| | | this.initGallery(); |
| | | }, 100); |
| | | } |
| | | } |
| | | |
| | | initElements() { |
| | | this.currentTaxonomies = new Set(); // Allowed Taxonomies, grabbed from active buttons |
| | | this.taxonomyFilters = {}; |
| | | this.elements = { |
| | | filterTrigger: '[data-filter]', |
| | | filters: { |
| | | content: '[data-filter="content"]', |
| | | orderby: '[data-filter="orderby"]', |
| | | order: '[data-filter="order"]', |
| | | match: '[data-filter="match"]', |
| | | favourites: '[data-filter="favourites"]', |
| | | taxonomy: '[data-filter^="taxonomy"]' |
| | | }, |
| | | selectedTax: '.selected-items', |
| | | clearFilter: 'button.clear-filters', |
| | | loadMore: 'button.load-more', |
| | | filterContainer: '.filters', |
| | | grid: '.item-grid', |
| | | }; |
| | | this.ui = window.uiFromSelectors(this.elements); |
| | | |
| | | |
| | | this.ui.content = this.ui.filterContainer.querySelectorAll('[name="content"]')??false; |
| | | this.ui.taxonomies = this.ui.filterContainer.querySelectorAll('[data-taxonomy]'); |
| | | if (this.ui.content && this.ui.content.length > 0) { |
| | | this.contentTypes = Array.from( |
| | | this.ui.content |
| | | ).map(content => content.value); |
| | | } else { |
| | | this.contentTypes = [this.container.dataset['content']]; |
| | | } |
| | | |
| | | if (this.ui.taxonomies.length>0) { |
| | | this.taxonomies = Array.from( |
| | | this.ui.taxonomies, |
| | | ).map(content => content.dataset.taxonomy); |
| | | } else { |
| | | this.taxonomies = []; |
| | | } |
| | | |
| | | |
| | | } |
| | | |
| | | async initTaxonomies() { |
| | | this.selector = window.jvbSelector; |
| | | const buttons = document.querySelectorAll('[data-filter="taxonomy"]'); |
| | | |
| | | this.selector.isInitializing = true; |
| | | buttons.forEach((button) => { |
| | | const taxonomy = button.dataset.taxonomy; |
| | | this.currentTaxonomies.add(taxonomy); |
| | | |
| | | this.selector.registerFilterButton(button, { |
| | | button: button, |
| | | buttonSelector: '[data-filter="taxonomy"]', |
| | | selected: this.ui.selectedTax |
| | | }); |
| | | |
| | | // Add preload listeners |
| | | this.addTaxonomyPreloadListeners(button, taxonomy); |
| | | }); |
| | | |
| | | this.selector.isInitializing = false; |
| | | |
| | | this.selector.subscribe((event, data) => { |
| | | if (event === 'selected-terms') this.handleTaxonomyChange(data); |
| | | }); |
| | | } |
| | | |
| | | addTaxonomyPreloadListeners(button, taxonomy) { |
| | | const preload = () => { |
| | | this.selector.preloadTaxonomy(taxonomy); |
| | | }; |
| | | |
| | | // Desktop hover |
| | | button.addEventListener('mouseenter', preload, { once: true }); |
| | | |
| | | // Touch/keyboard (fires before click) |
| | | button.addEventListener('pointerdown', preload, { once: true }); |
| | | |
| | | // Keyboard focus |
| | | button.addEventListener('focus', preload, { once: true }); |
| | | } |
| | | |
| | | handleTaxonomyChange(data) { |
| | | const { terms, taxonomy } = data; |
| | | |
| | | // Update only the current taxonomy's terms |
| | | if (terms.size > 0) { |
| | | this.taxonomyFilters[taxonomy] = Array.from(terms.keys()); |
| | | } else { |
| | | // Remove taxonomy if no terms selected |
| | | delete this.taxonomyFilters[taxonomy]; |
| | | } |
| | | |
| | | // Build filters object with all taxonomies |
| | | let filters = { |
| | | page: 1 |
| | | }; |
| | | |
| | | // Add taxonomy filters if any exist |
| | | if (Object.keys(this.taxonomyFilters).length > 0) { |
| | | filters.taxonomy = this.taxonomyFilters; |
| | | } |
| | | |
| | | this.updateFilter(filters); |
| | | } |
| | | |
| | | clearAllTaxonomies() { |
| | | this.taxonomyFilters = {}; |
| | | window.removeChildren(this.ui.selectedTax); |
| | | |
| | | this.updateFilter({ |
| | | taxonomy: null, |
| | | page: 1 |
| | | }); |
| | | } |
| | | |
| | | initFilters() { |
| | | //defaults |
| | | this.filters = { |
| | | content: this.contentTypes[0], |
| | | orderby: 'date', |
| | | order: 'desc', |
| | | page: 1 |
| | | }; |
| | | if (this.config.context) this.filters.context = this.config.context; |
| | | if (this.config.source) this.filters.source = this.config.source; |
| | | |
| | | //check the cache |
| | | this.processCachedFilters(); |
| | | //check url |
| | | this.processURLFilters(); |
| | | |
| | | // Set initial UI state |
| | | this.syncUIToFilters(); |
| | | } |
| | | syncUIToFilters() { |
| | | if (this.ui.filterContainer) { |
| | | // Check radio buttons |
| | | Object.entries(this.filters).forEach(([key, value]) => { |
| | | const input = this.ui.filterContainer.querySelector(`[data-filter="${key}"][value="${value}"]`); |
| | | if (input) { |
| | | input.checked = true; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | // Update content-specific visibility |
| | | this.updateContentFor(this.filters.content); |
| | | } |
| | | nextPage() { |
| | | this.store.setFilter('page', this.store.filters.page++); |
| | | } |
| | | |
| | | initStore() { |
| | | const store = window.jvbStore.register( |
| | | 'feed', |
| | | { |
| | | storeName: 'feed', |
| | | endpoint: 'feed', |
| | | keyPath: 'id', |
| | | indexes: [ |
| | | { name: 'content', keyPath: 'content'}, |
| | | { name: 'taxonomy', keyPath: 'taxonomy'}, |
| | | { name: 'user', keyPath: 'user'}, |
| | | { name: 'date', keyPath: 'modified'}, |
| | | { name: 'title', keyPath: 'title'} |
| | | ], |
| | | filters: this.filters, |
| | | TTL: 6 * 60 * 60 * 1000, |
| | | showLoading: true, |
| | | required: 'content', |
| | | delayFetch: true |
| | | } |
| | | ); |
| | | this.store = store.feed; |
| | | |
| | | this.store.subscribe((event, data) => { |
| | | switch (event) { |
| | | case 'data-loaded': |
| | | this.renderItems(); |
| | | this.ui.loadMore.hidden = true; |
| | | if (this.store.lastResponse && this.store.lastResponse['has_more']) { |
| | | this.ui.loadMore.hidden = !this.store.lastResponse['has_more']; |
| | | } |
| | | break; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | initGallery() { |
| | | this.gallery = (this.config.gallery) ? window.jvbGallery : false; |
| | | if (this.gallery) { |
| | | this.gallery.subscribe((event, data) => { |
| | | if (event === 'load-more' && this.store.lastResponse) { |
| | | if (this.store.lastResponse['has_more']) { |
| | | this.nextPage(); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | processCachedFilters() { |
| | | Object.keys(this.filters).forEach(filter => { |
| | | let cached = this.cache.get(`${this.config.source}_${this.config.context}_${filter}`); |
| | | if (cached && cached !== this.filters[filter]){ |
| | | this.filters[filter] = cached; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | processURLFilters() { |
| | | if (this.filters.page > 1) { |
| | | return false; |
| | | } |
| | | const params = new URLSearchParams(window.location.search); |
| | | |
| | | if (!params.toString()) { |
| | | return false; |
| | | } |
| | | let filters = ['content', 'order', 'orderby', 'favourites', 'match']; |
| | | filters.forEach(filter => { |
| | | let value = params.get(`f_${filter}`); |
| | | if (value) { |
| | | this.filters[filter] = value; |
| | | let input = this.ui.filters[filter]; |
| | | if (input) { |
| | | input.checked = true; |
| | | } |
| | | } |
| | | }); |
| | | |
| | | let hasTaxonomy = false; |
| | | // Load taxonomy filters from URL |
| | | params.forEach((value, key) => { |
| | | if (key.startsWith('f_tax_')) { |
| | | hasTaxonomy = true; |
| | | const taxonomy = key.replace('f_tax_', ''); |
| | | if (!this.taxonomyFilters[taxonomy]) { |
| | | this.taxonomyFilters[taxonomy] = []; |
| | | } |
| | | this.taxonomyFilters[taxonomy] = value.split(',').map(Number); |
| | | } |
| | | }); |
| | | if (this.ui.filterContainer && hasTaxonomy) { |
| | | for (let [tax, ids] in Object.entries(this.taxonomyFilters)) { |
| | | let button = this.ui.filterContainer.querySelector(`[data-taxonomy="${tax}"]`); |
| | | if (button) { |
| | | if (button.dataset.fieldId) { |
| | | let field = this.selector.get(button.dataset.fieldId); |
| | | field.selectedTerms = new Set(ids); |
| | | this.selector.initFieldDisplay(button.dataset.fieldId); |
| | | } else { |
| | | this.selector.registerField(button, { |
| | | button: button, |
| | | buttonSelector: '[data-filter="taxonomy"]', |
| | | selected: this.ui.selectedTax, |
| | | selectedItems: ids |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Update URL with current filters (for sharing/bookmarking) |
| | | */ |
| | | updateURL() { |
| | | const params = new URLSearchParams(); |
| | | |
| | | // Add simple filters |
| | | ['content', 'order', 'orderby', 'match'].forEach(key => { |
| | | if (this.filters[key]) { |
| | | params.set(`f_${key}`, this.filters[key]); |
| | | } |
| | | }); |
| | | |
| | | // Add taxonomy filters |
| | | Object.entries(this.taxonomyFilters).forEach(([taxonomy, terms]) => { |
| | | if (terms.length > 0) { |
| | | params.set(`f_tax_${taxonomy}`, terms.join(',')); |
| | | } |
| | | }); |
| | | |
| | | // Update URL without reload |
| | | const newURL = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}`; |
| | | window.history.pushState({ filters: this.filters }, '', newURL); |
| | | } |
| | | |
| | | renderItems() { |
| | | let items = this.store.getFiltered(); |
| | | if (this.store.filters['page'] === 1) { |
| | | window.removeChildren(this.ui.grid); |
| | | } |
| | | |
| | | if (items.length === 0) { |
| | | this.a11y.announceItems(0, this.store.filters['page'] > 0); |
| | | return; |
| | | } |
| | | |
| | | const fragment = document.createDocumentFragment(); |
| | | const batchSize = 10; |
| | | |
| | | const processBatch = (startIndex) => { |
| | | const endIndex = Math.min(startIndex + batchSize, items.length); |
| | | |
| | | for (let i = startIndex; i < endIndex; i++) { |
| | | const item = items[i]; |
| | | const element = this.createItemElement(item); |
| | | |
| | | fragment.appendChild(element); |
| | | } |
| | | |
| | | if (endIndex < items.length) { |
| | | requestAnimationFrame(() => processBatch(endIndex)); |
| | | } else { |
| | | this.removePlaceholders(); |
| | | this.ui.grid.append(fragment); |
| | | |
| | | if (this.config.gallery) { |
| | | this.gallery.updateGalleryItems(this.gallery.getGalleryItems()); |
| | | } |
| | | |
| | | this.a11y.makeNavigable(this.ui.grid.querySelectorAll('.item:not([data-keyboard-nav])')); |
| | | this.a11y.announceItems(items.length, this.store.filters['page'] > 1, this.store.hasMore); |
| | | } |
| | | }; |
| | | |
| | | if (items.length > 0) { |
| | | processBatch(0); |
| | | } else { |
| | | this.a11y.announceItems(0, this.store.filters['page'] >1, false); |
| | | } |
| | | |
| | | if (this.ui.filters.match) { |
| | | this.ui.filters.match.hidden = Object.keys(this.taxonomyFilters).length === 0; |
| | | } |
| | | if (this.ui.clearFilter) { |
| | | this.ui.clearFilter.hidden = Object.keys(this.taxonomyFilters).length === 0; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * |
| | | * @param {object} item |
| | | */ |
| | | createItemElement(item) { |
| | | let template = window.getTemplate(`feedItem${window.uppercaseFirst(item.content)}`); |
| | | |
| | | const isTimeline = Object.hasOwn(template.dataset, 'timeline'); |
| | | |
| | | // Format fields using helpers |
| | | for (let [fieldName, value] of Object.entries(item.fields)) { |
| | | if (isTimeline && ['timeline', 'number'].includes(fieldName)) continue; |
| | | let el = template.querySelector(`[data-field="${fieldName}"]`); |
| | | if (!el) continue; |
| | | |
| | | if (value === '') { |
| | | el.remove(); |
| | | continue; |
| | | } |
| | | |
| | | if (this.isImageField(item, value)) { |
| | | this.formatImageFields(el, value, item); |
| | | } else if (this.isTaxonomyField(item, fieldName)) { |
| | | this.formatTaxonomyField(el, item, fieldName, value); |
| | | } else if (this.isTimeField(el)) { |
| | | this.formatTimeField(el, value); |
| | | } else { |
| | | this.formatField(el, value); |
| | | } |
| | | } |
| | | |
| | | // Handle link |
| | | let link = template.querySelector('a'); |
| | | if (link && item.url !== '') { |
| | | [ |
| | | link.href, |
| | | link.title |
| | | ] = [ |
| | | item.url, |
| | | `View ${item.fields['post_title']??'Item'}` |
| | | ]; |
| | | } |
| | | |
| | | if (isTimeline) { |
| | | this.addTimelineElements(item, template); |
| | | } |
| | | |
| | | return template; |
| | | } |
| | | splitIDs(value) { |
| | | return String(value).split(',').map((value) => parseInt(value.trim())).filter(value=>value); |
| | | } |
| | | isImageField(item, value) { |
| | | if (!Object.hasOwn(item, 'images') || Object.keys(item.images).length === 0) { |
| | | return false; |
| | | } |
| | | let values = this.splitIDs(value); |
| | | |
| | | return values.some(v => |
| | | Object.keys(item.images).map(k => parseInt(k)).includes(parseInt(v)) |
| | | ); |
| | | } |
| | | formatImageFields(element, value, item) { |
| | | let values = this.splitIDs(value); // Convert string to array first |
| | | if (values.length === 0) return; |
| | | |
| | | if (values.length > 1) { |
| | | let image = element.querySelector('img'); |
| | | if (!image) return; |
| | | values.forEach(imgID => { |
| | | let img = image.cloneNode(true); |
| | | this.formatImageField(img, imgID, item); |
| | | element.append(img); |
| | | }); |
| | | image.remove(); |
| | | } else { |
| | | if (element.tagName !== 'IMG') { |
| | | element = element.querySelector('img'); |
| | | if (!element) return; |
| | | } |
| | | this.formatImageField(element, values[0], item); |
| | | } |
| | | } |
| | | formatImageField(element, value, item) { |
| | | let imgData = item.images[value]??false; |
| | | if (!imgData) return; |
| | | [ |
| | | element.src, |
| | | element.srcset, |
| | | element.alt |
| | | ] = [ |
| | | imgData.tiny, |
| | | `${imgData.tiny} 50w, ${imgData.small} 300w, ${imgData.medium} 1024w`, |
| | | imgData['image-alt-text'] |
| | | ] |
| | | } |
| | | isTaxonomyField(item, field) { |
| | | if (!Object.hasOwn(item, 'taxonomies') || Object.keys(item.taxonomies).length === 0) { |
| | | return false; |
| | | } |
| | | |
| | | return Object.keys(item.taxonomies).includes(field); |
| | | } |
| | | formatTaxonomyField(element, item, field, value) { |
| | | if (element.tagName !== 'UL' || !element.querySelector('li')) return; |
| | | let values = this.splitIDs(value); |
| | | if (values.length === 0) { |
| | | element.remove(); |
| | | } |
| | | let listItem = element.querySelector('li'); |
| | | for (let termID of values) { |
| | | let term = item.taxonomies[field][termID]??false; |
| | | if (!term) continue; |
| | | let termItem = listItem.cloneNode(true); |
| | | let link = termItem.querySelector('a'); |
| | | if (!link) continue; |
| | | |
| | | [ |
| | | link.href, |
| | | link.title, |
| | | link.textContent |
| | | ] = [ |
| | | term.url, |
| | | `See more ${term.title}`, |
| | | term.title |
| | | ]; |
| | | element.append(termItem); |
| | | } |
| | | listItem.remove(); |
| | | } |
| | | isTimeField(el) { |
| | | return el.tagName === 'TIME' || el.querySelector('time') !== null; |
| | | } |
| | | formatTimeField(element, value) { |
| | | if (element.tagName !== 'TIME') { |
| | | element = element.querySelector('time'); |
| | | if (!element) return; |
| | | } |
| | | element.setAttribute('datetime', value); |
| | | element.textContent = window.formatTimeAgo(value, 'F Y'); |
| | | } |
| | | formatField(element, value) { |
| | | element.textContent = value; |
| | | } |
| | | |
| | | addTimelineElements(item, template) { |
| | | let [ |
| | | afterEl, |
| | | number, |
| | | started, |
| | | last |
| | | ] = [ |
| | | template.querySelector('span.after-text'), |
| | | template.querySelector('[data-field="number"] b'), |
| | | template.querySelector('[data-field="started"] time'), |
| | | template.querySelector('[data-field="updated"] time') |
| | | ]; |
| | | |
| | | if (afterEl) { |
| | | afterEl.textContent = `After ${item.fields.number} Tx`; |
| | | } |
| | | if (number) { |
| | | number.textContent = item.fields.number; |
| | | } |
| | | if (started) { |
| | | this.formatTimeField(started, item.fields.timeline[0]['post_date']); |
| | | } |
| | | if (last) { |
| | | this.formatTimeField(last, item.fields.timeline[item.fields.timeline.length - 1]['post_date']); |
| | | } |
| | | } |
| | | |
| | | removePlaceholders() { |
| | | const placeholders = this.ui.grid.querySelectorAll('.placeholder'); |
| | | if (placeholders.length > 0) { |
| | | placeholders.forEach(p => p.remove()); |
| | | } |
| | | } |
| | | |
| | | |
| | | addPlaceholders() { |
| | | let total = this.contentTypes.length; |
| | | const fragment = document.createDocumentFragment(); |
| | | for (let i = 0; i < 12; i++) { |
| | | let template = window.getTemplate('placeholderTemplate'); |
| | | |
| | | let rand = Math.floor(Math.random() * total); |
| | | let icon; |
| | | if (this.ui.content && this.ui.content.length > 0) { |
| | | icon = this.ui.content.filter((content) => { return content.value === this.contentTypes[rand]}).querySelector('.icon').cloneNode(true); |
| | | } else { |
| | | icon = window.getIcon(this.container.dataset.icon); |
| | | } |
| | | template.append(icon); |
| | | fragment.append(template); |
| | | } |
| | | this.ui.grid.append(fragment); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * |
| | | * @param {object} filters {name: value} |
| | | */ |
| | | updateFilter(filters) { |
| | | //double check filters are what we're expecting |
| | | let allowed = ['taxonomy','favourites','match', ... Object.keys(this.filters)]; |
| | | |
| | | filters = Object.keys(filters) |
| | | .filter(key => allowed.includes(key)) |
| | | .reduce((obj, key) => { |
| | | obj[key] = filters[key]; |
| | | return obj; |
| | | }, {}); |
| | | |
| | | if (window.getDifferences.map(this.filters, filters)) { |
| | | this.filters = { ...this.filters, ...filters }; // Merge instead of replace |
| | | this.updateURL(); |
| | | this.store.setFilters(filters); |
| | | } |
| | | } |
| | | /** |
| | | * Update visible filters based on selected content type |
| | | */ |
| | | updateContentFor(contentType) { |
| | | // Update taxonomy filter visibility |
| | | const taxonomyButtons = this.ui.filterContainer.querySelectorAll('[data-filter="taxonomy"]'); |
| | | taxonomyButtons.forEach(button => { |
| | | const forTypes = button.dataset.for?.split(',') || []; |
| | | button.hidden = forTypes.length > 0 && !forTypes.includes(contentType); |
| | | }); |
| | | |
| | | // Update ordering options |
| | | const orderButtons = this.ui.filterContainer.querySelectorAll('[data-for]'); |
| | | orderButtons.forEach(button => { |
| | | const forTypes = button.dataset.for?.split(',') || []; |
| | | if (forTypes.length > 0) { |
| | | button.hidden = !forTypes.includes(contentType); |
| | | // Uncheck if hiding |
| | | if (button.hidden && button.checked) { |
| | | button.checked = false; |
| | | } |
| | | } |
| | | }); |
| | | |
| | | // Update order direction visibility based on selected orderby |
| | | const orderBy = this.ui.filterContainer.querySelector('[name="orderby"]:checked'); |
| | | this.updateOrderDirectionVisibility(orderBy?.value); |
| | | } |
| | | |
| | | /** |
| | | * Show/hide order direction based on orderby selection |
| | | */ |
| | | updateOrderDirectionVisibility(orderBy) { |
| | | const orderDirection = this.ui.filterContainer.querySelector('.order-direction'); |
| | | if (orderDirection) { |
| | | const forOrders = orderDirection.dataset.forOrder?.split(',') || []; |
| | | orderDirection.hidden = forOrders.length > 0 && !forOrders.includes(orderBy); |
| | | } |
| | | } |
| | | /********************************************************************* |
| | | LISTENERS |
| | | *********************************************************************/ |
| | | initListeners() { |
| | | this.popStateHandler = this.handlePopState.bind(this); |
| | | this.clickHandler = this.handleClick.bind(this); |
| | | this.changeHandler = this.handleChange.bind(this); |
| | | this.imageObserver = null; |
| | | this.resizeObserver = null; |
| | | if ('IntersectionObserver' in window) { |
| | | this.imageObserver = new IntersectionObserver(entries => { |
| | | entries.forEach(entry => { |
| | | this.loadImage(entry.target); |
| | | this.imageObserver.unobserve(entry.target); |
| | | }); |
| | | }, { |
| | | rootMargin: '100px', |
| | | threshold: .1 |
| | | }); |
| | | } |
| | | |
| | | if ('ResizeObserver' in window) { |
| | | this.resizeObserver = new ResizeObserver(() => { |
| | | window.debouncer.schedule( |
| | | 'feed-update-images', |
| | | () => this.updateImageSizes(), |
| | | 250 |
| | | ); |
| | | }); |
| | | } else { |
| | | window.addEventListener('resize', () => { |
| | | window.debouncer.schedule( |
| | | 'feed-update-images', |
| | | () => this.updateImageSizes(), |
| | | 250 |
| | | ); |
| | | }); |
| | | } |
| | | |
| | | window.addEventListener('popstate', this.popStateHandler); |
| | | document.addEventListener('click', this.clickHandler); |
| | | document.addEventListener('change', this.changeHandler); |
| | | } |
| | | |
| | | handlePopState(e) { |
| | | if (e.state?.filters) { |
| | | if (this.processURLFilters()) { |
| | | this.store.setFilters(this.filters); |
| | | this.a11y.announce('Feed filters updated from browser history'); |
| | | } |
| | | } |
| | | } |
| | | |
| | | handleClick(e) { |
| | | if (window.targetCheck(e, this.elements.loadMore)) { |
| | | this.nextPage(); |
| | | } else if (window.targetCheck(e, this.elements.clearFilter)) { |
| | | this.clearAllTaxonomies(); |
| | | } else if (window.targetCheck(e, '.remove-item')) { |
| | | this.handleRemoveSelectedTerm(e); |
| | | } |
| | | } |
| | | |
| | | handleRemoveSelectedTerm(e) { |
| | | const selectedItem = e.target.closest('.selected-item'); |
| | | if (!selectedItem) return; |
| | | |
| | | const termId = parseInt(selectedItem.dataset.id); |
| | | const taxonomy = selectedItem.dataset.taxonomy; |
| | | |
| | | // Remove from filters |
| | | if (this.taxonomyFilters[taxonomy]) { |
| | | this.taxonomyFilters[taxonomy] = this.taxonomyFilters[taxonomy] |
| | | .filter(id => id !== termId); |
| | | |
| | | if (this.taxonomyFilters[taxonomy].length === 0) { |
| | | delete this.taxonomyFilters[taxonomy]; |
| | | } |
| | | } |
| | | |
| | | // Remove from UI |
| | | selectedItem.remove(); |
| | | |
| | | // Update filters |
| | | this.updateFilter({ |
| | | taxonomy: Object.keys(this.taxonomyFilters).length > 0 |
| | | ? this.taxonomyFilters |
| | | : null, |
| | | page: 1 |
| | | }); |
| | | } |
| | | |
| | | handleChange(e) { |
| | | let target = e.target; |
| | | if (Object.hasOwn(target.dataset, 'filter')) { |
| | | if (target.dataset.filter === 'content') { |
| | | this.updateContentFor(target.value); |
| | | this.updateFilter({ content: target.value, page: 1 }); |
| | | } else if (target.dataset.filter === 'orderby') { |
| | | this.updateOrderDirectionVisibility(target.value); |
| | | this.updateFilter({ orderby: target.value, page: 1 }); |
| | | } else if (target.dataset.filter === 'order') { |
| | | this.updateFilter({ order: target.value, page: 1 }); |
| | | } else if (target.dataset.filter === 'match') { |
| | | this.updateFilter({ match: target.checked ? 'all' : 'any', page: 1 }); |
| | | } else if (target.dataset.filter === 'favourites') { |
| | | this.updateFilter({ favourites: target.checked, page: 1 }); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | document.addEventListener('DOMContentLoaded', async function() { |
| | | window.auth.subscribe(event => { |
| | | if (event === 'auth-loaded') { |
| | | window.feedBlock = new FeedBlock(); |
| | | } |
| | | }); |
| | | }); |