| | |
| | | favourites: '[data-filter="favourites"]', |
| | | taxonomy: '[data-filter^="taxonomy"]' |
| | | }, |
| | | selectedTax: '.selected-terms', |
| | | selectedTax: '.selected-items', |
| | | clearFilter: 'button.clear-filters', |
| | | loadMore: 'button.load-more', |
| | | filterContainer: '.filters', |
| | |
| | | }; |
| | | this.ui = window.uiFromSelectors(this.elements); |
| | | |
| | | this.ui.content = this.ui.filterContainer.querySelectorAll('[name="content"]'); |
| | | |
| | | this.ui.content = this.ui.filterContainer.querySelectorAll('[name="content"]')??false; |
| | | this.ui.taxonomies = this.ui.filterContainer.querySelectorAll('[data-taxonomy]'); |
| | | if (this.ui.content.length > 0) { |
| | | 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, |
| | |
| | | } else { |
| | | this.taxonomies = []; |
| | | } |
| | | |
| | | |
| | | } |
| | | |
| | | async initTaxonomies() { |
| | |
| | | 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); |
| | | }); |
| | | |
| | | // Make this non-blocking |
| | | setTimeout(() => { |
| | | this.selector.batchFetchTaxonomies(); |
| | | this.selector.isInitializing = false; |
| | | }, 0); |
| | | 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; |
| | | |
| | |
| | | this.syncUIToFilters(); |
| | | } |
| | | syncUIToFilters() { |
| | | // 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; |
| | | } |
| | | }); |
| | | 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); |
| | |
| | | } |
| | | |
| | | initStore() { |
| | | this.addPlaceholders(); |
| | | this.store = window.jvbStore.register( |
| | | const store = window.jvbStore.register( |
| | | 'feed', |
| | | { |
| | | storeName: 'feed', |
| | | endpoint: 'feed', |
| | | keyPath: 'id', |
| | | indexes: [ |
| | |
| | | delayFetch: true |
| | | } |
| | | ); |
| | | this.store = store.feed; |
| | | |
| | | this.store.subscribe((event, data) => { |
| | | switch (event) { |
| | |
| | | this.taxonomyFilters[taxonomy] = value.split(',').map(Number); |
| | | } |
| | | }); |
| | | if (hasTaxonomy) { |
| | | 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) { |
| | |
| | | let items = this.store.getFiltered(); |
| | | if (this.store.filters['page'] === 1) { |
| | | window.removeChildren(this.ui.grid); |
| | | this.addPlaceholders(); |
| | | } |
| | | |
| | | if (items.length === 0) { |
| | |
| | | this.removePlaceholders(); |
| | | this.ui.grid.append(fragment); |
| | | |
| | | // Observe images after adding to DOM |
| | | this.observeImages(this.ui.grid); |
| | | |
| | | if (this.config.gallery) { |
| | | this.gallery.updateGalleryItems(this.gallery.getGalleryItems()); |
| | | } |
| | |
| | | this.a11y.announceItems(0, this.store.filters['page'] >1, false); |
| | | } |
| | | |
| | | this.ui.filters.match.hidden = window.isEmptyObject(this.taxonomyFilters); |
| | | this.ui.clearFilter.hidden = window.isEmptyObject(this.taxonomyFilters); |
| | | 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('feed-item'); |
| | | let template = window.getTemplate(`feedItem${window.uppercaseFirst(item.content)}`); |
| | | if (Object.hasOwn(template.dataset, 'timeline')) { |
| | | return this.createTimelineElement(item, template); |
| | | } |
| | | //fields |
| | | for (let [fieldName, value] of Object.entries(item.fields)) { |
| | | let el = template.querySelector(`[data-field="${fieldName}"]`); |
| | | if (!el) { |
| | | continue; |
| | | } |
| | | if (Object.keys(item.images).map((img)=> parseInt(img)).includes(value)) { |
| | | [ |
| | | el.src, |
| | | el.srcset, |
| | | el.alt |
| | | ] = [ |
| | | item.images[value].tiny, |
| | | `${item.images[value].tiny} 50w, ${item.images[value].small} 300w, ${item.images[value].medium} 1024w`, |
| | | item.images[value]['image-alt-text'] |
| | | ]; |
| | | } else if (el.tagName === 'TIME') { |
| | | el.setAttribute('datetime', value); |
| | | el.textContent = window.formatTimeAgo(value); |
| | | } else { |
| | | el.textContent = value; |
| | | } |
| | | if (value === '') { |
| | | el.remove(); |
| | | } |
| | | } |
| | | let link = template.querySelector('a'); |
| | | if (link && item.url !== '') { |
| | | [ |
| | | link.href, |
| | | link.title |
| | | ] = [ |
| | | item.url, |
| | | `View ${item.fields['post_title']??'Item'}` |
| | | ]; |
| | | } |
| | | return template; |
| | | } |
| | | |
| | | |
| | | createTimelineElement(item, template) { |
| | | console.log(item); |
| | | console.log(template); |
| | | let [ |
| | | main, |
| | | link, |
| | |
| | | return template; |
| | | } |
| | | |
| | | 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.length > 0) { |
| | | 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.appendChild(icon); |
| | | this.ui.grid.appendChild(template); |
| | | template.append(icon); |
| | | fragment.append(template); |
| | | } |
| | | } |
| | | |
| | | removePlaceholders() { |
| | | if (this.ui.grid.querySelector('.placeholder')) { |
| | | window.removeChildren(this.ui.grid); |
| | | } |
| | | this.ui.grid.append(fragment); |
| | | } |
| | | |
| | | |
| | |
| | | } |
| | | |
| | | if ('ResizeObserver' in window) { |
| | | this.resizeObserver = new ResizeObserver(window.debounce(() => { |
| | | this.updateImageSizes(); |
| | | }, 250)); |
| | | this.resizeObserver = new ResizeObserver(() => { |
| | | window.debouncer.schedule( |
| | | 'feed-update-images', |
| | | () => this.updateImageSizes(), |
| | | 250 |
| | | ); |
| | | }); |
| | | } else { |
| | | window.addEventListener('resize', window.debounce(()=> { |
| | | this.updateImageSizes(); |
| | | }, 250)); |
| | | window.addEventListener('resize', () => { |
| | | window.debouncer.schedule( |
| | | 'feed-update-images', |
| | | () => this.updateImageSizes(), |
| | | 250 |
| | | ); |
| | | }); |
| | | } |
| | | |
| | | window.addEventListener('popstate', this.popStateHandler); |
| | |
| | | document.addEventListener('change', this.changeHandler); |
| | | } |
| | | |
| | | loadImage(img) { |
| | | const src = this.getAppropriateImageSize(img); |
| | | if (src && src !== img.src) { |
| | | img.src = src; |
| | | img.dataset.loaded = 'true'; |
| | | } |
| | | } |
| | | |
| | | getAppropriateImageSize(img) { |
| | | const width = window.innerWidth; |
| | | |
| | | if (width < 768 && img.dataset.small) { |
| | | return img.dataset.small; |
| | | } else if (img.dataset.medium) { |
| | | return img.dataset.medium; |
| | | } |
| | | |
| | | return img.src; // Fallback to current src |
| | | } |
| | | |
| | | observeImages(container) { |
| | | const images = container.querySelectorAll('img[data-small], img[data-medium]'); |
| | | images.forEach(img => { |
| | | if (!img.dataset.loaded) { |
| | | this.imageObserver.observe(img); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | handlePopState(e) { |
| | | if (e.state?.filters) { |
| | | if (this.processURLFilters()) { |
| | |
| | | } |
| | | } |
| | | |
| | | document.addEventListener('DOMContentLoaded', function() { |
| | | window.feedBlock = new FeedBlock(); |
| | | document.addEventListener('DOMContentLoaded', async function() { |
| | | window.auth.subscribe(event => { |
| | | if (event === 'auth-loaded') { |
| | | window.feedBlock = new FeedBlock(); |
| | | } |
| | | }); |
| | | |
| | | let item = { |
| | | content: "art", |
| | | date: "2025-12-24 03:37:26", |
| | | fields: { |
| | | gallery: "", |
| | | post_content: "", |
| | | post_thumbnail: 200, |
| | | post_title: "Great Gray Owl", |
| | | price: "", |
| | | }, |
| | | icon: "arrows-clockwise", |
| | | id: 195, |
| | | images: { |
| | | 200: { |
| | | 'image-alt-text': "", |
| | | 'image-caption': "", |
| | | 'image-title': "Great Gray Owl", |
| | | large: "http://jakevan.test/wp-content/uploads/2025/12/Great-Gray-Owl.jpg", |
| | | medium: "http://jakevan.test/wp-content/uploads/2025/12/Great-Gray-Owl-1024x1024.jpg", |
| | | small: "http://jakevan.test/wp-content/uploads/2025/12/Great-Gray-Owl-300x300.jpg", |
| | | tiny: "http://jakevan.test/wp-content/uploads/2025/12/Great-Gray-Owl-50x50.jpg" |
| | | } |
| | | }, |
| | | url: "http://jakevan.test/art/great-gray-owl/", |
| | | user_id: 3 |
| | | }; |
| | | }); |
| | | |
| | | |