| | |
| | | class FeedBlock { |
| | | constructor() { |
| | | this.cache = window.jvbCache; |
| | | this.a11y = window.jvbA11y; |
| | | this.loading = window.jvbLoading; |
| | | this.error = window.jvbError; |
| | | |
| | | |
| | | this.container = document.querySelector('section.feed-block'); |
| | | if (!this.container) { |
| | | return; |
| | | } |
| | | if(!this.container) return; |
| | | |
| | | this.openGallery = false; |
| | | this.a11y = window.jvbA11y; |
| | | this.error = window.jvbError; |
| | | this.cache = new window.jvbCache('feed'); |
| | | this.templates = window.jvbTemplates; |
| | | |
| | | this.initElements(); |
| | | this.addPlaceholders(); |
| | | this.config = { |
| | | api: feedSettings.apiUrl, |
| | | nonce: feedSettings.nonce, |
| | | user: jvbSettings.currentUser || null, |
| | | source: '', |
| | | context: '', |
| | | highlight: null, |
| | | gallery: false, |
| | | showAuthor: true, |
| | | showDate: false, |
| | | view: localStorage.getItem('feedViewMode') || 'grid', |
| | | view: this.cache.get('feedView') || 'grid', |
| | | ... this.container.dataset |
| | | }; |
| | | this.taxonomies = {}; |
| | | this.rendered = {}; |
| | | |
| | | this.feed = { |
| | | imageLoadThreshold: 5, |
| | | lazyLoadOffset: '100px', |
| | | gallery: [], |
| | | loaded: 0, |
| | | intsersectionObserver: null, |
| | | templates: new Map() |
| | | }; |
| | | |
| | | this.isLoading = false; |
| | | this.hasMore = true; |
| | | this.retries = { |
| | | count: 0, |
| | | max: 3, |
| | | delay: 1000 |
| | | }; |
| | | this.page = 1; |
| | | this.order = 'DESC'; |
| | | this.orderby = 'date'; |
| | | |
| | | this.gallery = (this.config.gallery) ? new window.jvbGallery(document.querySelector('dialog.gallery'), { |
| | | imageWrapper: '.item', |
| | | loadMore: ()=>this.fetchFeed.bind(this) |
| | | }) : false; |
| | | this.initListeners(); |
| | | if (this.page === 1) { |
| | | this.processURLFilters(); |
| | | } else { |
| | | this.updateFilters(); |
| | | } |
| | | |
| | | this.init(); |
| | | } |
| | | init() { |
| | | this.initElements(); |
| | | this.defineTemplates(); |
| | | this.initListeners(); |
| | | this.initFilters(); |
| | | |
| | | if ('requestIdleCallback' in window) { |
| | | requestIdleCallback(() => { |
| | | this.initStore(); |
| | | this.initTaxonomies(); |
| | | |
| | | this.processCachedFilters(); |
| | | this.processURLFilters(); |
| | | this.updateFilterUI(); |
| | | this.initGallery(); |
| | | }, { timeout: 2000 }); |
| | | } else { |
| | | setTimeout(() => { |
| | | this.initStore(); |
| | | this.initTaxonomies(); |
| | | |
| | | this.processCachedFilters(); |
| | | this.processURLFilters(); |
| | | this.updateFilterUI(); |
| | | this.initGallery(); |
| | | }, 100); |
| | | } |
| | | } |
| | | |
| | | initElements() { |
| | | this.filterSelector = 'form.feed-filters'; |
| | | this.filterForm = this.container.querySelector(this.filterSelector); |
| | | this.grid = this.container.querySelector('.item-grid'); |
| | | this.loadMore = this.container.querySelector('.load-more'); |
| | | this.filterControls = this.container.querySelector('.filter-actions'); |
| | | this.contentTypes = Array.from(this.filterForm.querySelectorAll('input[name="content"]')).map( |
| | | content => { |
| | | return content.value; |
| | | }); |
| | | this.selectedTerms = this.container.querySelector('.selected-items-section .selected-items'); |
| | | this.selectors = { |
| | | filterTrigger: '[data-filter]', |
| | | filters: { |
| | | actions: '.filter-actions .toggle-text', |
| | | container: '.all-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"]', |
| | | }, |
| | | grid: '.item-grid', |
| | | selected: '.selected-items', |
| | | buttons: { |
| | | loadMore: 'button.load-more', |
| | | remove: '.remove-term', |
| | | clearFilters: 'button.clear-filters', |
| | | refresh: 'button[data-action="refresh"]' |
| | | } |
| | | }; |
| | | this.ui = window.uiFromSelectors(this.selectors, this.container); |
| | | this.ui.buttons.refresh = document.querySelector(this.selectors.buttons.refresh); |
| | | |
| | | //Add content and taxonomies |
| | | this.ui.content = this.ui.filters.container.querySelectorAll('[name="content"]'); |
| | | if (this.ui.content.length === 0) this.ui.content = false; |
| | | this.ui.taxonomies = this.ui.filters.container.querySelectorAll('[data-taxonomy]'); |
| | | if (this.ui.taxonomies.length === 0) this.ui.taxonomies = false; |
| | | this.ui.orderbyWrap = this.ui.filters.container.querySelector('[data-for-order]'); |
| | | if (this.ui.orderbyWrap.length === 0) this.ui.orderbyWrap = false; |
| | | this.ui.order = this.ui.filters.container.querySelectorAll('[data-filter="order"]'); |
| | | if (this.ui.order.length === 0) this.ui.order = false; |
| | | this.ui.orderby = this.ui.filters.container.querySelectorAll('[data-filter="orderby"]'); |
| | | if (this.ui.orderby.length === 0) this.ui.orderby = false; |
| | | |
| | | this.orderbyFilters = (this.ui.orderby) |
| | | ? Array.from(this.ui.orderby).map(o => o.value) |
| | | : []; |
| | | |
| | | this.contentTypes = (this.ui.content) |
| | | ? Array.from(this.ui.content).map(c => c.value) |
| | | : [this.container.dataset.content]; |
| | | this.taxonomies = (this.ui.taxonomies?.length > 0) |
| | | ? Array.from(this.ui.taxonomies).map(t => t.dataset.taxonomy) |
| | | : []; |
| | | } |
| | | |
| | | initListeners() { |
| | | window.addEventListener('popstate', this.handlePopState.bind(this)); |
| | | document.addEventListener('click', this.handleClick.bind(this)); |
| | | document.addEventListener('change', this.handleChange.bind(this)); |
| | | this.popStateHandler = this.handlePopState.bind(this); |
| | | this.clickHandler = this.handleClick.bind(this); |
| | | this.changeHandler = this.handleChange.bind(this); |
| | | |
| | | window.addEventListener('popstate', this.popStateHandler); |
| | | document.addEventListener('click', this.clickHandler); |
| | | document.addEventListener('change', this.changeHandler); |
| | | } |
| | | |
| | | // Intersection observer for lazy loading |
| | | if ('IntersectionObserver' in window) { |
| | | this.imageObserver = new IntersectionObserver(entries => { |
| | | entries.forEach(entry => { |
| | | if (entry.isIntersecting) { |
| | | this.loadImage(entry.target); |
| | | this.imageObserver.unobserve(entry.target); |
| | | initFilters() { |
| | | this.allowedFilters = ['content', 'order', 'orderby', 'favourites', 'match']; |
| | | let defaults = { |
| | | content: this.contentTypes[0], |
| | | orderby: 'date', |
| | | order: 'desc', |
| | | page: 1, |
| | | }; |
| | | if (this.config.context) defaults.context = this.config.context; |
| | | if (this.config.source) defaults.source = this.config.source; |
| | | |
| | | this.filters = defaults; |
| | | this.defaults = {...defaults}; |
| | | } |
| | | updateFilterUI() { |
| | | if (this.ui.filters.container) { |
| | | //Get cached inputs |
| | | let groups = [ |
| | | this.ui.content, |
| | | this.ui.orderby, |
| | | this.ui.order |
| | | ]; |
| | | |
| | | groups.forEach(group => { |
| | | if(group) { |
| | | for (let input of group) { |
| | | let [filter, value] = [input.dataset.filter, input.value]; |
| | | if (!Object.hasOwn(this.store.filters, filter)) break; |
| | | let doit = this.store.filters[filter] === value; |
| | | if (doit) { |
| | | input.checked = doit; |
| | | break; |
| | | } |
| | | } |
| | | }); |
| | | }, { |
| | | rootMargin: '100px', |
| | | threshold: 0.1 |
| | | } |
| | | }); |
| | | } |
| | | // Resize observer for responsive images |
| | | if ('ResizeObserver' in window) { |
| | | this.resizeObserver = new ResizeObserver(window.debounce(() => { |
| | | this.updateImageSizes(); |
| | | }, 250)); |
| | | |
| | | // Observe the container |
| | | this.resizeObserver.observe(this.container); |
| | | } else { |
| | | // Fallback to window resize |
| | | window.addEventListener('resize', window.debounce(() => { |
| | | this.updateImageSizes(); |
| | | }, 250)); |
| | | } |
| | | |
| | | this.taxonomies = {}; |
| | | this.container.querySelectorAll('.jvb-selector:not([hidden])').forEach(selector => { |
| | | let taxonomy = selector.dataset.taxonomy; |
| | | if (!Object.hasOwn(this.taxonomies, taxonomy)) { |
| | | this.taxonomies[taxonomy] = new window.jvbTaxonomySelector( |
| | | selector, |
| | | { |
| | | multiple: true, |
| | | feed: true, |
| | | selected: {}, |
| | | onClose: () => this.setSelectedTerms(taxonomy), |
| | | } |
| | | ); |
| | | if (Object.hasOwn(this.store.filters, 'taxonomy')) { |
| | | for (let [taxonomy, terms] of Object.entries(this.store.filters.taxonomy)) { |
| | | terms.forEach(termId => { |
| | | termId = parseInt(termId); |
| | | const term = this.selector.store.get(termId); |
| | | if (term) { |
| | | this.createTermElement(termId); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Handle browser history navigation |
| | | */ |
| | | handlePopState(e) { |
| | | if (e.state && e.state.filters) { |
| | | if(this.processURLFilters()){ |
| | | // Load items with updated filters |
| | | this.resetPage(); |
| | | this.fetchFeed(); |
| | | |
| | | // Announce to screen readers |
| | | this.a11y.announce('Feed filters updated from browser history.'); |
| | | if (e.state?.filters) { |
| | | if (this.processURLFilters()) { |
| | | this.store.setFilters(this.filters); |
| | | this.a11y.announce('Feed filters updated from browser history'); |
| | | } |
| | | } |
| | | } |
| | | |
| | | processURLFilters() { |
| | | const params = new URLSearchParams(window.location.search); |
| | | //No parameters to process |
| | | if (!params.toString()) { |
| | | this.updateFilters(); |
| | | return; |
| | | } |
| | | |
| | | let filters = ['content', 'order', 'orderby', 'favourites','match']; |
| | | |
| | | filters.forEach(filter => { |
| | | let value = params.get('f_'+filter); |
| | | params.delete('f_'+filter); |
| | | if (value && this.filterForm.querySelector(`input[name="${filter}"][value="${value}"]`)) { |
| | | this.filterForm.querySelector(`input[name="${filter}"][value="${value}"]`).checked = true; |
| | | } |
| | | }); |
| | | |
| | | let unprocessed = {}; |
| | | for (var [key, value] of Object.entries(Object.fromEntries(params))) { |
| | | |
| | | key = key.replace('f_',''); |
| | | if (this.contentTypes.includes(key)) { |
| | | this.openGallery = value; |
| | | } else { |
| | | this.taxonomies[key].addTermsFromURL(value); |
| | | this.setSelectedTerms(key); |
| | | } |
| | | } |
| | | |
| | | this.updateFilters(); |
| | | } |
| | | |
| | | handleClick(e) { |
| | | if (e.target.classList.contains('load-more') || e.target.closest('.load-more')) { |
| | | this.fetchFeed(false); |
| | | e.target.disabled = true; |
| | | } else if (e.target.classList.contains('clear-filters') || e.target.closest('.clear-filters')) { |
| | | this.resetFilters(); |
| | | } else if (this.config.gallery && e.target.closest('.feed-image')) { |
| | | this.gallery.handleGalleryOpen(e); |
| | | } else if (e.target.classList.contains('.remove-item') || e.target.closest('.remove-item')) { |
| | | let tag = e.target.closest('.selected-item'); |
| | | let taxonomy = tag.dataset.taxonomy; |
| | | this.taxonomies[taxonomy].removeSelectedTerm(tag.dataset.id); |
| | | this.setSelectedTerms(taxonomy); |
| | | this.updateFilters(); |
| | | if (window.targetCheck(e, this.selectors.buttons.loadMore)) { |
| | | this.nextPage(); |
| | | } else if (window.targetCheck(e, this.selectors.buttons.clearFilters)) { |
| | | this.clearFilters(); |
| | | } |
| | | let remove = window.targetCheck(e, this.selectors.buttons.remove); |
| | | if (remove) { |
| | | this.removeSelectedTerm(remove); |
| | | } |
| | | |
| | | let refresh = window.targetCheck(e, this.selectors.buttons.refresh); |
| | | if (refresh) { |
| | | this.store.clearCache(); |
| | | this.store.fetch(); |
| | | } |
| | | |
| | | let orderbyButton = window.targetCheck(e, '[data-filter="orderby"]'); |
| | | if (orderbyButton && orderbyButton.value === 'random' && orderbyButton.checked) { |
| | | // Already selected random, just re-render to trigger new shuffle |
| | | this.renderItems(); |
| | | } |
| | | } |
| | | |
| | | nextPage() { |
| | | const nextPage = (this.store.filters.page || 1) + 1; |
| | | const maxPage = this.store.lastResponse?.pages || nextPage; |
| | | this.store.setFilters({ page: Math.min(nextPage, maxPage) }); |
| | | } |
| | | |
| | | handleChange(e) { |
| | | if (e.target.closest(this.filterSelector)) { |
| | | this.resetPage(); |
| | | window.removeChildren(this.grid); |
| | | this.addPlaceholders(); |
| | | //update filters |
| | | this.updateFilters(); |
| | | } |
| | | } |
| | | |
| | | updateFilters() { |
| | | this.page = 1; |
| | | const params = new URLSearchParams(window.location.search); |
| | | |
| | | let filters = Object.fromEntries(new FormData(this.filterForm)); |
| | | |
| | | let contents = []; |
| | | for (let [key, value] of Object.entries(filters)) { |
| | | let set = false; |
| | | switch (key) { |
| | | const target = e.target; |
| | | if (Object.hasOwn(target.dataset, 'filter')) { |
| | | if (this.allowedFilters.includes(target.dataset.filter)) { |
| | | let filters = {}; |
| | | filters[target.dataset.filter] = target.value; |
| | | this.resetFilters(filters); |
| | | } |
| | | switch (target.dataset.filter) { |
| | | case 'content': |
| | | if (value !== this.contentTypes[0]) { |
| | | set = true; |
| | | } else { |
| | | params.delete('f_'+key); |
| | | } |
| | | this.updateContentFor(target.value); |
| | | break; |
| | | case 'orderby': |
| | | if (value !== 'date') { |
| | | set = true; |
| | | } |
| | | this.updateOrderOptions(target.value); |
| | | break; |
| | | case 'order': |
| | | if (value !== 'desc') { |
| | | set = true; |
| | | } |
| | | break; |
| | | default: |
| | | set = true; |
| | | } |
| | | if (!set) { |
| | | params.delete('f_'+key); |
| | | } |
| | | } |
| | | |
| | | clearFilters() { |
| | | this.taxFilters = {}; |
| | | window.removeChildren(this.ui.selected); |
| | | |
| | | this.taxonomies.forEach(tax => { |
| | | let fieldId = this.getFieldId(tax); |
| | | this.selector.selectedTerms.get(fieldId)?.clear(); |
| | | }); |
| | | |
| | | this.store.setFilters({ |
| | | ...this.defaults, |
| | | taxonomy: null |
| | | }); |
| | | |
| | | this.updateURL(); |
| | | this.saveToCacheFilters(); |
| | | } |
| | | |
| | | resetFilters(filters) { |
| | | filters = { |
| | | ...this.store.filters, |
| | | page: 1, |
| | | ... filters |
| | | } |
| | | this.store.setFilters(filters); |
| | | |
| | | this.updateURL(); |
| | | this.saveToCacheFilters(); |
| | | } |
| | | |
| | | getFieldId(taxonomy) { |
| | | return this.selector.getFieldId(Array.from(this.ui.taxonomies).filter(tax => tax.dataset.taxonomy === taxonomy)[0]??null); |
| | | } |
| | | removeSelectedTerm(button) { |
| | | const termId = parseInt(button.dataset.id); |
| | | const taxonomy = button.dataset.taxonomy; |
| | | |
| | | if (Object.hasOwn(this.taxFilters, taxonomy)){ |
| | | this.taxFilters[taxonomy] = this.taxFilters[taxonomy] |
| | | .filter(id => id !== termId); |
| | | if (this.taxFilters[taxonomy].length === 0) { |
| | | delete this.taxFilters[taxonomy]; |
| | | } |
| | | } |
| | | button.remove(); |
| | | |
| | | |
| | | if (set && value !== false && value !== '') { |
| | | params.set('f_'+key, value); |
| | | } |
| | | if (value !== '') { |
| | | contents.push(value); |
| | | } |
| | | |
| | | const newURL = `${window.location.pathname}?${params.toString()}`; |
| | | history.pushState(filters, '', newURL); |
| | | |
| | | // Find the fieldId for this taxonomy |
| | | const field = this.getFieldId(taxonomy); |
| | | if (field) { |
| | | this.selector.activeField = field; |
| | | // Notify selector to remove from its selectedTerms |
| | | this.selector.removeSelected(termId, field); |
| | | } |
| | | |
| | | this.filters = filters; |
| | | this.updateContentFor(filters.content); |
| | | this.resetFilters({ |
| | | taxonomy: Object.keys(this.taxFilters).length > 0 |
| | | ? this.taxFilters |
| | | : null |
| | | }); |
| | | } |
| | | |
| | | this.updateFilterControls(); |
| | | |
| | | this.loading.setContent(contents); |
| | | this.fetchFeed(true); |
| | | updateContentFor(content) { |
| | | let checkIt = [ |
| | | this.ui.taxonomies, |
| | | this.ui.orderby |
| | | ]; |
| | | checkIt.forEach(check => { |
| | | if (!check) return; |
| | | check.forEach(button => { |
| | | const forTypes = button.dataset.for?.split(',')??[]; |
| | | button.hidden = forTypes.length > 0 && !forTypes.includes(content); |
| | | if (button.hidden && button.checked) { |
| | | button.checked = false; |
| | | } |
| | | }); |
| | | }); |
| | | } |
| | | updateOrderOptions(order) { |
| | | if (this.ui.orderbyWrap) { |
| | | let options = this.ui.orderbyWrap.dataset.forOrder.split(',')??[]; |
| | | this.ui.orderbyWrap.hidden = !options.includes(order); |
| | | } |
| | | } |
| | | |
| | | updateFilterControls() { |
| | | this.filterControls.hidden = this.selectedTerms.children.length < 2; |
| | | const keys = Object.keys(this.taxFilters); |
| | | if (this.ui.buttons.clearFilters) { |
| | | this.ui.buttons.clearFilters.hidden = keys.length === 0; |
| | | } |
| | | if (this.ui.filters.actions) { |
| | | this.ui.filters.actions.hidden = keys.length <= 1; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Toggles taxonomy selectors and certain order/orderby options |
| | | * depending on current content |
| | | * @param content |
| | | */ |
| | | updateContentFor(content) { |
| | | this.filterForm.querySelectorAll('.jvb-selector').forEach(tax => { |
| | | let hasContent = tax.dataset.for.includes(content); |
| | | tax.hidden = !hasContent; |
| | | if (!hasContent) { |
| | | let t = tax.dataset.taxonomy; |
| | | this.clearSelectedTerms(t); |
| | | async initTaxonomies() { |
| | | this.taxFilters = {}; |
| | | this.selector = window.jvbSelector; |
| | | // this.selector.scanExistingFields(this.ui.filters.container); |
| | | // this.taxonomies.map(tax => this.selector.batchFetch.add(tax)); |
| | | // this.selector.batchFetchTaxonomies(); |
| | | this.selector.subscribe((event, data) => { |
| | | switch (event) { |
| | | case 'selected-terms': |
| | | |
| | | this.handleTaxonomyChange(data); |
| | | break; |
| | | |
| | | } |
| | | }); |
| | | this.filterForm.querySelectorAll('input[data-for]').forEach(toggle => { |
| | | toggle.hidden = !toggle.dataset.for.includes(content); |
| | | } |
| | | handleTaxonomyChange(data) { |
| | | const {terms, taxonomy } = data; |
| | | if (terms.size === 0) return; |
| | | this.taxFilters[taxonomy] = Array.from(terms); |
| | | this.resetFilters({ taxonomy: this.taxFilters }); |
| | | |
| | | terms.forEach(t => { |
| | | this.createTermElement(t); |
| | | }); |
| | | this.filterForm.querySelectorAll('input[name="order"]').forEach(order => { |
| | | order.hidden = this.filters.order === 'random'; |
| | | this.updateFilterControls(); |
| | | } |
| | | getTaxonomyIcon(taxonomy) { |
| | | let iconButton = Array.from(this.ui.taxonomies) |
| | | .find(t => t.dataset.taxonomy === taxonomy); |
| | | return iconButton?.dataset.icon.trim() || 'tag'; |
| | | } |
| | | createTermElement(termId) { |
| | | const term = this.selector.store.get(termId); |
| | | if (!term) return; |
| | | if (this.ui.selected.querySelector(`[data-id="${termId}"]`)) return; |
| | | |
| | | term.icon = this.getTaxonomyIcon(term.taxonomy); |
| | | this.ui.selected.append(this.templates.create('feedTerm', term)); |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | clearSelectedTerms(taxonomy) { |
| | | this.filterForm.querySelector(`input[name="${taxonomy}"]`).value = ''; |
| | | if (Object.hasOwn(this.taxonomies, taxonomy)) { |
| | | this.taxonomies[taxonomy].selectedItems = {}; |
| | | processURLFilters() { |
| | | if (!this.isFirstPage()) return false; |
| | | const params = new URLSearchParams(window.location.search); |
| | | if (!params.toString()) return false; |
| | | let shouldUpdate = false; |
| | | this.allowedFilters.forEach(filter => { |
| | | let value = params.get(`f_${filter}`); |
| | | if (value) { |
| | | shouldUpdate = true; |
| | | this.filters[filter] = value; |
| | | } |
| | | }); |
| | | |
| | | let hasTax = false; |
| | | params.forEach((value, key) => { |
| | | if (key.startsWith('f_tax_')) { |
| | | hasTax = true; |
| | | shouldUpdate = true; |
| | | const taxonomy = key.replace('f_tax_',''); |
| | | this.taxFilters[taxonomy] = value.split(',').map(Number); |
| | | } |
| | | }); |
| | | if (shouldUpdate) { |
| | | if (hasTax) { |
| | | this.filters.taxonomy = this.taxFilters; |
| | | } |
| | | this.resetFilters(this.filters); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | setSelectedTerms(taxonomy) { |
| | | let input = this.filterForm.querySelector(`input[name="${taxonomy}"]`); |
| | | input.value = ''; |
| | | let selected = this.taxonomies[taxonomy].selectedTerms; |
| | | if (!window.isEmptyObject(selected)) { |
| | | let ids = Object.keys(selected); |
| | | input.value = ids.join(','); |
| | | } |
| | | this.updateFilters(); |
| | | } |
| | | updateURL() { |
| | | const params = new URLSearchParams(); |
| | | this.allowedFilters.forEach(key => { |
| | | if (Object.hasOwn(this.store.filters, key) && this.store.filters[key] !== this.defaults[key]) { |
| | | params.set(`f_${key}`, this.store.filters[key]); |
| | | } |
| | | }); |
| | | |
| | | |
| | | nextPage() { |
| | | if (this.hasMore) { |
| | | this.page++; |
| | | } |
| | | } |
| | | resetPage() { |
| | | this.page = 1; |
| | | this.hasMore = true; |
| | | } |
| | | resetState() { |
| | | this.resetPage(true); |
| | | this.isLoading = false; |
| | | this.retries = { |
| | | count: 0, |
| | | max: 3, |
| | | delay: 1000 |
| | | }; |
| | | } |
| | | |
| | | resetFilters() { |
| | | this.filterForm.reset(); |
| | | //check the first content |
| | | this.filterForm.querySelector('input[name="content"]').checked = true; |
| | | this.filterForm.querySelector('input[name="orderby"][value="date"]').checked = true; |
| | | this.page = 1; |
| | | this.updateFilters(); |
| | | } |
| | | |
| | | |
| | | buildFilterRequest() { |
| | | |
| | | let filters = {}; |
| | | |
| | | for (let [filter, value] of Object.entries(this.filters)) { |
| | | if (value !== false && value !== '') { |
| | | filters[filter] = value; |
| | | for (let [tax, terms] of Object.entries(this.taxFilters)) { |
| | | if (terms.length > 0) { |
| | | params.set(`f_tax_${tax}`, terms.join(',')); |
| | | } |
| | | } |
| | | filters.page = parseInt(this.page); |
| | | if (this.container.dataset.context) { |
| | | filters.context = this.container.dataset.context; |
| | | |
| | | const newURL = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}`; |
| | | const currentURL = window.location.pathname + window.location.search; // Change this line |
| | | |
| | | if (newURL !== currentURL) { |
| | | window.history.pushState({filters:this.store.filters}, '', newURL); |
| | | } |
| | | if (this.container.dataset.source) { |
| | | filters.source = this.container.dataset.source; |
| | | } |
| | | return new URLSearchParams(filters).toString(); |
| | | } |
| | | |
| | | async fetchFeed(reset = false, force = false) { |
| | | if (this.isLoading) { |
| | | return false; |
| | | } |
| | | this.loading.showLoading(this.filters); |
| | | try { |
| | | if (this.page === 1) { |
| | | window.removeChildren(this.grid); |
| | | this.addPlaceholders(); |
| | | } |
| | | saveToCacheFilters() { |
| | | Object.keys(this.store.filters).forEach(filter => { |
| | | const cacheKey = `${this.config.source}_${this.config.context}_${filter}`; |
| | | |
| | | const data = await this.cache.fetchWithCache( |
| | | `${this.config.api}feed?${this.buildFilterRequest()}`, |
| | | { |
| | | method: 'GET', |
| | | }, |
| | | { |
| | | context: 'feed', |
| | | forceRefresh: true |
| | | // forceRefresh: force |
| | | } |
| | | ); |
| | | |
| | | //Handle empty results |
| | | if (!data || !data.items || data.items.length === 0) { |
| | | if (this.page === 1) { |
| | | this.showEmptyState(); |
| | | } |
| | | this.hasMore = false; |
| | | return false; |
| | | if (this.store.filters[filter] !== this.defaults[filter]) { |
| | | this.cache.set(cacheKey, this.store.filters[filter]); |
| | | } else { |
| | | this.hasMore = data['has_more']; |
| | | this.cache.remove(cacheKey); |
| | | } |
| | | }); |
| | | |
| | | this.renderItems(data.items, this.page > 1); |
| | | const taxCacheKey = `${this.config.source}_${this.config.context}_taxonomy`; |
| | | if (Object.keys(this.taxFilters).length > 0) { |
| | | this.cache.set(taxCacheKey, this.taxFilters); |
| | | } else { |
| | | this.cache.remove(taxCacheKey); |
| | | } |
| | | } |
| | | |
| | | if (this.hasMore) { |
| | | initGallery() { |
| | | this.gallery = (this.config.gallery) ? window.jvbGallery : false; |
| | | if (this.gallery) { |
| | | this.gallery.subscribe((event, data) => { |
| | | if (event === 'load-more' && this.store.lastResponse?.has_more) { |
| | | this.nextPage(); |
| | | } |
| | | return true; |
| | | }); |
| | | } |
| | | } |
| | | initStore() { |
| | | let extraOrderby = this.orderbyFilters.filter(v => !['date','modified','title','random'].includes(v)); |
| | | let extraIndexes = []; |
| | | extraOrderby.forEach(orderby =>{ |
| | | extraIndexes.push({name:orderby, keyPath: orderby}); |
| | | }); |
| | | 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: 'date'}, |
| | | { name: 'modified', keyPath: 'modified'}, |
| | | { name: 'title', keyPath: 'title'}, |
| | | ... extraIndexes |
| | | ], |
| | | filters: this.filters, |
| | | TTL: 6 * 60 * 60 * 1000, //6 hours |
| | | showLoading: true, |
| | | required: 'content', |
| | | } |
| | | } catch (error) { |
| | | this.handleError(error); |
| | | } finally { |
| | | this.loading.hideLoading(); |
| | | if (this.openGallery !== false) { |
| | | this.gallery.openWhenReady = this.openGallery; |
| | | this.openGallery = false; |
| | | ); |
| | | |
| | | this.store = store.feed; |
| | | |
| | | this.store.subscribe((event, data) => { |
| | | switch (event) { |
| | | case 'data-loaded': |
| | | this.renderItems(data.items); |
| | | this.ui.buttons.loadMore.hidden = true; |
| | | if (this.store.lastResponse && this.store.lastResponse?.has_more) { |
| | | this.ui.buttons.loadMore.hidden = !this.store.lastResponse?.has_more??true; |
| | | } |
| | | break; |
| | | } |
| | | this.loadMore.disabled = false; |
| | | this.loadMore.hidden = !this.hasMore; |
| | | }); |
| | | } |
| | | |
| | | isFirstPage() { |
| | | return this.store.filters.page === 1; |
| | | } |
| | | |
| | | renderItems(items = null) { |
| | | items = items??this.store.getFiltered(); |
| | | if (this.isFirstPage()) { |
| | | window.removeChildren(this.ui.grid); |
| | | } |
| | | if (items.length === 0) { |
| | | this.showEmptyState(); |
| | | this.a11y.announceItems(0, this.isFirstPage()); |
| | | } else { |
| | | window.chunkIt( |
| | | items, |
| | | (item) => this.createItemElement(item), |
| | | (fragment) => { |
| | | this.removePlaceholders(); |
| | | this.ui.grid.append(fragment); |
| | | if (this.config.gallery) this.gallery.buildGalleryItems('.item img'); |
| | | this.a11y.makeNavigable(this.ui.grid.querySelectorAll('.item:not([data-keyboard-nav])')); |
| | | this.a11y.announceItems(items.length, !this.isFirstPage(), this.store.lastResponse?.has_more??false); |
| | | }, |
| | | 5 |
| | | ).then(()=>{}); |
| | | } |
| | | |
| | | this.updateFilterControls(); |
| | | } |
| | | |
| | | showEmptyState() { |
| | | window.removeChildren(this.ui.grid); |
| | | this.ui.grid.append(this.templates.create('emptyState')); |
| | | } |
| | | |
| | | createItemElement(item) { |
| | | if (typeof item !== 'object') { |
| | | item = this.store.get(item); |
| | | if (!item) return; |
| | | } |
| | | return this.templates.create(`feedItem${window.uppercaseFirst(item.content)}`, item); |
| | | } |
| | | 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; |
| | | |
| | | let title = window.decodeHTMLEntities(term.title); |
| | | |
| | | [ |
| | | link.href, |
| | | link.title, |
| | | link.textContent |
| | | ] = [ |
| | | term.url, |
| | | `See more ${title}`, |
| | | 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 = window.decodeHTMLEntities(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.number - 1} Tx`; |
| | | } |
| | | if (number) { |
| | | number.textContent = item.number - 1; |
| | | } |
| | | 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() { |
| | | if (this.grid.querySelector('.placeholder')) { |
| | | window.removeChildren(this.grid); |
| | | const placeholders = this.ui.grid.querySelectorAll('.placeholder'); |
| | | if (placeholders.length > 0) { |
| | | placeholders.forEach(p => p.remove()); |
| | | } |
| | | } |
| | | showEmptyState() { |
| | | window.removeChildren(this.grid); |
| | | let template = window.getTemplate('emptyState'); |
| | | let isFavourite = Object.hasOwn(this.filters, 'favourites') && this.filters.favourites === true; |
| | | if (isFavourite) { |
| | | [ |
| | | template.querySelector('h3').textContent, |
| | | template.querySelector('p:first-of-type').textContent, |
| | | template.querySelector('p:last-of-type').textContent, |
| | | ] = [ |
| | | '♡ BLANK CANVAS ♡', |
| | | 'You haven\'t fallen in love with any pieces... yet!', |
| | | 'Hit that heart icon when something stops your scroll — your dream collection is waiting to start.' |
| | | ]; |
| | | } |
| | | this.grid.append(template); |
| | | this.a11y.announceEmpty(isFavourite); |
| | | |
| | | } |
| | | handleError(error){ |
| | | return this.error.handleApiError( |
| | | error, |
| | | { |
| | | component: 'Feed Block', |
| | | action: 'loaditems' |
| | | defineTemplates() { |
| | | const T = this.templates; |
| | | const f = this; |
| | | |
| | | T.define('feedTerm', { |
| | | refs: { |
| | | icon: '.icon', |
| | | span: 'span' |
| | | }, |
| | | () => this.fetchFeed() |
| | | ); |
| | | } |
| | | |
| | | addPlaceholders() { |
| | | let total = this.contentTypes.length - 1; |
| | | for (let i = 0; i < 9; i++) { |
| | | let template = window.getTemplate('placeholderTemplate'); |
| | | let rand = Math.floor(Math.random()*total+1); |
| | | let icon = window.getIcon(this.contentTypes[rand]).cloneNode(true); |
| | | |
| | | template.append(icon); |
| | | this.grid.append(template); |
| | | } |
| | | } |
| | | renderItems(items, append = false) { |
| | | //Clear the grid if we aren't appending |
| | | if (!append) { |
| | | window.removeChildren(this.grid); |
| | | this.addPlaceholders(); |
| | | } |
| | | |
| | | |
| | | //Bail early if no items |
| | | if (items.length === 0) { |
| | | this.a11y.announceUpdate(0, append); |
| | | return; |
| | | } |
| | | |
| | | //Use DocumentFragment for better performance |
| | | 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); |
| | | |
| | | this.imageObserver.observe(element); |
| | | } |
| | | |
| | | if (endIndex < items.length) { |
| | | requestAnimationFrame(() => { |
| | | processBatch(endIndex); |
| | | }); |
| | | } else { |
| | | this.removePlaceholders(); |
| | | //all batches are processed, append fragment |
| | | this.grid.appendChild(fragment); |
| | | if (this.config.gallery) { |
| | | this.gallery.updateGalleryItems(this.gallery.getGalleryItems()); |
| | | } |
| | | this.a11y.makeNavigable(this.grid.querySelectorAll('.item:not([data-keyboard-nav])')); |
| | | this.a11y.announceItems(items.length, append, this.hasMore); |
| | | } |
| | | }; |
| | | |
| | | if (items.length > 0) { |
| | | processBatch(0); |
| | | } else { |
| | | this.a11y.announceUpdate(0, append); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Creates a feed-item. Used by RenderItems |
| | | */ |
| | | createItemElement(item) { |
| | | if(!this.rendered[item.icon]) { |
| | | this.rendered[item.icon] = new Map(); |
| | | } |
| | | if (this.rendered[item.icon].has(item.id)) { |
| | | return this.rendered[item.icon].get(item.id); |
| | | } |
| | | |
| | | const favourited = window.isFavourited(item.icon, item.id)??false; |
| | | const template = window.getTemplate('feed-item'); |
| | | |
| | | template.id = `${item.icon}-${item.id}`; |
| | | template.dataset.id = item.id; |
| | | template.classList.add(item.icon); |
| | | |
| | | if (item['umami_view']) { |
| | | this.buildUmamiData(template, item['umami_view']); |
| | | } |
| | | |
| | | let favouriteButton = template.querySelector('button.favourite'); |
| | | [ |
| | | favouriteButton.dataset.id, |
| | | favouriteButton.dataset.type, |
| | | favouriteButton.dataset.artist, |
| | | favouriteButton.title |
| | | ] = [ |
| | | item.id, |
| | | item.icon, |
| | | item['user_id'], |
| | | (favourited) ? 'Remove from Favourites' : 'Add to Favourites' |
| | | ]; |
| | | |
| | | let order = item.order; |
| | | let single = template.querySelector('.item'); |
| | | let list = template.querySelector('.item-list'); |
| | | let img = template.querySelector('.feed-images'); |
| | | let summary = template.querySelector('summary'); |
| | | let info = template.querySelector('.item-info'); |
| | | |
| | | for (let [index, id] of Object.entries(order)) { |
| | | let target; |
| | | let config = item[id]; |
| | | if (id === 'title') { |
| | | target = template.querySelector('h3 a'); |
| | | if (item.title !== '') { |
| | | [ |
| | | target.textContent, |
| | | target.href, |
| | | target.url |
| | | ] = [ |
| | | item.title, |
| | | item.url, |
| | | `Learn more about this ${item.icon}` |
| | | ]; |
| | | if (item.icon !== '') { |
| | | target.closest('h3').prepend(window.getIcon(item.icon)); |
| | | } |
| | | if (item.umami_click) { |
| | | this.buildUmamiData(target, item.umami_click); |
| | | } |
| | | } else { |
| | | target.remove(); |
| | | } |
| | | } else if (Object.hasOwn(config, 'terms')) { |
| | | //Taxonomy list |
| | | if (config.terms.length === 0) { |
| | | continue; |
| | | } |
| | | let taxonomy = list.cloneNode(true); |
| | | let label = taxonomy.querySelector('.label'); |
| | | let termList = taxonomy.querySelector('ul'); |
| | | let listItem = taxonomy.querySelector('li'); |
| | | |
| | | if (config.label) { |
| | | label.textContent = config.label; |
| | | } |
| | | if (config.icon) { |
| | | label.prepend(window.getIcon(config.icon)); |
| | | } |
| | | if (!config.label && !config.icon){ |
| | | label.remove(); |
| | | } |
| | | |
| | | config.terms.forEach(term => { |
| | | let termItem = listItem.cloneNode(true); |
| | | let link = termItem.querySelector('a'); |
| | | [ |
| | | link.href, |
| | | link.title, |
| | | link.textContent |
| | | ] = [ |
| | | term.url, |
| | | `Learn more about ${term.title}`, |
| | | term.title |
| | | ]; |
| | | if (term.umami_click.length > 0) { |
| | | this.buildUmamiData(link, term.umami_click); |
| | | } |
| | | termList.append(termItem); |
| | | }); |
| | | |
| | | listItem.remove(); |
| | | info.appendChild(taxonomy); |
| | | } else if (Object.hasOwn(config, 'value') && config.value !== '') { |
| | | let itemInfo = single.cloneNode(true); |
| | | let label = itemInfo.querySelector('.label'); |
| | | let link = itemInfo.querySelector('a'); |
| | | let p = itemInfo.querySelector('p'); |
| | | if (Object.hasOwn(config, 'label')) { |
| | | label.textContent = config.label; |
| | | } |
| | | if (Object.hasOwn(config, 'icon')) { |
| | | label.prepend(window.getIcon(config.icon)); |
| | | } |
| | | if (!Object.hasOwn(config, 'icon') && !Object.hasOwn(config, 'label')) { |
| | | label.remove(); |
| | | } |
| | | if (Object.hasOwn(config, 'url')) { |
| | | p.remove(); |
| | | [ |
| | | link.textContent, |
| | | link.href, |
| | | link.title |
| | | ] = [ |
| | | config.value, |
| | | config.url, |
| | | `Learn more about ${config.value}` |
| | | ]; |
| | | } else { |
| | | link.remove(); |
| | | p.textContent = config.value; |
| | | } |
| | | info.appendChild(itemInfo); |
| | | } else if (id === 'image') { |
| | | let images = summary.querySelector('.feed-images'); |
| | | let img = images.querySelector('a'); |
| | | |
| | | let main = img.cloneNode(true); |
| | | if (!this.config.gallery) { |
| | | main.href = item.url; |
| | | } |
| | | |
| | | main.classList.add('feed-image'); |
| | | this.buildImageData(main.querySelector('img'), item.image); |
| | | images.append(main); |
| | | |
| | | if (item.content?.length > 0) { |
| | | images.classList.add('multi'); |
| | | item.content.forEach(c => { |
| | | let image = img.cloneNode(true); |
| | | if (!this.config.gallery) { |
| | | image.href = c.url; |
| | | } |
| | | let itemImg = image.querySelector('img'); |
| | | itemImg.src = c.image.small; |
| | | itemImg.alt = c.image.alt; |
| | | images.append(image); |
| | | }); |
| | | } |
| | | img.remove(); |
| | | } |
| | | } |
| | | single.remove(); |
| | | list.remove(); |
| | | |
| | | this.rendered[item.icon].set(item.id, template); |
| | | |
| | | return template; |
| | | } |
| | | |
| | | buildImageData(img, data){ |
| | | if (typeof data.tiny !== 'string') { |
| | | return; |
| | | } |
| | | [ |
| | | img.src, |
| | | img.dataset.small, |
| | | img.dataset.medium, |
| | | img.dataset.large, |
| | | img.alt |
| | | ] = |
| | | [ |
| | | data.tiny, |
| | | data.small, |
| | | data.medium, |
| | | data.large, |
| | | data.alt |
| | | ]; |
| | | } |
| | | |
| | | buildUmamiData(item, data){ |
| | | for(let [key, value] of Object.entries(data)){ |
| | | item.dataset[key] = value; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Load Image, used by renderItems |
| | | * @param element |
| | | */ |
| | | loadImage(element) { |
| | | const img = element.querySelector('img'); |
| | | if (!img) return; |
| | | const size = this.getImageSize(); |
| | | |
| | | img.src = img.dataset[size] || img.dataset.src; |
| | | element.setAttribute('data-loaded', 'true'); |
| | | } |
| | | |
| | | /** |
| | | * Updates the image size according to screen size |
| | | */ |
| | | updateImageSizes() { |
| | | const size = this.getImageSize(); |
| | | const items = this.grid.querySelectorAll('.item'); |
| | | items.forEach(item => { |
| | | const img = item.querySelector('img'); |
| | | if (img && img.dataset[size] && img.src !== img.dataset[size]) { |
| | | img.src = img.dataset[size]; |
| | | setup({el, refs, manyRefs, data}) { |
| | | el.dataset.id = data.id; |
| | | el.dataset.taxonomy = data.taxonomy; |
| | | if (refs.icon) refs.icon.className = `icon icon-${data.icon}`; |
| | | if (refs.span) refs.span.textContent = window.decodeHTMLEntities(data.name); |
| | | } |
| | | }); |
| | | } |
| | | /** |
| | | * Get image size based on screen width |
| | | */ |
| | | getImageSize() { |
| | | const width = window.innerWidth; |
| | | if (width > 1024) return 'medium'; |
| | | if (width > 500) return 'medium'; |
| | | return 'small'; |
| | | T.define('emptyState'); |
| | | |
| | | this.contentTypes.forEach(content => { |
| | | T.define(`feedItem${window.uppercaseFirst(content)}`, { |
| | | refs: { |
| | | link: 'a', |
| | | }, |
| | | manyRefs: { |
| | | fields: '[data-field]', |
| | | }, |
| | | setup({el, refs, manyRefs, data}) { |
| | | const isTimeline = Object.hasOwn(el.dataset, 'timeline'); |
| | | if (manyRefs.fields) { |
| | | for (let field of manyRefs.fields) { |
| | | if (isTimeline && ['timeline','number'].includes(field.dataset.field)) continue; |
| | | |
| | | const value = Object.hasOwn(data.fields, field.dataset.field)? data.fields[field.dataset.field] : false; |
| | | if (!value) { |
| | | field.remove(); |
| | | continue; |
| | | } |
| | | if (f.isImageField(data, value)) { |
| | | f.formatImageField(field, value, data); |
| | | } else if (f.isTaxonomyField(data, field.dataset.field)) { |
| | | f.formatTaxonomyField(field, data, field.dataset.field, value); |
| | | } else if (f.isTimeField(field)) { |
| | | f.formatTimeField(field, value); |
| | | } else { |
| | | f.formatField(field, value); |
| | | } |
| | | } |
| | | if (refs.link && data.url !== '') { |
| | | refs.link.href = data.url; |
| | | refs.link.title = `View ${data.fields['post_title']??'Item'}`; |
| | | } |
| | | if (isTimeline ) f.addTimelineElements(data, el); |
| | | } |
| | | } |
| | | }) |
| | | }); |
| | | } |
| | | |
| | | // 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); |
| | | // } |
| | | } |
| | | document.addEventListener('DOMContentLoaded', () => { |
| | | window.feedBlock = new FeedBlock(); |
| | | |
| | | document.addEventListener('DOMContentLoaded', async function() { |
| | | window.auth.subscribe(event => { |
| | | if (event === 'auth-loaded') { |
| | | window.feedBlock = new FeedBlock(); |
| | | } |
| | | }); |
| | | }); |