@innovastudio/contentbox 1.6.164 → 1.6.166

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@innovastudio/contentbox",
3
3
  "type": "module",
4
- "version": "1.6.164",
4
+ "version": "1.6.166",
5
5
  "description": "",
6
6
  "main": "public/contentbox/contentbox.esm.js",
7
7
  "types": "index.d.ts",
@@ -59,7 +59,7 @@
59
59
  "ws": "^8.13.0"
60
60
  },
61
61
  "dependencies": {
62
- "@innovastudio/contentbuilder": "^1.5.162",
62
+ "@innovastudio/contentbuilder": "^1.5.164",
63
63
  "js-beautify": "^1.14.0",
64
64
  "sortablejs": "^1.15.2"
65
65
  }
@@ -12326,7 +12326,7 @@ class SettingsUIGenerator$1 {
12326
12326
  const plugin = this.runtime.getPlugin(pluginName);
12327
12327
 
12328
12328
  if (!plugin || !plugin.settings) {
12329
- console.warn(`Plugin "${pluginName}" has no settings schema`);
12329
+ // console.warn(`Plugin "${pluginName}" has no settings schema`);
12330
12330
  return this.createEmptyForm();
12331
12331
  }
12332
12332
 
@@ -12625,6 +12625,45 @@ class SettingsUIGenerator$1 {
12625
12625
  return label;
12626
12626
  }
12627
12627
 
12628
+ hexToRgba(color) {
12629
+ // If it's already rgba() or rgb(), just return as-is
12630
+ if (/^rgba?\(/i.test(color)) {
12631
+ return color;
12632
+ } // Otherwise assume hex
12633
+
12634
+
12635
+ color = color.replace(/^#/, ''); // Expand shorthand hex (#rgb or #rgba)
12636
+
12637
+ if (color.length === 3 || color.length === 4) {
12638
+ color = color.split('').map(ch => ch + ch).join('');
12639
+ } // Parse r, g, b, a
12640
+
12641
+
12642
+ const r = parseInt(color.substring(0, 2), 16);
12643
+ const g = parseInt(color.substring(2, 4), 16);
12644
+ const b = parseInt(color.substring(4, 6), 16);
12645
+ const a = color.length === 8 ? parseInt(color.substring(6, 8), 16) / 255 : 1;
12646
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
12647
+ }
12648
+
12649
+ toHex(colorStr) {
12650
+ const ctx = document.createElement('canvas').getContext('2d');
12651
+ ctx.fillStyle = colorStr;
12652
+ const computed = ctx.fillStyle; // browser-normalized color
12653
+ // If already hex and 7 or 9 chars long (#rrggbb or #rrggbbaa), return as-is
12654
+
12655
+ if (computed.startsWith('#')) return computed; // Parse rgba(...) or rgb(...)
12656
+
12657
+ const rgba = computed.match(/\d+(\.\d+)?/g).map(Number);
12658
+ const [r, g, b, a = 1] = rgba; // default alpha = 1
12659
+ // Convert RGB and Alpha to hex (2 digits each)
12660
+
12661
+ const rgbHex = [r, g, b].map(v => v.toString(16).padStart(2, '0')).join('');
12662
+ const alphaHex = Math.round(a * 255).toString(16).padStart(2, '0'); // Only include alpha if it's not fully opaque
12663
+
12664
+ return a < 1 ? `#${rgbHex}${alphaHex}` : `#${rgbHex}`;
12665
+ }
12666
+
12628
12667
  createColorInput(name, config, value) {
12629
12668
  const container = document.createElement('div');
12630
12669
  const label = document.createElement('label');
@@ -12645,13 +12684,15 @@ class SettingsUIGenerator$1 {
12645
12684
  colorBtn.type = 'button';
12646
12685
  colorBtn.title = config.label || 'Color';
12647
12686
  colorBtn.className = 'btn-color is-btn-color';
12648
- colorBtn.style.backgroundColor = input.value;
12687
+ colorBtn.style.backgroundColor = this.hexToRgba(input.value);
12649
12688
  colorBtn.setAttribute('aria-label', `Choose color for ${config.label || name}`);
12650
12689
 
12651
12690
  const openPicker = e => {
12652
12691
  e.preventDefault();
12653
12692
  this.builder.openColorPicker(input.value, color => {
12654
- input.value = color;
12693
+ // input.value = color;
12694
+ const hex = this.toHex(color);
12695
+ input.value = hex;
12655
12696
  colorBtn.style.backgroundColor = color;
12656
12697
  input.dispatchEvent(new Event('change', {
12657
12698
  bubbles: true
@@ -12717,10 +12758,10 @@ class SettingsUIGenerator$1 {
12717
12758
  input.type = 'range';
12718
12759
  input.className = 'is-rangeslider';
12719
12760
  input.name = name;
12720
- input.value = currentVal;
12721
12761
  if (config.min !== undefined) input.min = config.min;
12722
12762
  if (config.max !== undefined) input.max = config.max;
12723
12763
  if (config.step !== undefined) input.step = config.step;
12764
+ input.value = currentVal;
12724
12765
  input.addEventListener('input', () => {
12725
12766
  labelText.textContent = (config.label || name) + ': ' + input.value + (config.unit || '');
12726
12767
  });
@@ -12853,7 +12894,23 @@ class PanelPlugin {
12853
12894
  const originalElement = editableClone.cloneNode(false); // Let plugin handle everything - pass the editable clone
12854
12895
 
12855
12896
  const editorUI = plugin.editor.openContentEditor(editableClone, this.builder, async () => {
12856
- this.builder.editor.saveForUndo(); // Store edited content from clone
12897
+ this.builder.editor.saveForUndo(); // Add index for sortable grid (for easy reorder)
12898
+
12899
+ let grid = editableClone.querySelector('.grid-sortable');
12900
+
12901
+ if (!grid) {
12902
+ if (currentElement.classList.contains('grid-sortable')) {
12903
+ grid = editableClone;
12904
+ }
12905
+ }
12906
+
12907
+ if (grid) {
12908
+ Array.from(grid.children).forEach((child, index) => {
12909
+ if (child.nodeType === 1 && child.tagName !== 'STYLE' && child.tagName !== 'SCRIPT') child.setAttribute('data-index', index);
12910
+ });
12911
+ } //----
12912
+ // Store edited content from clone
12913
+
12857
12914
 
12858
12915
  currentElement.setAttribute('data-cb-original-content', editableClone.innerHTML); // Also performs similar value updates like the applyChanges()
12859
12916
  // currentElement.setAttribute('data-cb-content', editableClone.getAttribute('data-cb-content'));
@@ -12884,6 +12941,8 @@ class PanelPlugin {
12884
12941
 
12885
12942
  await runtime.reinitialize(currentElement.parentElement);
12886
12943
  this.builder.onChange();
12944
+ this.builder.editor.hideElementTools();
12945
+ this.builder.makeSortable(currentElement.parentElement);
12887
12946
  });
12888
12947
  div.appendChild(editorUI);
12889
12948
  } // Generate form
@@ -12906,6 +12965,8 @@ class PanelPlugin {
12906
12965
  const container = this.currentElement.parentElement || this.currentElement.closest('.is-wrapper');
12907
12966
  await runtime.reinitialize(container);
12908
12967
  this.builder.onChange(); // console.log('Settings applied and component reinitialized');
12968
+
12969
+ this.builder.makeSortable(container);
12909
12970
  }
12910
12971
 
12911
12972
  }
@@ -26934,6 +26995,7 @@ class ControlPanel {
26934
26995
  }
26935
26996
 
26936
26997
  const plugin = runtime.getPlugin(pluginName);
26998
+ if (!plugin) return;
26937
26999
  let titleHtml = out(plugin.displayName || plugin.name);
26938
27000
  this.title.innerHTML = titleHtml; // this.title.style.cssText = 'margin-bottom: 0';
26939
27001
 
@@ -32643,18 +32705,21 @@ class Util$1 {
32643
32705
  let builderActive = this.builder.doc.querySelector('.builder-active');
32644
32706
  if (builderActive) this.builder.applyBehaviorOn(builderActive);
32645
32707
  this.fixLayout(row);
32646
-
32647
- // cellElement.click(); //change active block to the newly created
32648
- if (element && element.tagName.toLowerCase() === 'img') {
32649
- element.onload = () => {
32708
+ try {
32709
+ // cellElement.click(); //change active block to the newly created
32710
+ if (element && element.tagName.toLowerCase() === 'img') {
32711
+ element.onload = () => {
32712
+ element.click();
32713
+ element.onload = null;
32714
+ setTimeout(() => {
32715
+ this.builder.element.image.repositionImageTool();
32716
+ }, 100);
32717
+ };
32718
+ } else if (element) {
32650
32719
  element.click();
32651
- element.onload = null;
32652
- setTimeout(() => {
32653
- this.builder.element.image.repositionImageTool();
32654
- }, 100);
32655
- };
32656
- } else if (element) {
32657
- element.click();
32720
+ }
32721
+ } catch (e) {
32722
+ // Do Nothing
32658
32723
  }
32659
32724
  }
32660
32725
  if (mode === 'row') {
@@ -32772,6 +32837,10 @@ class Util$1 {
32772
32837
 
32773
32838
  //Trigger Render event
32774
32839
  this.builder.opts.onRender();
32840
+
32841
+ // Reinit after drag drop block
32842
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize();
32843
+ this.builder.makeSortable(this.builder.doc);
32775
32844
  }
32776
32845
 
32777
32846
  // https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro
@@ -32864,6 +32933,7 @@ class Util$1 {
32864
32933
 
32865
32934
  //Hide Column tool (new!)
32866
32935
  this.builder.util.hideColumnTool();
32936
+ this.builder.makeSortable(rowElement);
32867
32937
  } else if (bSnippet) {
32868
32938
  if (noedit) {
32869
32939
  this.addContent(html, mode, 'data-noedit');
@@ -33037,6 +33107,7 @@ class Util$1 {
33037
33107
  this.builder.doc.querySelectorAll('.dummy-module').forEach(module => {
33038
33108
  module.classList.remove('dummy-module');
33039
33109
  });
33110
+ this.builder.makeSortable(this.builder.doc);
33040
33111
 
33041
33112
  // Change to row selection
33042
33113
  rowElement.className = rowElement.className.replace('row-outline', '');
@@ -33054,6 +33125,9 @@ class Util$1 {
33054
33125
 
33055
33126
  //Trigger Render event
33056
33127
  this.builder.opts.onRender();
33128
+
33129
+ // Reinit after drag drop block
33130
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize();
33057
33131
  }
33058
33132
  clearActiveCell() {
33059
33133
  // this.builder.lastActiveCol = this.cellSelected(); // get active cell before cleared (will be used by snippets dialog)
@@ -41660,6 +41734,7 @@ class HtmlUtil {
41660
41734
  // Re-init plugins
41661
41735
  if (this.builder.win.builderRuntime) await this.builder.win.builderRuntime.reinitialize();
41662
41736
  this.builder.hideModal(modal);
41737
+ this.builder.makeSortable(this.builder.doc);
41663
41738
  }
41664
41739
  viewHtmlExternal() {
41665
41740
  const util = this.builder.util;
@@ -42232,6 +42307,25 @@ class HtmlUtil {
42232
42307
  element.classList.remove(`cb-${type}`);
42233
42308
  }
42234
42309
  element.removeAttribute('data-cb-loaded');
42310
+ let grid;
42311
+ if (element.classList.contains('grid-sortable')) {
42312
+ grid = element;
42313
+ }
42314
+ if (element.querySelector('.grid-sortable')) {
42315
+ grid = element.querySelector('.grid-sortable');
42316
+ }
42317
+ element.removeAttribute('id');
42318
+ if (grid) {
42319
+ Array.from(grid.children).forEach(elm => {
42320
+ elm.removeAttribute('data-index');
42321
+ elm.removeAttribute('data-item-id');
42322
+ });
42323
+ }
42324
+ const elms = element.querySelectorAll('[data-scroll], [data-scroll-once]');
42325
+ elms.forEach(elm => {
42326
+ elm.removeAttribute('data-scroll');
42327
+ elm.removeAttribute('data-scroll-once');
42328
+ });
42235
42329
  });
42236
42330
  html = '';
42237
42331
  if (multiple) {
@@ -43364,6 +43458,9 @@ class Grid {
43364
43458
  this.builder.hideTools();
43365
43459
  cell.parentElement.insertBefore(cell, cell.previousElementSibling);
43366
43460
  this.builder.opts.onChange(true);
43461
+
43462
+ // Re-init plugins
43463
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(cell);
43367
43464
  }
43368
43465
  }
43369
43466
  moveColumnNext() {
@@ -43376,6 +43473,9 @@ class Grid {
43376
43473
  this.builder.hideTools();
43377
43474
  cell.parentElement.insertBefore(cellnext, cell);
43378
43475
  this.builder.opts.onChange(true);
43476
+
43477
+ // Re-init plugins
43478
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(cell);
43379
43479
  }
43380
43480
  }
43381
43481
  moveColumnUp() {
@@ -43405,6 +43505,9 @@ class Grid {
43405
43505
  //Move row up
43406
43506
  row.parentNode.insertBefore(row, row.previousElementSibling);
43407
43507
  this.builder.opts.onChange(true);
43508
+
43509
+ // Re-init plugins
43510
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(row);
43408
43511
  return;
43409
43512
  } else {
43410
43513
  this.builder.uo.saveForUndo();
@@ -43455,6 +43558,9 @@ class Grid {
43455
43558
  row = cell.parentNode; //update active row
43456
43559
  util.fixLayout(row);
43457
43560
  if (row.nextElementSibling) util.fixLayout(row.nextElementSibling);
43561
+
43562
+ // Re-init plugins
43563
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(row);
43458
43564
  }
43459
43565
  moveColumnDown() {
43460
43566
  let builder = this.builder;
@@ -43485,6 +43591,9 @@ class Grid {
43485
43591
  //Move row down
43486
43592
  row.parentNode.insertBefore(row.nextElementSibling, row);
43487
43593
  this.builder.opts.onChange(true);
43594
+
43595
+ // Re-init plugins
43596
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(row);
43488
43597
  return;
43489
43598
  } else {
43490
43599
  this.builder.uo.saveForUndo();
@@ -43535,6 +43644,9 @@ class Grid {
43535
43644
  row = cell.parentNode; //update active row
43536
43645
  util.fixLayout(row);
43537
43646
  if (row.previousElementSibling) util.fixLayout(row.previousElementSibling);
43647
+
43648
+ // Re-init plugins
43649
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(row);
43538
43650
  }
43539
43651
  duplicateColumn() {
43540
43652
  let builder = this.builder;
@@ -44238,6 +44350,9 @@ class Grid {
44238
44350
  row.parentNode.insertBefore(row, row.previousElementSibling);
44239
44351
  this.rowTool.position(row);
44240
44352
  this.builder.opts.onChange();
44353
+
44354
+ // Re-init plugins
44355
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(row);
44241
44356
  } else {
44242
44357
  // Move to previous container
44243
44358
 
@@ -44282,6 +44397,9 @@ class Grid {
44282
44397
  row.parentNode.insertBefore(row.nextElementSibling, row);
44283
44398
  this.rowTool.position(row);
44284
44399
  this.builder.opts.onChange();
44400
+
44401
+ // Re-init plugins
44402
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(row);
44285
44403
  } else {
44286
44404
  // Move to next container
44287
44405
 
@@ -123185,6 +123303,7 @@ Add an image for each feature.`, 'Create a new content showcasing a photo galler
123185
123303
  }
123186
123304
  });
123187
123305
  });
123306
+ this.makeSortable(this.doc);
123188
123307
  }
123189
123308
 
123190
123309
  // Load plugins
@@ -125053,6 +125172,9 @@ Add an image for each feature.`, 'Create a new content showcasing a photo galler
125053
125172
 
125054
125173
  // After snippet has been added, re-apply behavior on builder areas
125055
125174
  this.applyBehaviorOn(builder);
125175
+
125176
+ // Reinit after drag drop block
125177
+ if (this.win.builderRuntime) this.win.builderRuntime.reinitialize(builder);
125056
125178
  if (newRows.length > 0) {
125057
125179
  let newRow = newRows[0]; // get first added row
125058
125180
  if (newRow.children.length > 0) {
@@ -125125,8 +125247,9 @@ Add an image for each feature.`, 'Create a new content showcasing a photo galler
125125
125247
  this.setZoomOnControl(builder);
125126
125248
 
125127
125249
  // Re-init plugins
125128
- if (this.win.builderRuntime) this.win.builderRuntime.reinitialize(builder);
125250
+ // if(this.win.builderRuntime) this.win.builderRuntime.reinitialize(builder);
125129
125251
  }
125252
+
125130
125253
  applySortableGrid() {
125131
125254
  if (this.iframe) {
125132
125255
  const oldCss = this.contentStuff.querySelector('#css-scale');
@@ -125158,6 +125281,7 @@ Add an image for each feature.`, 'Create a new content showcasing a photo galler
125158
125281
  });
125159
125282
  });
125160
125283
  });
125284
+ this.makeSortable(this.doc);
125161
125285
  this.doc.querySelectorAll('.dummy-module').forEach(module => {
125162
125286
  module.classList.remove('dummy-module');
125163
125287
  });
@@ -126945,24 +127069,7 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
126945
127069
  });
126946
127070
 
126947
127071
  // On Load HTML
126948
- const grids = this.doc.querySelectorAll('.grid-sortable');
126949
- grids.forEach(grid => {
126950
- if (grid.closest('.is-builder')) new Sortable$1(grid, {
126951
- animation: 600,
126952
- dragClass: 'hide-drag-class',
126953
- onStart: () => {
126954
- this.uo.saveForUndo(true);
126955
- },
126956
- onSort: () => {
126957
- if (grid.closest('[data-html]')) {
126958
- const moduleElm = grid.closest('[data-html]');
126959
- const encodedHtml = encodeURIComponent(moduleElm.innerHTML);
126960
- moduleElm.setAttribute('data-html', encodedHtml);
126961
- this.opts.onChange();
126962
- }
126963
- }
126964
- });
126965
- });
127072
+ this.makeSortable(this.doc, true);
126966
127073
  this.refresh();
126967
127074
  if (this.win.Block) {
126968
127075
  this.win.Block.render();
@@ -127002,24 +127109,7 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
127002
127109
  this.applyBehavior();
127003
127110
 
127004
127111
  // On Load HTML
127005
- const grids = this.doc.querySelectorAll('.grid-sortable');
127006
- grids.forEach(grid => {
127007
- if (grid.closest('.is-builder')) new Sortable$1(grid, {
127008
- animation: 600,
127009
- dragClass: 'hide-drag-class',
127010
- onStart: () => {
127011
- this.uo.saveForUndo(true);
127012
- },
127013
- onSort: () => {
127014
- if (grid.closest('[data-html]')) {
127015
- const moduleElm = grid.closest('[data-html]');
127016
- const encodedHtml = encodeURIComponent(moduleElm.innerHTML);
127017
- moduleElm.setAttribute('data-html', encodedHtml);
127018
- this.opts.onChange();
127019
- }
127020
- }
127021
- });
127022
- });
127112
+ this.makeSortable(this.doc, true);
127023
127113
 
127024
127114
  // this.uo.saveForUndo(); //First time
127025
127115
 
@@ -129538,6 +129628,9 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
129538
129628
 
129539
129629
  // // Hide element tool
129540
129630
  // this.elmTool.hide();
129631
+
129632
+ // Reinit after drag drop block
129633
+ if (this.win.builderRuntime) this.win.builderRuntime.reinitialize();
129541
129634
  }
129542
129635
 
129543
129636
  // this.sortableOnPage.option('draggable', '.dummy');
@@ -129615,6 +129708,86 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
129615
129708
  // this.doc.querySelectorAll('.elm-inspected').forEach(elm => elm.classList.remove('elm-inspected'));
129616
129709
  // this.doc.querySelectorAll('.elm-active').forEach(elm => elm.classList.remove('elm-active'));
129617
129710
  }
129711
+
129712
+ // Make list inside plugin element sortable
129713
+ makeSortable(container, module) {
129714
+ const grids = container.querySelectorAll('.grid-sortable');
129715
+ grids.forEach(grid => {
129716
+ const sortable = new Sortable$1(grid, {
129717
+ animation: 600,
129718
+ onStart: () => {
129719
+ this.uo.saveForUndo(true);
129720
+ },
129721
+ onSort: async () => {
129722
+ if (module) if (grid.closest('[data-html]')) {
129723
+ const moduleElm = grid.closest('[data-html]');
129724
+ const encodedHtml = encodeURIComponent(moduleElm.innerHTML);
129725
+ moduleElm.setAttribute('data-html', encodedHtml);
129726
+ this.onChange();
129727
+ }
129728
+ let plugin = grid.closest('[data-cb-original-content]');
129729
+ if (plugin) {
129730
+ // grid.setAttribute('data-cb-original-content', plugin.innerHTML); // don't do this!
129731
+ this.updateCleanContentOrder(grid); // use this!
129732
+
129733
+ this.onChange();
129734
+ grid.click();
129735
+ }
129736
+ }
129737
+ });
129738
+ // Store instance for later access
129739
+ grid._sortable = sortable;
129740
+ });
129741
+ }
129742
+ async updateCleanContentOrder(grid) {
129743
+ if (!grid) return;
129744
+ const plugin = grid.closest('[data-cb-original-content]');
129745
+ if (!plugin) return;
129746
+
129747
+ // 1. Get the current visual order of data-index from the actual DOM
129748
+ const newIndexOrder = Array.from(grid.children).map(child => child.dataset.index).filter(index => index !== undefined); // in case some lack data-index
129749
+
129750
+ // 2. Parse the *original clean* HTML
129751
+ const cleanHtml = plugin.dataset.cbOriginalContent;
129752
+ const parser = new DOMParser();
129753
+ const doc = parser.parseFromString(cleanHtml, 'text/html');
129754
+ let cleanGrid = doc.querySelector('.grid-sortable');
129755
+ let pluginIsSortableRoot = false;
129756
+ if (!cleanGrid) {
129757
+ // console.log('[step] .grid-sortable not found inside parsed clean HTML — creating wrapper from body children');
129758
+ cleanGrid = doc.createElement('div');
129759
+ cleanGrid.classList.add('grid-sortable');
129760
+ Array.from(doc.body.children).forEach(child => {
129761
+ if (child.nodeType === 1) cleanGrid.appendChild(child);
129762
+ });
129763
+ doc.body.innerHTML = '';
129764
+ doc.body.appendChild(cleanGrid);
129765
+ pluginIsSortableRoot = true;
129766
+ }
129767
+ const cleanItems = Array.from(cleanGrid.children).filter(el => el.nodeType === 1);
129768
+
129769
+ // 3. Build a map: data-index → clean node
129770
+ const cleanItemMap = new Map();
129771
+ cleanItems.forEach(item => {
129772
+ const idx = item.dataset.index;
129773
+ if (idx !== undefined) cleanItemMap.set(idx, item);
129774
+ });
129775
+
129776
+ // 4. Reorder clean items to match newIndexOrder
129777
+ const reorderedCleanItems = newIndexOrder.map(idx => cleanItemMap.get(idx)).filter(Boolean); // skip missing
129778
+
129779
+ // 5. Serialize back to HTML string
129780
+ const newCleanHtml = reorderedCleanItems.map(item => item.outerHTML).join('');
129781
+
129782
+ // 6. Update the clean source
129783
+ if (pluginIsSortableRoot) {
129784
+ plugin.setAttribute('data-cb-original-content', newCleanHtml);
129785
+ } else {
129786
+ cleanGrid.innerHTML = newCleanHtml;
129787
+ const newHTML = doc.body.innerHTML;
129788
+ plugin.setAttribute('data-cb-original-content', newHTML);
129789
+ }
129790
+ }
129618
129791
  }
129619
129792
 
129620
129793
  class ContentStuff {
@@ -165276,23 +165449,7 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
165276
165449
  activeModule.appendChild(range.createContextualFragment(html)); // We use createContextualFragment so that embedded javascript code (code block) will be executed
165277
165450
 
165278
165451
  this.settings.onRender();
165279
- const grids = activeModule.querySelectorAll('.grid-sortable');
165280
- grids.forEach(grid => {
165281
- new Sortable(grid, {
165282
- animation: 600,
165283
- onStart: () => {
165284
- this.editor.saveForUndo(true);
165285
- },
165286
- onSort: () => {
165287
- if (grid.closest('[data-html]')) {
165288
- const moduleElm = grid.closest('[data-html]');
165289
- const encodedHtml = encodeURIComponent(moduleElm.innerHTML);
165290
- moduleElm.setAttribute('data-html', encodedHtml);
165291
- this.onChange();
165292
- }
165293
- }
165294
- });
165295
- });
165452
+ this.makeSortable(activeModule);
165296
165453
  this.settings.onChange();
165297
165454
  this.hideModal(modalEditModule);
165298
165455
  });
@@ -166359,27 +166516,89 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
166359
166516
  const typePanel = document.querySelector('#divSidebarTypography');
166360
166517
  if (typePanel.classList.contains('active')) this.sidebar.selectType();
166361
166518
  });
166362
- const grids = box.querySelectorAll('.grid-sortable'); // apply sortable to all grids, including any in is-container,
166363
- // since ContentBuilder loadHTML is not used here in ContentBox
166519
+ this.makeSortable(box, true);
166520
+ } // boxSetup
166521
+ // Make list inside plugin element sortable
166522
+
166364
166523
 
166524
+ makeSortable(container, module) {
166525
+ const grids = container.querySelectorAll('.grid-sortable');
166365
166526
  grids.forEach(grid => {
166366
- new Sortable(grid, {
166527
+ const sortable = new Sortable(grid, {
166367
166528
  animation: 600,
166368
166529
  onStart: () => {
166369
166530
  this.editor.saveForUndo(true);
166370
166531
  },
166371
- onSort: () => {
166372
- if (grid.closest('[data-html]')) {
166532
+ onSort: async () => {
166533
+ if (module) if (grid.closest('[data-html]')) {
166373
166534
  const moduleElm = grid.closest('[data-html]');
166374
166535
  const encodedHtml = encodeURIComponent(moduleElm.innerHTML);
166375
166536
  moduleElm.setAttribute('data-html', encodedHtml);
166376
166537
  this.onChange();
166377
166538
  }
166539
+ let plugin = grid.closest('[data-cb-original-content]');
166540
+
166541
+ if (plugin) {
166542
+ // grid.setAttribute('data-cb-original-content', plugin.innerHTML); // don't do this!
166543
+ this.updateCleanContentOrder(grid); // use this!
166544
+
166545
+ this.onChange();
166546
+ grid.click();
166547
+ }
166378
166548
  }
166379
- });
166549
+ }); // Store instance for later access
166550
+
166551
+ grid._sortable = sortable;
166380
166552
  });
166381
- } // boxSetup
166553
+ }
166554
+
166555
+ async updateCleanContentOrder(grid) {
166556
+ if (!grid) return;
166557
+ const plugin = grid.closest('[data-cb-original-content]');
166558
+ if (!plugin) return; // 1. Get the current visual order of data-index from the actual DOM
166559
+
166560
+ const newIndexOrder = Array.from(grid.children).map(child => child.dataset.index).filter(index => index !== undefined); // in case some lack data-index
166561
+ // 2. Parse the *original clean* HTML
166562
+
166563
+ const cleanHtml = plugin.dataset.cbOriginalContent;
166564
+ const parser = new DOMParser();
166565
+ const doc = parser.parseFromString(cleanHtml, 'text/html');
166566
+ let cleanGrid = doc.querySelector('.grid-sortable');
166567
+ let pluginIsSortableRoot = false;
166382
166568
 
166569
+ if (!cleanGrid) {
166570
+ // console.log('[step] .grid-sortable not found inside parsed clean HTML — creating wrapper from body children');
166571
+ cleanGrid = doc.createElement('div');
166572
+ cleanGrid.classList.add('grid-sortable');
166573
+ Array.from(doc.body.children).forEach(child => {
166574
+ if (child.nodeType === 1) cleanGrid.appendChild(child);
166575
+ });
166576
+ doc.body.innerHTML = '';
166577
+ doc.body.appendChild(cleanGrid);
166578
+ pluginIsSortableRoot = true;
166579
+ }
166580
+
166581
+ const cleanItems = Array.from(cleanGrid.children).filter(el => el.nodeType === 1); // 3. Build a map: data-index → clean node
166582
+
166583
+ const cleanItemMap = new Map();
166584
+ cleanItems.forEach(item => {
166585
+ const idx = item.dataset.index;
166586
+ if (idx !== undefined) cleanItemMap.set(idx, item);
166587
+ }); // 4. Reorder clean items to match newIndexOrder
166588
+
166589
+ const reorderedCleanItems = newIndexOrder.map(idx => cleanItemMap.get(idx)).filter(Boolean); // skip missing
166590
+ // 5. Serialize back to HTML string
166591
+
166592
+ const newCleanHtml = reorderedCleanItems.map(item => item.outerHTML).join(''); // 6. Update the clean source
166593
+
166594
+ if (pluginIsSortableRoot) {
166595
+ plugin.setAttribute('data-cb-original-content', newCleanHtml);
166596
+ } else {
166597
+ cleanGrid.innerHTML = newCleanHtml;
166598
+ const newHTML = doc.body.innerHTML;
166599
+ plugin.setAttribute('data-cb-original-content', newHTML);
166600
+ }
166601
+ }
166383
166602
 
166384
166603
  addSection(html, contentCss) {
166385
166604
  let newSection = this.addIdea(html);