@innovastudio/contentbuilder 1.5.158 → 1.5.160

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.
Files changed (44) hide show
  1. package/package.json +2 -1
  2. package/public/contentbuilder/contentbuilder.css +149 -6
  3. package/public/contentbuilder/contentbuilder.esm.js +842 -3
  4. package/public/contentbuilder/contentbuilder.min.js +6 -6
  5. package/public/contentbuilder/themes/colored-blue.css +3 -2
  6. package/public/contentbuilder/themes/colored-blue2.css +3 -2
  7. package/public/contentbuilder/themes/colored-blue3.css +3 -2
  8. package/public/contentbuilder/themes/colored-blue4.css +3 -2
  9. package/public/contentbuilder/themes/colored-blue5.css +3 -2
  10. package/public/contentbuilder/themes/colored-blue6.css +3 -2
  11. package/public/contentbuilder/themes/colored-blue7.css +3 -2
  12. package/public/contentbuilder/themes/colored-blue8.css +3 -2
  13. package/public/contentbuilder/themes/colored-darkblue.css +3 -2
  14. package/public/contentbuilder/themes/colored-gray.css +3 -2
  15. package/public/contentbuilder/themes/colored-green.css +3 -2
  16. package/public/contentbuilder/themes/colored-green2.css +3 -2
  17. package/public/contentbuilder/themes/colored-green3.css +3 -2
  18. package/public/contentbuilder/themes/colored-green4.css +3 -2
  19. package/public/contentbuilder/themes/colored-green5.css +3 -2
  20. package/public/contentbuilder/themes/colored-magenta.css +3 -2
  21. package/public/contentbuilder/themes/colored-orange.css +3 -2
  22. package/public/contentbuilder/themes/colored-orange2.css +3 -2
  23. package/public/contentbuilder/themes/colored-orange3.css +3 -2
  24. package/public/contentbuilder/themes/colored-pink.css +3 -2
  25. package/public/contentbuilder/themes/colored-pink2.css +3 -2
  26. package/public/contentbuilder/themes/colored-pink3.css +3 -2
  27. package/public/contentbuilder/themes/colored-pink4.css +3 -2
  28. package/public/contentbuilder/themes/colored-purple.css +3 -2
  29. package/public/contentbuilder/themes/colored-purple2.css +3 -2
  30. package/public/contentbuilder/themes/colored-red.css +3 -2
  31. package/public/contentbuilder/themes/colored-red2.css +3 -2
  32. package/public/contentbuilder/themes/colored-red3.css +3 -2
  33. package/public/contentbuilder/themes/colored-red4.css +3 -2
  34. package/public/contentbuilder/themes/colored-red5.css +3 -2
  35. package/public/contentbuilder/themes/colored-yellow.css +3 -2
  36. package/public/contentbuilder/themes/colored-yellow2.css +3 -2
  37. package/public/contentbuilder/themes/dark-blue.css +3 -2
  38. package/public/contentbuilder/themes/dark-blue2.css +3 -2
  39. package/public/contentbuilder/themes/dark-blue3.css +3 -2
  40. package/public/contentbuilder/themes/dark-gray.css +3 -2
  41. package/public/contentbuilder/themes/dark-pink.css +3 -2
  42. package/public/contentbuilder/themes/dark-purple.css +3 -2
  43. package/public/contentbuilder/themes/dark-red.css +3 -2
  44. package/public/contentbuilder/themes/dark.css +16 -2
@@ -14428,7 +14428,7 @@ class HtmlUtil {
14428
14428
  this.builder.opts.onRender();
14429
14429
 
14430
14430
  // Re-select
14431
- if (row.firstChild) row.firstChild.click();
14431
+ // if(row.firstChild) row.firstChild.click();
14432
14432
  //Change to row selection
14433
14433
  row.classList.remove('row-outline');
14434
14434
  //Hide Column tool (new!)
@@ -15040,6 +15040,7 @@ class HtmlUtil {
15040
15040
 
15041
15041
  // Remove runtime-added attributes
15042
15042
  element.removeAttribute('data-cb-original-content');
15043
+ element.removeAttribute('data-cb-loaded');
15043
15044
 
15044
15045
  // element.removeAttribute('data-cb-logo-loop-initialized');
15045
15046
 
@@ -50724,6 +50725,817 @@ class Spacer {
50724
50725
  }
50725
50726
  }
50726
50727
 
50728
+ /**
50729
+ * ContentBox Settings UI Generator
50730
+ * Automatically generates settings form based on plugin schema
50731
+ *
50732
+ * Usage in ContentBox Editor:
50733
+ * const generator = new SettingsUIGenerator(runtime);
50734
+ * const form = generator.generateForm('logo-loop', currentElement);
50735
+ * panel.appendChild(form);
50736
+ */
50737
+
50738
+ class SettingsUIGenerator {
50739
+ constructor(runtime, builder) {
50740
+ this.runtime = runtime;
50741
+ this.builder = builder;
50742
+ }
50743
+
50744
+ /**
50745
+ * Generate settings form for a plugin
50746
+ * @param {string} pluginName - Plugin name
50747
+ * @param {HTMLElement} element - Current element being edited
50748
+ * @returns {HTMLElement} Form element
50749
+ */
50750
+ generateForm(pluginName, element, onChange) {
50751
+ const plugin = this.runtime.getPlugin(pluginName);
50752
+ if (!plugin || !plugin.settings) {
50753
+ console.warn(`Plugin "${pluginName}" has no settings schema`);
50754
+ return this.createEmptyForm();
50755
+ }
50756
+ const form = document.createElement('div');
50757
+ form.className = 'cb-settings-form';
50758
+
50759
+ // Get current values from element
50760
+ const currentValues = this.getCurrentValues(element, plugin.settings);
50761
+
50762
+ // Check if plugin has grouped settings
50763
+ if (plugin.settings._groups) {
50764
+ this.generateGroupedForm(form, plugin.settings, currentValues);
50765
+ } else {
50766
+ this.generateFlatForm(form, plugin.settings, currentValues);
50767
+ }
50768
+
50769
+ // Attach listeners for auto-update
50770
+ if (typeof onChange === 'function') {
50771
+ form.addEventListener('input', () => {
50772
+ const values = this.getFormValues(form);
50773
+ onChange(values, element);
50774
+ });
50775
+ form.addEventListener('change', () => {
50776
+ const values = this.getFormValues(form);
50777
+ onChange(values, element);
50778
+ });
50779
+ }
50780
+ return form;
50781
+ }
50782
+
50783
+ /**
50784
+ * Generate flat form (no groups)
50785
+ */
50786
+ generateFlatForm(container, settings, currentValues) {
50787
+ Object.keys(settings).forEach(key => {
50788
+ if (key.startsWith('_')) return; // Skip meta keys
50789
+
50790
+ const field = settings[key];
50791
+ const fieldElement = this.createField(key, field, currentValues[key]);
50792
+ container.appendChild(fieldElement);
50793
+ });
50794
+ }
50795
+
50796
+ /**
50797
+ * Generate grouped form
50798
+ */
50799
+ generateGroupedForm(container, settings, currentValues) {
50800
+ const groups = settings._groups || [];
50801
+ groups.forEach(group => {
50802
+ const groupElement = document.createElement('div');
50803
+ groupElement.className = 'cb-settings-group';
50804
+ const groupTitle = document.createElement('h3');
50805
+ groupTitle.className = 'cb-settings-group-title';
50806
+ groupTitle.style.cssText = 'font-size: 17px;font-weight: 500;';
50807
+ groupTitle.textContent = group.label;
50808
+ groupElement.appendChild(groupTitle);
50809
+ group.fields.forEach(fieldKey => {
50810
+ const field = settings[fieldKey];
50811
+ if (!field) return;
50812
+ const fieldElement = this.createField(fieldKey, field, currentValues[fieldKey]);
50813
+ groupElement.appendChild(fieldElement);
50814
+ });
50815
+ container.appendChild(groupElement);
50816
+ });
50817
+ }
50818
+
50819
+ /**
50820
+ * Create a single form field
50821
+ */
50822
+ createField(name, config, value) {
50823
+ const wrapper = document.createElement('div');
50824
+ wrapper.className = 'cb-settings-field';
50825
+ wrapper.style.marginBottom = '20px';
50826
+ wrapper.setAttribute('data-field', name);
50827
+
50828
+ // Add conditional display logic
50829
+ if (config.dependsOn) {
50830
+ wrapper.setAttribute('data-depends-on', JSON.stringify(config.dependsOn));
50831
+ wrapper.style.display = 'none'; // Hidden by default, shown by JS
50832
+ }
50833
+
50834
+ // Label
50835
+ const label = document.createElement('label');
50836
+ label.className = 'cb-settings-label';
50837
+ label.style.fontWeight = 500;
50838
+ label.textContent = config.label || name;
50839
+ // wrapper.appendChild(label);
50840
+
50841
+ // Input based on type
50842
+ const input = this.createInput(name, config, value);
50843
+ wrapper.appendChild(input);
50844
+
50845
+ // Description
50846
+ // if (config.description) {
50847
+ // const desc = document.createElement('p');
50848
+ // desc.className = 'cb-settings-description';
50849
+ // desc.textContent = config.description;
50850
+ // wrapper.appendChild(desc);
50851
+ // }
50852
+
50853
+ return wrapper;
50854
+ }
50855
+
50856
+ /**
50857
+ * Create input element based on field type
50858
+ */
50859
+ createInput(name, config, value) {
50860
+ const currentValue = value !== undefined ? value : config.default;
50861
+ switch (config.type) {
50862
+ case 'number':
50863
+ return this.createNumberInput(name, config, currentValue);
50864
+ case 'boolean':
50865
+ return this.createCheckboxInput(name, config, currentValue);
50866
+ case 'select':
50867
+ return this.createSelectInput(name, config, currentValue);
50868
+ case 'radio':
50869
+ return this.createRadioInput(name, config, currentValue);
50870
+ case 'textarea':
50871
+ return this.createTextareaInput(name, config, currentValue);
50872
+ case 'color':
50873
+ return this.createColorInput(name, config, currentValue);
50874
+ case 'range':
50875
+ return this.createRangeInput(name, config, currentValue);
50876
+ case 'text':
50877
+ default:
50878
+ return this.createTextInput(name, config, currentValue);
50879
+ }
50880
+ }
50881
+ createTextInput(name, config, value) {
50882
+ const label = document.createElement('label');
50883
+ label.className = 'label';
50884
+ label.style.fontWeight = 500;
50885
+ const labelText = document.createElement('div');
50886
+ labelText.textContent = config.label || name;
50887
+ label.appendChild(labelText);
50888
+ const input = document.createElement('input');
50889
+ input.type = 'text';
50890
+ input.name = name;
50891
+ input.value = value || '';
50892
+ if (config.placeholder) input.placeholder = config.placeholder;
50893
+ label.appendChild(input);
50894
+
50895
+ // Add description if exists
50896
+ if (config.description) {
50897
+ const desc = document.createElement('small');
50898
+ desc.style.cssText = 'display: block;margin-top: 4px;';
50899
+ desc.textContent = config.description;
50900
+ label.appendChild(desc);
50901
+ }
50902
+ return label;
50903
+ }
50904
+ createNumberInput(name, config, value) {
50905
+ const label = document.createElement('label');
50906
+ label.className = 'label';
50907
+ label.style.fontWeight = 500;
50908
+ const labelText = document.createElement('div');
50909
+ labelText.textContent = config.label || name;
50910
+ if (config.unit) {
50911
+ labelText.textContent += ` (${config.unit})`;
50912
+ }
50913
+ label.appendChild(labelText);
50914
+ const input = document.createElement('input');
50915
+ input.type = 'number';
50916
+ input.style.cssText = 'width: 100px;';
50917
+ input.name = name;
50918
+ input.value = value !== undefined ? value : config.default;
50919
+ if (config.min !== undefined) input.min = config.min;
50920
+ if (config.max !== undefined) input.max = config.max;
50921
+ if (config.step !== undefined) input.step = config.step;
50922
+ label.appendChild(input);
50923
+ if (config.description) {
50924
+ const desc = document.createElement('small');
50925
+ desc.style.cssText = 'display: block;margin-top: 4px;';
50926
+ desc.textContent = config.description;
50927
+ label.appendChild(desc);
50928
+ }
50929
+ return label;
50930
+ }
50931
+ createCheckboxInput(name, config, value) {
50932
+ const label = document.createElement('label');
50933
+ label.className = 'label checkbox';
50934
+ const div = document.createElement('div');
50935
+ const input = document.createElement('input');
50936
+ input.type = 'checkbox';
50937
+ input.name = name;
50938
+ input.checked = value !== undefined ? value : config.default;
50939
+ const span = document.createElement('span');
50940
+ span.textContent = config.label || name;
50941
+ label.appendChild(input);
50942
+ label.appendChild(span);
50943
+ div.appendChild(label);
50944
+ if (config.description) {
50945
+ const desc = document.createElement('small');
50946
+ desc.style.cssText = 'display: block;margin-top: 4px;';
50947
+ desc.textContent = config.description;
50948
+ div.appendChild(desc);
50949
+ }
50950
+ return label;
50951
+ }
50952
+ createSelectInput(name, config, value) {
50953
+ const label = document.createElement('label');
50954
+ label.className = 'label';
50955
+ label.style.fontWeight = 500;
50956
+ const labelText = document.createTextNode((config.label || name) + ':');
50957
+ label.appendChild(labelText);
50958
+ const select = document.createElement('select');
50959
+ select.name = name;
50960
+ config.options.forEach(option => {
50961
+ const opt = document.createElement('option');
50962
+ opt.value = option.value;
50963
+ opt.textContent = option.label;
50964
+ if (option.value === value) opt.selected = true;
50965
+ select.appendChild(opt);
50966
+ });
50967
+ label.appendChild(select);
50968
+ if (config.description) {
50969
+ const desc = document.createElement('small');
50970
+ desc.style.cssText = 'display: block;margin-top: 4px;';
50971
+ desc.textContent = config.description;
50972
+ label.appendChild(desc);
50973
+ }
50974
+ return label;
50975
+ }
50976
+ createRadioInput(name, config, value) {
50977
+ const wrapper = document.createElement('div');
50978
+ const title = document.createElement('div');
50979
+ title.textContent = config.label || name;
50980
+ title.style.cssText = 'margin-bottom: 8px;font-weight: 590;';
50981
+ wrapper.appendChild(title);
50982
+ config.options.forEach(option => {
50983
+ const label = document.createElement('label');
50984
+ label.className = 'label checkbox';
50985
+ const input = document.createElement('input');
50986
+ input.type = 'radio';
50987
+ input.name = name;
50988
+ input.value = option.value;
50989
+ input.checked = option.value === value;
50990
+ const span = document.createElement('span');
50991
+ span.textContent = option.label;
50992
+ label.appendChild(input);
50993
+ label.appendChild(span);
50994
+ wrapper.appendChild(label);
50995
+ });
50996
+ if (config.description) {
50997
+ const desc = document.createElement('small');
50998
+ desc.style.cssText = 'display: block;margin-top: 4px;';
50999
+ desc.textContent = config.description;
51000
+ wrapper.appendChild(desc);
51001
+ }
51002
+ return wrapper;
51003
+ }
51004
+ createTextareaInput(name, config, value) {
51005
+ const label = document.createElement('label');
51006
+ label.className = 'label';
51007
+ label.style.fontWeight = 500;
51008
+ const labelText = document.createElement('div');
51009
+ labelText.textContent = config.label || name;
51010
+ label.appendChild(labelText);
51011
+ const textarea = document.createElement('textarea');
51012
+ textarea.name = name;
51013
+ textarea.value = value || '';
51014
+ textarea.rows = config.rows || 4;
51015
+ if (config.placeholder) textarea.placeholder = config.placeholder;
51016
+ label.appendChild(textarea);
51017
+ if (config.description) {
51018
+ const desc = document.createElement('small');
51019
+ desc.style.cssText = 'display: block;margin-top: 4px;';
51020
+ desc.textContent = config.description;
51021
+ label.appendChild(desc);
51022
+ }
51023
+ return label;
51024
+ }
51025
+ createColorInput(name, config, value) {
51026
+ const container = document.createElement('div');
51027
+ const label = document.createElement('label');
51028
+ label.className = 'label';
51029
+ label.style.fontWeight = 500;
51030
+ label.style.cursor = 'pointer';
51031
+ const labelText = document.createElement('div');
51032
+ labelText.textContent = config.label || name;
51033
+ label.appendChild(labelText);
51034
+ const input = document.createElement('input');
51035
+ input.type = 'hidden';
51036
+ input.name = name;
51037
+ input.value = value || config.default || '#000000';
51038
+ const group = document.createElement('div');
51039
+ group.className = 'group';
51040
+ group.style.cssText = 'width: 52px; margin:0; overflow: visible;';
51041
+ const colorBtn = document.createElement('button');
51042
+ colorBtn.type = 'button';
51043
+ colorBtn.title = config.label || 'Color';
51044
+ colorBtn.className = 'btn-color is-btn-color';
51045
+ colorBtn.style.backgroundColor = input.value;
51046
+ colorBtn.setAttribute('aria-label', `Choose color for ${config.label || name}`);
51047
+ const openPicker = e => {
51048
+ e.preventDefault();
51049
+ this.builder.openColorPicker(input.value, color => {
51050
+ input.value = color;
51051
+ colorBtn.style.backgroundColor = color;
51052
+ input.dispatchEvent(new Event('change', {
51053
+ bubbles: true
51054
+ }));
51055
+ }, colorBtn);
51056
+ };
51057
+ colorBtn.addEventListener('click', openPicker);
51058
+ // Make label click trigger the button
51059
+ label.addEventListener('click', e => {
51060
+ if (e.target === label || e.target === labelText) {
51061
+ colorBtn.click();
51062
+ }
51063
+ });
51064
+ group.appendChild(colorBtn);
51065
+ label.appendChild(input);
51066
+ if (config.description) {
51067
+ const desc = document.createElement('small');
51068
+ desc.style.cssText = 'display: block;margin-top: 4px;';
51069
+ desc.textContent = config.description;
51070
+ label.appendChild(desc);
51071
+ }
51072
+ container.appendChild(label);
51073
+ container.appendChild(group);
51074
+ return container;
51075
+ }
51076
+ createColorInput2(name, config, value) {
51077
+ const label = document.createElement('label');
51078
+ label.className = 'label';
51079
+ label.style.fontWeight = 500;
51080
+ const labelText = document.createElement('div');
51081
+ labelText.textContent = config.label || name;
51082
+ label.appendChild(labelText);
51083
+ const input = document.createElement('input');
51084
+ input.type = 'color';
51085
+ input.name = name;
51086
+ input.style.cssText = 'width:40px;height:40px';
51087
+ input.value = value || config.default || '#000000';
51088
+ label.appendChild(input);
51089
+ if (config.description) {
51090
+ const desc = document.createElement('small');
51091
+ desc.style.cssText = 'display: block;margin-top: 4px;';
51092
+ desc.textContent = config.description;
51093
+ label.appendChild(desc);
51094
+ }
51095
+ return label;
51096
+ }
51097
+ createRangeInput(name, config, value) {
51098
+ const label = document.createElement('label');
51099
+ label.className = 'label';
51100
+ label.style.fontWeight = 500;
51101
+ const currentVal = value !== undefined ? value : config.default;
51102
+ const labelText = document.createElement('div');
51103
+ labelText.textContent = (config.label || name) + ': ' + currentVal + (config.unit || '');
51104
+ label.appendChild(labelText);
51105
+ const input = document.createElement('input');
51106
+ input.type = 'range';
51107
+ input.className = 'is-rangeslider';
51108
+ input.name = name;
51109
+ input.value = currentVal;
51110
+ if (config.min !== undefined) input.min = config.min;
51111
+ if (config.max !== undefined) input.max = config.max;
51112
+ if (config.step !== undefined) input.step = config.step;
51113
+ input.addEventListener('input', () => {
51114
+ labelText.textContent = (config.label || name) + ': ' + input.value + (config.unit || '');
51115
+ });
51116
+ label.appendChild(input);
51117
+ if (config.description) {
51118
+ const desc = document.createElement('small');
51119
+ desc.style.cssText = 'display: block;margin-top: 4px;';
51120
+ desc.textContent = config.description;
51121
+ label.appendChild(desc);
51122
+ }
51123
+ return label;
51124
+ }
51125
+
51126
+ /**
51127
+ * Get current values from element attributes
51128
+ */
51129
+ getCurrentValues(element, settings) {
51130
+ const values = {};
51131
+ Object.keys(settings).forEach(key => {
51132
+ if (key.startsWith('_')) return;
51133
+ const attrName = 'data-cb-' + key.replace(/([A-Z])/g, '-$1').toLowerCase();
51134
+ const attrValue = element.getAttribute(attrName);
51135
+ if (attrValue !== null) {
51136
+ values[key] = this.parseValue(attrValue, settings[key].type);
51137
+ }
51138
+ });
51139
+ return values;
51140
+ }
51141
+
51142
+ /**
51143
+ * Parse attribute value based on type
51144
+ */
51145
+ parseValue(value, type) {
51146
+ switch (type) {
51147
+ case 'number':
51148
+ return parseFloat(value);
51149
+ case 'boolean':
51150
+ return value === 'true';
51151
+ default:
51152
+ return value;
51153
+ }
51154
+ }
51155
+
51156
+ /**
51157
+ * Get form values
51158
+ */
51159
+ getFormValues(form) {
51160
+ const values = {};
51161
+ const inputs = form.querySelectorAll('input, select, textarea');
51162
+ inputs.forEach(input => {
51163
+ const name = input.name;
51164
+ if (!name) return;
51165
+ if (input.type === 'checkbox') {
51166
+ values[name] = input.checked;
51167
+ } else if (input.type === 'radio') {
51168
+ if (input.checked) {
51169
+ values[name] = input.value;
51170
+ }
51171
+ } else {
51172
+ values[name] = input.value;
51173
+ }
51174
+ });
51175
+ return values;
51176
+ }
51177
+
51178
+ /**
51179
+ * Apply form values to element
51180
+ */
51181
+ applyValues(element, values) {
51182
+ Object.keys(values).forEach(key => {
51183
+ const attrName = 'data-cb-' + key.replace(/([A-Z])/g, '-$1').toLowerCase();
51184
+ element.setAttribute(attrName, values[key]);
51185
+ });
51186
+ }
51187
+ createEmptyForm() {
51188
+ const form = document.createElement('div');
51189
+ form.className = 'cb-settings-form';
51190
+ form.innerHTML = '<p>This plugin has no configurable settings.</p>';
51191
+ return form;
51192
+ }
51193
+ }
51194
+
51195
+ class Plugin {
51196
+ constructor(builder) {
51197
+ this.builder = builder;
51198
+ }
51199
+ renderTool() {
51200
+ const builderStuff = this.builder.builderStuff;
51201
+ const contentStuff = this.builder.contentStuff;
51202
+ const util = this.builder.util;
51203
+ const dom = this.builder.dom;
51204
+ let pluginTool = builderStuff.querySelector('.is-plugin-tool');
51205
+ if (!pluginTool) {
51206
+ let html = `
51207
+ <div class="is-tool is-plugin-tool">
51208
+ <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>
51209
+ </div>
51210
+ `;
51211
+ dom.appendHtml(builderStuff, html);
51212
+ if (!this.builder.iframe) {
51213
+ pluginTool = builderStuff.querySelector('.is-plugin-tool');
51214
+ } else {
51215
+ pluginTool = contentStuff.querySelector('.is-plugin-tool');
51216
+ }
51217
+ this.pluginTool = pluginTool;
51218
+ let btn = pluginTool.querySelector('button');
51219
+ dom.addEventListener(btn, 'click', () => {
51220
+ // old 10317
51221
+
51222
+ if (this.pluginModal && this.pluginModal.classList.contains('active')) {
51223
+ this.hidePluginEditor();
51224
+ } else {
51225
+ this.renderPanel();
51226
+ this.showPluginEditor();
51227
+ }
51228
+ btn.setAttribute('data-focus', true);
51229
+ });
51230
+ }
51231
+ }
51232
+ renderPanel() {
51233
+ const builderStuff = this.builder.builderStuff;
51234
+ const util = this.builder.util;
51235
+ const dom = this.builder.dom;
51236
+ let pluginModal = builderStuff.querySelector('.is-modal.pluginsettings');
51237
+ if (!pluginModal) {
51238
+ let html = `
51239
+ <div class="is-modal is-modal-content pluginsettings" tabindex="-1" role="dialog" aria-modal="true" aria-hidden="true">
51240
+ <div class="is-modal-bar is-draggable">
51241
+ <span>${util.out('Plugin Settings')}</span>
51242
+ <button class="is-modal-close" tabindex="-1" title="${util.out('Close')}">&#10005;</button>
51243
+ </div>
51244
+ <div class="is-modal-controls" style="box-sizing:border-box;padding:30px;overflow-y: auto;height: calc(100% - 30px);">
51245
+ <div class="is-tabs" data-group="table">
51246
+ <a title="${util.out('Style')}" id="tabTableGeneral" href="" data-content="divTableGeneral" class="active">${util.out('Style')}</a>
51247
+ <a title="${util.out('Layout')}" id="tabTableLayout" href="" data-content="divTableLayout">${util.out('Layout')}</a>
51248
+ </div>
51249
+ <div id="divTableGeneral" class="is-tab-content active" tabindex="-1" data-group="table" style="display:block">
51250
+
51251
+ <div style="display:flex;padding-bottom:12px">
51252
+ <div style="padding-right:15px">
51253
+ <div>${util.out('Background')}:</div>
51254
+ <div>
51255
+ <button title="${util.out('Background Color')}" class="input-table-bgcolor is-btn-color"></button>
51256
+ </div>
51257
+ </div>
51258
+ <div>
51259
+ <div>${util.out('Text Color')}:</div>
51260
+ <div>
51261
+ <button title="${util.out('Text Color')}" class="input-table-textcolor is-btn-color"></button>
51262
+ </div>
51263
+ </div>
51264
+ </div>
51265
+
51266
+ <div style="padding-bottom:12px;">
51267
+ <div>${util.out('Border Thickness')}:</div>
51268
+ <div>
51269
+ <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>
51270
+ </div>
51271
+ </div>
51272
+
51273
+ <div style="padding-bottom:12px;">
51274
+ <div>${util.out('Border Color')}:</div>
51275
+ <div>
51276
+ <button title="${util.out('Border Color')}" class="input-table-bordercolor is-btn-color"></button>
51277
+ </div>
51278
+ </div>
51279
+
51280
+ <div style="padding-bottom:12px;">
51281
+ <div>${util.out('Apply To')}:</div>
51282
+ <div>
51283
+ <select id="selTableApplyTo" style="width:120px;">
51284
+ <option value="table">${util.out('Table')}</option>
51285
+ <option value="currentrow">${util.out('Current Row')}</option>
51286
+ <option value="currentcol">${util.out('Current Column')}</option>
51287
+ <option value="evenrows">${util.out('Even Rows')}</option>
51288
+ <option value="oddrows">${util.out('Odd Rows')}</option>
51289
+ <option value="currentcell">${util.out('Current Cell')}</option>
51290
+ </select>
51291
+ </div>
51292
+ </div>
51293
+
51294
+ </div>
51295
+
51296
+ <div id="divTableLayout" class="is-tab-content" tabindex="-1" data-group="table">
51297
+
51298
+ <div style="padding-bottom:12px;">
51299
+ <div>${util.out('Insert Row')}:</div>
51300
+ <div style="display:flex">
51301
+ <button class="classic" title="${util.out('Above')}" data-table-cmd="rowabove" title="${util.out('Above')}" style="margin-right:15px"> ${util.out('Above')} </button>
51302
+ <button class="classic" title="${util.out('Below')}" data-table-cmd="rowbelow" title="${util.out('Below')}" style=""> ${util.out('Below')} </button>
51303
+ </div>
51304
+ </div>
51305
+
51306
+ <div style="padding-bottom:15px;">
51307
+ <div>${util.out('Insert Column')}:</div>
51308
+ <div style="display:flex">
51309
+ <button class="classic" title="${util.out('Left')}" data-table-cmd="columnleft" title="${util.out('Left')}" style="margin-right:15px"> ${util.out('Left')} </button>
51310
+ <button class="classic" title="${util.out('Right')}" data-table-cmd="columnright" title="${util.out('Right')}" style=""> ${util.out('Right')} </button>
51311
+ </div>
51312
+ </div>
51313
+
51314
+ <div style="padding-bottom:15px;">
51315
+ <button class="classic" title="${util.out('Delete Row')}" data-table-cmd="delrow" title="Delete Row" style=""> ${util.out('Delete Row')} </button>
51316
+ </div>
51317
+
51318
+ <div style="padding-bottom:15px;">
51319
+ <button class="classic" title="${util.out('Delete Column')}" data-table-cmd="delcolumn" title="Delete Column" style=""> ${util.out('Delete Column')} </button>
51320
+ </div>
51321
+
51322
+ <div>
51323
+ <button class="classic" title="${util.out('Merge Cell')}" data-table-cmd="mergecell" style="">${util.out('Merge Cell')}</button>
51324
+ </div>
51325
+ </div>
51326
+ </div>
51327
+ </div>
51328
+ `;
51329
+ dom.appendHtml(builderStuff, html);
51330
+ pluginModal = builderStuff.querySelector('.is-modal.pluginsettings');
51331
+ this.pluginModal = pluginModal;
51332
+ let btnOk = pluginModal.querySelector('.input-ok');
51333
+ dom.addEventListener(btnOk, 'click', () => {
51334
+ this.builder.uo.saveForUndo();
51335
+ this.builder.opts.onChange();
51336
+ util.hideModal(pluginModal);
51337
+ });
51338
+ let btnCancel = pluginModal.querySelector('.input-cancel');
51339
+ dom.addEventListener(btnCancel, 'click', () => {
51340
+ util.hideModal(pluginModal);
51341
+ });
51342
+ new Tabs({
51343
+ element: pluginModal
51344
+ });
51345
+ new Draggable$2({
51346
+ selector: '.is-modal.pluginsettings .is-draggable'
51347
+ });
51348
+ }
51349
+ }
51350
+ showPluginEditor() {
51351
+ const pluginModal = this.pluginModal;
51352
+ this.builder.util.showModal(pluginModal);
51353
+ this.realtime();
51354
+ const handlePluginClick = e => {
51355
+ const clrPicker = document.querySelector('.pop-picker.active') || document.querySelector('.pickgradientcolor.active');
51356
+ let elm = e.target;
51357
+ let elmRemoved = !document.contains(elm); // e.g. when deleting a card in card list plugin
51358
+
51359
+ if (this.builder.doc.activeElement) {
51360
+ if (this.builder.doc.activeElement.closest('.is-modal')) {
51361
+ // prevent modal close when mouseup outside the modal
51362
+ return;
51363
+ }
51364
+ }
51365
+ if (!elm) return;
51366
+ 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) {
51367
+ // click outside
51368
+
51369
+ // hide
51370
+ this.hidePluginEditor();
51371
+ // console.log('HIDE');
51372
+
51373
+ document.removeEventListener('click', handlePluginClick);
51374
+ if (this.builder.iframeDocument) {
51375
+ this.builder.doc.removeEventListener('click', handlePluginClick);
51376
+ }
51377
+ this.builder.handlePluginClick_ = false;
51378
+ }
51379
+ if (elm.closest('[data-cb-type]')) {
51380
+ this.realtime();
51381
+ }
51382
+ };
51383
+ if (!this.builder.handlePluginClick_) {
51384
+ document.addEventListener('click', handlePluginClick);
51385
+ if (this.builder.iframeDocument) {
51386
+ this.builder.doc.addEventListener('click', handlePluginClick);
51387
+ }
51388
+ this.builder.handlePluginClick_ = true;
51389
+ }
51390
+ }
51391
+ hidePluginEditor() {
51392
+ const pluginModal = this.pluginModal;
51393
+ if (pluginModal) this.builder.util.hideModal(pluginModal);
51394
+ }
51395
+ realtime() {
51396
+ const util = this.builder.util;
51397
+ const pluginModal = this.pluginModal;
51398
+ let currentElement = this.builder.activePlugin;
51399
+ const runtime = this.builder.win.builderRuntime;
51400
+ if (currentElement && runtime) {
51401
+ const pluginName = currentElement.getAttribute('data-cb-type');
51402
+ const plugin = runtime.getPlugin(pluginName);
51403
+ let titleHtml = util.out(plugin.displayName || plugin.name);
51404
+ const modalBar = pluginModal.querySelector('.is-modal-bar > span');
51405
+ modalBar.innerHTML = titleHtml;
51406
+ const container = pluginModal.querySelector('.is-modal-controls');
51407
+
51408
+ // Clear panel
51409
+ container.innerHTML = '';
51410
+ const div = document.createElement('div');
51411
+ div.classList.add('submain');
51412
+ container.appendChild(div);
51413
+
51414
+ // Store references
51415
+ this.currentElement = currentElement;
51416
+ this.generator = new SettingsUIGenerator(runtime, this.builder);
51417
+
51418
+ // Check if plugin has custom content editor
51419
+ const hasContentEditor = plugin && plugin.editor && plugin.editor.openContentEditor;
51420
+ if (hasContentEditor) {
51421
+ // Get original content for the editor to work with
51422
+ const originalContent = currentElement.getAttribute('data-cb-original-content');
51423
+
51424
+ // Create a temporary element for editing (so editor can manipulate it)
51425
+ let editableClone = document.querySelector('.editable-clone');
51426
+ if (editableClone) editableClone.remove();
51427
+ // editableClone = document.createElement('div');
51428
+ editableClone = currentElement.cloneNode(false);
51429
+ editableClone.innerHTML = originalContent;
51430
+ editableClone.className = 'editable-clone';
51431
+ editableClone.style.display = 'none'; // Hidden, just for editing
51432
+ document.body.appendChild(editableClone);
51433
+ const originalElement = editableClone.cloneNode(false);
51434
+
51435
+ // Let plugin handle everything - pass the editable clone
51436
+ const editorUI = plugin.editor.openContentEditor(editableClone, this.builder, async () => {
51437
+ this.builder.uo.saveForUndo();
51438
+
51439
+ // Store edited content from clone
51440
+ currentElement.setAttribute('data-cb-original-content', editableClone.innerHTML);
51441
+
51442
+ // Also performs similar value updates like the applyChanges()
51443
+ // currentElement.setAttribute('data-cb-content', editableClone.getAttribute('data-cb-content'));
51444
+ const getChangedDataAttributes = (el1, el2) => {
51445
+ const changed = [];
51446
+ for (const key of Object.keys(el1.dataset)) {
51447
+ if (key in el2.dataset && el1.dataset[key] !== el2.dataset[key]) {
51448
+ changed.push(key);
51449
+ }
51450
+ }
51451
+ return changed;
51452
+ };
51453
+ const changedAttributes = getChangedDataAttributes(originalElement, editableClone);
51454
+ // let hasChange = changedAttributes.length > 0;
51455
+ for (const attrName of changedAttributes) {
51456
+ // console.log(attrName);
51457
+ currentElement.dataset[attrName] = editableClone.dataset[attrName];
51458
+ // update form (not working)
51459
+ // const convertCbKey = (str) => {
51460
+ // return str.replace(/^cb/, '').replace(/^./, c => c.toLowerCase());
51461
+ // }
51462
+ // const name = convertCbKey(attrName);
51463
+ // this.currentForm.querySelector(`[name="${name}"]`).value = editableClone.dataset[attrName];
51464
+ }
51465
+ // console.log(hasChange)
51466
+ // -------
51467
+
51468
+ currentElement.innerHTML = editableClone.innerHTML; // Update current content
51469
+
51470
+ // Remove temporary clone
51471
+ editableClone.remove();
51472
+
51473
+ // update form (working)
51474
+ // currentElement.click();
51475
+ // return;
51476
+
51477
+ // Reinitialize plugin with new content
51478
+ await runtime.reinitialize(currentElement.parentElement);
51479
+ this.builder.onChange();
51480
+ });
51481
+ div.appendChild(editorUI);
51482
+ }
51483
+
51484
+ // Generate form
51485
+ this.currentForm = this.generator.generateForm(pluginName, currentElement, () => {
51486
+ this.builder.uo.saveForUndo();
51487
+ this.applyChanges(runtime);
51488
+ });
51489
+ this.currentForm.style.marginTop = '20px';
51490
+ div.appendChild(this.currentForm);
51491
+ }
51492
+ }
51493
+ async applyChanges(runtime) {
51494
+ if (!this.currentElement || !this.currentForm) return;
51495
+
51496
+ // 1. Get form values
51497
+ const values = this.generator.getFormValues(this.currentForm);
51498
+
51499
+ // 2. Apply to element attributes
51500
+ this.generator.applyValues(this.currentElement, values);
51501
+
51502
+ // 3. Reinitialize component
51503
+ const container = this.currentElement.parentElement || this.currentElement.closest('.is-wrapper');
51504
+ await runtime.reinitialize(container);
51505
+
51506
+ //Trigger Change event
51507
+ this.builder.opts.onChange();
51508
+
51509
+ // console.log('Settings applied and component reinitialized');
51510
+ }
51511
+
51512
+ click(e) {
51513
+ const plugin = e.target.closest('[data-cb-type]');
51514
+ if (plugin) {
51515
+ this.builder.activePlugin = plugin;
51516
+ if (!this.builder.isContentBox) {
51517
+ this.renderTool();
51518
+ this.pluginTool.style.display = 'flex';
51519
+ let _toolwidth = this.pluginTool.offsetWidth; //to get value, element must not hidden (display:none). So set display:flex before this.
51520
+
51521
+ let w = plugin.offsetWidth * this.builder.opts.zoom;
51522
+ let top = plugin.getBoundingClientRect().top + this.builder.win.pageYOffset;
51523
+ let left = plugin.getBoundingClientRect().left - 2;
51524
+ left = left + (w - _toolwidth);
51525
+
51526
+ //Adjust left in case an element is outside the screen
51527
+ const _screenwidth = window.innerWidth;
51528
+ if (_toolwidth + left > _screenwidth) left = plugin.getBoundingClientRect().left;
51529
+ this.pluginTool.style.top = top + 'px';
51530
+ this.pluginTool.style.left = left + 'px';
51531
+ }
51532
+ } else {
51533
+ this.builder.activePlugin = null;
51534
+ if (this.pluginTool) this.pluginTool.style.display = '';
51535
+ }
51536
+ }
51537
+ }
51538
+
50727
51539
  class Module {
50728
51540
  constructor(builder) {
50729
51541
  this.builder = builder;
@@ -52565,6 +53377,7 @@ class Element$1 {
52565
53377
  this.button = new Button(builder);
52566
53378
  this.image = new Image$1(builder);
52567
53379
  this.spacer = new Spacer(builder);
53380
+ this.plugin = new Plugin(builder);
52568
53381
  this.module = new Module(builder);
52569
53382
  this.code = new Code(builder);
52570
53383
  this.iframe = new Iframe(builder);
@@ -52771,6 +53584,9 @@ class Element$1 {
52771
53584
 
52772
53585
  // Module
52773
53586
  this.module.click(col, e);
53587
+
53588
+ // Plugin
53589
+ this.plugin.click(e);
52774
53590
  }
52775
53591
  }
52776
53592
 
@@ -61926,6 +62742,12 @@ class ElementTool {
61926
62742
  const btnMore = elementTool.querySelector('.elm-more');
61927
62743
  dom.addEventListener(btnMore, 'click', () => {
61928
62744
  const viewportHeight = window.innerHeight;
62745
+ const elmSettings = elementMore.querySelector('.elm-settings');
62746
+ if (this.builder.activeElement.hasAttribute('data-cb-type')) {
62747
+ elmSettings.style.display = 'none';
62748
+ } else {
62749
+ elmSettings.style.display = '';
62750
+ }
61929
62751
 
61930
62752
  /*
61931
62753
  let top, left;
@@ -62191,7 +63013,10 @@ class ElementTool {
62191
63013
  if (dom.parentsHasClass(elm, 'is-subblock')) {
62192
63014
  subblock = true;
62193
63015
  }
62194
- if ((customcode || noedit || _protected) && !subblock) ; else {
63016
+ const plugin = elm.closest('[data-cb-type]');
63017
+ if ((customcode || noedit || _protected) && !subblock) ; else if (plugin) {
63018
+ activeElement = plugin;
63019
+ } else {
62195
63020
  const tagName = elm.tagName.toLowerCase();
62196
63021
  // LATER: label, code, figcaption ?
62197
63022
  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')) {
@@ -62343,7 +63168,8 @@ class ElementTool {
62343
63168
  let elm = this.builder.activeElement;
62344
63169
  if (!elm) return;
62345
63170
  if (elm.closest('.is-dock')) return;
62346
- if (elm.closest('[data-cb-type]')) return;
63171
+ // if(elm.closest('[data-cb-type]')) return;
63172
+
62347
63173
  let top, left;
62348
63174
  if (!this.builder.iframe) {
62349
63175
  top = elm.getBoundingClientRect().top + window.pageYOffset;
@@ -79992,6 +80818,9 @@ class ContentStuff {
79992
80818
  <div class="is-tool is-iframe-tool">
79993
80819
  <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>
79994
80820
  </div>
80821
+ <div class="is-tool is-plugin-tool">
80822
+ <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>
80823
+ </div>
79995
80824
  <div class="is-tool is-module-tool">
79996
80825
  <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>
79997
80826
  <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>
@@ -80397,20 +81226,24 @@ class ContentStuff {
80397
81226
  /*
80398
81227
  .is-tool.is-video-tool,
80399
81228
  .is-tool.is-audio-tool,
81229
+ .is-tool.is-plugin-tool,
80400
81230
  .is-tool.is-iframe-tool {
80401
81231
  background: rgba(0, 0, 0, 0.15);
80402
81232
  border: transparent 1px solid;
80403
81233
  }
80404
81234
  .is-tool.is-video-tool > div,
80405
81235
  .is-tool.is-audio-tool > div,
81236
+ .is-tool.is-plugin-tool > div,
80406
81237
  .is-tool.is-iframe-tool > div,
80407
81238
  .is-tool.is-video-tool > button,
80408
81239
  .is-tool.is-audio-tool > button,
81240
+ .is-tool.is-plugin-tool > button,
80409
81241
  .is-tool.is-iframe-tool > button {
80410
81242
  background: transparent;
80411
81243
  }
80412
81244
  .is-tool.is-video-tool svg,
80413
81245
  .is-tool.is-audio-tool svg,
81246
+ .is-tool.is-plugin-tool svg,
80414
81247
  .is-tool.is-iframe-tool svg {
80415
81248
  fill: #fff;
80416
81249
  }
@@ -80420,6 +81253,7 @@ class ContentStuff {
80420
81253
  .is-tool.is-table-tool,
80421
81254
  .is-tool.is-code-tool,
80422
81255
  .is-tool.is-module-tool,
81256
+ .is-tool.is-plugin-tool,
80423
81257
  .is-tool#divLinkTool,
80424
81258
  .is-tool#divButtonTool,
80425
81259
  .is-tool.is-svg-tool {
@@ -80433,6 +81267,7 @@ class ContentStuff {
80433
81267
  .is-tool.is-table-tool > button,
80434
81268
  .is-tool.is-code-tool > button,
80435
81269
  .is-tool.is-module-tool > button,
81270
+ .is-tool.is-plugin-tool > button,
80436
81271
  .is-tool#divLinkTool > button,
80437
81272
  .is-tool#divButtonTool > button,
80438
81273
  .is-tool.is-svg-tool > button {
@@ -80445,6 +81280,7 @@ class ContentStuff {
80445
81280
  .is-tool.is-table-tool > button svg,
80446
81281
  .is-tool.is-code-tool > button svg,
80447
81282
  .is-tool.is-module-tool > button svg,
81283
+ .is-tool.is-plugin-tool > button svg,
80448
81284
  .is-tool#divLinkTool > button svg,
80449
81285
  .is-tool#divButtonTool > button svg,
80450
81286
  .is-tool.is-svg-tool > button svg {
@@ -80459,6 +81295,7 @@ class ContentStuff {
80459
81295
 
80460
81296
  .is-tool.is-video-tool,
80461
81297
  .is-tool.is-audio-tool,
81298
+ .is-tool.is-plugin-tool,
80462
81299
  .is-tool.is-iframe-tool {
80463
81300
  background: rgba(255, 255, 255, 0.97) !important;
80464
81301
  border: transparent 1px solid;
@@ -80470,6 +81307,7 @@ class ContentStuff {
80470
81307
  .is-tool.is-video-tool > button,
80471
81308
  .is-tool.is-audio-tool > div,
80472
81309
  .is-tool.is-audio-tool > button,
81310
+ .is-tool.is-plugin-tool > div,
80473
81311
  .is-tool.is-iframe-tool > div,
80474
81312
  .is-tool.is-iframe-tool > button {
80475
81313
  width: 35px !important;
@@ -80479,6 +81317,7 @@ class ContentStuff {
80479
81317
 
80480
81318
  .is-tool.is-video-tool svg,
80481
81319
  .is-tool.is-audio-tool svg,
81320
+ .is-tool.is-plugin-tool svg,
80482
81321
  .is-tool.is-iframe-tool svg {
80483
81322
  width: 17px;
80484
81323
  height: 17px;