From d7dbe7fee362d587dfc334135d9581b6216a4295 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 23 Nov 2025 04:13:56 +0000
Subject: [PATCH] =Timeline block, and feed block updated. DataStore.js refactored to not block rendering

---
 assets/js/dash/CRUD.js |  263 +++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 178 insertions(+), 85 deletions(-)

diff --git a/assets/js/dash/CRUD.js b/assets/js/dash/CRUD.js
index dfb568b..6780203 100644
--- a/assets/js/dash/CRUD.js
+++ b/assets/js/dash/CRUD.js
@@ -4,7 +4,6 @@
 class CRUDManager {
 	constructor(config) {
 		this.queue = window.jvbQueue;
-		console.log(this.queue);
 		this.config = config;
 		this.content = config.content || false;
 		this.settings = window.jvbUserSettings;
@@ -13,37 +12,71 @@
 			return;
 		}
 		this.isTimeline = false;
+		this.currentItemID = null;
 
 		this.initElements();
 		this.updateBulkOptions();
 
 		// Initialize components
-		this.store = new window.jvbStore({
-			name: this.content,
-			storeName: this.content,
-			endpoint: 'content',
-			headers: {
-				'action_nonce': jvbSettings.dash,
-			},
-			indexes: [
-				{ name: 'status', keyPath: 'post_status'},
-				{ name: 'modified', keyPath: 'modified'},
-			],
-			filters: {
-				content: this.content,
-				user: jvbSettings.currentUser,
-				page: 1,
-				status: 'all'
-			},
-			TTL: 3600000,
-			cacheDOM: true
-		});
+		this.store = window.jvbStore.register(
+			this.content,
+			{
+				keyPath: 'id',
+				endpoint: 'content',
+				headers: {
+					'action_nonce': jvbSettings.dash,
+				},
+				indexes: [
+					{name: 'id', keyPath: 'id'},
+					{ name: 'status', keyPath: 'status'},
+					{ name: 'date', keyPath: 'date'},
+					{ name: 'modified', keyPath: 'modified'},
+					{ name: 'title', keyPath: 'title'}
+				],
+				filters: {
+					content: this.content,
+					user: jvbSettings.currentUser,
+					page: 1,
+					status: 'all',
+					orderby: 'modified', //or title
+					order: 'desc'
+				},
+				TTL: 30 * 60 * 1000,	//30 minutes cache
+			});
 
 		this.status = 'all';
 		this.filterTimeout = null;
 
 		this.viewController = new window.jvbViews(this.ui.container, this.store);
-		this.formController = new window.jvbForm();
+		this.tableForm = null;
+		this.tableChanges = new Map();
+
+
+		this.formController = (this.isTimeline) ? new window.jvbForm({collectFormData: () => this.collectTimelineData.bind(this)}) : new window.jvbForm();
+		this.viewController.subscribe((event, form) => {
+			if (event === 'table-view' && !this.tableForm) {
+				if (!this.tableForm) {
+					this.tableForm = this.formController.registerForm(form, {
+						autosave: false,
+						formStatus: false,
+						isTable: true,
+					});
+				}
+
+			} else if (event === 'not-table-view') {
+				if (this.tableForm) {
+
+				}
+			} else if (event === 'order-changed') {
+				let data = this.store.get(form);
+				if (!data) {
+					return;
+				}
+				let changes = {};
+				changes[form] = data;
+				this.savePosts(changes, `Updating progression order`);
+			}
+		});
 
 		this.formController.subscribe((event, data) => {
 			switch(event) {
@@ -54,15 +87,16 @@
 			}
 		});
 
-		if (window.jvbQueue) {
-			window.jvbQueue.subscribe((event, data) => {
-				if (event === 'operation-completed' && data.source === 'form') {
-					this.handleQueueSuccess(event, data);
-				} else if (event === 'operation-failed-permanent' && data.source === 'form') {
-					this.handleQueueFailure(event, data);
-				}
-			});
-		}
+		this.queue.subscribe((event, data) => {
+			if (!Object.hasOwn(data, 'endpoint') || data.endpoint !== 'content') return;
+			console.log('Queue Subscription in CRUD.js: ', data);
+			if (event === 'operation-completed') {
+				this.handleQueueSuccess(event, data);
+			} else if (event === 'operation-failed-permanent') {
+				this.handleQueueFailure(event, data);
+			}
+		});
+
 
 		// Track initialization
 		this.initialized = false;
@@ -70,35 +104,32 @@
 		this.init();
 	}
 	handleFormChange(event, data) {
+		let title = data.fullData.post_title;
+		let changes = (Object.hasOwn(data, 'changes')) ? data.changes : data.fullData;
+
+		let theChanges = {};
 		if (this.isTimeline) {
-			data.changes.content = this.content;
-			let changes = {};
-			let postID = data.config.id.replace('edit-', '');
-			changes[postID] = data.changes;
-			this.savePosts(changes, `Saving ${data.fullData.post_title}'s Changes`);
+			theChanges[this.currentItemID] = changes;
+			this.savePosts(theChanges, title);
 			return;
 		}
-		data.changes.content = this.content;
-		let changes = {};
-		let title = '';
+
 		let itemsToRemove = [];
 		switch (true) {
 			case data.config.element === this.ui.forms.edit:
-				let postID = data.config.id.replace('edit-', '');
-				console.log(postID);
-				changes[postID] = data.changes;
-				title = `Saving ${data.fullData['post_title']} Changes`;
+				theChanges[this.currentItemID] = changes;
+				title = `Saving ${title} Changes`;
 				// Check if status change requires removal
-				if (data.changes.post_status && this.shouldRemoveItem(data.changes.post_status)) {
-					itemsToRemove.push(postID);
+				if (changes.post_status && this.shouldRemoveItem(changes.post_status)) {
+					itemsToRemove.push(this.currentItemID);
 				}
 				break;
 			case data.config.element === this.ui.forms.bulkEdit:
 				let selected = data.config.element.querySelectorAll('.selected input:checked');
 				selected.forEach(sel => {
-					changes[sel.value] = data.changes;
+					theChanges[sel.value] = changes;
 					// Check if status change requires removal
-					if (data.changes.post_status && this.shouldRemoveItem(data.changes.post_status)) {
+					if (changes.post_status && this.shouldRemoveItem(changes.post_status)) {
 						itemsToRemove.push(sel.value);
 					}
 				});
@@ -107,8 +138,8 @@
 				break;
 			case data.config.element === this.ui.forms.create:
 				if (event === 'form-submit') {
-					changes[data.config.data['form-id']] = data.fullData;
-					title = `Saving ${data.fullData['post_title']} Changes`;
+					theChanges[data.config.data['form-id']] = changes;
+					title = `Saving ${title} Changes`;
 				}
 				break;
 		}
@@ -134,11 +165,11 @@
 			}
 		}
 
-		if (window.isEmptyObject(changes)) {
+		if (window.isEmptyObject(theChanges)) {
 			return;
 		}
 
-		this.savePosts(changes, title);
+		this.savePosts(theChanges, title);
 	}
 
 	shouldRemoveItem(newStatus) {
@@ -150,6 +181,13 @@
 		if (window.isEmptyObject(changes)) {
 			return;
 		}
+
+		//ensure content is in each post
+		for (let postId in changes) {
+			if (!changes[postId]['content']) {
+				changes[postId]['content'] = this.content;
+			}
+		}
 		let operation = {
 			endpoint: 'content',
 			headers: {
@@ -165,15 +203,15 @@
 		this.queue.addToQueue(operation);
 
 	}
-	handleQueueSuccess(event, data) {
-		console.log('Handling queue success...');
-		console.log('Event', event);
-		console.log('Data', data);
+	async handleQueueSuccess(event, data) {
+		this.store.clearCache();
+		this.store.clearHttpHeaders();
+		this.store.fetch();
 	}
 	handleQueueFailure(event, data) {
-		console.log('Handling queue failure...');
-		console.log('Event', event);
-		console.log('Data', data);
+		console.error('Operation failed permanently:', data);
+		// Optionally show error notification to user
+		this.a11y?.announce(`Operation failed: ${data.error_message || 'Unknown error'}`);
 	}
 
 	initElements() {
@@ -194,14 +232,11 @@
 			uploader: 'details.uploader'
 		};
 		this.ui = window.uiFromSelectors(this.elements);
-		this.isTimeline = Object.hasOwn(this.ui.forms.edit.dataset, 'timeline');
+		this.isTimeline = !!document.querySelector('[data-timeline]');
 	}
 	init() {
 		this.settings.addSetting(this.ui.uploader, 'open');
 		this.ui.uploader.addEventListener('toggle', (e) =>{
-			console.log(e);
-			console.log('Is Open: ', this.ui.uploader.open);
-			console.log(this.ui.uploader.open ? 'on' : 'off');
 			this.settings.saveSetting('open', this.ui.uploader.open ? 'on' : 'off');
 		});
 
@@ -219,9 +254,9 @@
 			this.modals[name].subscribe((event, data) => {
 				switch (event) {
 					case 'modal-close':
+						this.currentItemID = null;
 						this.formController.cleanupForm(this.modals[name].modal.querySelector('form').dataset.formId);
 						//double check we have finished saving
-						console.log('Data on modal close: ', data);
 						break;
 					case 'modal-open':
 						//probably not needed in this class
@@ -235,15 +270,6 @@
 
 		this.setupFilters();
 
-
-		this.queue.subscribe((event, data) => {
-			switch (event) {
-				case 'operation-status':
-					//update items?
-					break;
-			}
-		});
-
 		this.initialized = true;
 	}
 
@@ -347,6 +373,14 @@
 		});
 	}
 	handleChange(e) {
+		if (e.target.closest('[data-id]')) {
+			if (this.isTimeline) {
+				this.handleTimelineTableChange(e);
+			} else {
+				this.handleTableChange(e);
+			}
+			return;
+		}
 		if (e.target.classList.contains('bulk-action-select')) {
 			if (e.target.value.startsWith('tax-')) {
 				const taxonomy = e.target.value.replace('tax-', '');
@@ -378,6 +412,73 @@
 			}
 		}
 	}
+	handleTableChange(e) {
+		const row = e.target.closest('tr[data-id]');
+		if (!row) return;
+
+		const input = e.target;
+		const postID = parseInt(row.dataset.id);
+		const fieldName = input.closest(['data-field'])?.dataset.field;
+		if (!fieldName) return;
+
+		const item = this.store.get(postID);
+		if (!item) return;
+
+		item.fields[fieldName] = this.getInputValue(input);
+
+		this.store.save(item);
+
+		let post = {};
+		post[postID] = item.fields;
+		this.savePosts(post, `Saving changes to ${this.content}`);
+	}
+	handleTimelineTableChange(e) {
+		const tbody = e.target.closest('tbody[data-id]');
+		if (!tbody) return;
+
+		const input = e.target;
+		const fieldName = input.closest('[data-field]')?.dataset.field;
+
+		if (!fieldName) return;
+
+		const parentID = parseInt(tbody.dataset.id);
+		const timelinePoint = input.closest('tr.timeline-point');
+
+		const item = this.store.get(parentID);
+		if (!item) return;
+
+		const value = this.getInputValue(input);
+
+		// Check if this is a specific point, or a shared value
+		if (timelinePoint) {
+			const imgID = timelinePoint.dataset.imageId;
+			if (!item.fields.timeline) {
+				item.fields.timeline = {};
+			}
+			if (!item.fields.timeline[imgID]) {
+				item.fields.timeline[imgID] = {};
+			}
+			item.fields.timeline[imgID][fieldName] = value;
+		} else {
+			item.fields[fieldName] = value;
+		}
+
+		//Update store directly
+		this.store.save(item);
+
+		let changes = {};
+		changes[parentID] = item.fields;
+		this.savePosts(changes, 'Updating progress post');
+	}
+	getInputValue(input) {
+		if (input.type === 'checkbox') {
+			return input.checked ? (input.value || '1') : '';
+		}
+		if (input.type === 'radio') {
+			return input.checked ? input.value : null;
+		}
+		return input.value;
+	}
 
 	openTaxonomyModal(taxonomy) {
 		// Check if jvbSelector exists
@@ -393,14 +494,13 @@
 		);
 	}
 	handleBulkTaxonomy(selectedIds, taxonomy) {
-		console.log(taxonomy, selectedIds);
 		// Callback when terms are selected
 		if (selectedIds.length > 0) {
 			selectedIds = selectedIds.join(',');
 			let changes = {};
 			let selected = Array.from(this.viewController.selectedItems);
 
-			console.log('selected',selected);
+
 			selected.forEach(sel => {
 				changes[sel] = {
 					content: this.content
@@ -408,7 +508,6 @@
 				changes[sel][taxonomy] = selectedIds;
 			});
 
-			console.log('Taxonomy changes: ', changes);
 
 			let title = `Adding ${selected.length} ${this.config.plural??'posts'} to ${selectedIds.length} ${jvbSettings.labels[taxonomy].plural}`;
 			this.viewController.clearSelection();
@@ -420,7 +519,7 @@
 		if (!['publish', 'draft', 'trash', 'delete'].includes(status)){
 			return;
 		}
-		console.log(`Setting status: ${status}`);
+
 		let changes = {};
 		for (let selected of this.viewController.selectedItems) {
 			changes[selected] = {
@@ -436,7 +535,7 @@
 			default:
 				title = window.uppercaseFirst(status)+'ing';
 		}
-		console.log(this.status);
+
 		if ((this.status === 'all' && !['publish', 'draft'].includes(status)) || status !== this.status) {
 			let delay = 0;
 			for (let selected of this.viewController.selectedItems) {
@@ -503,9 +602,8 @@
 
 		window.removeChildren(container);
 		for (let selected of this.viewController.selectedItems) {
-			console.log(selected);
 			let item = this.store.get(selected);
-			console.log(item);
+
 			const img = window.getTemplate('bulkItem');
 			if (!img) return;
 
@@ -533,14 +631,12 @@
 		];
 
 		this.formController.registerForm(this.ui.forms.bulkEdit);
-		console.log('Bulk Edit form registered');
 	}
 
 	populateEditForm(itemID) {
-		console.log(itemID);
+		this.currentItemID = itemID;
 
 		let item = this.store.get(parseInt(itemID));
-		console.log(item);
 		if (item) {
 			this.ui.modals.edit.dataset.itemID = itemID;
 			this.ui.modals.edit.dataset.content = this.content;
@@ -552,10 +648,8 @@
 				`Editing ${item.fields.post_title}`
 			];
 			form.dataset.formId = `edit-${itemID}`;
-			console.log(form.dataset.formId);
 			new window.jvbPopulate(form, item.fields, item.images);
 			this.formController.registerForm(this.ui.forms.edit);
-			console.log('Edit form registered');
 		}
 	}
 
@@ -593,7 +687,6 @@
 		document.querySelectorAll('[data-filter]').forEach(filter => {
 			filter.removeEventListener('change', this.filterHandler);
 		});
-		this.store.subscribers.clear();
 	}
 }
 

--
Gitblit v1.10.0