From 3b5abc4ab44fb899b92ea0e40e0719454c057f52 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Wed, 11 Feb 2026 01:38:22 +0000
Subject: [PATCH] =attempt to fix race condition that overrides image changes immediately after processing them
---
assets/js/concise/CRUD.js | 201 ++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 184 insertions(+), 17 deletions(-)
diff --git a/assets/js/concise/CRUD.js b/assets/js/concise/CRUD.js
index d399866..7bad8c8 100644
--- a/assets/js/concise/CRUD.js
+++ b/assets/js/concise/CRUD.js
@@ -350,6 +350,21 @@
});
}
}
+
+ if (event === 'sent-to-queue' && data.field) {
+ const fieldName = data.field.config.name;
+ const itemId = data.field.config.itemID;
+ if (itemId && fieldName && this.changes.has(itemId)) {
+ delete this.changes.get(itemId)[fieldName];
+ }
+ // Also clear from IndexedDB store
+ this.changesStore.get(itemId).then(stored => {
+ if (stored && stored[fieldName] !== undefined) {
+ delete stored[fieldName];
+ this.changesStore.save(stored);
+ }
+ });
+ }
});
}
initModals() {
@@ -402,7 +417,7 @@
keyPath: 'id',
endpoint: this.endpoint??'content', //for taxonomy stores
headers: {
- 'action_nonce': window.auth.getNonce('dash'),
+ 'X-Action-Nonce': window.auth.getNonce('dash'),
},
indexes: [
{name: 'id', keyPath: 'id'},
@@ -482,23 +497,33 @@
// }
// });
+ if (window.jvbUploads) {
+ window.jvbUploads.subscribe((event, data) => {
+ if (event === 'groups_uploaded' && data.content === this.content) {
+ this.handleGroupsUploaded(data);
+ }
+ });
+ }
+
this.queue.subscribe((event, data) => {
if (['image_upload', 'video_upload', 'document_upload'].includes(data.type)
&& event === 'operation-status'
&& data.status === 'completed') {
this.store.clearCache();
}
+
+
if (event === 'operation-status'
&& data.status === 'completed'
&& data.endpoint === 'uploads/groups') {
-
- console.log('Cleared local cache. Refresh to see changes');
+ if (data.result && data.result.group_mappings) {
+ this.handleGroupMappings(data.result.group_mappings);
+ }
this.store.clearCache();
}
if (event === 'operation-status'
&& data.status === 'completed'
&& data.type === 'content_update') {
- console.log('Cleared local cache. Refresh to see changes');
this.store.clearCache();
// Check for result data (from ContentExecutor)
@@ -508,9 +533,7 @@
}
// Get successfully processed post IDs
- const successfulIds = Object.keys(data.result.posts).filter(id => {
- return data.result.posts[id]?.success === true;
- });
+ const successfulIds = Object.keys(data.result.posts);
if (successfulIds.length === 0) {
return;
@@ -628,14 +651,62 @@
const form = e.target;
const modal = form.closest('dialog');
if (!modal) return;
- let title = `Saving changes for multiple ${this.plural}`;
- if (modal.classList.contains('edit')) {
- title = 'Saving your edits...';
- } else if (modal.classList.contains('create')) {
- title = `Creating your new ${this.singular}`;
+
+ if (modal.classList.contains('create')) {
+ this.handleCreateSubmit(modal);
+ return;
}
+
+ let title = `Saving changes for multiple ${this.plural}`;
+
this.scheduleSave(0);
}
+
+ async handleCreateSubmit(modal) {
+ const itemId = modal.dataset.itemId;
+
+ // 1. Flush changes to store
+ if (this.changes.size > 0) {
+ this.cancelBackup();
+ await this.handleBackup();
+ }
+
+ const changes = await this.changesStore.getAll();
+ if (changes.length === 0) return;
+
+ let allChanges = {};
+ changes.forEach(change => {
+ const { id, ...rest } = change;
+ allChanges[id] = rest;
+ });
+
+ // 2. Queue content creation, get operationId
+ let contentOpId = this.queue.addToQueue({
+ endpoint: this.endpoint,
+ headers: {
+ 'X-Action-Nonce': window.auth.getNonce('dash'),
+ },
+ data: {
+ posts: allChanges,
+ },
+ popup: `Creating your new ${this.singular}`,
+ title: `Creating your new ${this.singular}`,
+ });
+
+ if (!contentOpId) return;
+
+ // 3. Queue any pending uploads with dependency on content creation
+ const uploadFields = modal.querySelectorAll('[data-upload-field]');
+ for (const fieldEl of uploadFields) {
+ const fieldId = fieldEl.dataset.uploader;
+ if (!fieldId) continue;
+
+ const uploads = window.jvbUploads.stores.uploads.filterByIndex({ field: fieldId });
+ if (uploads.length === 0) continue;
+
+ await window.jvbUploads.queueUploads('uploads', fieldId, contentOpId);
+ }
+ }
handleChange(e) {
// Early bailout - target must be in an item or be a filter
const inItem = e.target.closest('[data-item-id]');
@@ -737,6 +808,9 @@
if (!item) return;
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);
@@ -749,7 +823,10 @@
this.changes.get(itemId)[name] = value;
this.scheduleBackup();
- this.scheduleSave();
+ //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')) {
+ this.scheduleSave();
+ }
}
scheduleBackup() {
window.debouncer.schedule(
@@ -762,7 +839,6 @@
2000
);
}
-
cancelBackup() {
window.debouncer.cancel(`changes-${this.content}`);
}
@@ -1121,7 +1197,8 @@
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});
+ this.forms.registerForm(this.ui.modals.edit.form, {cache: false,
+ autoUpload: true,});
this.isPopulating = true;
this.populate.populate(this.ui.modals.edit.form, item);
@@ -1170,7 +1247,6 @@
await this.handleBackup();
}
const changes = await this.changesStore.getAll();
-
if (changes.length === 0) return;
if (title === '') {
@@ -1199,7 +1275,7 @@
let operation = {
endpoint: this.endpoint,
headers: {
- 'action_nonce': window.auth.getNonce('dash'),
+ 'X-Action-Nonce': window.auth.getNonce('dash'),
},
data: {
posts: allChanges,
@@ -1398,6 +1474,97 @@
});
}
/***************************************************************
+ UPLOAD GROUP SUPPORT
+ Handles:
+ - immediate UI feedback once the uploaded groups are sent to server
+ ***************************************************************/
+ handleGroupsUploaded(data) {
+ const { posts, fieldId } = data;
+ let uploader = window.jvbUploads;
+ let field = uploader.fields.get(fieldId);
+
+ let added = [];
+ posts.forEach(post => {
+ const placeholderPost = {
+ id: post.groupId,
+ title: post.fields.post_title || `New ${this.singular}`,
+ status: 'draft',
+ date: new Date().toISOString(),
+ modified: new Date().toISOString(),
+ thumbnail: null,
+ icon: this.content,
+ taxonomies: {},
+ fields: post.fields,
+ images: {},
+ };
+
+ post.images.forEach((uploadId, index) => {
+ let id = uploadId['upload_id'];
+ if (index === 0) {
+ placeholderPost.fields['post_thumbnail'] = uploadId;
+ }
+ let upload = uploader.stores.uploads.get(id);
+ if (upload) {
+ placeholderPost.images[id] = {
+ 'image-alt-text': '',
+ 'image-caption': '',
+ 'image-title': upload.fields.originalName,
+ medium: uploader.createPreviewUrl(uploader.formatFile(upload))
+ };
+ }
+
+ });
+ //
+ // // Add to store (won't persist since it's a fake ID)
+ // this.store.data.set(post.groupId, placeholderPost);
+ //
+ //
+ // // Render immediately
+ // let element;
+ // switch (this.view) {
+ // case 'grid':
+ // element = this.renderGridItem(placeholderPost);
+ // this.ui.grid.prepend(element);
+ // break;
+ // case 'list':
+ // element = this.renderListItem(placeholderPost);
+ // this.ui.grid.prepend(element);
+ // break;
+ // case 'table':
+ // element = this.renderTableItem(placeholderPost);
+ // if (this.ui.table.body) {
+ // this.ui.table.body.prepend(element);
+ // }
+ // break;
+ // }
+ // element.classList.add('uploading');
+ added.push(placeholderPost);
+ });
+ this.store.saveMany(added).then(() => this.render());
+
+
+ this.a11y.announce(`${posts.length} ${posts.length === 1 ? this.singular : this.plural} created. Waiting for server confirmation...`);
+ }
+
+ handleGroupMappings(mappings) {
+ // mappings = { "group_abc123": 456, "group_def456": 789 }
+
+ for (const [groupId, postId] of Object.entries(mappings)) {
+ // Get any pending changes for this temp item
+ let changes = {};
+ if (this.changes.has(groupId)) {
+ changes = this.changes.get(groupId);
+ this.changes.delete(groupId);
+ }
+ let storedChanges = this.changesStore.get(groupId)??{};
+ if (changes.size > 0 || storedChanges.size > 0) {
+ changes = window.deepMerge(storedChanges, changes);
+ this.changes.set(postId, changes);
+ this.scheduleBackup();
+ }
+ }
+ }
+ /***************************************************************
UTILITY
***************************************************************/
shouldRemoveItemUI(newStatus) {
--
Gitblit v1.10.0