@innovastudio/contentbox 1.6.165 → 1.6.167

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.
@@ -12381,7 +12381,7 @@ class SettingsUIGenerator$1 {
12381
12381
  groupElement.className = 'cb-settings-group';
12382
12382
  const groupTitle = document.createElement('h3');
12383
12383
  groupTitle.className = 'cb-settings-group-title';
12384
- groupTitle.style.cssText = 'font-size: 17px;font-weight: 500;';
12384
+ groupTitle.style.cssText = 'font-size: 16px;font-weight: 400;';
12385
12385
  groupTitle.textContent = group.label;
12386
12386
  groupElement.appendChild(groupTitle);
12387
12387
  group.fields.forEach(fieldKey => {
@@ -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
  }
@@ -13825,6 +13886,12 @@ class PanelBox {
13825
13886
 
13826
13887
  </div>
13827
13888
 
13889
+ <div class="submain box-plugin-settings">
13890
+
13891
+ <hr style="width: 100%;border-bottom: #ededed 2px solid;margin-top: 20px;">
13892
+
13893
+ </div>
13894
+
13828
13895
  <button class="accordion-item btn-accordion-content" aria-expanded="false" aria-controls="boxcontent1">
13829
13896
  ${out('Content')}
13830
13897
  <span><svg><use xlink:href="#icon-chevron-down"></use></svg></span>
@@ -15203,9 +15270,134 @@ class PanelBox {
15203
15270
  chkAutoLayout.checked = true;
15204
15271
  } else {
15205
15272
  chkAutoLayout.checked = false;
15273
+ } // PLUGIN SETTINGS
15274
+
15275
+
15276
+ const divPluginSettings = panel.querySelector('.box-plugin-settings');
15277
+ this.divPluginSettings = divPluginSettings;
15278
+ const runtime = this.builder.win.builderRuntime;
15279
+
15280
+ if (overlayContent && runtime) {
15281
+ const currentElement = overlayContent.querySelector('[data-cb-type]');
15282
+
15283
+ if (currentElement) {
15284
+ divPluginSettings.style.display = '';
15285
+ this.renderPluginSettings(currentElement);
15286
+ } else {
15287
+ divPluginSettings.style.display = 'none';
15288
+ }
15206
15289
  }
15207
15290
  }
15208
15291
 
15292
+ renderPluginSettings(currentElement) {
15293
+ this.currentElement = currentElement;
15294
+ this.generator = null;
15295
+ this.currentForm = null;
15296
+ const divPluginSettings = this.divPluginSettings;
15297
+ divPluginSettings.innerHTML = '';
15298
+ const runtime = this.builder.win.builderRuntime;
15299
+ const pluginName = currentElement.getAttribute('data-cb-type');
15300
+ const plugin = runtime.getPlugin(pluginName);
15301
+ if (!plugin) return;
15302
+ let displayName = out(plugin.displayName || plugin.name);
15303
+ const divTitle = document.createElement('h3');
15304
+ divTitle.style.cssText = 'font-family: sans-serif;font-weight: 300;font-size: 20px;margin: 0 0 12px;';
15305
+ divTitle.innerText = displayName;
15306
+ divPluginSettings.appendChild(divTitle);
15307
+ this.generator = new SettingsUIGenerator$1(runtime, this.builder);
15308
+ const hasContentEditor = plugin && plugin.editor && plugin.editor.openContentEditor;
15309
+
15310
+ if (hasContentEditor) {
15311
+ // Get original content for the editor to work with
15312
+ const originalContent = currentElement.getAttribute('data-cb-original-content'); // Create a temporary element for editing (so editor can manipulate it)
15313
+
15314
+ let editableClone = document.querySelector('.editable-clone');
15315
+ if (editableClone) editableClone.remove(); // editableClone = document.createElement('div');
15316
+
15317
+ editableClone = currentElement.cloneNode(false);
15318
+ editableClone.innerHTML = originalContent;
15319
+ editableClone.className = 'editable-clone';
15320
+ editableClone.style.display = 'none'; // Hidden, just for editing
15321
+
15322
+ document.body.appendChild(editableClone);
15323
+ const originalElement = editableClone.cloneNode(false); // Let plugin handle everything - pass the editable clone
15324
+
15325
+ const editorUI = plugin.editor.openContentEditor(editableClone, this.builder, async () => {
15326
+ this.builder.editor.saveForUndo(); // Add index for sortable grid (for easy reorder)
15327
+
15328
+ let grid = editableClone.querySelector('.grid-sortable');
15329
+
15330
+ if (!grid) {
15331
+ if (currentElement.classList.contains('grid-sortable')) {
15332
+ grid = editableClone;
15333
+ }
15334
+ }
15335
+
15336
+ if (grid) {
15337
+ Array.from(grid.children).forEach((child, index) => {
15338
+ if (child.nodeType === 1 && child.tagName !== 'STYLE' && child.tagName !== 'SCRIPT') child.setAttribute('data-index', index);
15339
+ });
15340
+ } //----
15341
+ // Store edited content from clone
15342
+
15343
+
15344
+ currentElement.setAttribute('data-cb-original-content', editableClone.innerHTML); // Also performs similar value updates like the applyChanges()
15345
+ // currentElement.setAttribute('data-cb-content', editableClone.getAttribute('data-cb-content'));
15346
+
15347
+ const getChangedDataAttributes = (el1, el2) => {
15348
+ const changed = [];
15349
+
15350
+ for (const key of Object.keys(el1.dataset)) {
15351
+ if (key in el2.dataset && el1.dataset[key] !== el2.dataset[key]) {
15352
+ changed.push(key);
15353
+ }
15354
+ }
15355
+
15356
+ return changed;
15357
+ };
15358
+
15359
+ const changedAttributes = getChangedDataAttributes(originalElement, editableClone);
15360
+ changedAttributes.forEach(attrName => {
15361
+ // console.log(attrName)
15362
+ // console.log(editableClone.dataset[attrName])
15363
+ currentElement.dataset[attrName] = editableClone.dataset[attrName];
15364
+ }); // -------
15365
+
15366
+ currentElement.innerHTML = editableClone.innerHTML; // Update current content
15367
+ // Remove temporary clone
15368
+
15369
+ editableClone.remove(); // Reinitialize plugin with new content
15370
+
15371
+ await runtime.reinitialize(currentElement.parentElement);
15372
+ this.builder.onChange();
15373
+ this.builder.editor.hideElementTools();
15374
+ this.builder.makeSortable(currentElement.parentElement);
15375
+ });
15376
+ divPluginSettings.appendChild(editorUI);
15377
+ } // Generate form
15378
+
15379
+
15380
+ this.currentForm = this.generator.generateForm(pluginName, currentElement, () => {
15381
+ this.builder.editor.saveForUndo();
15382
+ this.applyChanges(runtime);
15383
+ });
15384
+ divPluginSettings.appendChild(this.currentForm);
15385
+ }
15386
+
15387
+ async applyChanges(runtime) {
15388
+ if (!this.currentElement || !this.currentForm) return; // 1. Get form values
15389
+
15390
+ const values = this.generator.getFormValues(this.currentForm); // 2. Apply to element attributes
15391
+
15392
+ this.generator.applyValues(this.currentElement, values); // 3. Reinitialize component
15393
+
15394
+ const container = this.currentElement.parentElement || this.currentElement.closest('.is-wrapper');
15395
+ await runtime.reinitialize(container);
15396
+ this.builder.onChange(); // console.log('Settings applied and component reinitialized');
15397
+
15398
+ this.builder.makeSortable(container);
15399
+ }
15400
+
15209
15401
  getStateTargeted() {
15210
15402
  let activeBox = this.builder.controlpanel.activeBox;
15211
15403
  if (!activeBox) return;
@@ -26210,8 +26402,14 @@ class ControlPanel {
26210
26402
 
26211
26403
  this.builder.onPageContentClick = e => {
26212
26404
  if (old) old.call(this);
26213
- if (this.builder.controlPanel && !document.body.classList.contains('controlpanel')) this.open();
26214
- if (document.body.classList.contains('controlpanel')) this.clickContent(e);
26405
+
26406
+ if (this.builder.controlPanel && !document.body.classList.contains('controlpanel')) {
26407
+ //if previously close, open
26408
+ this.open();
26409
+ } else {
26410
+ // if already open
26411
+ if (document.body.classList.contains('controlpanel')) this.clickContent(e);
26412
+ }
26215
26413
  };
26216
26414
 
26217
26415
  const btnClose = controlPanel.querySelector('.btn-close');
@@ -26934,6 +27132,7 @@ class ControlPanel {
26934
27132
  }
26935
27133
 
26936
27134
  const plugin = runtime.getPlugin(pluginName);
27135
+ if (!plugin) return;
26937
27136
  let titleHtml = out(plugin.displayName || plugin.name);
26938
27137
  this.title.innerHTML = titleHtml; // this.title.style.cssText = 'margin-bottom: 0';
26939
27138
 
@@ -32643,18 +32842,21 @@ class Util$1 {
32643
32842
  let builderActive = this.builder.doc.querySelector('.builder-active');
32644
32843
  if (builderActive) this.builder.applyBehaviorOn(builderActive);
32645
32844
  this.fixLayout(row);
32646
-
32647
- // cellElement.click(); //change active block to the newly created
32648
- if (element && element.tagName.toLowerCase() === 'img') {
32649
- element.onload = () => {
32845
+ try {
32846
+ // cellElement.click(); //change active block to the newly created
32847
+ if (element && element.tagName.toLowerCase() === 'img') {
32848
+ element.onload = () => {
32849
+ element.click();
32850
+ element.onload = null;
32851
+ setTimeout(() => {
32852
+ this.builder.element.image.repositionImageTool();
32853
+ }, 100);
32854
+ };
32855
+ } else if (element) {
32650
32856
  element.click();
32651
- element.onload = null;
32652
- setTimeout(() => {
32653
- this.builder.element.image.repositionImageTool();
32654
- }, 100);
32655
- };
32656
- } else if (element) {
32657
- element.click();
32857
+ }
32858
+ } catch (e) {
32859
+ // Do Nothing
32658
32860
  }
32659
32861
  }
32660
32862
  if (mode === 'row') {
@@ -32772,6 +32974,12 @@ class Util$1 {
32772
32974
 
32773
32975
  //Trigger Render event
32774
32976
  this.builder.opts.onRender();
32977
+
32978
+ // Reinit after drag drop block
32979
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize();
32980
+ setTimeout(() => {
32981
+ this.builder.makeSortable(this.builder.doc);
32982
+ }, 400);
32775
32983
  }
32776
32984
 
32777
32985
  // https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro
@@ -32864,6 +33072,11 @@ class Util$1 {
32864
33072
 
32865
33073
  //Hide Column tool (new!)
32866
33074
  this.builder.util.hideColumnTool();
33075
+
33076
+ // this.builder.makeSortable(rowElement); // cause other sortable not working
33077
+ setTimeout(() => {
33078
+ this.builder.makeSortable(this.builder.doc);
33079
+ }, 400);
32867
33080
  } else if (bSnippet) {
32868
33081
  if (noedit) {
32869
33082
  this.addContent(html, mode, 'data-noedit');
@@ -33037,6 +33250,9 @@ class Util$1 {
33037
33250
  this.builder.doc.querySelectorAll('.dummy-module').forEach(module => {
33038
33251
  module.classList.remove('dummy-module');
33039
33252
  });
33253
+ setTimeout(() => {
33254
+ this.builder.makeSortable(this.builder.doc);
33255
+ }, 400);
33040
33256
 
33041
33257
  // Change to row selection
33042
33258
  rowElement.className = rowElement.className.replace('row-outline', '');
@@ -33254,6 +33470,11 @@ class Util$1 {
33254
33470
  }
33255
33471
  }
33256
33472
 
33473
+ clearModals() {
33474
+ const builderStuff = this.builder.builderStuff;
33475
+ let modal = builderStuff.querySelector('.pluginsettings.active');
33476
+ this.hideModal(modal);
33477
+ }
33257
33478
  clearControls() {
33258
33479
  const dom = this.dom;
33259
33480
  const builderStuff = this.builder.builderStuff;
@@ -41663,6 +41884,7 @@ class HtmlUtil {
41663
41884
  // Re-init plugins
41664
41885
  if (this.builder.win.builderRuntime) await this.builder.win.builderRuntime.reinitialize();
41665
41886
  this.builder.hideModal(modal);
41887
+ this.builder.makeSortable(this.builder.doc);
41666
41888
  }
41667
41889
  viewHtmlExternal() {
41668
41890
  const util = this.builder.util;
@@ -42235,6 +42457,25 @@ class HtmlUtil {
42235
42457
  element.classList.remove(`cb-${type}`);
42236
42458
  }
42237
42459
  element.removeAttribute('data-cb-loaded');
42460
+ let grid;
42461
+ if (element.classList.contains('grid-sortable')) {
42462
+ grid = element;
42463
+ }
42464
+ if (element.querySelector('.grid-sortable')) {
42465
+ grid = element.querySelector('.grid-sortable');
42466
+ }
42467
+ element.removeAttribute('id');
42468
+ if (grid) {
42469
+ Array.from(grid.children).forEach(elm => {
42470
+ elm.removeAttribute('data-index');
42471
+ elm.removeAttribute('data-item-id');
42472
+ });
42473
+ }
42474
+ const elms = element.querySelectorAll('[data-scroll], [data-scroll-once]');
42475
+ elms.forEach(elm => {
42476
+ elm.removeAttribute('data-scroll');
42477
+ elm.removeAttribute('data-scroll-once');
42478
+ });
42238
42479
  });
42239
42480
  html = '';
42240
42481
  if (multiple) {
@@ -43367,6 +43608,9 @@ class Grid {
43367
43608
  this.builder.hideTools();
43368
43609
  cell.parentElement.insertBefore(cell, cell.previousElementSibling);
43369
43610
  this.builder.opts.onChange(true);
43611
+
43612
+ // Re-init plugins
43613
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(cell);
43370
43614
  }
43371
43615
  }
43372
43616
  moveColumnNext() {
@@ -43379,6 +43623,9 @@ class Grid {
43379
43623
  this.builder.hideTools();
43380
43624
  cell.parentElement.insertBefore(cellnext, cell);
43381
43625
  this.builder.opts.onChange(true);
43626
+
43627
+ // Re-init plugins
43628
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(cell);
43382
43629
  }
43383
43630
  }
43384
43631
  moveColumnUp() {
@@ -43408,6 +43655,9 @@ class Grid {
43408
43655
  //Move row up
43409
43656
  row.parentNode.insertBefore(row, row.previousElementSibling);
43410
43657
  this.builder.opts.onChange(true);
43658
+
43659
+ // Re-init plugins
43660
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(row);
43411
43661
  return;
43412
43662
  } else {
43413
43663
  this.builder.uo.saveForUndo();
@@ -43458,6 +43708,9 @@ class Grid {
43458
43708
  row = cell.parentNode; //update active row
43459
43709
  util.fixLayout(row);
43460
43710
  if (row.nextElementSibling) util.fixLayout(row.nextElementSibling);
43711
+
43712
+ // Re-init plugins
43713
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(row);
43461
43714
  }
43462
43715
  moveColumnDown() {
43463
43716
  let builder = this.builder;
@@ -43488,6 +43741,9 @@ class Grid {
43488
43741
  //Move row down
43489
43742
  row.parentNode.insertBefore(row.nextElementSibling, row);
43490
43743
  this.builder.opts.onChange(true);
43744
+
43745
+ // Re-init plugins
43746
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(row);
43491
43747
  return;
43492
43748
  } else {
43493
43749
  this.builder.uo.saveForUndo();
@@ -43538,6 +43794,9 @@ class Grid {
43538
43794
  row = cell.parentNode; //update active row
43539
43795
  util.fixLayout(row);
43540
43796
  if (row.previousElementSibling) util.fixLayout(row.previousElementSibling);
43797
+
43798
+ // Re-init plugins
43799
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(row);
43541
43800
  }
43542
43801
  duplicateColumn() {
43543
43802
  let builder = this.builder;
@@ -43579,6 +43838,7 @@ class Grid {
43579
43838
 
43580
43839
  // Re-init plugins
43581
43840
  if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(cellElement);
43841
+ this.builder.makeSortable(cellElement);
43582
43842
  }
43583
43843
  removeColumn() {
43584
43844
  let util = this.util;
@@ -44241,6 +44501,9 @@ class Grid {
44241
44501
  row.parentNode.insertBefore(row, row.previousElementSibling);
44242
44502
  this.rowTool.position(row);
44243
44503
  this.builder.opts.onChange();
44504
+
44505
+ // Re-init plugins
44506
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(row);
44244
44507
  } else {
44245
44508
  // Move to previous container
44246
44509
 
@@ -44285,6 +44548,9 @@ class Grid {
44285
44548
  row.parentNode.insertBefore(row.nextElementSibling, row);
44286
44549
  this.rowTool.position(row);
44287
44550
  this.builder.opts.onChange();
44551
+
44552
+ // Re-init plugins
44553
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(row);
44288
44554
  } else {
44289
44555
  // Move to next container
44290
44556
 
@@ -44355,6 +44621,7 @@ class Grid {
44355
44621
 
44356
44622
  // Re-init plugins
44357
44623
  if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize(rowElement);
44624
+ this.builder.makeSortable(rowElement);
44358
44625
  }
44359
44626
  }
44360
44627
  class RowTool$1 {
@@ -81073,6 +81340,7 @@ class RowTool {
81073
81340
  const grid = new Grid(this.builder);
81074
81341
  grid.removeColumn();
81075
81342
  util.clearControls();
81343
+ util.clearModals();
81076
81344
  if (this.builder.onSelectChange) this.builder.onSelectChange();
81077
81345
  });
81078
81346
  let btnGridEditor = rowtool.querySelector('.row-grideditor');
@@ -81183,6 +81451,7 @@ class RowTool {
81183
81451
  if (elm) dom.addEventListener(elm, 'click', () => {
81184
81452
  this.grid.removeRow();
81185
81453
  util.clearControls();
81454
+ util.clearModals();
81186
81455
  if (this.builder.onSelectChange) this.builder.onSelectChange();
81187
81456
  });
81188
81457
  }
@@ -120580,6 +120849,9 @@ class BlockModal {
120580
120849
  new Tabs({
120581
120850
  element: modal
120582
120851
  });
120852
+ new Draggable$2({
120853
+ selector: '.is-modal.editblock .is-draggable'
120854
+ });
120583
120855
  } // constructor
120584
120856
 
120585
120857
  getPage() {
@@ -123188,6 +123460,7 @@ Add an image for each feature.`, 'Create a new content showcasing a photo galler
123188
123460
  }
123189
123461
  });
123190
123462
  });
123463
+ this.makeSortable(this.doc);
123191
123464
  }
123192
123465
 
123193
123466
  // Load plugins
@@ -125063,8 +125336,16 @@ Add an image for each feature.`, 'Create a new content showcasing a photo galler
125063
125336
  let newRow = newRows[0]; // get first added row
125064
125337
  if (newRow.children.length > 0) {
125065
125338
  const newCol = newRow.children[0];
125339
+ const isPlugin = newCol.querySelector('[data-cb-type]');
125066
125340
  if (newCol.children.length > 0) {
125067
- newCol.children[0].click(); // Focus on first element
125341
+ if (isPlugin) {
125342
+ setTimeout(() => {
125343
+ // give time for plugin to render (apply style) => for correct elm tool position
125344
+ newCol.children[0].click();
125345
+ }, 400);
125346
+ } else {
125347
+ newCol.children[0].click(); // Focus on first element
125348
+ }
125068
125349
  }
125069
125350
  }
125070
125351
  }
@@ -125165,6 +125446,9 @@ Add an image for each feature.`, 'Create a new content showcasing a photo galler
125165
125446
  });
125166
125447
  });
125167
125448
  });
125449
+ setTimeout(() => {
125450
+ this.makeSortable(this.doc);
125451
+ }, 400);
125168
125452
  this.doc.querySelectorAll('.dummy-module').forEach(module => {
125169
125453
  module.classList.remove('dummy-module');
125170
125454
  });
@@ -126829,6 +127113,7 @@ Add an image for each feature.`, 'Create a new content showcasing a photo galler
126829
127113
  this.rte.viewZoom();
126830
127114
  }
126831
127115
  async loadSnippets(source, snippetOpen) {
127116
+ this.snippetUrl = source;
126832
127117
  if (this.preview) return;
126833
127118
  if (this.opts.snippetList === '#divSnippetList') {
126834
127119
  let snippetPanel = document.querySelector(this.opts.snippetList);
@@ -126952,24 +127237,7 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
126952
127237
  });
126953
127238
 
126954
127239
  // On Load HTML
126955
- const grids = this.doc.querySelectorAll('.grid-sortable');
126956
- grids.forEach(grid => {
126957
- if (grid.closest('.is-builder')) new Sortable$1(grid, {
126958
- animation: 600,
126959
- dragClass: 'hide-drag-class',
126960
- onStart: () => {
126961
- this.uo.saveForUndo(true);
126962
- },
126963
- onSort: () => {
126964
- if (grid.closest('[data-html]')) {
126965
- const moduleElm = grid.closest('[data-html]');
126966
- const encodedHtml = encodeURIComponent(moduleElm.innerHTML);
126967
- moduleElm.setAttribute('data-html', encodedHtml);
126968
- this.opts.onChange();
126969
- }
126970
- }
126971
- });
126972
- });
127240
+ this.makeSortable(this.doc, true);
126973
127241
  this.refresh();
126974
127242
  if (this.win.Block) {
126975
127243
  this.win.Block.render();
@@ -127009,24 +127277,7 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
127009
127277
  this.applyBehavior();
127010
127278
 
127011
127279
  // On Load HTML
127012
- const grids = this.doc.querySelectorAll('.grid-sortable');
127013
- grids.forEach(grid => {
127014
- if (grid.closest('.is-builder')) new Sortable$1(grid, {
127015
- animation: 600,
127016
- dragClass: 'hide-drag-class',
127017
- onStart: () => {
127018
- this.uo.saveForUndo(true);
127019
- },
127020
- onSort: () => {
127021
- if (grid.closest('[data-html]')) {
127022
- const moduleElm = grid.closest('[data-html]');
127023
- const encodedHtml = encodeURIComponent(moduleElm.innerHTML);
127024
- moduleElm.setAttribute('data-html', encodedHtml);
127025
- this.opts.onChange();
127026
- }
127027
- }
127028
- });
127029
- });
127280
+ this.makeSortable(this.doc, true);
127030
127281
 
127031
127282
  // this.uo.saveForUndo(); //First time
127032
127283
 
@@ -129625,6 +129876,86 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
129625
129876
  // this.doc.querySelectorAll('.elm-inspected').forEach(elm => elm.classList.remove('elm-inspected'));
129626
129877
  // this.doc.querySelectorAll('.elm-active').forEach(elm => elm.classList.remove('elm-active'));
129627
129878
  }
129879
+
129880
+ // Make list inside plugin element sortable
129881
+ makeSortable(container, module) {
129882
+ const grids = container.querySelectorAll('.grid-sortable');
129883
+ grids.forEach(grid => {
129884
+ const sortable = new Sortable$1(grid, {
129885
+ animation: 600,
129886
+ onStart: () => {
129887
+ this.uo.saveForUndo(true);
129888
+ },
129889
+ onSort: async () => {
129890
+ if (module) if (grid.closest('[data-html]')) {
129891
+ const moduleElm = grid.closest('[data-html]');
129892
+ const encodedHtml = encodeURIComponent(moduleElm.innerHTML);
129893
+ moduleElm.setAttribute('data-html', encodedHtml);
129894
+ this.onChange();
129895
+ }
129896
+ let plugin = grid.closest('[data-cb-original-content]');
129897
+ if (plugin) {
129898
+ // grid.setAttribute('data-cb-original-content', plugin.innerHTML); // don't do this!
129899
+ this.updateCleanContentOrder(grid); // use this!
129900
+
129901
+ this.onChange();
129902
+ grid.click();
129903
+ }
129904
+ }
129905
+ });
129906
+ // Store instance for later access
129907
+ grid._sortable = sortable;
129908
+ });
129909
+ }
129910
+ async updateCleanContentOrder(grid) {
129911
+ if (!grid) return;
129912
+ const plugin = grid.closest('[data-cb-original-content]');
129913
+ if (!plugin) return;
129914
+
129915
+ // 1. Get the current visual order of data-index from the actual DOM
129916
+ const newIndexOrder = Array.from(grid.children).map(child => child.dataset.index).filter(index => index !== undefined); // in case some lack data-index
129917
+
129918
+ // 2. Parse the *original clean* HTML
129919
+ const cleanHtml = plugin.dataset.cbOriginalContent;
129920
+ const parser = new DOMParser();
129921
+ const doc = parser.parseFromString(cleanHtml, 'text/html');
129922
+ let cleanGrid = doc.querySelector('.grid-sortable');
129923
+ let pluginIsSortableRoot = false;
129924
+ if (!cleanGrid) {
129925
+ // console.log('[step] .grid-sortable not found inside parsed clean HTML — creating wrapper from body children');
129926
+ cleanGrid = doc.createElement('div');
129927
+ cleanGrid.classList.add('grid-sortable');
129928
+ Array.from(doc.body.children).forEach(child => {
129929
+ if (child.nodeType === 1) cleanGrid.appendChild(child);
129930
+ });
129931
+ doc.body.innerHTML = '';
129932
+ doc.body.appendChild(cleanGrid);
129933
+ pluginIsSortableRoot = true;
129934
+ }
129935
+ const cleanItems = Array.from(cleanGrid.children).filter(el => el.nodeType === 1);
129936
+
129937
+ // 3. Build a map: data-index → clean node
129938
+ const cleanItemMap = new Map();
129939
+ cleanItems.forEach(item => {
129940
+ const idx = item.dataset.index;
129941
+ if (idx !== undefined) cleanItemMap.set(idx, item);
129942
+ });
129943
+
129944
+ // 4. Reorder clean items to match newIndexOrder
129945
+ const reorderedCleanItems = newIndexOrder.map(idx => cleanItemMap.get(idx)).filter(Boolean); // skip missing
129946
+
129947
+ // 5. Serialize back to HTML string
129948
+ const newCleanHtml = reorderedCleanItems.map(item => item.outerHTML).join('');
129949
+
129950
+ // 6. Update the clean source
129951
+ if (pluginIsSortableRoot) {
129952
+ plugin.setAttribute('data-cb-original-content', newCleanHtml);
129953
+ } else {
129954
+ cleanGrid.innerHTML = newCleanHtml;
129955
+ const newHTML = doc.body.innerHTML;
129956
+ plugin.setAttribute('data-cb-original-content', newHTML);
129957
+ }
129958
+ }
129628
129959
  }
129629
129960
 
129630
129961
  class ContentStuff {
@@ -165286,23 +165617,7 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
165286
165617
  activeModule.appendChild(range.createContextualFragment(html)); // We use createContextualFragment so that embedded javascript code (code block) will be executed
165287
165618
 
165288
165619
  this.settings.onRender();
165289
- const grids = activeModule.querySelectorAll('.grid-sortable');
165290
- grids.forEach(grid => {
165291
- new Sortable(grid, {
165292
- animation: 600,
165293
- onStart: () => {
165294
- this.editor.saveForUndo(true);
165295
- },
165296
- onSort: () => {
165297
- if (grid.closest('[data-html]')) {
165298
- const moduleElm = grid.closest('[data-html]');
165299
- const encodedHtml = encodeURIComponent(moduleElm.innerHTML);
165300
- moduleElm.setAttribute('data-html', encodedHtml);
165301
- this.onChange();
165302
- }
165303
- }
165304
- });
165305
- });
165620
+ this.makeSortable(activeModule);
165306
165621
  this.settings.onChange();
165307
165622
  this.hideModal(modalEditModule);
165308
165623
  });
@@ -166369,27 +166684,89 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
166369
166684
  const typePanel = document.querySelector('#divSidebarTypography');
166370
166685
  if (typePanel.classList.contains('active')) this.sidebar.selectType();
166371
166686
  });
166372
- const grids = box.querySelectorAll('.grid-sortable'); // apply sortable to all grids, including any in is-container,
166373
- // since ContentBuilder loadHTML is not used here in ContentBox
166687
+ this.makeSortable(box, true);
166688
+ } // boxSetup
166689
+ // Make list inside plugin element sortable
166374
166690
 
166691
+
166692
+ makeSortable(container, module) {
166693
+ const grids = container.querySelectorAll('.grid-sortable');
166375
166694
  grids.forEach(grid => {
166376
- new Sortable(grid, {
166695
+ const sortable = new Sortable(grid, {
166377
166696
  animation: 600,
166378
166697
  onStart: () => {
166379
166698
  this.editor.saveForUndo(true);
166380
166699
  },
166381
- onSort: () => {
166382
- if (grid.closest('[data-html]')) {
166700
+ onSort: async () => {
166701
+ if (module) if (grid.closest('[data-html]')) {
166383
166702
  const moduleElm = grid.closest('[data-html]');
166384
166703
  const encodedHtml = encodeURIComponent(moduleElm.innerHTML);
166385
166704
  moduleElm.setAttribute('data-html', encodedHtml);
166386
166705
  this.onChange();
166387
166706
  }
166707
+ let plugin = grid.closest('[data-cb-original-content]');
166708
+
166709
+ if (plugin) {
166710
+ // grid.setAttribute('data-cb-original-content', plugin.innerHTML); // don't do this!
166711
+ this.updateCleanContentOrder(grid); // use this!
166712
+
166713
+ this.onChange();
166714
+ grid.click();
166715
+ }
166388
166716
  }
166389
- });
166717
+ }); // Store instance for later access
166718
+
166719
+ grid._sortable = sortable;
166390
166720
  });
166391
- } // boxSetup
166721
+ }
166722
+
166723
+ async updateCleanContentOrder(grid) {
166724
+ if (!grid) return;
166725
+ const plugin = grid.closest('[data-cb-original-content]');
166726
+ if (!plugin) return; // 1. Get the current visual order of data-index from the actual DOM
166727
+
166728
+ const newIndexOrder = Array.from(grid.children).map(child => child.dataset.index).filter(index => index !== undefined); // in case some lack data-index
166729
+ // 2. Parse the *original clean* HTML
166392
166730
 
166731
+ const cleanHtml = plugin.dataset.cbOriginalContent;
166732
+ const parser = new DOMParser();
166733
+ const doc = parser.parseFromString(cleanHtml, 'text/html');
166734
+ let cleanGrid = doc.querySelector('.grid-sortable');
166735
+ let pluginIsSortableRoot = false;
166736
+
166737
+ if (!cleanGrid) {
166738
+ // console.log('[step] .grid-sortable not found inside parsed clean HTML — creating wrapper from body children');
166739
+ cleanGrid = doc.createElement('div');
166740
+ cleanGrid.classList.add('grid-sortable');
166741
+ Array.from(doc.body.children).forEach(child => {
166742
+ if (child.nodeType === 1) cleanGrid.appendChild(child);
166743
+ });
166744
+ doc.body.innerHTML = '';
166745
+ doc.body.appendChild(cleanGrid);
166746
+ pluginIsSortableRoot = true;
166747
+ }
166748
+
166749
+ const cleanItems = Array.from(cleanGrid.children).filter(el => el.nodeType === 1); // 3. Build a map: data-index → clean node
166750
+
166751
+ const cleanItemMap = new Map();
166752
+ cleanItems.forEach(item => {
166753
+ const idx = item.dataset.index;
166754
+ if (idx !== undefined) cleanItemMap.set(idx, item);
166755
+ }); // 4. Reorder clean items to match newIndexOrder
166756
+
166757
+ const reorderedCleanItems = newIndexOrder.map(idx => cleanItemMap.get(idx)).filter(Boolean); // skip missing
166758
+ // 5. Serialize back to HTML string
166759
+
166760
+ const newCleanHtml = reorderedCleanItems.map(item => item.outerHTML).join(''); // 6. Update the clean source
166761
+
166762
+ if (pluginIsSortableRoot) {
166763
+ plugin.setAttribute('data-cb-original-content', newCleanHtml);
166764
+ } else {
166765
+ cleanGrid.innerHTML = newCleanHtml;
166766
+ const newHTML = doc.body.innerHTML;
166767
+ plugin.setAttribute('data-cb-original-content', newHTML);
166768
+ }
166769
+ }
166393
166770
 
166394
166771
  addSection(html, contentCss) {
166395
166772
  let newSection = this.addIdea(html);