From a24a06002081ad71a78ffeff9072725ba39cf121 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 17 Feb 2026 20:05:31 +0000
Subject: [PATCH] =minor changes, particularly around the JVB_CHILD_URL pattern

---
 assets/js/concise/CRUD.js |  114 ++++++++++++++++++++++++++++++++++++++++++++-------------
 1 files changed, 88 insertions(+), 26 deletions(-)

diff --git a/assets/js/concise/CRUD.js b/assets/js/concise/CRUD.js
index b7b88fb..a189ac9 100644
--- a/assets/js/concise/CRUD.js
+++ b/assets/js/concise/CRUD.js
@@ -12,7 +12,6 @@
 		this.error = window.jvbError;
 		this.populate = window.jvbPopulate;
 		this.cache = new window.jvbCache(this.content);
-		this.uploadedFields = new Set(); //tracks which upload fields are currently uploading; so don't send any of these changes to server
 
 		this.activeItem = null;
 		this.isTimeline = false;
@@ -356,7 +355,6 @@
 					const fieldName = data.field.config.name;
 					const itemId = data.field.config.itemID;
 					if (itemId && fieldName) {
-						this.uploadedFields.add(`${itemId}_${fieldName}`);
 						if (this.changes.has(itemId)) {
 							delete this.changes.get(itemId)[fieldName];
 						}
@@ -800,35 +798,50 @@
 	}
 
 	handleItemUpdate(e) {
-		let item = window.targetCheck(e, '[data-item-id]');
 
+		let item = window.targetCheck(e, '[data-item-id]');
 		if (!item) return;
+
+		let field = e.target.closest('[data-field-type="repeater"],[data-field-type="tag-list"],[data-field]');
+		let name = field.dataset.field;
+		let value = this.forms.getFieldValue(e.target);
 		item.dataset.itemId.split(',').forEach(itemId => {
-			let field = this.forms.getField(e.target);
-			if (['repeater', 'tag-list'].includes(field.dataset.fieldType)) {
-				return;
-			}
-			let name = field.dataset.field;
-			let value = this.forms.getFieldValue(e.target);
 			this.updateItem(itemId, name, value);
 		});
 	}
 	updateItem(itemId, name, value) {
+		if (this.isPopulating) {
+			return;
+		}
+
+		const stored = this.store.get(itemId);
+		if (stored) {
+			const storedValue = stored.fields?.[name] ?? stored[name];
+			const diff = window.getDifferences.map(storedValue, value);
+
+			if (diff === null) {
+				// Value matches stored — clean up any pending change for this field
+				if (this.changes.has(itemId)) {
+					delete this.changes.get(itemId)[name];
+					// If no real changes left, remove the item entirely
+					const remaining = Object.keys(this.changes.get(itemId))
+						.filter(k => k !== 'id' && k !== 'content');
+					if (remaining.length === 0) {
+						this.changes.delete(itemId);
+						this.changesStore.delete(itemId);
+					}
+				}
+				return;
+			}
+		}
+
 		if (!this.changes.has(itemId)) {
 			this.changes.set(itemId, { id: itemId, content: this.content });
 		}
 		this.changes.get(itemId)[name] = value;
 
-		for (const key of this.uploadedFields) {
-			const [itemId, fieldName] = key.split('_');
-			if (this.changes.has(itemId)) {
-				delete this.changes.get(itemId)[fieldName];
-			}
-		}
-
 		this.scheduleBackup();
-		//Only send actual itemIds to server. If this is a recently uploaded item, just store changes for now
-		if (typeof itemId === 'number' || !itemId.includes('group')) {
+		if (typeof itemId === 'number' || !String(itemId).includes('group')) {
 			this.scheduleSave();
 		}
 	}
@@ -989,8 +1002,8 @@
 			this.forms.registerForm(this.ui.modals.create.form,{
 				cache: false,
 			});
-
 			this.ui.modals.create.modal.dataset.itemId = window.generateID('new');
+
 			this.modals.create.handleOpen();
 		}
 		handleActionButton(button) {
@@ -1201,12 +1214,17 @@
 		this.ui.modals.edit.h2.textContent = `Editing ${item.fields.post_title === '' ? this.singular : item.fields.post_title}`;
 		this.ui.modals.edit.form.dataset.formId = `edit-${itemID}`;
 
+
 		this.forms.registerForm(this.ui.modals.edit.form, {cache: false,
 			autoUpload: true,});
-
 		this.isPopulating = true;
 		this.populate.populate(this.ui.modals.edit.form, item);
-		this.isPopulating = false;
+		//For quill/taxonomy selector's async setups
+		requestAnimationFrame(() => {
+			requestAnimationFrame(() => {
+				this.isPopulating = false;
+			});
+		});
 
 		this.modals.edit.handleOpen();
 	}
@@ -1234,11 +1252,15 @@
 		}
 		this.modals.bulkEdit.handleOpen();
 
-		this.forms.registerForm(this.ui.modals.bulkEdit.form, {cache:false});
 
+		this.forms.registerForm(this.ui.modals.bulkEdit.form, {cache:false});
 		this.isPopulating = true;
 		this.populate.populate(this.ui.modals.edit.form, item);
-		this.isPopulating = false;
+		requestAnimationFrame(() => {
+			requestAnimationFrame(() => {
+				this.isPopulating = false;
+			});
+		});
 	}
 
 	/*****************************************************************
@@ -1250,7 +1272,11 @@
 			this.cancelBackup();
 			await this.handleBackup();
 		}
-		const changes = await this.changesStore.getAll();
+		let changes = await this.changesStore.getAll();
+		if (changes.length === 0) return;
+
+		// Filter out false positives
+		changes = this.validateChanges(changes);
 		if (changes.length === 0) return;
 
 		if (title === '') {
@@ -1262,8 +1288,6 @@
 
 		changes.forEach(change => {
 			let itemId = change.id;
-
-			// Create a new object without the id field (don't mutate original!)
 			const { id, ...changeWithoutId } = change;
 			allChanges[itemId] = changeWithoutId;
 
@@ -1291,6 +1315,44 @@
 		this.queue.addToQueue(operation);
 	}
 
+	/**
+	 * Compare pending changes against the store, removing unchanged fields.
+	 * Returns cleaned array (may be empty if nothing actually changed).
+	 */
+	validateChanges(changes) {
+		return changes.reduce((valid, change) => {
+			const { id, content, ...fields } = change;
+			const stored = this.store.get(id);
+
+			if (!stored) {
+				valid.push(change);
+				return valid;
+			}
+
+			const realChanges = { id, content };
+			let hasRealChange = false;
+
+			for (const [name, value] of Object.entries(fields)) {
+				const storedValue = stored.fields?.[name] ?? stored[name];
+				const diff = window.getDifferences.map(storedValue, value);
+
+				if (diff !== null) {
+					realChanges[name] = value;
+					hasRealChange = true;
+				}
+			}
+
+			if (hasRealChange) {
+				valid.push(realChanges);
+			} else {
+				this.changes.delete(id);
+				this.changesStore.delete(id);
+			}
+
+			return valid;
+		}, []);
+	}
+
 
 	setBulkStatus(status) {
 		if (!['publish', 'draft', 'trash', 'delete'].includes(status)) return;

--
Gitblit v1.10.0