@innovastudio/contentbox 1.6.161 → 1.6.162

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.
@@ -12300,6 +12300,616 @@ class PanelSpacer {
12300
12300
 
12301
12301
  }
12302
12302
 
12303
+ /**
12304
+ * ContentBox Settings UI Generator
12305
+ * Automatically generates settings form based on plugin schema
12306
+ *
12307
+ * Usage in ContentBox Editor:
12308
+ * const generator = new SettingsUIGenerator(runtime);
12309
+ * const form = generator.generateForm('logo-loop', currentElement);
12310
+ * panel.appendChild(form);
12311
+ */
12312
+ class SettingsUIGenerator$1 {
12313
+ constructor(runtime, builder) {
12314
+ this.runtime = runtime;
12315
+ this.builder = builder;
12316
+ }
12317
+ /**
12318
+ * Generate settings form for a plugin
12319
+ * @param {string} pluginName - Plugin name
12320
+ * @param {HTMLElement} element - Current element being edited
12321
+ * @returns {HTMLElement} Form element
12322
+ */
12323
+
12324
+
12325
+ generateForm(pluginName, element, onChange) {
12326
+ const plugin = this.runtime.getPlugin(pluginName);
12327
+
12328
+ if (!plugin || !plugin.settings) {
12329
+ console.warn(`Plugin "${pluginName}" has no settings schema`);
12330
+ return this.createEmptyForm();
12331
+ }
12332
+
12333
+ const form = document.createElement('div');
12334
+ form.className = 'cb-settings-form'; // Get current values from element
12335
+
12336
+ const currentValues = this.getCurrentValues(element, plugin.settings); // Check if plugin has grouped settings
12337
+
12338
+ if (plugin.settings._groups) {
12339
+ this.generateGroupedForm(form, plugin.settings, currentValues);
12340
+ } else {
12341
+ this.generateFlatForm(form, plugin.settings, currentValues);
12342
+ } // Attach listeners for auto-update
12343
+
12344
+
12345
+ if (typeof onChange === 'function') {
12346
+ form.addEventListener('input', () => {
12347
+ const values = this.getFormValues(form);
12348
+ onChange(values, element);
12349
+ });
12350
+ form.addEventListener('change', () => {
12351
+ const values = this.getFormValues(form);
12352
+ onChange(values, element);
12353
+ });
12354
+ }
12355
+
12356
+ return form;
12357
+ }
12358
+ /**
12359
+ * Generate flat form (no groups)
12360
+ */
12361
+
12362
+
12363
+ generateFlatForm(container, settings, currentValues) {
12364
+ Object.keys(settings).forEach(key => {
12365
+ if (key.startsWith('_')) return; // Skip meta keys
12366
+
12367
+ const field = settings[key];
12368
+ const fieldElement = this.createField(key, field, currentValues[key]);
12369
+ container.appendChild(fieldElement);
12370
+ });
12371
+ }
12372
+ /**
12373
+ * Generate grouped form
12374
+ */
12375
+
12376
+
12377
+ generateGroupedForm(container, settings, currentValues) {
12378
+ const groups = settings._groups || [];
12379
+ groups.forEach(group => {
12380
+ const groupElement = document.createElement('div');
12381
+ groupElement.className = 'cb-settings-group';
12382
+ const groupTitle = document.createElement('h3');
12383
+ groupTitle.className = 'cb-settings-group-title';
12384
+ groupTitle.style.cssText = 'font-size: 17px;font-weight: 500;';
12385
+ groupTitle.textContent = group.label;
12386
+ groupElement.appendChild(groupTitle);
12387
+ group.fields.forEach(fieldKey => {
12388
+ const field = settings[fieldKey];
12389
+ if (!field) return;
12390
+ const fieldElement = this.createField(fieldKey, field, currentValues[fieldKey]);
12391
+ groupElement.appendChild(fieldElement);
12392
+ });
12393
+ container.appendChild(groupElement);
12394
+ });
12395
+ }
12396
+ /**
12397
+ * Create a single form field
12398
+ */
12399
+
12400
+
12401
+ createField(name, config, value) {
12402
+ const wrapper = document.createElement('div');
12403
+ wrapper.className = 'cb-settings-field';
12404
+ wrapper.style.marginBottom = '20px';
12405
+ wrapper.setAttribute('data-field', name); // Add conditional display logic
12406
+
12407
+ if (config.dependsOn) {
12408
+ wrapper.setAttribute('data-depends-on', JSON.stringify(config.dependsOn));
12409
+ wrapper.style.display = 'none'; // Hidden by default, shown by JS
12410
+ } // Label
12411
+
12412
+
12413
+ const label = document.createElement('label');
12414
+ label.className = 'cb-settings-label';
12415
+ label.style.fontWeight = 500;
12416
+ label.textContent = config.label || name; // wrapper.appendChild(label);
12417
+ // Input based on type
12418
+
12419
+ const input = this.createInput(name, config, value);
12420
+ wrapper.appendChild(input); // Description
12421
+ // if (config.description) {
12422
+ // const desc = document.createElement('p');
12423
+ // desc.className = 'cb-settings-description';
12424
+ // desc.textContent = config.description;
12425
+ // wrapper.appendChild(desc);
12426
+ // }
12427
+
12428
+ return wrapper;
12429
+ }
12430
+ /**
12431
+ * Create input element based on field type
12432
+ */
12433
+
12434
+
12435
+ createInput(name, config, value) {
12436
+ const currentValue = value !== undefined ? value : config.default;
12437
+
12438
+ switch (config.type) {
12439
+ case 'number':
12440
+ return this.createNumberInput(name, config, currentValue);
12441
+
12442
+ case 'boolean':
12443
+ return this.createCheckboxInput(name, config, currentValue);
12444
+
12445
+ case 'select':
12446
+ return this.createSelectInput(name, config, currentValue);
12447
+
12448
+ case 'radio':
12449
+ return this.createRadioInput(name, config, currentValue);
12450
+
12451
+ case 'textarea':
12452
+ return this.createTextareaInput(name, config, currentValue);
12453
+
12454
+ case 'color':
12455
+ return this.createColorInput(name, config, currentValue);
12456
+
12457
+ case 'range':
12458
+ return this.createRangeInput(name, config, currentValue);
12459
+
12460
+ case 'text':
12461
+ default:
12462
+ return this.createTextInput(name, config, currentValue);
12463
+ }
12464
+ }
12465
+
12466
+ createTextInput(name, config, value) {
12467
+ const label = document.createElement('label');
12468
+ label.className = 'label';
12469
+ label.style.fontWeight = 500;
12470
+ const labelText = document.createElement('div');
12471
+ labelText.textContent = config.label || name;
12472
+ label.appendChild(labelText);
12473
+ const input = document.createElement('input');
12474
+ input.type = 'text';
12475
+ input.name = name;
12476
+ input.value = value || '';
12477
+ if (config.placeholder) input.placeholder = config.placeholder;
12478
+ label.appendChild(input); // Add description if exists
12479
+
12480
+ if (config.description) {
12481
+ const desc = document.createElement('small');
12482
+ desc.style.cssText = 'display: block;margin-top: 4px;';
12483
+ desc.textContent = config.description;
12484
+ label.appendChild(desc);
12485
+ }
12486
+
12487
+ return label;
12488
+ }
12489
+
12490
+ createNumberInput(name, config, value) {
12491
+ const label = document.createElement('label');
12492
+ label.className = 'label';
12493
+ label.style.fontWeight = 500;
12494
+ const labelText = document.createElement('div');
12495
+ labelText.textContent = config.label || name;
12496
+
12497
+ if (config.unit) {
12498
+ labelText.textContent += ` (${config.unit})`;
12499
+ }
12500
+
12501
+ label.appendChild(labelText);
12502
+ const input = document.createElement('input');
12503
+ input.type = 'number';
12504
+ input.style.cssText = 'width: 100px;';
12505
+ input.name = name;
12506
+ input.value = value !== undefined ? value : config.default;
12507
+ if (config.min !== undefined) input.min = config.min;
12508
+ if (config.max !== undefined) input.max = config.max;
12509
+ if (config.step !== undefined) input.step = config.step;
12510
+ label.appendChild(input);
12511
+
12512
+ if (config.description) {
12513
+ const desc = document.createElement('small');
12514
+ desc.style.cssText = 'display: block;margin-top: 4px;';
12515
+ desc.textContent = config.description;
12516
+ label.appendChild(desc);
12517
+ }
12518
+
12519
+ return label;
12520
+ }
12521
+
12522
+ createCheckboxInput(name, config, value) {
12523
+ const label = document.createElement('label');
12524
+ label.className = 'label checkbox';
12525
+ const div = document.createElement('div');
12526
+ const input = document.createElement('input');
12527
+ input.type = 'checkbox';
12528
+ input.name = name;
12529
+ input.checked = value !== undefined ? value : config.default;
12530
+ const span = document.createElement('span');
12531
+ span.textContent = config.label || name;
12532
+ label.appendChild(input);
12533
+ label.appendChild(span);
12534
+ div.appendChild(label);
12535
+
12536
+ if (config.description) {
12537
+ const desc = document.createElement('small');
12538
+ desc.style.cssText = 'display: block;margin-top: 4px;';
12539
+ desc.textContent = config.description;
12540
+ div.appendChild(desc);
12541
+ }
12542
+
12543
+ return label;
12544
+ }
12545
+
12546
+ createSelectInput(name, config, value) {
12547
+ const label = document.createElement('label');
12548
+ label.className = 'label';
12549
+ label.style.fontWeight = 500;
12550
+ const labelText = document.createTextNode((config.label || name) + ':');
12551
+ label.appendChild(labelText);
12552
+ const select = document.createElement('select');
12553
+ select.name = name;
12554
+ config.options.forEach(option => {
12555
+ const opt = document.createElement('option');
12556
+ opt.value = option.value;
12557
+ opt.textContent = option.label;
12558
+ if (option.value === value) opt.selected = true;
12559
+ select.appendChild(opt);
12560
+ });
12561
+ label.appendChild(select);
12562
+
12563
+ if (config.description) {
12564
+ const desc = document.createElement('small');
12565
+ desc.style.cssText = 'display: block;margin-top: 4px;';
12566
+ desc.textContent = config.description;
12567
+ label.appendChild(desc);
12568
+ }
12569
+
12570
+ return label;
12571
+ }
12572
+
12573
+ createRadioInput(name, config, value) {
12574
+ const wrapper = document.createElement('div');
12575
+ const title = document.createElement('div');
12576
+ title.textContent = config.label || name;
12577
+ title.style.cssText = 'margin-bottom: 8px;font-weight: 590;';
12578
+ wrapper.appendChild(title);
12579
+ config.options.forEach(option => {
12580
+ const label = document.createElement('label');
12581
+ label.className = 'label checkbox';
12582
+ const input = document.createElement('input');
12583
+ input.type = 'radio';
12584
+ input.name = name;
12585
+ input.value = option.value;
12586
+ input.checked = option.value === value;
12587
+ const span = document.createElement('span');
12588
+ span.textContent = option.label;
12589
+ label.appendChild(input);
12590
+ label.appendChild(span);
12591
+ wrapper.appendChild(label);
12592
+ });
12593
+
12594
+ if (config.description) {
12595
+ const desc = document.createElement('small');
12596
+ desc.style.cssText = 'display: block;margin-top: 4px;';
12597
+ desc.textContent = config.description;
12598
+ wrapper.appendChild(desc);
12599
+ }
12600
+
12601
+ return wrapper;
12602
+ }
12603
+
12604
+ createTextareaInput(name, config, value) {
12605
+ const label = document.createElement('label');
12606
+ label.className = 'label';
12607
+ label.style.fontWeight = 500;
12608
+ const labelText = document.createElement('div');
12609
+ labelText.textContent = config.label || name;
12610
+ label.appendChild(labelText);
12611
+ const textarea = document.createElement('textarea');
12612
+ textarea.name = name;
12613
+ textarea.value = value || '';
12614
+ textarea.rows = config.rows || 4;
12615
+ if (config.placeholder) textarea.placeholder = config.placeholder;
12616
+ label.appendChild(textarea);
12617
+
12618
+ if (config.description) {
12619
+ const desc = document.createElement('small');
12620
+ desc.style.cssText = 'display: block;margin-top: 4px;';
12621
+ desc.textContent = config.description;
12622
+ label.appendChild(desc);
12623
+ }
12624
+
12625
+ return label;
12626
+ }
12627
+
12628
+ createColorInput(name, config, value) {
12629
+ const container = document.createElement('div');
12630
+ const label = document.createElement('label');
12631
+ label.className = 'label';
12632
+ label.style.fontWeight = 500;
12633
+ label.style.cursor = 'pointer';
12634
+ const labelText = document.createElement('div');
12635
+ labelText.textContent = config.label || name;
12636
+ label.appendChild(labelText);
12637
+ const input = document.createElement('input');
12638
+ input.type = 'hidden';
12639
+ input.name = name;
12640
+ input.value = value || config.default || '#000000';
12641
+ const group = document.createElement('div');
12642
+ group.className = 'group';
12643
+ group.style.cssText = 'width: 52px; margin:0; overflow: visible;';
12644
+ const colorBtn = document.createElement('button');
12645
+ colorBtn.type = 'button';
12646
+ colorBtn.title = config.label || 'Color';
12647
+ colorBtn.className = 'btn-color is-btn-color';
12648
+ colorBtn.style.backgroundColor = input.value;
12649
+ colorBtn.setAttribute('aria-label', `Choose color for ${config.label || name}`);
12650
+
12651
+ const openPicker = e => {
12652
+ e.preventDefault();
12653
+ this.builder.openColorPicker(input.value, color => {
12654
+ input.value = color;
12655
+ colorBtn.style.backgroundColor = color;
12656
+ input.dispatchEvent(new Event('change', {
12657
+ bubbles: true
12658
+ }));
12659
+ }, colorBtn);
12660
+ };
12661
+
12662
+ colorBtn.addEventListener('click', openPicker); // Make label click trigger the button
12663
+
12664
+ label.addEventListener('click', e => {
12665
+ if (e.target === label || e.target === labelText) {
12666
+ colorBtn.click();
12667
+ }
12668
+ });
12669
+ group.appendChild(colorBtn);
12670
+ label.appendChild(input);
12671
+
12672
+ if (config.description) {
12673
+ const desc = document.createElement('small');
12674
+ desc.style.cssText = 'display: block;margin-top: 4px;';
12675
+ desc.textContent = config.description;
12676
+ label.appendChild(desc);
12677
+ }
12678
+
12679
+ container.appendChild(label);
12680
+ container.appendChild(group);
12681
+ return container;
12682
+ }
12683
+
12684
+ createColorInput2(name, config, value) {
12685
+ const label = document.createElement('label');
12686
+ label.className = 'label';
12687
+ label.style.fontWeight = 500;
12688
+ const labelText = document.createElement('div');
12689
+ labelText.textContent = config.label || name;
12690
+ label.appendChild(labelText);
12691
+ const input = document.createElement('input');
12692
+ input.type = 'color';
12693
+ input.name = name;
12694
+ input.style.cssText = 'width:40px;height:40px';
12695
+ input.value = value || config.default || '#000000';
12696
+ label.appendChild(input);
12697
+
12698
+ if (config.description) {
12699
+ const desc = document.createElement('small');
12700
+ desc.style.cssText = 'display: block;margin-top: 4px;';
12701
+ desc.textContent = config.description;
12702
+ label.appendChild(desc);
12703
+ }
12704
+
12705
+ return label;
12706
+ }
12707
+
12708
+ createRangeInput(name, config, value) {
12709
+ const label = document.createElement('label');
12710
+ label.className = 'label';
12711
+ label.style.fontWeight = 500;
12712
+ const currentVal = value !== undefined ? value : config.default;
12713
+ const labelText = document.createElement('div');
12714
+ labelText.textContent = (config.label || name) + ': ' + currentVal + (config.unit || '');
12715
+ label.appendChild(labelText);
12716
+ const input = document.createElement('input');
12717
+ input.type = 'range';
12718
+ input.className = 'is-rangeslider';
12719
+ input.name = name;
12720
+ input.value = currentVal;
12721
+ if (config.min !== undefined) input.min = config.min;
12722
+ if (config.max !== undefined) input.max = config.max;
12723
+ if (config.step !== undefined) input.step = config.step;
12724
+ input.addEventListener('input', () => {
12725
+ labelText.textContent = (config.label || name) + ': ' + input.value + (config.unit || '');
12726
+ });
12727
+ label.appendChild(input);
12728
+
12729
+ if (config.description) {
12730
+ const desc = document.createElement('small');
12731
+ desc.style.cssText = 'display: block;margin-top: 4px;';
12732
+ desc.textContent = config.description;
12733
+ label.appendChild(desc);
12734
+ }
12735
+
12736
+ return label;
12737
+ }
12738
+ /**
12739
+ * Get current values from element attributes
12740
+ */
12741
+
12742
+
12743
+ getCurrentValues(element, settings) {
12744
+ const values = {};
12745
+ Object.keys(settings).forEach(key => {
12746
+ if (key.startsWith('_')) return;
12747
+ const attrName = 'data-cb-' + key.replace(/([A-Z])/g, '-$1').toLowerCase();
12748
+ const attrValue = element.getAttribute(attrName);
12749
+
12750
+ if (attrValue !== null) {
12751
+ values[key] = this.parseValue(attrValue, settings[key].type);
12752
+ }
12753
+ });
12754
+ return values;
12755
+ }
12756
+ /**
12757
+ * Parse attribute value based on type
12758
+ */
12759
+
12760
+
12761
+ parseValue(value, type) {
12762
+ switch (type) {
12763
+ case 'number':
12764
+ return parseFloat(value);
12765
+
12766
+ case 'boolean':
12767
+ return value === 'true';
12768
+
12769
+ default:
12770
+ return value;
12771
+ }
12772
+ }
12773
+ /**
12774
+ * Get form values
12775
+ */
12776
+
12777
+
12778
+ getFormValues(form) {
12779
+ const values = {};
12780
+ const inputs = form.querySelectorAll('input, select, textarea');
12781
+ inputs.forEach(input => {
12782
+ const name = input.name;
12783
+ if (!name) return;
12784
+
12785
+ if (input.type === 'checkbox') {
12786
+ values[name] = input.checked;
12787
+ } else if (input.type === 'radio') {
12788
+ if (input.checked) {
12789
+ values[name] = input.value;
12790
+ }
12791
+ } else {
12792
+ values[name] = input.value;
12793
+ }
12794
+ });
12795
+ return values;
12796
+ }
12797
+ /**
12798
+ * Apply form values to element
12799
+ */
12800
+
12801
+
12802
+ applyValues(element, values) {
12803
+ Object.keys(values).forEach(key => {
12804
+ const attrName = 'data-cb-' + key.replace(/([A-Z])/g, '-$1').toLowerCase();
12805
+ element.setAttribute(attrName, values[key]);
12806
+ });
12807
+ }
12808
+
12809
+ createEmptyForm() {
12810
+ const form = document.createElement('div');
12811
+ form.className = 'cb-settings-form';
12812
+ form.innerHTML = '<p>This plugin has no configurable settings.</p>';
12813
+ return form;
12814
+ }
12815
+
12816
+ }
12817
+
12818
+ class PanelPlugin {
12819
+ constructor(panel, builder) {
12820
+ this.builder = builder;
12821
+ this.panel = panel;
12822
+ this.generator = null;
12823
+ this.currentElement = null;
12824
+ this.currentForm = null;
12825
+ }
12826
+
12827
+ render(pluginName, currentElement, runtime) {
12828
+ // Clear panel
12829
+ this.panel.innerHTML = '';
12830
+ const div = document.createElement('div');
12831
+ div.classList.add('submain');
12832
+ this.panel.appendChild(div); // Store references
12833
+
12834
+ this.currentElement = currentElement;
12835
+ this.generator = new SettingsUIGenerator$1(runtime, this.builder); // Check if plugin has custom content editor
12836
+
12837
+ const plugin = runtime.getPlugin(pluginName);
12838
+ const hasContentEditor = plugin && plugin.editor && plugin.editor.openContentEditor;
12839
+
12840
+ if (hasContentEditor) {
12841
+ // Get original content for the editor to work with
12842
+ const originalContent = currentElement.getAttribute('data-cb-original-content'); // Create a temporary element for editing (so editor can manipulate it)
12843
+
12844
+ let editableClone = document.querySelector('.editable-clone');
12845
+ if (editableClone) editableClone.remove(); // editableClone = document.createElement('div');
12846
+
12847
+ editableClone = currentElement.cloneNode(false);
12848
+ editableClone.innerHTML = originalContent;
12849
+ editableClone.className = 'editable-clone';
12850
+ editableClone.style.display = 'none'; // Hidden, just for editing
12851
+
12852
+ document.body.appendChild(editableClone);
12853
+ const originalElement = editableClone.cloneNode(false); // Let plugin handle everything - pass the editable clone
12854
+
12855
+ const editorUI = plugin.editor.openContentEditor(editableClone, this.builder, async () => {
12856
+ this.builder.editor.saveForUndo(); // Store edited content from clone
12857
+
12858
+ currentElement.setAttribute('data-cb-original-content', editableClone.innerHTML); // Also performs similar value updates like the applyChanges()
12859
+ // currentElement.setAttribute('data-cb-content', editableClone.getAttribute('data-cb-content'));
12860
+
12861
+ const getChangedDataAttributes = (el1, el2) => {
12862
+ const changed = [];
12863
+
12864
+ for (const key of Object.keys(el1.dataset)) {
12865
+ if (key in el2.dataset && el1.dataset[key] !== el2.dataset[key]) {
12866
+ changed.push(key);
12867
+ }
12868
+ }
12869
+
12870
+ return changed;
12871
+ };
12872
+
12873
+ const changedAttributes = getChangedDataAttributes(originalElement, editableClone);
12874
+ changedAttributes.forEach(attrName => {
12875
+ // console.log(attrName)
12876
+ // console.log(editableClone.dataset[attrName])
12877
+ currentElement.dataset[attrName] = editableClone.dataset[attrName];
12878
+ }); // -------
12879
+
12880
+ currentElement.innerHTML = editableClone.innerHTML; // Update current content
12881
+ // Remove temporary clone
12882
+
12883
+ editableClone.remove(); // Reinitialize plugin with new content
12884
+
12885
+ await runtime.reinitialize(currentElement.parentElement);
12886
+ this.builder.onChange();
12887
+ });
12888
+ div.appendChild(editorUI);
12889
+ } // Generate form
12890
+
12891
+
12892
+ this.currentForm = this.generator.generateForm(pluginName, currentElement, () => {
12893
+ this.builder.editor.saveForUndo();
12894
+ this.applyChanges(runtime);
12895
+ });
12896
+ div.appendChild(this.currentForm);
12897
+ }
12898
+
12899
+ async applyChanges(runtime) {
12900
+ if (!this.currentElement || !this.currentForm) return; // 1. Get form values
12901
+
12902
+ const values = this.generator.getFormValues(this.currentForm); // 2. Apply to element attributes
12903
+
12904
+ this.generator.applyValues(this.currentElement, values); // 3. Reinitialize component
12905
+
12906
+ const container = this.currentElement.parentElement || this.currentElement.closest('.is-wrapper');
12907
+ await runtime.reinitialize(container);
12908
+ this.builder.onChange(); // console.log('Settings applied and component reinitialized');
12909
+ }
12910
+
12911
+ }
12912
+
12303
12913
  class PanelIcon {
12304
12914
  constructor(panel, builder) {
12305
12915
  this.builder = builder;
@@ -25494,6 +26104,7 @@ class ControlPanel {
25494
26104
  <div class="panel-audio"></div>
25495
26105
  <div class="panel-iframe"></div>
25496
26106
  <div class="panel-spacer"></div>
26107
+ <div class="panel-plugin"></div>
25497
26108
  <div class="panel-column"></div>
25498
26109
  <div class="panel-row"></div>
25499
26110
  <div class="panel-container"></div>
@@ -25544,6 +26155,7 @@ class ControlPanel {
25544
26155
  this.panelAudio = controlPanel.querySelector('.panel-audio');
25545
26156
  this.panelIframe = controlPanel.querySelector('.panel-iframe');
25546
26157
  this.panelSpacer = controlPanel.querySelector('.panel-spacer');
26158
+ this.panelPlugin = controlPanel.querySelector('.panel-plugin');
25547
26159
  this.panelIcon = controlPanel.querySelector('.panel-icon');
25548
26160
  this.panelSvg = controlPanel.querySelector('.panel-svg');
25549
26161
  this.panelCode = controlPanel.querySelector('.panel-code');
@@ -25584,6 +26196,7 @@ class ControlPanel {
25584
26196
  this.objPanelAudio = new PanelAudio(this.panelAudio, this.builder);
25585
26197
  this.objPanelIframe = new PanelIframe(this.panelIframe, this.builder);
25586
26198
  this.objPanelSpacer = new PanelSpacer(this.panelSpacer, this.builder);
26199
+ this.objPanelPlugin = new PanelPlugin(this.panelPlugin, this.builder);
25587
26200
  this.objPanelIcon = new PanelIcon(this.panelIcon, this.builder);
25588
26201
  this.objPanelSvg = new PanelSvg(this.panelSvg, this.builder);
25589
26202
  this.objPanelCode = new PanelCode(this.panelCode, this.builder);
@@ -26240,6 +26853,7 @@ class ControlPanel {
26240
26853
  this.panelAudio.classList.remove('active');
26241
26854
  this.panelIframe.classList.remove('active');
26242
26855
  this.panelSpacer.classList.remove('active');
26856
+ this.panelPlugin.classList.remove('active');
26243
26857
  this.panelIcon.classList.remove('active');
26244
26858
  this.panelSvg.classList.remove('active');
26245
26859
  this.panelCode.classList.remove('active');
@@ -26302,13 +26916,32 @@ class ControlPanel {
26302
26916
  this.title.innerHTML = titleHtml;
26303
26917
  this.panelCode.classList.add('active');
26304
26918
  this.objPanelCode.getState();
26305
- } else if (element.closest('[data-noedit]') && !element.closest('[data-html]')) {
26919
+ } else if (element.closest('[data-noedit]') && !element.closest('[data-html]') && !element.closest('[data-cb-type]')) {
26306
26920
  let titleHtml = out('Locked');
26307
26921
  this.title.innerHTML = titleHtml;
26308
26922
  } else {
26309
26923
  if (element.querySelector('.icon') && element.childElementCount === 1) ;
26310
26924
 
26311
- if (element.tagName.toLowerCase() === 'img') {
26925
+ if (element.closest('[data-cb-type]')) {
26926
+ const runtime = this.builder.win.builderRuntime;
26927
+
26928
+ if (runtime) {
26929
+ const pluginElement = element.closest('[data-cb-type]');
26930
+ const pluginName = pluginElement.getAttribute('data-cb-type');
26931
+
26932
+ if (!pluginElement.hasAttribute('data-cb-editmode')) {
26933
+ pluginElement.setAttribute('contentEditable', false);
26934
+ }
26935
+
26936
+ const plugin = runtime.getPlugin(pluginName);
26937
+ let titleHtml = out(plugin.displayName || plugin.name);
26938
+ this.title.innerHTML = titleHtml; // this.title.style.cssText = 'margin-bottom: 0';
26939
+
26940
+ this.objPanelPlugin.render(pluginName, pluginElement, runtime);
26941
+ }
26942
+
26943
+ this.panelPlugin.classList.add('active');
26944
+ } else if (element.tagName.toLowerCase() === 'img') {
26312
26945
  // Image
26313
26946
  let titleHtml = out('Image');
26314
26947
  this.title.innerHTML = titleHtml;
@@ -40969,7 +41602,7 @@ class HtmlUtil {
40969
41602
  this.builder.opts.onRender();
40970
41603
 
40971
41604
  // Re-select
40972
- if (row.firstChild) row.firstChild.click();
41605
+ // if(row.firstChild) row.firstChild.click();
40973
41606
  //Change to row selection
40974
41607
  row.classList.remove('row-outline');
40975
41608
  //Hide Column tool (new!)
@@ -41022,6 +41655,8 @@ class HtmlUtil {
41022
41655
  }
41023
41656
  util.clearControls(); // NEW
41024
41657
 
41658
+ // Re-init plugins
41659
+ if (this.builder.win.builderRuntime) this.builder.win.builderRuntime.reinitialize();
41025
41660
  this.builder.hideModal(modal);
41026
41661
  }
41027
41662
  viewHtmlExternal() {
@@ -41568,6 +42203,35 @@ class HtmlUtil {
41568
42203
  elm.innerHTML = '';
41569
42204
  });
41570
42205
  }
42206
+
42207
+ // Clean all interactive components
42208
+ const components = tmp.querySelectorAll('[data-cb-type]');
42209
+ components.forEach(element => {
42210
+ const original = element.getAttribute('data-cb-original-content');
42211
+ if (original) {
42212
+ // Restore original HTML
42213
+ element.innerHTML = original;
42214
+
42215
+ // Remove runtime-added attributes
42216
+ element.removeAttribute('data-cb-original-content');
42217
+ element.removeAttribute('data-cb-loaded');
42218
+
42219
+ // element.removeAttribute('data-cb-logo-loop-initialized');
42220
+
42221
+ Array.from(element.attributes).forEach(attr => {
42222
+ const name = attr.name;
42223
+ // Remove any attribute that starts with "data-cb-" and ends with "-initialized"
42224
+ if (name.startsWith('data-cb-') && name.endsWith('-initialized')) {
42225
+ element.removeAttribute(name);
42226
+ }
42227
+ });
42228
+ element.removeAttribute('data-cb-editmode');
42229
+
42230
+ // Remove runtime-added classes
42231
+ const type = element.getAttribute('data-cb-type');
42232
+ element.classList.remove(`cb-${type}`);
42233
+ }
42234
+ });
41571
42235
  html = '';
41572
42236
  if (multiple) {
41573
42237
  //ContentBox
@@ -42423,7 +43087,7 @@ const prepareSvgIcons = builder => {
42423
43087
  <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
42424
43088
  <path d="M5 4h4l3 3h7a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2"></path>
42425
43089
  </symbol>
42426
- <symbol id="icon-folder" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
43090
+ <symbol id="icon-folder" viewBox="0 0 24 24" stroke-width="1.4" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
42427
43091
  <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
42428
43092
  <path d="M11 19h-6a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2h4l3 3h7a2 2 0 0 1 2 2v2.5"></path>
42429
43093
  <path d="M18 18m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0"></path>
@@ -77235,6 +77899,817 @@ class Spacer {
77235
77899
  }
77236
77900
  }
77237
77901
 
77902
+ /**
77903
+ * ContentBox Settings UI Generator
77904
+ * Automatically generates settings form based on plugin schema
77905
+ *
77906
+ * Usage in ContentBox Editor:
77907
+ * const generator = new SettingsUIGenerator(runtime);
77908
+ * const form = generator.generateForm('logo-loop', currentElement);
77909
+ * panel.appendChild(form);
77910
+ */
77911
+
77912
+ class SettingsUIGenerator {
77913
+ constructor(runtime, builder) {
77914
+ this.runtime = runtime;
77915
+ this.builder = builder;
77916
+ }
77917
+
77918
+ /**
77919
+ * Generate settings form for a plugin
77920
+ * @param {string} pluginName - Plugin name
77921
+ * @param {HTMLElement} element - Current element being edited
77922
+ * @returns {HTMLElement} Form element
77923
+ */
77924
+ generateForm(pluginName, element, onChange) {
77925
+ const plugin = this.runtime.getPlugin(pluginName);
77926
+ if (!plugin || !plugin.settings) {
77927
+ console.warn(`Plugin "${pluginName}" has no settings schema`);
77928
+ return this.createEmptyForm();
77929
+ }
77930
+ const form = document.createElement('div');
77931
+ form.className = 'cb-settings-form';
77932
+
77933
+ // Get current values from element
77934
+ const currentValues = this.getCurrentValues(element, plugin.settings);
77935
+
77936
+ // Check if plugin has grouped settings
77937
+ if (plugin.settings._groups) {
77938
+ this.generateGroupedForm(form, plugin.settings, currentValues);
77939
+ } else {
77940
+ this.generateFlatForm(form, plugin.settings, currentValues);
77941
+ }
77942
+
77943
+ // Attach listeners for auto-update
77944
+ if (typeof onChange === 'function') {
77945
+ form.addEventListener('input', () => {
77946
+ const values = this.getFormValues(form);
77947
+ onChange(values, element);
77948
+ });
77949
+ form.addEventListener('change', () => {
77950
+ const values = this.getFormValues(form);
77951
+ onChange(values, element);
77952
+ });
77953
+ }
77954
+ return form;
77955
+ }
77956
+
77957
+ /**
77958
+ * Generate flat form (no groups)
77959
+ */
77960
+ generateFlatForm(container, settings, currentValues) {
77961
+ Object.keys(settings).forEach(key => {
77962
+ if (key.startsWith('_')) return; // Skip meta keys
77963
+
77964
+ const field = settings[key];
77965
+ const fieldElement = this.createField(key, field, currentValues[key]);
77966
+ container.appendChild(fieldElement);
77967
+ });
77968
+ }
77969
+
77970
+ /**
77971
+ * Generate grouped form
77972
+ */
77973
+ generateGroupedForm(container, settings, currentValues) {
77974
+ const groups = settings._groups || [];
77975
+ groups.forEach(group => {
77976
+ const groupElement = document.createElement('div');
77977
+ groupElement.className = 'cb-settings-group';
77978
+ const groupTitle = document.createElement('h3');
77979
+ groupTitle.className = 'cb-settings-group-title';
77980
+ groupTitle.style.cssText = 'font-size: 17px;font-weight: 500;';
77981
+ groupTitle.textContent = group.label;
77982
+ groupElement.appendChild(groupTitle);
77983
+ group.fields.forEach(fieldKey => {
77984
+ const field = settings[fieldKey];
77985
+ if (!field) return;
77986
+ const fieldElement = this.createField(fieldKey, field, currentValues[fieldKey]);
77987
+ groupElement.appendChild(fieldElement);
77988
+ });
77989
+ container.appendChild(groupElement);
77990
+ });
77991
+ }
77992
+
77993
+ /**
77994
+ * Create a single form field
77995
+ */
77996
+ createField(name, config, value) {
77997
+ const wrapper = document.createElement('div');
77998
+ wrapper.className = 'cb-settings-field';
77999
+ wrapper.style.marginBottom = '20px';
78000
+ wrapper.setAttribute('data-field', name);
78001
+
78002
+ // Add conditional display logic
78003
+ if (config.dependsOn) {
78004
+ wrapper.setAttribute('data-depends-on', JSON.stringify(config.dependsOn));
78005
+ wrapper.style.display = 'none'; // Hidden by default, shown by JS
78006
+ }
78007
+
78008
+ // Label
78009
+ const label = document.createElement('label');
78010
+ label.className = 'cb-settings-label';
78011
+ label.style.fontWeight = 500;
78012
+ label.textContent = config.label || name;
78013
+ // wrapper.appendChild(label);
78014
+
78015
+ // Input based on type
78016
+ const input = this.createInput(name, config, value);
78017
+ wrapper.appendChild(input);
78018
+
78019
+ // Description
78020
+ // if (config.description) {
78021
+ // const desc = document.createElement('p');
78022
+ // desc.className = 'cb-settings-description';
78023
+ // desc.textContent = config.description;
78024
+ // wrapper.appendChild(desc);
78025
+ // }
78026
+
78027
+ return wrapper;
78028
+ }
78029
+
78030
+ /**
78031
+ * Create input element based on field type
78032
+ */
78033
+ createInput(name, config, value) {
78034
+ const currentValue = value !== undefined ? value : config.default;
78035
+ switch (config.type) {
78036
+ case 'number':
78037
+ return this.createNumberInput(name, config, currentValue);
78038
+ case 'boolean':
78039
+ return this.createCheckboxInput(name, config, currentValue);
78040
+ case 'select':
78041
+ return this.createSelectInput(name, config, currentValue);
78042
+ case 'radio':
78043
+ return this.createRadioInput(name, config, currentValue);
78044
+ case 'textarea':
78045
+ return this.createTextareaInput(name, config, currentValue);
78046
+ case 'color':
78047
+ return this.createColorInput(name, config, currentValue);
78048
+ case 'range':
78049
+ return this.createRangeInput(name, config, currentValue);
78050
+ case 'text':
78051
+ default:
78052
+ return this.createTextInput(name, config, currentValue);
78053
+ }
78054
+ }
78055
+ createTextInput(name, config, value) {
78056
+ const label = document.createElement('label');
78057
+ label.className = 'label';
78058
+ label.style.fontWeight = 500;
78059
+ const labelText = document.createElement('div');
78060
+ labelText.textContent = config.label || name;
78061
+ label.appendChild(labelText);
78062
+ const input = document.createElement('input');
78063
+ input.type = 'text';
78064
+ input.name = name;
78065
+ input.value = value || '';
78066
+ if (config.placeholder) input.placeholder = config.placeholder;
78067
+ label.appendChild(input);
78068
+
78069
+ // Add description if exists
78070
+ if (config.description) {
78071
+ const desc = document.createElement('small');
78072
+ desc.style.cssText = 'display: block;margin-top: 4px;';
78073
+ desc.textContent = config.description;
78074
+ label.appendChild(desc);
78075
+ }
78076
+ return label;
78077
+ }
78078
+ createNumberInput(name, config, value) {
78079
+ const label = document.createElement('label');
78080
+ label.className = 'label';
78081
+ label.style.fontWeight = 500;
78082
+ const labelText = document.createElement('div');
78083
+ labelText.textContent = config.label || name;
78084
+ if (config.unit) {
78085
+ labelText.textContent += ` (${config.unit})`;
78086
+ }
78087
+ label.appendChild(labelText);
78088
+ const input = document.createElement('input');
78089
+ input.type = 'number';
78090
+ input.style.cssText = 'width: 100px;';
78091
+ input.name = name;
78092
+ input.value = value !== undefined ? value : config.default;
78093
+ if (config.min !== undefined) input.min = config.min;
78094
+ if (config.max !== undefined) input.max = config.max;
78095
+ if (config.step !== undefined) input.step = config.step;
78096
+ label.appendChild(input);
78097
+ if (config.description) {
78098
+ const desc = document.createElement('small');
78099
+ desc.style.cssText = 'display: block;margin-top: 4px;';
78100
+ desc.textContent = config.description;
78101
+ label.appendChild(desc);
78102
+ }
78103
+ return label;
78104
+ }
78105
+ createCheckboxInput(name, config, value) {
78106
+ const label = document.createElement('label');
78107
+ label.className = 'label checkbox';
78108
+ const div = document.createElement('div');
78109
+ const input = document.createElement('input');
78110
+ input.type = 'checkbox';
78111
+ input.name = name;
78112
+ input.checked = value !== undefined ? value : config.default;
78113
+ const span = document.createElement('span');
78114
+ span.textContent = config.label || name;
78115
+ label.appendChild(input);
78116
+ label.appendChild(span);
78117
+ div.appendChild(label);
78118
+ if (config.description) {
78119
+ const desc = document.createElement('small');
78120
+ desc.style.cssText = 'display: block;margin-top: 4px;';
78121
+ desc.textContent = config.description;
78122
+ div.appendChild(desc);
78123
+ }
78124
+ return label;
78125
+ }
78126
+ createSelectInput(name, config, value) {
78127
+ const label = document.createElement('label');
78128
+ label.className = 'label';
78129
+ label.style.fontWeight = 500;
78130
+ const labelText = document.createTextNode((config.label || name) + ':');
78131
+ label.appendChild(labelText);
78132
+ const select = document.createElement('select');
78133
+ select.name = name;
78134
+ config.options.forEach(option => {
78135
+ const opt = document.createElement('option');
78136
+ opt.value = option.value;
78137
+ opt.textContent = option.label;
78138
+ if (option.value === value) opt.selected = true;
78139
+ select.appendChild(opt);
78140
+ });
78141
+ label.appendChild(select);
78142
+ if (config.description) {
78143
+ const desc = document.createElement('small');
78144
+ desc.style.cssText = 'display: block;margin-top: 4px;';
78145
+ desc.textContent = config.description;
78146
+ label.appendChild(desc);
78147
+ }
78148
+ return label;
78149
+ }
78150
+ createRadioInput(name, config, value) {
78151
+ const wrapper = document.createElement('div');
78152
+ const title = document.createElement('div');
78153
+ title.textContent = config.label || name;
78154
+ title.style.cssText = 'margin-bottom: 8px;font-weight: 590;';
78155
+ wrapper.appendChild(title);
78156
+ config.options.forEach(option => {
78157
+ const label = document.createElement('label');
78158
+ label.className = 'label checkbox';
78159
+ const input = document.createElement('input');
78160
+ input.type = 'radio';
78161
+ input.name = name;
78162
+ input.value = option.value;
78163
+ input.checked = option.value === value;
78164
+ const span = document.createElement('span');
78165
+ span.textContent = option.label;
78166
+ label.appendChild(input);
78167
+ label.appendChild(span);
78168
+ wrapper.appendChild(label);
78169
+ });
78170
+ if (config.description) {
78171
+ const desc = document.createElement('small');
78172
+ desc.style.cssText = 'display: block;margin-top: 4px;';
78173
+ desc.textContent = config.description;
78174
+ wrapper.appendChild(desc);
78175
+ }
78176
+ return wrapper;
78177
+ }
78178
+ createTextareaInput(name, config, value) {
78179
+ const label = document.createElement('label');
78180
+ label.className = 'label';
78181
+ label.style.fontWeight = 500;
78182
+ const labelText = document.createElement('div');
78183
+ labelText.textContent = config.label || name;
78184
+ label.appendChild(labelText);
78185
+ const textarea = document.createElement('textarea');
78186
+ textarea.name = name;
78187
+ textarea.value = value || '';
78188
+ textarea.rows = config.rows || 4;
78189
+ if (config.placeholder) textarea.placeholder = config.placeholder;
78190
+ label.appendChild(textarea);
78191
+ if (config.description) {
78192
+ const desc = document.createElement('small');
78193
+ desc.style.cssText = 'display: block;margin-top: 4px;';
78194
+ desc.textContent = config.description;
78195
+ label.appendChild(desc);
78196
+ }
78197
+ return label;
78198
+ }
78199
+ createColorInput(name, config, value) {
78200
+ const container = document.createElement('div');
78201
+ const label = document.createElement('label');
78202
+ label.className = 'label';
78203
+ label.style.fontWeight = 500;
78204
+ label.style.cursor = 'pointer';
78205
+ const labelText = document.createElement('div');
78206
+ labelText.textContent = config.label || name;
78207
+ label.appendChild(labelText);
78208
+ const input = document.createElement('input');
78209
+ input.type = 'hidden';
78210
+ input.name = name;
78211
+ input.value = value || config.default || '#000000';
78212
+ const group = document.createElement('div');
78213
+ group.className = 'group';
78214
+ group.style.cssText = 'width: 52px; margin:0; overflow: visible;';
78215
+ const colorBtn = document.createElement('button');
78216
+ colorBtn.type = 'button';
78217
+ colorBtn.title = config.label || 'Color';
78218
+ colorBtn.className = 'btn-color is-btn-color';
78219
+ colorBtn.style.backgroundColor = input.value;
78220
+ colorBtn.setAttribute('aria-label', `Choose color for ${config.label || name}`);
78221
+ const openPicker = e => {
78222
+ e.preventDefault();
78223
+ this.builder.openColorPicker(input.value, color => {
78224
+ input.value = color;
78225
+ colorBtn.style.backgroundColor = color;
78226
+ input.dispatchEvent(new Event('change', {
78227
+ bubbles: true
78228
+ }));
78229
+ }, colorBtn);
78230
+ };
78231
+ colorBtn.addEventListener('click', openPicker);
78232
+ // Make label click trigger the button
78233
+ label.addEventListener('click', e => {
78234
+ if (e.target === label || e.target === labelText) {
78235
+ colorBtn.click();
78236
+ }
78237
+ });
78238
+ group.appendChild(colorBtn);
78239
+ label.appendChild(input);
78240
+ if (config.description) {
78241
+ const desc = document.createElement('small');
78242
+ desc.style.cssText = 'display: block;margin-top: 4px;';
78243
+ desc.textContent = config.description;
78244
+ label.appendChild(desc);
78245
+ }
78246
+ container.appendChild(label);
78247
+ container.appendChild(group);
78248
+ return container;
78249
+ }
78250
+ createColorInput2(name, config, value) {
78251
+ const label = document.createElement('label');
78252
+ label.className = 'label';
78253
+ label.style.fontWeight = 500;
78254
+ const labelText = document.createElement('div');
78255
+ labelText.textContent = config.label || name;
78256
+ label.appendChild(labelText);
78257
+ const input = document.createElement('input');
78258
+ input.type = 'color';
78259
+ input.name = name;
78260
+ input.style.cssText = 'width:40px;height:40px';
78261
+ input.value = value || config.default || '#000000';
78262
+ label.appendChild(input);
78263
+ if (config.description) {
78264
+ const desc = document.createElement('small');
78265
+ desc.style.cssText = 'display: block;margin-top: 4px;';
78266
+ desc.textContent = config.description;
78267
+ label.appendChild(desc);
78268
+ }
78269
+ return label;
78270
+ }
78271
+ createRangeInput(name, config, value) {
78272
+ const label = document.createElement('label');
78273
+ label.className = 'label';
78274
+ label.style.fontWeight = 500;
78275
+ const currentVal = value !== undefined ? value : config.default;
78276
+ const labelText = document.createElement('div');
78277
+ labelText.textContent = (config.label || name) + ': ' + currentVal + (config.unit || '');
78278
+ label.appendChild(labelText);
78279
+ const input = document.createElement('input');
78280
+ input.type = 'range';
78281
+ input.className = 'is-rangeslider';
78282
+ input.name = name;
78283
+ input.value = currentVal;
78284
+ if (config.min !== undefined) input.min = config.min;
78285
+ if (config.max !== undefined) input.max = config.max;
78286
+ if (config.step !== undefined) input.step = config.step;
78287
+ input.addEventListener('input', () => {
78288
+ labelText.textContent = (config.label || name) + ': ' + input.value + (config.unit || '');
78289
+ });
78290
+ label.appendChild(input);
78291
+ if (config.description) {
78292
+ const desc = document.createElement('small');
78293
+ desc.style.cssText = 'display: block;margin-top: 4px;';
78294
+ desc.textContent = config.description;
78295
+ label.appendChild(desc);
78296
+ }
78297
+ return label;
78298
+ }
78299
+
78300
+ /**
78301
+ * Get current values from element attributes
78302
+ */
78303
+ getCurrentValues(element, settings) {
78304
+ const values = {};
78305
+ Object.keys(settings).forEach(key => {
78306
+ if (key.startsWith('_')) return;
78307
+ const attrName = 'data-cb-' + key.replace(/([A-Z])/g, '-$1').toLowerCase();
78308
+ const attrValue = element.getAttribute(attrName);
78309
+ if (attrValue !== null) {
78310
+ values[key] = this.parseValue(attrValue, settings[key].type);
78311
+ }
78312
+ });
78313
+ return values;
78314
+ }
78315
+
78316
+ /**
78317
+ * Parse attribute value based on type
78318
+ */
78319
+ parseValue(value, type) {
78320
+ switch (type) {
78321
+ case 'number':
78322
+ return parseFloat(value);
78323
+ case 'boolean':
78324
+ return value === 'true';
78325
+ default:
78326
+ return value;
78327
+ }
78328
+ }
78329
+
78330
+ /**
78331
+ * Get form values
78332
+ */
78333
+ getFormValues(form) {
78334
+ const values = {};
78335
+ const inputs = form.querySelectorAll('input, select, textarea');
78336
+ inputs.forEach(input => {
78337
+ const name = input.name;
78338
+ if (!name) return;
78339
+ if (input.type === 'checkbox') {
78340
+ values[name] = input.checked;
78341
+ } else if (input.type === 'radio') {
78342
+ if (input.checked) {
78343
+ values[name] = input.value;
78344
+ }
78345
+ } else {
78346
+ values[name] = input.value;
78347
+ }
78348
+ });
78349
+ return values;
78350
+ }
78351
+
78352
+ /**
78353
+ * Apply form values to element
78354
+ */
78355
+ applyValues(element, values) {
78356
+ Object.keys(values).forEach(key => {
78357
+ const attrName = 'data-cb-' + key.replace(/([A-Z])/g, '-$1').toLowerCase();
78358
+ element.setAttribute(attrName, values[key]);
78359
+ });
78360
+ }
78361
+ createEmptyForm() {
78362
+ const form = document.createElement('div');
78363
+ form.className = 'cb-settings-form';
78364
+ form.innerHTML = '<p>This plugin has no configurable settings.</p>';
78365
+ return form;
78366
+ }
78367
+ }
78368
+
78369
+ class Plugin {
78370
+ constructor(builder) {
78371
+ this.builder = builder;
78372
+ }
78373
+ renderTool() {
78374
+ const builderStuff = this.builder.builderStuff;
78375
+ const contentStuff = this.builder.contentStuff;
78376
+ const util = this.builder.util;
78377
+ const dom = this.builder.dom;
78378
+ let pluginTool = builderStuff.querySelector('.is-plugin-tool');
78379
+ if (!pluginTool) {
78380
+ let html = `
78381
+ <div class="is-tool is-plugin-tool">
78382
+ <button title="${util.out('Settings')}" data-title="${util.out('Settings')}" style="width:40px;height:40px;background:none;"><svg class="is-icon-flex"><use xlink:href="#icon-cog"></use></svg></button>
78383
+ </div>
78384
+ `;
78385
+ dom.appendHtml(builderStuff, html);
78386
+ if (!this.builder.iframe) {
78387
+ pluginTool = builderStuff.querySelector('.is-plugin-tool');
78388
+ } else {
78389
+ pluginTool = contentStuff.querySelector('.is-plugin-tool');
78390
+ }
78391
+ this.pluginTool = pluginTool;
78392
+ let btn = pluginTool.querySelector('button');
78393
+ dom.addEventListener(btn, 'click', () => {
78394
+ // old 10317
78395
+
78396
+ if (this.pluginModal && this.pluginModal.classList.contains('active')) {
78397
+ this.hidePluginEditor();
78398
+ } else {
78399
+ this.renderPanel();
78400
+ this.showPluginEditor();
78401
+ }
78402
+ btn.setAttribute('data-focus', true);
78403
+ });
78404
+ }
78405
+ }
78406
+ renderPanel() {
78407
+ const builderStuff = this.builder.builderStuff;
78408
+ const util = this.builder.util;
78409
+ const dom = this.builder.dom;
78410
+ let pluginModal = builderStuff.querySelector('.is-modal.pluginsettings');
78411
+ if (!pluginModal) {
78412
+ let html = `
78413
+ <div class="is-modal is-modal-content pluginsettings" tabindex="-1" role="dialog" aria-modal="true" aria-hidden="true">
78414
+ <div class="is-modal-bar is-draggable">
78415
+ <span>${util.out('Plugin Settings')}</span>
78416
+ <button class="is-modal-close" tabindex="-1" title="${util.out('Close')}">&#10005;</button>
78417
+ </div>
78418
+ <div class="is-modal-controls" style="box-sizing:border-box;padding:30px;overflow-y: auto;height: calc(100% - 30px);">
78419
+ <div class="is-tabs" data-group="table">
78420
+ <a title="${util.out('Style')}" id="tabTableGeneral" href="" data-content="divTableGeneral" class="active">${util.out('Style')}</a>
78421
+ <a title="${util.out('Layout')}" id="tabTableLayout" href="" data-content="divTableLayout">${util.out('Layout')}</a>
78422
+ </div>
78423
+ <div id="divTableGeneral" class="is-tab-content active" tabindex="-1" data-group="table" style="display:block">
78424
+
78425
+ <div style="display:flex;padding-bottom:12px">
78426
+ <div style="padding-right:15px">
78427
+ <div>${util.out('Background')}:</div>
78428
+ <div>
78429
+ <button title="${util.out('Background Color')}" class="input-table-bgcolor is-btn-color"></button>
78430
+ </div>
78431
+ </div>
78432
+ <div>
78433
+ <div>${util.out('Text Color')}:</div>
78434
+ <div>
78435
+ <button title="${util.out('Text Color')}" class="input-table-textcolor is-btn-color"></button>
78436
+ </div>
78437
+ </div>
78438
+ </div>
78439
+
78440
+ <div style="padding-bottom:12px;">
78441
+ <div>${util.out('Border Thickness')}:</div>
78442
+ <div>
78443
+ <select id="selCellBorderWidth" style="width:120px;"><option value="0">No Border</option><option value="1">1</option><option value="2">2</option><option value="3">3</option></select>
78444
+ </div>
78445
+ </div>
78446
+
78447
+ <div style="padding-bottom:12px;">
78448
+ <div>${util.out('Border Color')}:</div>
78449
+ <div>
78450
+ <button title="${util.out('Border Color')}" class="input-table-bordercolor is-btn-color"></button>
78451
+ </div>
78452
+ </div>
78453
+
78454
+ <div style="padding-bottom:12px;">
78455
+ <div>${util.out('Apply To')}:</div>
78456
+ <div>
78457
+ <select id="selTableApplyTo" style="width:120px;">
78458
+ <option value="table">${util.out('Table')}</option>
78459
+ <option value="currentrow">${util.out('Current Row')}</option>
78460
+ <option value="currentcol">${util.out('Current Column')}</option>
78461
+ <option value="evenrows">${util.out('Even Rows')}</option>
78462
+ <option value="oddrows">${util.out('Odd Rows')}</option>
78463
+ <option value="currentcell">${util.out('Current Cell')}</option>
78464
+ </select>
78465
+ </div>
78466
+ </div>
78467
+
78468
+ </div>
78469
+
78470
+ <div id="divTableLayout" class="is-tab-content" tabindex="-1" data-group="table">
78471
+
78472
+ <div style="padding-bottom:12px;">
78473
+ <div>${util.out('Insert Row')}:</div>
78474
+ <div style="display:flex">
78475
+ <button class="classic" title="${util.out('Above')}" data-table-cmd="rowabove" title="${util.out('Above')}" style="margin-right:15px"> ${util.out('Above')} </button>
78476
+ <button class="classic" title="${util.out('Below')}" data-table-cmd="rowbelow" title="${util.out('Below')}" style=""> ${util.out('Below')} </button>
78477
+ </div>
78478
+ </div>
78479
+
78480
+ <div style="padding-bottom:15px;">
78481
+ <div>${util.out('Insert Column')}:</div>
78482
+ <div style="display:flex">
78483
+ <button class="classic" title="${util.out('Left')}" data-table-cmd="columnleft" title="${util.out('Left')}" style="margin-right:15px"> ${util.out('Left')} </button>
78484
+ <button class="classic" title="${util.out('Right')}" data-table-cmd="columnright" title="${util.out('Right')}" style=""> ${util.out('Right')} </button>
78485
+ </div>
78486
+ </div>
78487
+
78488
+ <div style="padding-bottom:15px;">
78489
+ <button class="classic" title="${util.out('Delete Row')}" data-table-cmd="delrow" title="Delete Row" style=""> ${util.out('Delete Row')} </button>
78490
+ </div>
78491
+
78492
+ <div style="padding-bottom:15px;">
78493
+ <button class="classic" title="${util.out('Delete Column')}" data-table-cmd="delcolumn" title="Delete Column" style=""> ${util.out('Delete Column')} </button>
78494
+ </div>
78495
+
78496
+ <div>
78497
+ <button class="classic" title="${util.out('Merge Cell')}" data-table-cmd="mergecell" style="">${util.out('Merge Cell')}</button>
78498
+ </div>
78499
+ </div>
78500
+ </div>
78501
+ </div>
78502
+ `;
78503
+ dom.appendHtml(builderStuff, html);
78504
+ pluginModal = builderStuff.querySelector('.is-modal.pluginsettings');
78505
+ this.pluginModal = pluginModal;
78506
+ let btnOk = pluginModal.querySelector('.input-ok');
78507
+ dom.addEventListener(btnOk, 'click', () => {
78508
+ this.builder.uo.saveForUndo();
78509
+ this.builder.opts.onChange();
78510
+ util.hideModal(pluginModal);
78511
+ });
78512
+ let btnCancel = pluginModal.querySelector('.input-cancel');
78513
+ dom.addEventListener(btnCancel, 'click', () => {
78514
+ util.hideModal(pluginModal);
78515
+ });
78516
+ new Tabs({
78517
+ element: pluginModal
78518
+ });
78519
+ new Draggable$2({
78520
+ selector: '.is-modal.pluginsettings .is-draggable'
78521
+ });
78522
+ }
78523
+ }
78524
+ showPluginEditor() {
78525
+ const pluginModal = this.pluginModal;
78526
+ this.builder.util.showModal(pluginModal);
78527
+ this.realtime();
78528
+ const handlePluginClick = e => {
78529
+ const clrPicker = document.querySelector('.pop-picker.active') || document.querySelector('.pickgradientcolor.active');
78530
+ let elm = e.target;
78531
+ let elmRemoved = !document.contains(elm); // e.g. when deleting a card in card list plugin
78532
+
78533
+ if (this.builder.doc.activeElement) {
78534
+ if (this.builder.doc.activeElement.closest('.is-modal')) {
78535
+ // prevent modal close when mouseup outside the modal
78536
+ return;
78537
+ }
78538
+ }
78539
+ if (!elm) return;
78540
+ if (!elm.closest('.is-plugin-tool') && !elm.closest('.is-sidebar') && !elm.closest('.is-modal') && !elm.closest('.keep-selection') && !elm.closest('[data-cb-type]') && !clrPicker && !elmRemoved) {
78541
+ // click outside
78542
+
78543
+ // hide
78544
+ this.hidePluginEditor();
78545
+ // console.log('HIDE');
78546
+
78547
+ document.removeEventListener('click', handlePluginClick);
78548
+ if (this.builder.iframeDocument) {
78549
+ this.builder.doc.removeEventListener('click', handlePluginClick);
78550
+ }
78551
+ this.builder.handlePluginClick_ = false;
78552
+ }
78553
+ if (elm.closest('[data-cb-type]')) {
78554
+ this.realtime();
78555
+ }
78556
+ };
78557
+ if (!this.builder.handlePluginClick_) {
78558
+ document.addEventListener('click', handlePluginClick);
78559
+ if (this.builder.iframeDocument) {
78560
+ this.builder.doc.addEventListener('click', handlePluginClick);
78561
+ }
78562
+ this.builder.handlePluginClick_ = true;
78563
+ }
78564
+ }
78565
+ hidePluginEditor() {
78566
+ const pluginModal = this.pluginModal;
78567
+ if (pluginModal) this.builder.util.hideModal(pluginModal);
78568
+ }
78569
+ realtime() {
78570
+ const util = this.builder.util;
78571
+ const pluginModal = this.pluginModal;
78572
+ let currentElement = this.builder.activePlugin;
78573
+ const runtime = this.builder.win.builderRuntime;
78574
+ if (currentElement && runtime) {
78575
+ const pluginName = currentElement.getAttribute('data-cb-type');
78576
+ const plugin = runtime.getPlugin(pluginName);
78577
+ let titleHtml = util.out(plugin.displayName || plugin.name);
78578
+ const modalBar = pluginModal.querySelector('.is-modal-bar > span');
78579
+ modalBar.innerHTML = titleHtml;
78580
+ const container = pluginModal.querySelector('.is-modal-controls');
78581
+
78582
+ // Clear panel
78583
+ container.innerHTML = '';
78584
+ const div = document.createElement('div');
78585
+ div.classList.add('submain');
78586
+ container.appendChild(div);
78587
+
78588
+ // Store references
78589
+ this.currentElement = currentElement;
78590
+ this.generator = new SettingsUIGenerator(runtime, this.builder);
78591
+
78592
+ // Check if plugin has custom content editor
78593
+ const hasContentEditor = plugin && plugin.editor && plugin.editor.openContentEditor;
78594
+ if (hasContentEditor) {
78595
+ // Get original content for the editor to work with
78596
+ const originalContent = currentElement.getAttribute('data-cb-original-content');
78597
+
78598
+ // Create a temporary element for editing (so editor can manipulate it)
78599
+ let editableClone = document.querySelector('.editable-clone');
78600
+ if (editableClone) editableClone.remove();
78601
+ // editableClone = document.createElement('div');
78602
+ editableClone = currentElement.cloneNode(false);
78603
+ editableClone.innerHTML = originalContent;
78604
+ editableClone.className = 'editable-clone';
78605
+ editableClone.style.display = 'none'; // Hidden, just for editing
78606
+ document.body.appendChild(editableClone);
78607
+ const originalElement = editableClone.cloneNode(false);
78608
+
78609
+ // Let plugin handle everything - pass the editable clone
78610
+ const editorUI = plugin.editor.openContentEditor(editableClone, this.builder, async () => {
78611
+ this.builder.uo.saveForUndo();
78612
+
78613
+ // Store edited content from clone
78614
+ currentElement.setAttribute('data-cb-original-content', editableClone.innerHTML);
78615
+
78616
+ // Also performs similar value updates like the applyChanges()
78617
+ // currentElement.setAttribute('data-cb-content', editableClone.getAttribute('data-cb-content'));
78618
+ const getChangedDataAttributes = (el1, el2) => {
78619
+ const changed = [];
78620
+ for (const key of Object.keys(el1.dataset)) {
78621
+ if (key in el2.dataset && el1.dataset[key] !== el2.dataset[key]) {
78622
+ changed.push(key);
78623
+ }
78624
+ }
78625
+ return changed;
78626
+ };
78627
+ const changedAttributes = getChangedDataAttributes(originalElement, editableClone);
78628
+ // let hasChange = changedAttributes.length > 0;
78629
+ for (const attrName of changedAttributes) {
78630
+ // console.log(attrName);
78631
+ currentElement.dataset[attrName] = editableClone.dataset[attrName];
78632
+ // update form (not working)
78633
+ // const convertCbKey = (str) => {
78634
+ // return str.replace(/^cb/, '').replace(/^./, c => c.toLowerCase());
78635
+ // }
78636
+ // const name = convertCbKey(attrName);
78637
+ // this.currentForm.querySelector(`[name="${name}"]`).value = editableClone.dataset[attrName];
78638
+ }
78639
+ // console.log(hasChange)
78640
+ // -------
78641
+
78642
+ currentElement.innerHTML = editableClone.innerHTML; // Update current content
78643
+
78644
+ // Remove temporary clone
78645
+ editableClone.remove();
78646
+
78647
+ // update form (working)
78648
+ // currentElement.click();
78649
+ // return;
78650
+
78651
+ // Reinitialize plugin with new content
78652
+ await runtime.reinitialize(currentElement.parentElement);
78653
+ this.builder.onChange();
78654
+ });
78655
+ div.appendChild(editorUI);
78656
+ }
78657
+
78658
+ // Generate form
78659
+ this.currentForm = this.generator.generateForm(pluginName, currentElement, () => {
78660
+ this.builder.uo.saveForUndo();
78661
+ this.applyChanges(runtime);
78662
+ });
78663
+ this.currentForm.style.marginTop = '20px';
78664
+ div.appendChild(this.currentForm);
78665
+ }
78666
+ }
78667
+ async applyChanges(runtime) {
78668
+ if (!this.currentElement || !this.currentForm) return;
78669
+
78670
+ // 1. Get form values
78671
+ const values = this.generator.getFormValues(this.currentForm);
78672
+
78673
+ // 2. Apply to element attributes
78674
+ this.generator.applyValues(this.currentElement, values);
78675
+
78676
+ // 3. Reinitialize component
78677
+ const container = this.currentElement.parentElement || this.currentElement.closest('.is-wrapper');
78678
+ await runtime.reinitialize(container);
78679
+
78680
+ //Trigger Change event
78681
+ this.builder.opts.onChange();
78682
+
78683
+ // console.log('Settings applied and component reinitialized');
78684
+ }
78685
+
78686
+ click(e) {
78687
+ const plugin = e.target.closest('[data-cb-type]');
78688
+ if (plugin) {
78689
+ this.builder.activePlugin = plugin;
78690
+ if (!this.builder.isContentBox) {
78691
+ this.renderTool();
78692
+ this.pluginTool.style.display = 'flex';
78693
+ let _toolwidth = this.pluginTool.offsetWidth; //to get value, element must not hidden (display:none). So set display:flex before this.
78694
+
78695
+ let w = plugin.offsetWidth * this.builder.opts.zoom;
78696
+ let top = plugin.getBoundingClientRect().top + this.builder.win.pageYOffset;
78697
+ let left = plugin.getBoundingClientRect().left - 2;
78698
+ left = left + (w - _toolwidth);
78699
+
78700
+ //Adjust left in case an element is outside the screen
78701
+ const _screenwidth = window.innerWidth;
78702
+ if (_toolwidth + left > _screenwidth) left = plugin.getBoundingClientRect().left;
78703
+ this.pluginTool.style.top = top + 'px';
78704
+ this.pluginTool.style.left = left + 'px';
78705
+ }
78706
+ } else {
78707
+ this.builder.activePlugin = null;
78708
+ if (this.pluginTool) this.pluginTool.style.display = '';
78709
+ }
78710
+ }
78711
+ }
78712
+
77238
78713
  class Module {
77239
78714
  constructor(builder) {
77240
78715
  this.builder = builder;
@@ -79076,6 +80551,7 @@ class Element$1 {
79076
80551
  this.button = new Button(builder);
79077
80552
  this.image = new Image$1(builder);
79078
80553
  this.spacer = new Spacer(builder);
80554
+ this.plugin = new Plugin(builder);
79079
80555
  this.module = new Module(builder);
79080
80556
  this.code = new Code(builder);
79081
80557
  this.iframe = new Iframe(builder);
@@ -79114,6 +80590,10 @@ class Element$1 {
79114
80590
 
79115
80591
  // Set contentEditable FALSE on special elements
79116
80592
 
80593
+ const plugins = col.querySelectorAll('[data-cb-type]'); // plugins
80594
+ Array.prototype.forEach.call(plugins, plugin => {
80595
+ plugin.contentEditable = false;
80596
+ });
79117
80597
  let sociallinks = col.querySelectorAll('.is-social');
79118
80598
  Array.prototype.forEach.call(sociallinks, sociallink => {
79119
80599
  sociallink.contentEditable = false;
@@ -79205,6 +80685,9 @@ class Element$1 {
79205
80685
  subblock = true;
79206
80686
  }
79207
80687
  }
80688
+ if (elm.closest('[data-cb-type]')) {
80689
+ customcode = true;
80690
+ }
79208
80691
  if (!customcode && !noedit && !_protected || subblock) {
79209
80692
  //previously this is commented: && !noedit && !_protected
79210
80693
 
@@ -79275,6 +80758,9 @@ class Element$1 {
79275
80758
 
79276
80759
  // Module
79277
80760
  this.module.click(col, e);
80761
+
80762
+ // Plugin
80763
+ this.plugin.click(e);
79278
80764
  }
79279
80765
  }
79280
80766
 
@@ -88430,6 +89916,12 @@ class ElementTool {
88430
89916
  const btnMore = elementTool.querySelector('.elm-more');
88431
89917
  dom.addEventListener(btnMore, 'click', () => {
88432
89918
  const viewportHeight = window.innerHeight;
89919
+ const elmSettings = elementMore.querySelector('.elm-settings');
89920
+ if (this.builder.activeElement.hasAttribute('data-cb-type')) {
89921
+ elmSettings.style.display = 'none';
89922
+ } else {
89923
+ elmSettings.style.display = '';
89924
+ }
88433
89925
 
88434
89926
  /*
88435
89927
  let top, left;
@@ -88695,7 +90187,10 @@ class ElementTool {
88695
90187
  if (dom.parentsHasClass(elm, 'is-subblock')) {
88696
90188
  subblock = true;
88697
90189
  }
88698
- if ((customcode || noedit || _protected) && !subblock) ; else {
90190
+ const plugin = elm.closest('[data-cb-type]');
90191
+ if ((customcode || noedit || _protected) && !subblock) ; else if (plugin) {
90192
+ activeElement = plugin;
90193
+ } else {
88699
90194
  const tagName = elm.tagName.toLowerCase();
88700
90195
  // LATER: label, code, figcaption ?
88701
90196
  if (!elm.classList.contains('cell-active') && (tagName === 'h1' || tagName === 'h2' || tagName === 'h3' || tagName === 'h4' || tagName === 'h5' || tagName === 'h6' || tagName === 'p' || tagName === 'div' || tagName === 'pre' || tagName === 'blockquote' || tagName === 'li' || tagName === 'img' || tagName === 'iframe')) {
@@ -88847,6 +90342,8 @@ class ElementTool {
88847
90342
  let elm = this.builder.activeElement;
88848
90343
  if (!elm) return;
88849
90344
  if (elm.closest('.is-dock')) return;
90345
+ // if(elm.closest('[data-cb-type]')) return;
90346
+
88850
90347
  let top, left;
88851
90348
  if (!this.builder.iframe) {
88852
90349
  top = elm.getBoundingClientRect().top + window.pageYOffset;
@@ -106495,6 +107992,9 @@ class ContentStuff$1 {
106495
107992
  <div class="is-tool is-iframe-tool">
106496
107993
  <button title="${util.out('Settings')}" data-title="${util.out('Settings')}" style="width:40px;height:40px;background:none;"><svg class="is-icon-flex"><use xlink:href="#icon-cog"></use></svg></button>
106497
107994
  </div>
107995
+ <div class="is-tool is-plugin-tool">
107996
+ <button title="${util.out('Settings')}" data-title="${util.out('Settings')}" style="width:40px;height:40px;background:none;"><svg class="is-icon-flex"><use xlink:href="#icon-cog"></use></svg></button>
107997
+ </div>
106498
107998
  <div class="is-tool is-module-tool">
106499
107999
  <button class="btn-module-refresh" title="${util.out('Refresh')}" data-title="${util.out('Refresh')}" style="width:40px;height:40px;"><svg class="is-icon-flex"><use xlink:href="#icon-reload"></use></svg></button>
106500
108000
  <button class="btn-module-settings" title="${util.out('Settings')}" data-title="${util.out('Settings')}" style="width:40px;height:40px;"><svg class="is-icon-flex"><use xlink:href="#icon-cog"></use></svg></button>
@@ -106900,20 +108400,24 @@ class ContentStuff$1 {
106900
108400
  /*
106901
108401
  .is-tool.is-video-tool,
106902
108402
  .is-tool.is-audio-tool,
108403
+ .is-tool.is-plugin-tool,
106903
108404
  .is-tool.is-iframe-tool {
106904
108405
  background: rgba(0, 0, 0, 0.15);
106905
108406
  border: transparent 1px solid;
106906
108407
  }
106907
108408
  .is-tool.is-video-tool > div,
106908
108409
  .is-tool.is-audio-tool > div,
108410
+ .is-tool.is-plugin-tool > div,
106909
108411
  .is-tool.is-iframe-tool > div,
106910
108412
  .is-tool.is-video-tool > button,
106911
108413
  .is-tool.is-audio-tool > button,
108414
+ .is-tool.is-plugin-tool > button,
106912
108415
  .is-tool.is-iframe-tool > button {
106913
108416
  background: transparent;
106914
108417
  }
106915
108418
  .is-tool.is-video-tool svg,
106916
108419
  .is-tool.is-audio-tool svg,
108420
+ .is-tool.is-plugin-tool svg,
106917
108421
  .is-tool.is-iframe-tool svg {
106918
108422
  fill: #fff;
106919
108423
  }
@@ -106923,6 +108427,7 @@ class ContentStuff$1 {
106923
108427
  .is-tool.is-table-tool,
106924
108428
  .is-tool.is-code-tool,
106925
108429
  .is-tool.is-module-tool,
108430
+ .is-tool.is-plugin-tool,
106926
108431
  .is-tool#divLinkTool,
106927
108432
  .is-tool#divButtonTool,
106928
108433
  .is-tool.is-svg-tool {
@@ -106936,6 +108441,7 @@ class ContentStuff$1 {
106936
108441
  .is-tool.is-table-tool > button,
106937
108442
  .is-tool.is-code-tool > button,
106938
108443
  .is-tool.is-module-tool > button,
108444
+ .is-tool.is-plugin-tool > button,
106939
108445
  .is-tool#divLinkTool > button,
106940
108446
  .is-tool#divButtonTool > button,
106941
108447
  .is-tool.is-svg-tool > button {
@@ -106948,6 +108454,7 @@ class ContentStuff$1 {
106948
108454
  .is-tool.is-table-tool > button svg,
106949
108455
  .is-tool.is-code-tool > button svg,
106950
108456
  .is-tool.is-module-tool > button svg,
108457
+ .is-tool.is-plugin-tool > button svg,
106951
108458
  .is-tool#divLinkTool > button svg,
106952
108459
  .is-tool#divButtonTool > button svg,
106953
108460
  .is-tool.is-svg-tool > button svg {
@@ -106962,6 +108469,7 @@ class ContentStuff$1 {
106962
108469
 
106963
108470
  .is-tool.is-video-tool,
106964
108471
  .is-tool.is-audio-tool,
108472
+ .is-tool.is-plugin-tool,
106965
108473
  .is-tool.is-iframe-tool {
106966
108474
  background: rgba(255, 255, 255, 0.97) !important;
106967
108475
  border: transparent 1px solid;
@@ -106973,6 +108481,7 @@ class ContentStuff$1 {
106973
108481
  .is-tool.is-video-tool > button,
106974
108482
  .is-tool.is-audio-tool > div,
106975
108483
  .is-tool.is-audio-tool > button,
108484
+ .is-tool.is-plugin-tool > div,
106976
108485
  .is-tool.is-iframe-tool > div,
106977
108486
  .is-tool.is-iframe-tool > button {
106978
108487
  width: 35px !important;
@@ -106982,6 +108491,7 @@ class ContentStuff$1 {
106982
108491
 
106983
108492
  .is-tool.is-video-tool svg,
106984
108493
  .is-tool.is-audio-tool svg,
108494
+ .is-tool.is-plugin-tool svg,
106985
108495
  .is-tool.is-iframe-tool svg {
106986
108496
  width: 17px;
106987
108497
  height: 17px;
@@ -123687,6 +125197,143 @@ Add an image for each feature.`, 'Create a new content showcasing a photo galler
123687
125197
  fromViewToActual(html) {
123688
125198
  return this.htmlutil.fromViewToActual(html);
123689
125199
  }
125200
+ openColorPicker(currentColor, callback, btn) {
125201
+ this.colorPicker.open(callback, currentColor, () => {}, btn);
125202
+ }
125203
+ openIconPicker(callback) {
125204
+ let iconModal = this.builderStuff.querySelector('.is-modal.iconselect');
125205
+ if (!iconModal) {
125206
+ // Create modal with search input and event listeners once
125207
+ let html = `
125208
+ <style>
125209
+ .is-modal.iconselect .icon-search-wrapper {
125210
+ position: sticky;
125211
+ top: 0;
125212
+ background: white;
125213
+ padding: 20px;
125214
+ border-bottom: 1px solid #ddd;
125215
+ z-index: 10;
125216
+ }
125217
+ .is-modal.iconselect .icon-search {
125218
+ width: 100%;
125219
+ padding: 10px 15px;
125220
+ font-size: 16px;
125221
+ border: 1px solid #ccc;
125222
+ border-radius: 4px;
125223
+ box-sizing: border-box;
125224
+ }
125225
+ .is-modal.iconselect .icon-search:focus {
125226
+ outline: none;
125227
+ border-color: #007bff;
125228
+ }
125229
+ .is-modal.iconselect .icon-list {
125230
+ display: flex;
125231
+ gap: 10px;
125232
+ flex-flow: wrap;
125233
+ padding: 20px;
125234
+ }
125235
+ .is-modal.iconselect .icon-list > button {
125236
+ display: flex !important;
125237
+ width: 80px !important;
125238
+ height: 80px !important;
125239
+ flex: none;
125240
+ font-size: 30px !important;
125241
+ box-shadow: none !important;
125242
+ align-items: center;
125243
+ justify-content: center;
125244
+ }
125245
+ .is-modal.iconselect .icon-list > button.hidden {
125246
+ display: none !important;
125247
+ }
125248
+ .is-modal.iconselect .no-results {
125249
+ padding: 40px 20px;
125250
+ text-align: center;
125251
+ color: #666;
125252
+ display: none;
125253
+ }
125254
+ .is-modal.iconselect .no-results.show {
125255
+ display: block;
125256
+ }
125257
+ </style>
125258
+ <div class="is-modal iconselect" tabindex="-1" role="dialog" aria-modal="true" aria-hidden="true">
125259
+ <div class="is-modal-content scroll" style="width: 90vw; max-width: 908px; height: 70vh; min-height: 525px; padding: 0;">
125260
+ <div style="height: 100%; overflow-y: auto; display: flex; flex-direction: column;">
125261
+ <div class="icon-search-wrapper">
125262
+ <input type="text" class="icon-search" placeholder="Search icons... (e.g., alarm, wrench)" />
125263
+ </div>
125264
+ <div class="icon-list"></div>
125265
+ <div class="no-results">No icons found matching your search.</div>
125266
+ </div>
125267
+ </div>
125268
+ </div>
125269
+ `;
125270
+ this.dom.appendHtml(this.builderStuff, html);
125271
+ iconModal = this.builderStuff.querySelector('.is-modal.iconselect');
125272
+ this.iconModal = iconModal;
125273
+ const iconList = this.builderStuff.querySelector('.icon-list');
125274
+ const searchInput = this.builderStuff.querySelector('.icon-search');
125275
+ const noResults = this.builderStuff.querySelector('.no-results');
125276
+ iconList.innerHTML = this.rte.getIcons2() + this.rte.getIcons();
125277
+ let icons = iconList.querySelectorAll('button');
125278
+
125279
+ // Add click handlers for icons
125280
+ icons.forEach(icon => {
125281
+ icon.addEventListener('click', () => {
125282
+ // Get the current callback from the modal's data
125283
+ const currentCallback = iconModal._currentCallback;
125284
+ if (currentCallback) {
125285
+ currentCallback(icon.innerHTML);
125286
+ }
125287
+ this.util.hideModal(iconModal);
125288
+ });
125289
+ });
125290
+
125291
+ // Add search/filter functionality
125292
+ searchInput.addEventListener('input', e => {
125293
+ const searchTerm = e.target.value.toLowerCase().trim();
125294
+ let visibleCount = 0;
125295
+ icons.forEach(icon => {
125296
+ const iconElement = icon.querySelector('i');
125297
+ if (!iconElement) return;
125298
+
125299
+ // Get class names from the icon
125300
+ const iconClasses = iconElement.className.toLowerCase();
125301
+
125302
+ // Check if search term matches any part of the icon classes
125303
+ if (searchTerm === '' || iconClasses.includes(searchTerm)) {
125304
+ icon.classList.remove('hidden');
125305
+ visibleCount++;
125306
+ } else {
125307
+ icon.classList.add('hidden');
125308
+ }
125309
+ });
125310
+
125311
+ // Show/hide no results message
125312
+ if (visibleCount === 0 && searchTerm !== '') {
125313
+ noResults.classList.add('show');
125314
+ } else {
125315
+ noResults.classList.remove('show');
125316
+ }
125317
+ });
125318
+
125319
+ // Clear search when modal is opened
125320
+ iconModal.addEventListener('modalshow', () => {
125321
+ searchInput.value = '';
125322
+ searchInput.dispatchEvent(new Event('input'));
125323
+ });
125324
+ }
125325
+
125326
+ // Store the current callback and reset search
125327
+ iconModal._currentCallback = callback;
125328
+ const searchInput = iconModal.querySelector('.icon-search');
125329
+ if (searchInput) {
125330
+ searchInput.value = '';
125331
+ searchInput.dispatchEvent(new Event('input'));
125332
+ // Focus search input after a brief delay to ensure modal is visible
125333
+ setTimeout(() => searchInput.focus(), 100);
125334
+ }
125335
+ this.util.showModal(iconModal);
125336
+ }
123690
125337
  colorpicker(onPick, defaultcolor) {
123691
125338
  // return new ColorPicker({
123692
125339
  // onPick: onPick,
@@ -124338,6 +125985,9 @@ Add an image for each feature.`, 'Create a new content showcasing a photo galler
124338
125985
  }
124339
125986
  });
124340
125987
  }
125988
+ openFilePicker(type, callback) {
125989
+ this.openAssetSelect(type, callback);
125990
+ }
124341
125991
  openAssetSelect(targetAssetType, callback, defaultValue) {
124342
125992
  const inpUrl = document.createElement('input');
124343
125993
 
@@ -162390,7 +164040,8 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
162390
164040
  blockContainer: '.is-box',
162391
164041
  slider: this.settings.slider,
162392
164042
  onRender: () => {
162393
- this.settings.onRender();
164043
+ this.settings.onRender(); // Re-init plugins
164044
+ // if(this.win.builderRuntime) this.win.builderRuntime.reinitialize();
162394
164045
  },
162395
164046
  onChange: () => {
162396
164047
  this.settings.onChange();
@@ -164137,6 +165788,18 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
164137
165788
  } // init
164138
165789
 
164139
165790
 
165791
+ openFilePicker(type, callback) {
165792
+ this.editor.openAssetSelect(type, callback);
165793
+ }
165794
+
165795
+ openColorPicker(currentColor, callback, btn) {
165796
+ this.editor.openColorPicker(currentColor, callback, btn);
165797
+ }
165798
+
165799
+ openIconPicker(callback) {
165800
+ this.editor.openIconPicker(callback);
165801
+ }
165802
+
164140
165803
  openPreview() {
164141
165804
  this.animateScroll.openPreview();
164142
165805
  }
@@ -164738,7 +166401,7 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
164738
166401
  this.markScrollTarget(this.activeSection); // adding class 'scroll-target'
164739
166402
  }
164740
166403
 
164741
- doUndoRedo() {
166404
+ async doUndoRedo() {
164742
166405
  /*
164743
166406
  // Clean
164744
166407
  const elms = this.wrapperEl.querySelectorAll(`[data-center],[data-center-top],[data--50-bottom],
@@ -164807,6 +166470,9 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
164807
166470
  if (this.controlPanel) {
164808
166471
  this.controlpanel.select('');
164809
166472
  }
166473
+
166474
+ const runtime = this.win.builderRuntime;
166475
+ if (runtime) runtime.reinitialize();
164810
166476
  }
164811
166477
 
164812
166478
  refreshAnim() {
@@ -165051,7 +166717,9 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
165051
166717
  });
165052
166718
  this.editor.applyBehavior(); // Re-apply
165053
166719
 
165054
- this.settings.onRender();
166720
+ this.settings.onRender(); // Re-init plugins
166721
+
166722
+ if (this.win.builderRuntime) this.win.builderRuntime.reinitialize();
165055
166723
  const scrollButton = this.wrapperEl.querySelectorAll('.is-arrow-down a');
165056
166724
  scrollButton.forEach(btn => {
165057
166725
  btn.addEventListener('click', e => {
@@ -165838,7 +167506,9 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
165838
167506
  this.pageSetupDone = true; //prevent duplicate for traditional pageSetup on init
165839
167507
  // Re-apply
165840
167508
 
165841
- this.settings.onRender();
167509
+ this.settings.onRender(); // Re-init plugins
167510
+
167511
+ if (this.win.builderRuntime) this.win.builderRuntime.reinitialize();
165842
167512
  const scrollButton = wrapper.querySelectorAll('.is-arrow-down a');
165843
167513
  scrollButton.forEach(btn => {
165844
167514
  btn.addEventListener('click', e => {