From 2127b1bdd73ecd2423e443992da4b442f5a3c1a3 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Wed, 04 Feb 2026 21:19:25 +0000
Subject: [PATCH] =Major overhaul of MetaManager.php -> Meta.php and RestRouteManager.php -> Rest.php. Seems to work for JakeVan
---
assets/js/concise/CRUD.js | 210 +++++++++++++++++++++++++++++++++++-----------------
1 files changed, 142 insertions(+), 68 deletions(-)
diff --git a/assets/js/concise/CRUD.js b/assets/js/concise/CRUD.js
index e3adbea..d97155b 100644
--- a/assets/js/concise/CRUD.js
+++ b/assets/js/concise/CRUD.js
@@ -42,8 +42,8 @@
const baseSetup = (el, refs, data) => {
el.dataset.itemId = data.id;
-
- window.prefixInput(refs.checkbox, `select-${data.id}`, true);
+ let wrapper = refs.checkbox.closest('.preview');
+ window.prefixInput(refs.checkbox, `select-${data.id}`, wrapper, true);
refs.checkbox.value = data.id;
refs.checkbox.checked = crud.selected.has(parseInt(data.id));
if (refs.selectLabel) refs.selectLabel.htmlFor = `select-${data.id}`;
@@ -131,7 +131,8 @@
baseSetup(el, refs, data);
manyRefs?.inputs?.forEach(el => {
- window.prefixInput(el, `${data.id}-`);
+ let wrapper = el.closest('[data-field]');
+ window.prefixInput(el, `${data.id}-`, wrapper);
});
manyRefs?.status?.forEach(el => {
@@ -143,7 +144,8 @@
if (crud.isTimeline) {
if (refs.sharedRow) {
refs.sharedRow.querySelectorAll('input,select,textarea').forEach(input => {
- window.prefixInput(input, `${data.id}-`);
+ let wrapper = input.closest('[data-field]');
+ window.prefixInput(input, `${data.id}-`, wrapper);
});
crud.populate.populate(refs.sharedRow, data);
@@ -164,7 +166,8 @@
point.dataset.itemId = timeline.id;
point.querySelectorAll('input,select,textarea').forEach(input => {
- window.prefixInput(input, `${timeline.id}-`);
+ let wrapper = input.closest('[data-field]');
+ window.prefixInput(input, `${timeline.id}-`, wrapper);
});
crud.populate.populate(point, {
@@ -185,7 +188,8 @@
if (crud.ui.table.form?.dataset.edit !== undefined) {
// Non-timeline: prefix all inputs normally
manyRefs?.inputs?.forEach(input => {
- window.prefixInput(input, `${data.id}-`);
+ let wrapper = input.closest('[data-field]');
+ window.prefixInput(input, `${data.id}-`, wrapper);
});
manyRefs?.status?.forEach(el => {
@@ -362,11 +366,17 @@
this.forms.clearForm(formId);
}
- this.ui.modals[name].form.reset();
+ this.resetForm(this.ui.modals[name].form);
if (name === 'date') {
this.handleCustomDateSelection()
}
+ if (['edit','bulkEdit','create'].includes(name)) {
+ //handle escapes (not form submits)
+ if (window.debouncer.timeouts.has(`save-${this.content}`)) {
+ this.scheduleSave(0);
+ }
+ }
break;
case 'modal-open':
@@ -480,37 +490,35 @@
}
if (event === 'operation-status'
&& data.status === 'completed'
- && data.endpoint === 'content'
- && Object.keys(data.data?.posts??{}).length > 0) {
+ && data.endpoint === 'uploads/groups') {
+ console.log('Cleared local cache. Refresh to see changes');
this.store.clearCache();
- let ids = Object.keys(data.data.posts);
- let storedChanges = this.changesStore.getMany(ids);
+ }
+ if (event === 'operation-status'
+ && data.status === 'completed'
+ && data.type === 'content_update') {
+ console.log('Cleared local cache. Refresh to see changes');
+ this.store.clearCache();
- this.changesStore.deleteMany(ids);
-
- for (let id of ids) {
- let stored = storedChanges.filter(change => change.id === id)[0]??false;
-
- let sentChanges = data.data.posts[id];
- let remainingChanges = {};
-
- for (let [key, value] of Object.entries(sentChanges)) {
- if (stored && !Object.hasOwn(stored, key)) continue;
- if (stored[key] === value) {
- delete stored[key];
- }
- remainingChanges[key] = value;
- }
- if (Object.keys(remainingChanges).length > 0) {
- remainingChanges['id'] = id;
- remainingChanges['content'] = this.content;
- this.changes.set(id, remainingChanges);
- }
+ // Check for result data (from ContentExecutor)
+ if (!data.result || !data.result.posts) {
+ console.warn('Content update completed but no result.posts', data);
+ return;
}
- if (Object.values(this.changes).length > 0) {
- this.scheduleBackup();
+
+ // Get successfully processed post IDs
+ const successfulIds = Object.keys(data.result.posts).filter(id => {
+ return data.result.posts[id]?.success === true;
+ });
+
+ if (successfulIds.length === 0) {
+ return;
}
+
+ // Clear from both persistent and in-memory storage
+ this.changesStore.deleteMany(successfulIds);
+ successfulIds.forEach(id => this.changes.delete(id));
}
});
@@ -626,7 +634,7 @@
} else if (modal.classList.contains('create')) {
title = `Creating your new ${this.singular}`;
}
- this.savePosts(title,false);
+ this.scheduleSave(0);
}
handleChange(e) {
// Early bailout - target must be in an item or be a filter
@@ -706,11 +714,8 @@
handleBulkTaxonomy(result) {
if (!result.termIds.length || !this.selected.size) return;
- const changes = {};
- const taxonomyField = `tax_${result.taxonomy}`;
-
this.selected.forEach(itemID => {
- const item = this.store.get(parseInt(itemID));
+ const item = this.store.get(itemID);
if (!item) return;
// Merge existing terms with new ones
@@ -718,18 +723,10 @@
const existingIds = existingTerms.map(t => t.id);
const newIds = [...new Set([...existingIds, ...result.termIds])];
- changes[itemID] = {
- [taxonomyField]: newIds.join(','),
- content: this.content
- };
+ this.updateItem(itemID, result.taxonomy, newIds);
});
- if (Object.keys(changes).length > 0) {
- this.savePosts(
- `Adding ${result.terms.length} ${result.taxonomy} to ${this.selected.size} ${this.plural}...`,
- false
- ).then(()=>{});
- }
+ this.savePosts(`Adding ${result.terms.length} ${result.taxonomy} to ${this.selected.size} ${this.plural}...`,).then(()=> {});
this.selectionHandler.clearSelection();
}
@@ -740,11 +737,13 @@
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);
});
- this.savePosts('', true).then(()=>{});
}
updateItem(itemId, name, value) {
if (!this.changes.has(itemId)) {
@@ -753,19 +752,53 @@
this.changes.get(itemId)[name] = value;
this.scheduleBackup();
+ this.scheduleSave();
}
scheduleBackup() {
window.debouncer.schedule(
`changes-${this.content}`,
async () => {
if (this.changes.size > 0) {
- await this.changesStore.saveMany(this.changes);
- this.changes.clear();
+ await this.handleBackup();
}
},
2000
);
}
+ cancelBackup() {
+ window.debouncer.cancel(`changes-${this.content}`);
+ }
+ async handleBackup() {
+ const changesArray = Array.from(this.changes.values());
+ this.changes.clear();
+
+ const ids = changesArray.map(c => c.id);
+ const existing = await Promise.all(
+ ids.map(id => this.changesStore.get(id))
+ );
+
+ const changes = changesArray.map((change, i) =>
+ existing[i] ? window.deepMerge(existing[i], change) : change
+ );
+
+ await this.changesStore.saveMany(changes);
+ }
+
+ scheduleSave(delay = 10000) {
+ window.debouncer.schedule(
+ `save-${this.content}`,
+ async () => {
+ // Ensure latest changes are in IndexedDB
+ if (this.changes.size > 0) {
+ this.cancelBackup();
+ await this.handleBackup();
+ }
+
+ await this.savePosts('', false);
+ },
+ delay
+ );
+ }
handleFilterChange(target) {
let filter = target.dataset.filter;
@@ -875,7 +908,6 @@
}
}
openCreateModal(){
- this.ui.modals.create.form.reset();
this.forms.registerForm(this.ui.modals.create.form,{
cache: false,
});
@@ -899,9 +931,18 @@
}
break;
case 'trash':
- this.updateItem(itemID, 'post_status', 'trash');
- window.fade(button.closest('.item'), false);
- this.savePosts(`Sending ${this.singular} to trash...`).then(()=>{});
+ if (this.status === 'trash') {
+ if (confirm('Delete this item? This cannot be undone')) {
+ this.updateItem(itemID, 'post_status', 'delete');
+ window.fade(button.closest('.item'), false);
+ this.savePosts(`Permanently deleting ${this.singular}...`).then(()=>{});
+ this.store.delete(itemID);
+ }
+ } else {
+ this.updateItem(itemID, 'post_status', 'trash');
+ window.fade(button.closest('.item'), false);
+ this.savePosts(`Sending ${this.singular} to trash...`).then(()=>{});
+ }
break;
case 'bulk-edit':
if (this.selected.size > 0) {
@@ -921,7 +962,7 @@
}
}
handleBulkDelete() {
- let isTrash = this.store.filters.status === 'trash';
+ let isTrash = this.status === 'trash';
if (this.selected.size > 0 && confirm(`${isTrash ? 'Permanently delete' : 'Send'} ${this.selected.size} ${this.selected.size === 1 ? this.singular : this.plural}${isTrash ? '' : 'to trash'}?`)) {
this.selected.forEach(id => {
this.store.delete(id);
@@ -1082,8 +1123,6 @@
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.ui.modals.edit.form.reset();
-
this.forms.registerForm(this.ui.modals.edit.form, {cache: false});
this.isPopulating = true;
@@ -1118,9 +1157,9 @@
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;
+ this.isPopulating = true;
+ this.populate.populate(this.ui.modals.edit.form, item);
+ this.isPopulating = false;
}
/*****************************************************************
@@ -1128,9 +1167,12 @@
*****************************************************************/
async savePosts(title = '', delay = false) {
- const memoryChanges = Array.from(this.changes.values());
- const storedChanges = await this.changesStore.getAll();
- const changes = window.deepMerge(storedChanges, memoryChanges);
+ if (this.changes.size > 0) {
+ this.cancelBackup();
+ await this.handleBackup();
+ }
+ const changes = await this.changesStore.getAll();
+ console.log('Saving Changes: ', changes);
if (changes.length === 0) return;
if (title === '') {
@@ -1221,11 +1263,13 @@
updateUI() {
if (this.ui.bulk.action) {
let options = false;
- let hasEdit =this.ui.bulk.action.querySelector('[value="edit"]');
- if (this.status === 'trash' && hasEdit) {
+ let hasEdit = this.ui.bulk.action.querySelector('[value="edit"]');
+ let currentStatus = this.status;
+
+ if (currentStatus === 'trash' && hasEdit) {
window.removeChildren(this.ui.bulk.action);
options = window.jvbTemplates.create('trashOptions');
- } else if (this.status !== 'trash' && !hasEdit) {
+ } else if (currentStatus !== 'trash' && !hasEdit) {
window.removeChildren(this.ui.bulk.action);
options = window.jvbTemplates.create('notTrashOptions');
}
@@ -1387,9 +1431,13 @@
setFilter(name, value) {
if (!this.allowedFilters.includes(name)) return;
this.cache.set(name, value);
+
+ if (name === 'status') this.status = value;
+ if (name === 'orderby') this.orderby = value;
+ if (name === 'order') this.order = value;
+
let el = this.findFilterEl(name, value);
this.setElValue(el, value);
- //TODO: If we set the element to checked, does that automatically call the change listener, which then also sets the store filter and cache?
this.store.setFilter(name, value);
}
@@ -1461,6 +1509,32 @@
/***************************************************************
CLEANUP
***************************************************************/
+ resetForm(form) {
+ // Clear text inputs, textareas
+ form.querySelectorAll('input[type="hidden"], input[type="text"], input[type="number"], input[type="email"], input[type="url"], textarea').forEach(input => {
+ input.value = '';
+ });
+
+ // Uncheck checkboxes and radios
+ form.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(input => {
+ input.checked = false;
+ });
+
+ // Reset selects to first option
+ form.querySelectorAll('select').forEach(select => {
+ select.selectedIndex = 0;
+ });
+
+ // Clear any selected items displays
+ form.querySelectorAll('.selected-items').forEach(container => {
+ window.removeChildren(container);
+ });
+
+ // Clear upload previews
+ form.querySelectorAll('.item-grid.preview').forEach(grid => {
+ window.removeChildren(grid);
+ });
+ }
destroy() {
window.debouncer.cancel(`changes-${this.content}`);
if (this.changes.size > 0) {
--
Gitblit v1.10.0