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/FormController.js |  156 ++++++++++++++++++++++++++++++++++++----------------
 1 files changed, 108 insertions(+), 48 deletions(-)

diff --git a/assets/js/concise/FormController.js b/assets/js/concise/FormController.js
index 8996cbf..70e5b6a 100644
--- a/assets/js/concise/FormController.js
+++ b/assets/js/concise/FormController.js
@@ -92,7 +92,7 @@
 				input: 'input[type="number"]'
 			},
 			limits: {
-				hasLimit: '[data-limit]',
+				hasLimit: '[data-maxlength]',
 				limit: '.limit',
 				current: '.current',
 			}
@@ -327,6 +327,25 @@
 
 		let field = this.getField(e.target);
 
+		// Check if this input lives inside a collection field
+		const collectionField = e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');
+		if (collectionField) {
+			// Dependencies still need checking
+			if (this.dependencies.has(field.dataset.field)) {
+				let dependency = this.dependencies.get(field.dataset.field);
+				dependency.items.forEach(item => {
+					this.checkFieldDependency(item, field.dataset.field);
+				});
+			}
+			const collectionName = collectionField.dataset.field;
+			window.debouncer.schedule(
+				`collection:${collectionName}`,
+				() => this.updateCollectionField(collectionField),
+				150
+			);
+			return;
+		}
+
 		//Dependencies
 		if (this.dependencies.has(field.dataset.field)) {
 			let dependency = this.dependencies.get(field.dataset.field);
@@ -335,11 +354,6 @@
 			});
 		}
 
-		if (field.dataset.fieldType === 'repeater' || field.dataset.fieldType === 'tag-list') {
-			this.updateCollectionField(field);
-			return;
-		}
-
 		let form = this.getForm(e.target);
 		this.updateItem(field.dataset.field, this.getFieldValue(e.target), form);
 	}
@@ -354,10 +368,18 @@
 		window.debouncer.cancel(`form:${form.id}:validate:${fieldName}`);
 		this.validateField(e.target);
 
+		// If inside a collection, update the whole collection instead
+		const collectionField = e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');
+		if (collectionField) {
+			this.updateCollectionField(collectionField);
+			return;
+		}
+
 		this.updateItem(fieldName, this.getFieldValue(e.target), form);
 	}
 
 	handleInput(e){
+		if (e.target.closest('[data-ignore]') || this.isRestoring) return;
 		let form = this.getForm(e.target);
 		if (!form) return;
 
@@ -719,7 +741,8 @@
 				}
 			}
 
-			let inputs = Array.from(container.querySelectorAll(this.inputSelectors));
+			let inputs = Array.from(container.querySelectorAll(this.inputSelectors))
+				.filter(input => !input.closest('.ql-clipboard'));
 			inputs.map(input => {
 				this.getItem(input, config?.id);
 			});
@@ -808,6 +831,7 @@
 						element: repeater,
 						field: this.getField(repeater),
 						sortable: false,
+						rows: []
 					};
 
 					if (!config.ui.add) return;
@@ -823,9 +847,8 @@
 								let index = config.ui.items?.children?.length??0;
 								el.dataset.index = index;
 
-
 								manyRefs.inputs?.forEach(input => {
-									window.prefixInput(input, `${data.repeater.dataset.field}:${index}:`, el);
+									window.prefixInput(input, `${data.repeater.dataset.field}:${index}:`, el, false, true);
 								});
 							}
 						},
@@ -840,6 +863,7 @@
 							}
 						});
 					}
+
 					repeater.dataset.repeaterId = config.id;
 					this.addRepeaterListeners(repeater);
 					this.repeaters.set(config.id, config);
@@ -862,8 +886,18 @@
 				addRepeaterRow(repeater) {
 					let data = {};
 					data.repeater = repeater;
-					repeater.append(this.templates.create(repeater.dataset.repeaterId, data));
-					this.initializeFields(repeater, this.getField(repeater).config??{});
+					let config = this.repeaters.get(repeater.dataset.repeaterId);
+
+					let row = this.templates.create(repeater.dataset.repeaterId, data);
+					config.rows.push({
+						element: row,
+						fields: Array.from(row.querySelectorAll('[data-field]'))
+					});
+					this.repeaters.set(config.id, config);
+					config.ui.items.append(row);
+
+					let form = this.getForm(repeater);
+					this.initializeFields(repeater, form);
 					this.a11y.announce('Row added');
 				}
 				removeRepeaterRow(row) {
@@ -901,7 +935,7 @@
 								el.dataset.index = index;
 								manyRefs.inputs?.forEach(input => {
 									let wrapper = input.closest('.tag-item');
-									window.prefixInput(input, `${data.fieldName}:${index}:`, wrapper)
+									window.prefixInput(input, `${data.fieldName}:${index}:`, wrapper, false, true)
 								});
 
 								if (refs.label) {
@@ -919,11 +953,11 @@
 			}
 				addTagListListeners(el) {
 					el.addEventListener('click', this.tagListClick);
-					el.addEventListener('keypress', this.tagListInput, {passive: true})
+					el.addEventListener('keypress', this.tagListInput);
 				}
 				removeTagListListeners(el) {
 					el.removeEventListener('click', this.tagListClick);
-					el.removeEventListener('keypress', this.tagListInput)
+					el.removeEventListener('keypress', this.tagListInput);
 				}
 
 				handleTagListClick(e) {
@@ -1129,25 +1163,32 @@
 						}
 					});
 				}
-			checkForCharacterLimits(form) {
-				if (!form.querySelector(this.selectors.limits.hasLimit)) return;
-				this.countUpdaters = this.updateCount.bind(this);
+	checkForCharacterLimits(form) {
+		if (!form.querySelector(this.selectors.limits.hasLimit)) return;
+		this.countUpdaters = this.updateCount.bind(this);
 
-				form.querySelectorAll(`${this.selectors.limits.hasLimit}`).forEach(input => {
-					let id = window.generateID('limit');
-					input.dataset.charLimitId = id;
-					let config = {
-						element: input,
-						form: form.dataset.formId,
-						ui: window.uiFromSelectors(this.selectors.limits, input.closest('.field'))
-					};
-					config.ui.limit.textContent = input.dataset.limit;
-					this.charLimits.set(id, config);
+		form.querySelectorAll(this.selectors.limits.hasLimit).forEach(field => {
+			const input = this.getFieldInput(field);
+			if (!input) return;
 
-					this.addCharacterLimitListeners(input);
-				});
+			let id = window.generateID('limit');
+			input.dataset.charLimitId = id;
+			input.dataset.limit = field.dataset.maxlength;
 
+			let config = {
+				element: input,
+				form: form.dataset.formId,
+				ui: window.uiFromSelectors(this.selectors.limits, field)
+			};
+
+			if (config.ui.limit) {
+				config.ui.limit.textContent = field.dataset.maxlength;
 			}
+
+			this.charLimits.set(id, config);
+			this.addCharacterLimitListeners(input);
+		});
+	}
 				addCharacterLimitListeners(input) {
 					input.addEventListener('input', this.countUpdaters, {passive: true});
 				}
@@ -1250,7 +1291,9 @@
 				window.prefixInput(
 					input,
 					`${fieldName}:${index}:`,
-					item  // Pass the item as wrapper for label lookup
+					item,
+					false,
+					true
 				);
 			});
 		});
@@ -1274,7 +1317,7 @@
 		if (!form) return;
 
 		// Get all current data for the collection
-		const value = this.getFieldValue(field.querySelector('input, select, textarea'));
+		const value = this.getFieldValue(field);
 		this.updateItem(field.dataset.field, value, form);
 	}
 	/**********************************************************************
@@ -1557,10 +1600,13 @@
 
 			case 'selector':
 			case 'upload':
+			case 'gallery':
+			case 'image':
 				return this.getHiddenInputValue(element, conf, fieldName);
 
 			case 'true-false':
-				return element.value === '1'||element.value === 'on'||element.value ==='true';
+			case 'toggle-text':
+				return element.checked;
 			case 'checkbox':
 				// Handle multi-checkbox (name ends with [])
 				if (element.name.endsWith('[]')) {
@@ -1614,20 +1660,31 @@
 		return typeof value === 'object' && Object.keys(value).length === 0;
 	}
 		getRepeaterValue(element, conf) {
-			if (!conf.container) {
-				conf.container = conf.field?.querySelector('.repeater-items');
-				this.saveItem(conf);
-			}
+			const items = element.querySelector('.repeater-items');
+			if (!items) return [];
+			let ignore = ['image_data','image-title','image-caption','image-description','image-alt-text']
 			let value = [];
-			Array.from(conf.container.children).forEach(row => {
+			Array.from(items.children).forEach(row => {
 				let rowData = {};
 				row.querySelectorAll('[data-field]').forEach(field => {
-					rowData[field.dataset.field] = this.getFieldValue(field);
+					if (!ignore.includes(field.dataset.field)) {
+						const input = this.getFieldInput(field);
+						if (input) {
+							rowData[field.dataset.field] = this.getFieldValue(input);
+						}
+					}
 				});
 				value.push(rowData);
 			});
 			return value;
 		}
+	getFieldInput(field) {
+		// For quill fields, target the specific editor textarea
+		const quillTextarea = field.querySelector('textarea[data-editor]');
+		if (quillTextarea) return quillTextarea;
+
+		return field.querySelector(this.inputSelectors);
+	}
 		getTagListValue(element, conf) {
 			if (!conf.container) {
 				conf.container = conf.field?.querySelector('.tag-items');
@@ -1655,13 +1712,14 @@
 			});
 			return value;
 		}
-		getHiddenInputValue(element, conf, fieldName) {
-			if (!conf.value) {
-				conf.value = conf.field?.querySelector(`input[type=hidden][name="${fieldName}"]`);
-				this.saveItem(conf);
-			}
-			return conf.value.value;
+	getHiddenInputValue(element, conf, fieldName) {
+		if (!conf.value) {
+			conf.value = conf.field?.querySelector(`input[type=hidden][name="${fieldName}"]`)
+				|| conf.field?.querySelector(`input[type=hidden]`);
+			this.saveItem(conf);
 		}
+		return conf.value?.value ?? '';
+	}
 
 	/**
 	 * Format field value for display in summary
@@ -1701,6 +1759,8 @@
 
 			case 'selector':
 			case 'upload':
+			case 'image':	//legacy, shouldn't be needed
+			case 'gallery':	//legacy, shouldn't be needed
 				// These might need special handling depending on your needs
 				return this.formatHiddenFieldForSummary(value, input, fieldType);
 
@@ -1823,7 +1883,7 @@
 	 * Format hidden field types (upload, selector)
 	 */
 	formatHiddenFieldForSummary(value, input, fieldType) {
-		if (fieldType === 'upload') {
+		if (['upload', 'gallery', 'image'].includes(fieldType)) {
 			// Get upload preview images if available
 			const uploadField = input.field?.querySelector('[data-upload-field]');
 			if (uploadField) {
@@ -1965,10 +2025,10 @@
 			});
 			this.tagLists.clear();
 		}
-		if(this.charLimits.size > 0) {
+		if (this.charLimits.size > 0) {
 			Array.from(this.charLimits.values()).forEach(charLimit => {
-				charLimit.removeEventListener('input', this.countUpdaters);
-			})
+				charLimit.element.removeEventListener('input', this.countUpdaters);
+			});
 		}
 		this.inputs.clear();
 		this.forms.clear();

--
Gitblit v1.10.0