| | |
| | | (()=>{class e{constructor(){this.cache=window.jvbCache,this.a11y=window.jvbA11y,this.loading=window.jvbLoading,this.error=window.jvbError,this.container=document.querySelector("section.feed-block"),this.container&&(this.openGallery=!1,this.initElements(),this.addPlaceholders(),this.config={api:feedSettings.apiUrl,nonce:feedSettings.nonce,user:jvbSettings.currentUser||null,source:"",context:"",highlight:null,gallery:!1,showAuthor:!0,showDate:!1,view:localStorage.getItem("feedViewMode")||"grid",...this.container.dataset},this.taxonomies={},this.rendered={},this.feed={imageLoadThreshold:5,lazyLoadOffset:"100px",gallery:[],loaded:0,intsersectionObserver:null,templates:new Map},this.isLoading=!1,this.hasMore=!0,this.retries={count:0,max:3,delay:1e3},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)}),this.initListeners(),1===this.page?this.processURLFilters():this.updateFilters())}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((e=>e.value)),this.selectedTerms=this.container.querySelector(".selected-items-section .selected-items")}initListeners(){window.addEventListener("popstate",this.handlePopState.bind(this)),document.addEventListener("click",this.handleClick.bind(this)),document.addEventListener("change",this.handleChange.bind(this)),"IntersectionObserver"in window&&(this.imageObserver=new IntersectionObserver((e=>{e.forEach((e=>{e.isIntersecting&&(this.loadImage(e.target),this.imageObserver.unobserve(e.target))}))}),{rootMargin:"100px",threshold:.1})),"ResizeObserver"in window?(this.resizeObserver=new ResizeObserver(window.debounce((()=>{this.updateImageSizes()}),250)),this.resizeObserver.observe(this.container)):window.addEventListener("resize",window.debounce((()=>{this.updateImageSizes()}),250)),this.taxonomies={},this.container.querySelectorAll(".jvb-selector:not([hidden])").forEach((e=>{let t=e.dataset.taxonomy;Object.hasOwn(this.taxonomies,t)||(this.taxonomies[t]=new window.jvbTaxonomySelector(e,{multiple:!0,feed:!0,selected:{},onClose:()=>this.setSelectedTerms(t)}))}))}handlePopState(e){e.state&&e.state.filters&&this.processURLFilters()&&(this.resetPage(),this.fetchFeed(),this.a11y.announce("Feed filters updated from browser history."))}processURLFilters(){const e=new URLSearchParams(window.location.search);if(e.toString()){for(var[t,i]of(["content","order","orderby","favourites","match"].forEach((t=>{let i=e.get("f_"+t);e.delete("f_"+t),i&&this.filterForm.querySelector(`input[name="${t}"][value="${i}"]`)&&(this.filterForm.querySelector(`input[name="${t}"][value="${i}"]`).checked=!0)})),Object.entries(Object.fromEntries(e))))t=t.replace("f_",""),this.contentTypes.includes(t)?this.openGallery=i:(this.taxonomies[t].addTermsFromURL(i),this.setSelectedTerms(t));this.updateFilters()}else this.updateFilters()}handleClick(e){if(e.target.classList.contains("load-more")||e.target.closest(".load-more"))this.fetchFeed(!1),e.target.disabled=!0;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 t=e.target.closest(".selected-item"),i=t.dataset.taxonomy;this.taxonomies[i].removeSelectedTerm(t.dataset.id),this.setSelectedTerms(i),this.updateFilters()}}handleChange(e){e.target.closest(this.filterSelector)&&(this.resetPage(),window.removeChildren(this.grid),this.addPlaceholders(),this.updateFilters())}updateFilters(){this.page=1;const e=new URLSearchParams(window.location.search);let t=Object.fromEntries(new FormData(this.filterForm)),i=[];for(let[r,s]of Object.entries(t)){let a=!1;switch(r){case"content":s!==this.contentTypes[0]?a=!0:e.delete("f_"+r);break;case"orderby":"date"!==s&&(a=!0);break;case"order":"desc"!==s&&(a=!0);break;default:a=!0}a||e.delete("f_"+r),a&&!1!==s&&""!==s&&e.set("f_"+r,s),""!==s&&i.push(s);const o=`${window.location.pathname}?${e.toString()}`;history.pushState(t,"",o)}this.filters=t,this.updateContentFor(t.content),this.updateFilterControls(),this.loading.setContent(i),this.fetchFeed(!0)}updateFilterControls(){this.filterControls.hidden=this.selectedTerms.children.length<2}updateContentFor(e){this.filterForm.querySelectorAll(".jvb-selector").forEach((t=>{let i=t.dataset.for.includes(e);if(t.hidden=!i,!i){let e=t.dataset.taxonomy;this.clearSelectedTerms(e)}})),this.filterForm.querySelectorAll("input[data-for]").forEach((t=>{t.hidden=!t.dataset.for.includes(e)})),this.filterForm.querySelectorAll('input[name="order"]').forEach((e=>{e.hidden="random"===this.filters.order}))}clearSelectedTerms(e){this.filterForm.querySelector(`input[name="${e}"]`).value="",Object.hasOwn(this.taxonomies,e)&&(this.taxonomies[e].selectedItems={})}setSelectedTerms(e){let t=this.filterForm.querySelector(`input[name="${e}"]`);t.value="";let i=this.taxonomies[e].selectedTerms;if(!window.isEmptyObject(i)){let e=Object.keys(i);t.value=e.join(",")}this.updateFilters()}nextPage(){this.hasMore&&this.page++}resetPage(){this.page=1,this.hasMore=!0}resetState(){this.resetPage(!0),this.isLoading=!1,this.retries={count:0,max:3,delay:1e3}}resetFilters(){this.filterForm.reset(),this.filterForm.querySelector('input[name="content"]').checked=!0,this.filterForm.querySelector('input[name="orderby"][value="date"]').checked=!0,this.page=1,this.updateFilters()}buildFilterRequest(){let e={};for(let[t,i]of Object.entries(this.filters))!1!==i&&""!==i&&(e[t]=i);return e.page=parseInt(this.page),this.container.dataset.context&&(e.context=this.container.dataset.context),this.container.dataset.source&&(e.source=this.container.dataset.source),new URLSearchParams(e).toString()}async fetchFeed(e=!1,t=!1){if(this.isLoading)return!1;this.loading.showLoading(this.filters);try{1===this.page&&(window.removeChildren(this.grid),this.addPlaceholders());const e=await this.cache.fetchWithCache(`${this.config.api}feed?${this.buildFilterRequest()}`,{method:"GET"},{context:"feed",forceRefresh:!0});return e&&e.items&&0!==e.items.length?(this.hasMore=e.has_more,this.renderItems(e.items,this.page>1),this.hasMore&&this.nextPage(),!0):(1===this.page&&this.showEmptyState(),this.hasMore=!1,!1)}catch(e){this.handleError(e)}finally{this.loading.hideLoading(),!1!==this.openGallery&&(this.gallery.openWhenReady=this.openGallery,this.openGallery=!1),this.loadMore.disabled=!1,this.loadMore.hidden=!this.hasMore}}removePlaceholders(){this.grid.querySelector(".placeholder")&&window.removeChildren(this.grid)}showEmptyState(){window.removeChildren(this.grid);let e=window.getTemplate("emptyState"),t=Object.hasOwn(this.filters,"favourites")&&!0===this.filters.favourites;t&&([e.querySelector("h3").textContent,e.querySelector("p:first-of-type").textContent,e.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(e),this.a11y.announceEmpty(t)}handleError(e){return this.error.handleApiError(e,{component:"Feed Block",action:"loaditems"},(()=>this.fetchFeed()))}addPlaceholders(){let e=this.contentTypes.length-1;for(let t=0;t<9;t++){let t=window.getTemplate("placeholderTemplate"),i=Math.floor(Math.random()*e+1),r=window.getIcon(this.contentTypes[i]).cloneNode(!0);t.append(r),this.grid.append(t)}}renderItems(e,t=!1){if(t||(window.removeChildren(this.grid),this.addPlaceholders()),0===e.length)return void this.a11y.announceUpdate(0,t);const i=document.createDocumentFragment(),r=s=>{const a=Math.min(s+10,e.length);for(let t=s;t<a;t++){const r=e[t],s=this.createItemElement(r);i.appendChild(s),this.imageObserver.observe(s)}a<e.length?requestAnimationFrame((()=>{r(a)})):(this.removePlaceholders(),this.grid.appendChild(i),this.config.gallery&&this.gallery.updateGalleryItems(this.gallery.getGalleryItems()),this.a11y.makeNavigable(this.grid.querySelectorAll(".item:not([data-keyboard-nav])")),this.a11y.announceItems(e.length,t,this.hasMore))};e.length>0?r(0):this.a11y.announceUpdate(0,t)}createItemElement(e){var t;if(this.rendered[e.icon]||(this.rendered[e.icon]=new Map),this.rendered[e.icon].has(e.id))return this.rendered[e.icon].get(e.id);const i=null!==(t=window.isFavourited(e.icon,e.id))&&void 0!==t&&t,r=window.getTemplate("feed-item");r.id=`${e.icon}-${e.id}`,r.dataset.id=e.id,r.classList.add(e.icon),e.umami_view&&this.buildUmamiData(r,e.umami_view);let s=r.querySelector("button.favourite");[s.dataset.id,s.dataset.type,s.dataset.artist,s.title]=[e.id,e.icon,e.user_id,i?"Remove from Favourites":"Add to Favourites"];let a=e.order,o=r.querySelector(".item"),n=r.querySelector(".item-list"),l=(r.querySelector(".feed-images"),r.querySelector("summary")),d=r.querySelector(".item-info");for(let[t,i]of Object.entries(a)){let t,s=e[i];if("title"===i)t=r.querySelector("h3 a"),""!==e.title?([t.textContent,t.href,t.url]=[e.title,e.url,`Learn more about this ${e.icon}`],""!==e.icon&&t.closest("h3").prepend(window.getIcon(e.icon)),e.umami_click&&this.buildUmamiData(t,e.umami_click)):t.remove();else if(Object.hasOwn(s,"terms")){if(0===s.terms.length)continue;let e=n.cloneNode(!0),t=e.querySelector(".label"),i=e.querySelector("ul"),r=e.querySelector("li");s.label&&(t.textContent=s.label),s.icon&&t.prepend(window.getIcon(s.icon)),s.label||s.icon||t.remove(),s.terms.forEach((e=>{let t=r.cloneNode(!0),s=t.querySelector("a");[s.href,s.title,s.textContent]=[e.url,`Learn more about ${e.title}`,e.title],e.umami_click.length>0&&this.buildUmamiData(s,e.umami_click),i.append(t)})),r.remove(),d.appendChild(e)}else if(Object.hasOwn(s,"value")&&""!==s.value){let e=o.cloneNode(!0),t=e.querySelector(".label"),i=e.querySelector("a"),r=e.querySelector("p");Object.hasOwn(s,"label")&&(t.textContent=s.label),Object.hasOwn(s,"icon")&&t.prepend(window.getIcon(s.icon)),Object.hasOwn(s,"icon")||Object.hasOwn(s,"label")||t.remove(),Object.hasOwn(s,"url")?(r.remove(),[i.textContent,i.href,i.title]=[s.value,s.url,`Learn more about ${s.value}`]):(i.remove(),r.textContent=s.value),d.appendChild(e)}else if("image"===i){let t=l.querySelector(".feed-images"),i=t.querySelector("a"),r=i.cloneNode(!0);this.config.gallery||(r.href=e.url),r.classList.add("feed-image"),this.buildImageData(r.querySelector("img"),e.image),t.append(r),e.content?.length>0&&(t.classList.add("multi"),e.content.forEach((e=>{let r=i.cloneNode(!0);this.config.gallery||(r.href=e.url);let s=r.querySelector("img");s.src=e.image.small,s.alt=e.image.alt,t.append(r)}))),i.remove()}}return o.remove(),n.remove(),this.rendered[e.icon].set(e.id,r),r}buildImageData(e,t){"string"==typeof t.tiny&&([e.src,e.dataset.small,e.dataset.medium,e.dataset.large,e.alt]=[t.tiny,t.small,t.medium,t.large,t.alt])}buildUmamiData(e,t){for(let[i,r]of Object.entries(t))e.dataset[i]=r}loadImage(e){const t=e.querySelector("img");if(!t)return;const i=this.getImageSize();t.src=t.dataset[i]||t.dataset.src,e.setAttribute("data-loaded","true")}updateImageSizes(){const e=this.getImageSize();this.grid.querySelectorAll(".item").forEach((t=>{const i=t.querySelector("img");i&&i.dataset[e]&&i.src!==i.dataset[e]&&(i.src=i.dataset[e])}))}getImageSize(){const e=window.innerWidth;return e>1024||e>500?"medium":"small"}}document.addEventListener("DOMContentLoaded",(()=>{window.feedBlock=new e}))})(); |
| | | /******/ (() => { // webpackBootstrap |
| | | /*!**************************!*\ |
| | | !*** ./src/feed/view.js ***! |
| | | \**************************/ |
| | | class FeedBlock { |
| | | constructor() { |
| | | this.container = document.querySelector('section.feed-block'); |
| | | if (!this.container) return; |
| | | this.a11y = window.jvbA11y; |
| | | this.error = window.jvbError; |
| | | this.cache = new window.jvbCache('feed'); |
| | | this.templates = window.jvbTemplates; |
| | | this.config = { |
| | | contextId: '', |
| | | context: '', |
| | | highlight: null, |
| | | gallery: false, |
| | | view: this.cache.get('feedView') || 'grid', |
| | | ...this.container.dataset |
| | | }; |
| | | console.log(this.config); |
| | | 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.selectors = { |
| | | filterTrigger: '[data-filter]', |
| | | filters: { |
| | | actions: '.filter-actions .toggle-text', |
| | | container: '.all-filters', |
| | | showing: '.all-filters summary .current', |
| | | content: '[data-filter="content"]', |
| | | ordering: '.ordering', |
| | | orderby: '[data-filter="orderby"]', |
| | | order: '[data-filter="order"]', |
| | | orderWrap: '.order-direction', |
| | | 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 |
| | | let getAll = ['content', 'orderby', 'order', 'taxonomy']; |
| | | getAll.forEach(item => { |
| | | let items = this.ui.filters.container.querySelectorAll(this.selectors.filters[item]); |
| | | this.ui[item] = Array.from(items); |
| | | }); |
| | | this.contentTypes = this.ui.content.length > 0 ? 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) : []; |
| | | } |
| | | |
| | | /** |
| | | * |
| | | * @param {string} item |
| | | */ |
| | | getChecked(item) { |
| | | if (!['content', 'orderby', 'order'].includes(item)) { |
| | | console.log('Invalid item to check: ', item); |
| | | } |
| | | let items = this.ui[item]; |
| | | if (!items) { |
| | | return; |
| | | } |
| | | let checked = items.filter(i => i.checked); |
| | | if (item === 'content' && checked.length > 0) { |
| | | this.updateContentFor(checked[0].value); |
| | | } |
| | | return checked.length === 0 ? items[0].value : checked[0].value; |
| | | } |
| | | initListeners() { |
| | | 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); |
| | | } |
| | | initFilters() { |
| | | this.allowedFilters = ['content', 'order', 'orderby', 'favourites', 'match']; |
| | | let defaults = { |
| | | content: this.getChecked('content'), |
| | | orderby: this.getChecked('orderby'), |
| | | order: this.getChecked('order'), |
| | | page: 1 |
| | | }; |
| | | if (this.config.context) defaults.context = this.config.context; |
| | | if (this.config.contextId) defaults.contextId = this.config.contextId; |
| | | this.filters = defaults; |
| | | console.log(this.filters); |
| | | 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; |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | 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); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | 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.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) { |
| | | 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': |
| | | this.updateContentFor(target.value); |
| | | break; |
| | | case 'orderby': |
| | | this.updateOrderOptions(target.value); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | 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(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(); |
| | | |
| | | // 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.resetFilters({ |
| | | taxonomy: Object.keys(this.taxFilters).length > 0 ? this.taxFilters : null |
| | | }); |
| | | } |
| | | updateContentFor(content) { |
| | | let checkIt = [this.ui.taxonomies, this.ui.orderby]; |
| | | this.ui.filters.showing.textContent = this.ui.content.filter(c => c.value === content)[0].dataset.label; |
| | | 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.filters.orderWrap) { |
| | | let options = this.ui.filters.orderWrap.dataset.forOrder.split(',') ?? []; |
| | | this.ui.filters.orderWrap.hidden = !options.includes(order); |
| | | } |
| | | } |
| | | updateFilterControls() { |
| | | 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; |
| | | } |
| | | } |
| | | 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; |
| | | } |
| | | }); |
| | | } |
| | | 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.updateFilterControls(); |
| | | } |
| | | getTaxonomyIcon(taxonomy) { |
| | | let iconButton = 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.contextId}_${this.config.context}_${filter}`); |
| | | if (cached && cached !== this.filters[filter]) { |
| | | this.filters[filter] = cached; |
| | | } |
| | | }); |
| | | } |
| | | 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; |
| | | } |
| | | 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]); |
| | | } |
| | | }); |
| | | for (let [tax, terms] of Object.entries(this.taxFilters)) { |
| | | if (terms.length > 0) { |
| | | params.set(`f_tax_${tax}`, terms.join(',')); |
| | | } |
| | | } |
| | | 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); |
| | | } |
| | | } |
| | | saveToCacheFilters() { |
| | | Object.keys(this.store.filters).forEach(filter => { |
| | | const cacheKey = `${this.config.contextId}_${this.config.context}_${filter}`; |
| | | if (this.store.filters[filter] !== this.defaults[filter]) { |
| | | this.cache.set(cacheKey, this.store.filters[filter]); |
| | | } else { |
| | | this.cache.remove(cacheKey); |
| | | } |
| | | }); |
| | | const taxCacheKey = `${this.config.contextId}_${this.config.context}_taxonomy`; |
| | | if (Object.keys(this.taxFilters).length > 0) { |
| | | this.cache.set(taxCacheKey, this.taxFilters); |
| | | } else { |
| | | this.cache.remove(taxCacheKey); |
| | | } |
| | | } |
| | | 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(); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | initStore() { |
| | | let extraOrderby = this.ui.orderby.filter(v => !['date', 'date_modified', 'title', 'random'].includes(v.value)); |
| | | let extraIndexes = []; |
| | | extraOrderby.forEach(orderby => { |
| | | extraIndexes.push({ |
| | | name: orderby.value, |
| | | keyPath: orderby.value |
| | | }); |
| | | }); |
| | | 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' |
| | | }); |
| | | 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; |
| | | } |
| | | }); |
| | | } |
| | | 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() { |
| | | const placeholders = this.ui.grid.querySelectorAll('.placeholder'); |
| | | if (placeholders.length > 0) { |
| | | placeholders.forEach(p => p.remove()); |
| | | } |
| | | } |
| | | defineTemplates() { |
| | | const T = this.templates; |
| | | const f = this; |
| | | T.define('feedTerm', { |
| | | refs: { |
| | | icon: '.icon', |
| | | span: 'span' |
| | | }, |
| | | 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); |
| | | } |
| | | }); |
| | | 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', async function () { |
| | | window.auth.subscribe(event => { |
| | | if (event === 'auth-loaded') { |
| | | window.feedBlock = new FeedBlock(); |
| | | } |
| | | }); |
| | | }); |
| | | /******/ })() |
| | | ; |
| | | //# sourceMappingURL=view.js.map |