Jake Vanderwerf
2026-02-04 2127b1bdd73ecd2423e443992da4b442f5a3c1a3
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 => {
@@ -367,6 +371,12 @@
                     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,9 +634,7 @@
      } else if (modal.classList.contains('create')) {
         title = `Creating your new ${this.singular}`;
      }
      this.cancelBackup();
      this.handleBackup().then(()=>{});
      this.savePosts(title,false).then(()=>{});
      this.scheduleSave(0);
   }
   handleChange(e) {
      // Early bailout - target must be in an item or be a filter
@@ -731,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)) {
@@ -744,6 +752,7 @@
      this.changes.get(itemId)[name] = value;
      this.scheduleBackup();
      this.scheduleSave();
   }
   scheduleBackup() {
      window.debouncer.schedule(
@@ -756,13 +765,39 @@
         2000
      );
   }
   cancelBackup() {
      window.debouncer.cancel(`changes-${this.content}`);
   }
   async handleBackup() {
      await this.changesStore.saveMany(this.changes);
      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;
@@ -1137,7 +1172,7 @@
         await this.handleBackup();
      }
      const changes = await this.changesStore.getAll();
      console.log('Saving Changes: ', changes);
      if (changes.length === 0) return;
      if (title === '') {