From b5abd615697146beeca6dba4acd057d049554a30 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Fri, 02 Jan 2026 00:16:00 +0000
Subject: [PATCH] Merge branch 'main' of https://github.com/jakevdwerf/jvb
---
src/feed/view.js | 306 ++++++++++++++++++++++++++++++++++----------------
1 files changed, 207 insertions(+), 99 deletions(-)
diff --git a/src/feed/view.js b/src/feed/view.js
index 710dd6c..7110cad 100644
--- a/src/feed/view.js
+++ b/src/feed/view.js
@@ -63,15 +63,17 @@
};
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,
@@ -79,6 +81,8 @@
} else {
this.taxonomies = [];
}
+
+
}
async initTaxonomies() {
@@ -176,13 +180,15 @@
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);
@@ -282,7 +288,7 @@
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) {
@@ -359,9 +365,6 @@
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());
}
@@ -377,8 +380,12 @@
this.a11y.announceItems(0, this.store.filters['page'] >1, false);
}
- this.ui.filters.match.hidden = Object.keys(this.taxonomyFilters).length === 0;
- this.ui.clearFilter.hidden = Object.keys(this.taxonomyFilters).length === 0;
+ 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;
+ }
}
/**
@@ -386,69 +393,171 @@
* @param {object} item
*/
createItemElement(item) {
- let template = window.getTemplate('feed-item');
- if (Object.hasOwn(template.dataset, 'timeline')) {
- return this.createTimelineElement(item, template);
+ let template = window.getTemplate(`feedItem${window.uppercaseFirst(item.content)}`);
+
+ const isTimeline = Object.hasOwn(template.dataset, 'timeline');
+
+ // Format fields using helpers
+ for (let [fieldName, value] of Object.entries(item.fields)) {
+ if (isTimeline && ['timeline', 'number'].includes(fieldName)) continue;
+ let el = template.querySelector(`[data-field="${fieldName}"]`);
+ if (!el) continue;
+
+ if (value === '') {
+ el.remove();
+ continue;
+ }
+
+ if (this.isImageField(item, value)) {
+ this.formatImageFields(el, value, item);
+ } else if (this.isTaxonomyField(item, fieldName)) {
+ this.formatTaxonomyField(el, item, fieldName, value);
+ } else if (this.isTimeField(el)) {
+ this.formatTimeField(el, value);
+ } else {
+ this.formatField(el, value);
+ }
}
+
+ // Handle link
+ let link = template.querySelector('a');
+ if (link && item.url !== '') {
+ [
+ link.href,
+ link.title
+ ] = [
+ item.url,
+ `View ${item.fields['post_title']??'Item'}`
+ ];
+ }
+
+ if (isTimeline) {
+ this.addTimelineElements(item, template);
+ }
+
return template;
}
+ splitIDs(value) {
+ return String(value).split(',').map((value) => parseInt(value.trim())).filter(value=>value);
+ }
+ isImageField(item, value) {
+ if (!Object.hasOwn(item, 'images') || Object.keys(item.images).length === 0) {
+ return false;
+ }
+ let values = this.splitIDs(value);
- createTimelineElement(item, template) {
+ return values.some(v =>
+ Object.keys(item.images).map(k => parseInt(k)).includes(parseInt(v))
+ );
+ }
+ formatImageFields(element, value, item) {
+ let values = this.splitIDs(value); // Convert string to array first
+ if (values.length === 0) return;
+
+ if (values.length > 1) {
+ let image = element.querySelector('img');
+ if (!image) return;
+ values.forEach(imgID => {
+ let img = image.cloneNode(true);
+ this.formatImageField(img, imgID, item);
+ element.append(img);
+ });
+ image.remove();
+ } else {
+ if (element.tagName !== 'IMG') {
+ element = element.querySelector('img');
+ if (!element) return;
+ }
+ this.formatImageField(element, values[0], item);
+ }
+ }
+ formatImageField(element, value, item) {
+ let imgData = item.images[value]??false;
+ if (!imgData) return;
+ [
+ element.src,
+ element.srcset,
+ element.alt
+ ] = [
+ imgData.tiny,
+ `${imgData.tiny} 50w, ${imgData.small} 300w, ${imgData.medium} 1024w`,
+ imgData['image-alt-text']
+ ]
+ }
+ isTaxonomyField(item, field) {
+ if (!Object.hasOwn(item, 'taxonomies') || Object.keys(item.taxonomies).length === 0) {
+ return false;
+ }
+
+ return Object.keys(item.taxonomies).includes(field);
+ }
+ formatTaxonomyField(element, item, field, value) {
+ if (element.tagName !== 'UL' || !element.querySelector('li')) return;
+ let values = this.splitIDs(value);
+ if (values.length === 0) {
+ element.remove();
+ }
+ let listItem = element.querySelector('li');
+ for (let termID of values) {
+ let term = item.taxonomies[field][termID]??false;
+ if (!term) continue;
+ let termItem = listItem.cloneNode(true);
+ let link = termItem.querySelector('a');
+ if (!link) continue;
+
+ [
+ link.href,
+ link.title,
+ link.textContent
+ ] = [
+ term.url,
+ `See more ${term.title}`,
+ term.title
+ ];
+ element.append(termItem);
+ }
+ listItem.remove();
+ }
+ isTimeField(el) {
+ return el.tagName === 'TIME' || el.querySelector('time') !== null;
+ }
+ formatTimeField(element, value) {
+ if (element.tagName !== 'TIME') {
+ element = element.querySelector('time');
+ if (!element) return;
+ }
+ element.setAttribute('datetime', value);
+ element.textContent = window.formatTimeAgo(value, 'F Y');
+ }
+ formatField(element, value) {
+ element.textContent = value;
+ }
+
+ addTimelineElements(item, template) {
let [
- main,
- link,
- beforeImg,
- afterImg,
- afterText,
+ afterEl,
+ number,
started,
- lastTreated,
- total,
- termList,
- timeline
+ last
] = [
- template,
- template.querySelector('a'),
- template.querySelector('img.before'),
- template.querySelector('img.after'),
- template.querySelector('summary span:last-of-type'),
- template.querySelector('p.started time'),
- template.querySelector('p.updated time'),
- template.querySelector('p.total b'),
- template.querySelector('.term-list'),
- Object.values(item.fields.order)
+ template.querySelector('span.after-text'),
+ template.querySelector('[data-field="number"] b'),
+ template.querySelector('[data-field="started"] time'),
+ template.querySelector('[data-field="updated"] time')
];
- let numberTreatments = timeline.length - 1;
- let beforeImgData = item.images[timeline[0]['post_thumbnail']];
- let afterImgData = item.images[timeline[numberTreatments]['post_thumbnail']];
- [
- main.dataset.id,
- link.href,
- beforeImg.src,
- beforeImg.dataset.small,
- beforeImg.dataset.medium,
- afterImg.src,
- afterImg.dataset.small,
- afterImg.dataset.medium,
- afterText.textContent,
- started.textContent,
- lastTreated.textContent,
- total.textContent
- ] = [
- item.id,
- item.url,
- beforeImgData['tiny'],
- beforeImgData.small,
- beforeImgData.medium,
- afterImgData['tiny'],
- afterImgData.small,
- afterImgData.medium,
- `${afterText.textContent} ${numberTreatments} Tx`,
- timeline[0].date??item.date,
- timeline[numberTreatments].date??'',
- `${numberTreatments} Treatments`
- ];
- return template;
+ if (afterEl) {
+ afterEl.textContent = `After ${item.fields.number} Tx`;
+ }
+ if (number) {
+ number.textContent = item.fields.number;
+ }
+ if (started) {
+ this.formatTimeField(started, item.fields.timeline[0]['post_date']);
+ }
+ if (last) {
+ this.formatTimeField(last, item.fields.timeline[item.fields.timeline.length - 1]['post_date']);
+ }
}
removePlaceholders() {
@@ -467,7 +576,7 @@
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);
@@ -584,35 +693,6 @@
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()) {
@@ -688,4 +768,32 @@
}
});
+ 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
+ };
});
+
+
--
Gitblit v1.10.0