From 56a9a1ccf764ff7a6af8f8a2292cb07443cb4aa7 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 28 May 2026 18:19:57 +0000
Subject: [PATCH] =New Gitbit setpu
---
assets/js/concise/CRUD.js | 234 +++++++++++++++++++++++++++++++++++++++++++++++++---------
1 files changed, 198 insertions(+), 36 deletions(-)
diff --git a/assets/js/concise/CRUD.js b/assets/js/concise/CRUD.js
index 2bbe11a..82b8b4c 100644
--- a/assets/js/concise/CRUD.js
+++ b/assets/js/concise/CRUD.js
@@ -52,8 +52,9 @@
if (refs.trash) refs.trash.dataset.id = data.id;
};
const imageSetup = function(el, refs, data) {
- if (data?.fields?.post_thumbnail) {
- const thumbnail = data.images[data.fields.post_thumbnail] ?? {};
+ let hasThumbnail = data?.fields?.post_thumbnail || data?.fields?.thumbnail;
+ if (hasThumbnail) {
+ const thumbnail = data.images[hasThumbnail] ?? {};
refs.img.src = thumbnail.medium??'';
refs.img.alt = thumbnail.alt??data.fields.post_title??'';
}
@@ -323,7 +324,11 @@
},
date: '[data-filter="date"]'
},
- uploader: 'details.uploader'
+ uploader: {
+ details: 'details.uploader',
+ form: 'details.uploader form',
+ uploader: 'details.uploader [data-field-type="upload"]'
+ }
}
this.ui = window.uiFromSelectors(this.selectors);
@@ -339,17 +344,32 @@
this.isTimeline = !!document.querySelector('[data-timeline]');
}
initUploader() {
- if (!this.ui.uploader) return;
+ if (!this.ui.uploader.form) return;
+ this.uploadForm = this.forms.registerForm(this.ui.uploader.form).id??false;
- window.jvbUploads.scanFields(this.ui.uploader);
+ // window.jvbUploads.scanFields(this.ui.uploader);
window.jvbUploads.subscribe((event, data) => {
if (event === 'sent-to-queue') {
- if (data === this.ui.uploader.dataset.uploader) {
+ if (data.field.id === this.ui.uploader.uploader.dataset.uploader) {
+ if (this.uploadForm ) {
+ this.forms.store.delete(this.uploadForm);
+ }
+
window.debouncer.schedule('crud-complete', ()=> {
this.store.clearCache();
});
}
}
+
+ if (event === 'sent-to-queue' && data.field) {
+ const fieldName = data.field.config.name;
+ const itemId = data.field.config.itemID;
+ if (itemId && fieldName) {
+ if (this.changes.has(itemId)) {
+ delete this.changes.get(itemId)[fieldName];
+ }
+ }
+ }
});
}
initModals() {
@@ -411,6 +431,7 @@
{ name: 'modified', keyPath: 'modified'},
{ name: 'title', keyPath: 'title'},
],
+ isAuth: true,
filters: filters,
ignore: ['content', 'user'],
TTL: 60 * 60 * 1000, //1 hour cache
@@ -502,35 +523,86 @@
&& data.status === 'completed'
&& data.endpoint === 'uploads/groups') {
if (data.result && data.result.group_mappings) {
+ console.log('Handling group mapping from queue response');
this.handleGroupMappings(data.result.group_mappings);
}
+
this.store.clearCache();
}
+
if (event === 'operation-status'
&& data.status === 'completed'
&& data.type === 'content_update') {
+
this.store.clearCache();
- // Check for result data (from ContentExecutor)
- if (!data.result || !data.result.posts) {
- console.warn('Content update completed but no result.posts', data);
+ if (!data.result || !data.result.success || !data.result.errors)
+ {
+ console.warn('Content update completed but no results', data);
return;
}
- // Get successfully processed post IDs
- const successfulIds = Object.keys(data.result.posts);
-
- if (successfulIds.length === 0) {
+ if (Object.keys(data.result.success).length > 0) {
+ this.checkCompletedChanges(Object.entries(data.result.success));
+ }
+ if (Object.keys(data.result.errors).length > 0) {
+ this.checkFailedChanges(Object.entries(data.result.errors));
return;
}
- // Clear from both persistent and in-memory storage
- this.changesStore.deleteMany(successfulIds);
- successfulIds.forEach(id => this.changes.delete(id));
+ if (Object.keys(data.result.success).length === 0) {
+ console.log(data.result.success);
+ data.result.success.forEach(id => this.changesStore.delete(id));
+
+ this.store.clearCache();
+ }
+ }
+
+ if (event === 'sent-to-server' && data.type === 'content_update') {
+ if (data instanceof FormData) return;
+
+ for ( let [id, changes] of Object.entries(data.posts)) {
+ this.compareStored(id, changes);
+ }
}
});
}
+ checkCompletedChanges(items) {
+ for (let [id, data] of items) {
+ this.compareStored(id, data);
+ }
+ }
+ compareStored(id, data) {
+ let stored = this.changesStore.get(id);
+ if (!stored) return;
+
+ for (let [field, value] of Object.entries(data)) {
+ if (Object.hasOwn(stored, field)) {
+ let changes = window.getDifferences.map(stored[field], value);
+ if (!changes) {
+ delete stored[field];
+ } else {
+ stored[field] = changes;
+ }
+ }
+ }
+
+ let hasID = Object.hasOwn(stored, 'id');
+ let hasContent = Object.hasOwn(stored, 'content');
+ if ((hasID && hasContent && Object.keys(stored).length === 2)
+ || ((hasID || hasContent) && Object.keys(stored).length === 1)
+ || Object.keys(stored).length === 0
+ ) {
+ this.changesStore.delete(id);
+ this.store.clearCache();
+ } else {
+ this.changesStore.save(stored);
+ }
+ }
+ checkFailedChanges(items) {
+ //TODO do something.
+ }
initSettings() {
this.defaults = {
@@ -593,7 +665,7 @@
default: 'closed',
},
showUploader: {
- element: this.ui.uploader,
+ element: this.ui.uploader.details,
default: 'open'
}
};
@@ -645,6 +717,7 @@
let title = `Saving changes for multiple ${this.plural}`;
this.scheduleSave(0);
+ this.modals.edit.handleClose();
}
async handleCreateSubmit(modal) {
@@ -789,27 +862,59 @@
handleItemUpdate(e) {
let item = window.targetCheck(e, '[data-item-id]');
-
if (!item) return;
+
+ // Check if inside a collection field first
+ const collection = e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');
+
+ let name, value;
+ if (collection) {
+ name = collection.dataset.field;
+ value = this.forms.getFieldValue(collection);
+ } else {
+ let field = e.target.closest('[data-field]');
+ name = field.dataset.field;
+ 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;
+ }
+ name.replace(`[${itemId}]`, '');
+
+ 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;
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();
}
}
@@ -962,7 +1067,7 @@
return;
}
- if (e.target.matches(this.selectors.buttons.create)) {
+ if (e.target.matches(this.selectors.buttons.create) || e.target.closest(this.selectors.buttons.create)) {
this.openCreateModal();
}
}
@@ -970,8 +1075,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) {
@@ -1179,17 +1284,30 @@
this.activeItem = item.id;
this.ui.modals.edit.modal.dataset.itemId = itemID;
this.ui.modals.edit.modal.dataset.content = this.content;
- this.ui.modals.edit.h2.textContent = `Editing ${item.fields.post_title === '' ? this.singular : item.fields.post_title}`;
+ let title;
+ if (Object.hasOwn(item.fields, 'post_title')) {
+ title = item.fields.post_title;
+ } else if (Object.hasOwn(item.fields, 'name')) {
+ title = item.fields.name;
+ }
+ this.ui.modals.edit.h2.textContent = `Editing ${title === '' ? this.singular : title}`;
this.ui.modals.edit.form.dataset.formId = `edit-${itemID}`;
+
+ this.modals.edit.handleOpen();
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();
}
openBulkEditModal() {
window.removeChildren(this.ui.modals.bulkEdit.selected);
@@ -1215,11 +1333,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;
+ });
+ });
}
/*****************************************************************
@@ -1231,7 +1353,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 === '') {
@@ -1243,8 +1369,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;
@@ -1272,6 +1396,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