@innovastudio/contentbuilder 1.5.157 → 1.5.159
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +6 -0
- package/package.json +2 -1
- package/public/contentbuilder/contentbuilder.css +149 -6
- package/public/contentbuilder/contentbuilder.esm.js +968 -3
- package/public/contentbuilder/contentbuilder.min.js +6 -6
- package/public/contentbuilder/themes/colored-blue.css +3 -2
- package/public/contentbuilder/themes/colored-blue2.css +3 -2
- package/public/contentbuilder/themes/colored-blue3.css +3 -2
- package/public/contentbuilder/themes/colored-blue4.css +3 -2
- package/public/contentbuilder/themes/colored-blue5.css +3 -2
- package/public/contentbuilder/themes/colored-blue6.css +3 -2
- package/public/contentbuilder/themes/colored-blue7.css +3 -2
- package/public/contentbuilder/themes/colored-blue8.css +3 -2
- package/public/contentbuilder/themes/colored-darkblue.css +3 -2
- package/public/contentbuilder/themes/colored-gray.css +3 -2
- package/public/contentbuilder/themes/colored-green.css +3 -2
- package/public/contentbuilder/themes/colored-green2.css +3 -2
- package/public/contentbuilder/themes/colored-green3.css +3 -2
- package/public/contentbuilder/themes/colored-green4.css +3 -2
- package/public/contentbuilder/themes/colored-green5.css +3 -2
- package/public/contentbuilder/themes/colored-magenta.css +3 -2
- package/public/contentbuilder/themes/colored-orange.css +3 -2
- package/public/contentbuilder/themes/colored-orange2.css +3 -2
- package/public/contentbuilder/themes/colored-orange3.css +3 -2
- package/public/contentbuilder/themes/colored-pink.css +3 -2
- package/public/contentbuilder/themes/colored-pink2.css +3 -2
- package/public/contentbuilder/themes/colored-pink3.css +3 -2
- package/public/contentbuilder/themes/colored-pink4.css +3 -2
- package/public/contentbuilder/themes/colored-purple.css +3 -2
- package/public/contentbuilder/themes/colored-purple2.css +3 -2
- package/public/contentbuilder/themes/colored-red.css +3 -2
- package/public/contentbuilder/themes/colored-red2.css +3 -2
- package/public/contentbuilder/themes/colored-red3.css +3 -2
- package/public/contentbuilder/themes/colored-red4.css +3 -2
- package/public/contentbuilder/themes/colored-red5.css +3 -2
- package/public/contentbuilder/themes/colored-yellow.css +3 -2
- package/public/contentbuilder/themes/colored-yellow2.css +3 -2
- package/public/contentbuilder/themes/dark-blue.css +3 -2
- package/public/contentbuilder/themes/dark-blue2.css +3 -2
- package/public/contentbuilder/themes/dark-blue3.css +3 -2
- package/public/contentbuilder/themes/dark-gray.css +3 -2
- package/public/contentbuilder/themes/dark-pink.css +3 -2
- package/public/contentbuilder/themes/dark-purple.css +3 -2
- package/public/contentbuilder/themes/dark-red.css +3 -2
- 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
|
|
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,815 @@ 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')}">✕</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
|
+
this.renderTool();
|
|
51517
|
+
this.pluginTool.style.display = 'flex';
|
|
51518
|
+
let _toolwidth = this.pluginTool.offsetWidth; //to get value, element must not hidden (display:none). So set display:flex before this.
|
|
51519
|
+
|
|
51520
|
+
let w = plugin.offsetWidth * this.builder.opts.zoom;
|
|
51521
|
+
let top = plugin.getBoundingClientRect().top + this.builder.win.pageYOffset;
|
|
51522
|
+
let left = plugin.getBoundingClientRect().left - 2;
|
|
51523
|
+
left = left + (w - _toolwidth);
|
|
51524
|
+
|
|
51525
|
+
//Adjust left in case an element is outside the screen
|
|
51526
|
+
const _screenwidth = window.innerWidth;
|
|
51527
|
+
if (_toolwidth + left > _screenwidth) left = plugin.getBoundingClientRect().left;
|
|
51528
|
+
this.pluginTool.style.top = top + 'px';
|
|
51529
|
+
this.pluginTool.style.left = left + 'px';
|
|
51530
|
+
} else {
|
|
51531
|
+
this.builder.activePlugin = null;
|
|
51532
|
+
if (this.pluginTool) this.pluginTool.style.display = '';
|
|
51533
|
+
}
|
|
51534
|
+
}
|
|
51535
|
+
}
|
|
51536
|
+
|
|
50727
51537
|
class Module {
|
|
50728
51538
|
constructor(builder) {
|
|
50729
51539
|
this.builder = builder;
|
|
@@ -52565,6 +53375,7 @@ class Element$1 {
|
|
|
52565
53375
|
this.button = new Button(builder);
|
|
52566
53376
|
this.image = new Image$1(builder);
|
|
52567
53377
|
this.spacer = new Spacer(builder);
|
|
53378
|
+
this.plugin = new Plugin(builder);
|
|
52568
53379
|
this.module = new Module(builder);
|
|
52569
53380
|
this.code = new Code(builder);
|
|
52570
53381
|
this.iframe = new Iframe(builder);
|
|
@@ -52603,6 +53414,10 @@ class Element$1 {
|
|
|
52603
53414
|
|
|
52604
53415
|
// Set contentEditable FALSE on special elements
|
|
52605
53416
|
|
|
53417
|
+
const plugins = col.querySelectorAll('[data-cb-type]'); // plugins
|
|
53418
|
+
Array.prototype.forEach.call(plugins, plugin => {
|
|
53419
|
+
plugin.contentEditable = false;
|
|
53420
|
+
});
|
|
52606
53421
|
let sociallinks = col.querySelectorAll('.is-social');
|
|
52607
53422
|
Array.prototype.forEach.call(sociallinks, sociallink => {
|
|
52608
53423
|
sociallink.contentEditable = false;
|
|
@@ -52767,6 +53582,9 @@ class Element$1 {
|
|
|
52767
53582
|
|
|
52768
53583
|
// Module
|
|
52769
53584
|
this.module.click(col, e);
|
|
53585
|
+
|
|
53586
|
+
// Plugin
|
|
53587
|
+
this.plugin.click(e);
|
|
52770
53588
|
}
|
|
52771
53589
|
}
|
|
52772
53590
|
|
|
@@ -61922,6 +62740,12 @@ class ElementTool {
|
|
|
61922
62740
|
const btnMore = elementTool.querySelector('.elm-more');
|
|
61923
62741
|
dom.addEventListener(btnMore, 'click', () => {
|
|
61924
62742
|
const viewportHeight = window.innerHeight;
|
|
62743
|
+
const elmSettings = elementMore.querySelector('.elm-settings');
|
|
62744
|
+
if (this.builder.activeElement.hasAttribute('data-cb-type')) {
|
|
62745
|
+
elmSettings.style.display = 'none';
|
|
62746
|
+
} else {
|
|
62747
|
+
elmSettings.style.display = '';
|
|
62748
|
+
}
|
|
61925
62749
|
|
|
61926
62750
|
/*
|
|
61927
62751
|
let top, left;
|
|
@@ -62187,7 +63011,10 @@ class ElementTool {
|
|
|
62187
63011
|
if (dom.parentsHasClass(elm, 'is-subblock')) {
|
|
62188
63012
|
subblock = true;
|
|
62189
63013
|
}
|
|
62190
|
-
|
|
63014
|
+
const plugin = elm.closest('[data-cb-type]');
|
|
63015
|
+
if ((customcode || noedit || _protected) && !subblock) ; else if (plugin) {
|
|
63016
|
+
activeElement = plugin;
|
|
63017
|
+
} else {
|
|
62191
63018
|
const tagName = elm.tagName.toLowerCase();
|
|
62192
63019
|
// LATER: label, code, figcaption ?
|
|
62193
63020
|
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')) {
|
|
@@ -62339,7 +63166,8 @@ class ElementTool {
|
|
|
62339
63166
|
let elm = this.builder.activeElement;
|
|
62340
63167
|
if (!elm) return;
|
|
62341
63168
|
if (elm.closest('.is-dock')) return;
|
|
62342
|
-
if
|
|
63169
|
+
// if(elm.closest('[data-cb-type]')) return;
|
|
63170
|
+
|
|
62343
63171
|
let top, left;
|
|
62344
63172
|
if (!this.builder.iframe) {
|
|
62345
63173
|
top = elm.getBoundingClientRect().top + window.pageYOffset;
|
|
@@ -97180,6 +98008,143 @@ Add an image for each feature.`, 'Create a new content showcasing a photo galler
|
|
|
97180
98008
|
fromViewToActual(html) {
|
|
97181
98009
|
return this.htmlutil.fromViewToActual(html);
|
|
97182
98010
|
}
|
|
98011
|
+
openColorPicker(currentColor, callback, btn) {
|
|
98012
|
+
this.colorPicker.open(callback, currentColor, () => {}, btn);
|
|
98013
|
+
}
|
|
98014
|
+
openIconPicker(callback) {
|
|
98015
|
+
let iconModal = this.builderStuff.querySelector('.is-modal.iconselect');
|
|
98016
|
+
if (!iconModal) {
|
|
98017
|
+
// Create modal with search input and event listeners once
|
|
98018
|
+
let html = `
|
|
98019
|
+
<style>
|
|
98020
|
+
.is-modal.iconselect .icon-search-wrapper {
|
|
98021
|
+
position: sticky;
|
|
98022
|
+
top: 0;
|
|
98023
|
+
background: white;
|
|
98024
|
+
padding: 20px;
|
|
98025
|
+
border-bottom: 1px solid #ddd;
|
|
98026
|
+
z-index: 10;
|
|
98027
|
+
}
|
|
98028
|
+
.is-modal.iconselect .icon-search {
|
|
98029
|
+
width: 100%;
|
|
98030
|
+
padding: 10px 15px;
|
|
98031
|
+
font-size: 16px;
|
|
98032
|
+
border: 1px solid #ccc;
|
|
98033
|
+
border-radius: 4px;
|
|
98034
|
+
box-sizing: border-box;
|
|
98035
|
+
}
|
|
98036
|
+
.is-modal.iconselect .icon-search:focus {
|
|
98037
|
+
outline: none;
|
|
98038
|
+
border-color: #007bff;
|
|
98039
|
+
}
|
|
98040
|
+
.is-modal.iconselect .icon-list {
|
|
98041
|
+
display: flex;
|
|
98042
|
+
gap: 10px;
|
|
98043
|
+
flex-flow: wrap;
|
|
98044
|
+
padding: 20px;
|
|
98045
|
+
}
|
|
98046
|
+
.is-modal.iconselect .icon-list > button {
|
|
98047
|
+
display: flex !important;
|
|
98048
|
+
width: 80px !important;
|
|
98049
|
+
height: 80px !important;
|
|
98050
|
+
flex: none;
|
|
98051
|
+
font-size: 30px !important;
|
|
98052
|
+
box-shadow: none !important;
|
|
98053
|
+
align-items: center;
|
|
98054
|
+
justify-content: center;
|
|
98055
|
+
}
|
|
98056
|
+
.is-modal.iconselect .icon-list > button.hidden {
|
|
98057
|
+
display: none !important;
|
|
98058
|
+
}
|
|
98059
|
+
.is-modal.iconselect .no-results {
|
|
98060
|
+
padding: 40px 20px;
|
|
98061
|
+
text-align: center;
|
|
98062
|
+
color: #666;
|
|
98063
|
+
display: none;
|
|
98064
|
+
}
|
|
98065
|
+
.is-modal.iconselect .no-results.show {
|
|
98066
|
+
display: block;
|
|
98067
|
+
}
|
|
98068
|
+
</style>
|
|
98069
|
+
<div class="is-modal iconselect" tabindex="-1" role="dialog" aria-modal="true" aria-hidden="true">
|
|
98070
|
+
<div class="is-modal-content scroll" style="width: 90vw; max-width: 908px; height: 70vh; min-height: 525px; padding: 0;">
|
|
98071
|
+
<div style="height: 100%; overflow-y: auto; display: flex; flex-direction: column;">
|
|
98072
|
+
<div class="icon-search-wrapper">
|
|
98073
|
+
<input type="text" class="icon-search" placeholder="Search icons... (e.g., alarm, wrench)" />
|
|
98074
|
+
</div>
|
|
98075
|
+
<div class="icon-list"></div>
|
|
98076
|
+
<div class="no-results">No icons found matching your search.</div>
|
|
98077
|
+
</div>
|
|
98078
|
+
</div>
|
|
98079
|
+
</div>
|
|
98080
|
+
`;
|
|
98081
|
+
this.dom.appendHtml(this.builderStuff, html);
|
|
98082
|
+
iconModal = this.builderStuff.querySelector('.is-modal.iconselect');
|
|
98083
|
+
this.iconModal = iconModal;
|
|
98084
|
+
const iconList = this.builderStuff.querySelector('.icon-list');
|
|
98085
|
+
const searchInput = this.builderStuff.querySelector('.icon-search');
|
|
98086
|
+
const noResults = this.builderStuff.querySelector('.no-results');
|
|
98087
|
+
iconList.innerHTML = this.rte.getIcons2() + this.rte.getIcons();
|
|
98088
|
+
let icons = iconList.querySelectorAll('button');
|
|
98089
|
+
|
|
98090
|
+
// Add click handlers for icons
|
|
98091
|
+
icons.forEach(icon => {
|
|
98092
|
+
icon.addEventListener('click', () => {
|
|
98093
|
+
// Get the current callback from the modal's data
|
|
98094
|
+
const currentCallback = iconModal._currentCallback;
|
|
98095
|
+
if (currentCallback) {
|
|
98096
|
+
currentCallback(icon.innerHTML);
|
|
98097
|
+
}
|
|
98098
|
+
this.util.hideModal(iconModal);
|
|
98099
|
+
});
|
|
98100
|
+
});
|
|
98101
|
+
|
|
98102
|
+
// Add search/filter functionality
|
|
98103
|
+
searchInput.addEventListener('input', e => {
|
|
98104
|
+
const searchTerm = e.target.value.toLowerCase().trim();
|
|
98105
|
+
let visibleCount = 0;
|
|
98106
|
+
icons.forEach(icon => {
|
|
98107
|
+
const iconElement = icon.querySelector('i');
|
|
98108
|
+
if (!iconElement) return;
|
|
98109
|
+
|
|
98110
|
+
// Get class names from the icon
|
|
98111
|
+
const iconClasses = iconElement.className.toLowerCase();
|
|
98112
|
+
|
|
98113
|
+
// Check if search term matches any part of the icon classes
|
|
98114
|
+
if (searchTerm === '' || iconClasses.includes(searchTerm)) {
|
|
98115
|
+
icon.classList.remove('hidden');
|
|
98116
|
+
visibleCount++;
|
|
98117
|
+
} else {
|
|
98118
|
+
icon.classList.add('hidden');
|
|
98119
|
+
}
|
|
98120
|
+
});
|
|
98121
|
+
|
|
98122
|
+
// Show/hide no results message
|
|
98123
|
+
if (visibleCount === 0 && searchTerm !== '') {
|
|
98124
|
+
noResults.classList.add('show');
|
|
98125
|
+
} else {
|
|
98126
|
+
noResults.classList.remove('show');
|
|
98127
|
+
}
|
|
98128
|
+
});
|
|
98129
|
+
|
|
98130
|
+
// Clear search when modal is opened
|
|
98131
|
+
iconModal.addEventListener('modalshow', () => {
|
|
98132
|
+
searchInput.value = '';
|
|
98133
|
+
searchInput.dispatchEvent(new Event('input'));
|
|
98134
|
+
});
|
|
98135
|
+
}
|
|
98136
|
+
|
|
98137
|
+
// Store the current callback and reset search
|
|
98138
|
+
iconModal._currentCallback = callback;
|
|
98139
|
+
const searchInput = iconModal.querySelector('.icon-search');
|
|
98140
|
+
if (searchInput) {
|
|
98141
|
+
searchInput.value = '';
|
|
98142
|
+
searchInput.dispatchEvent(new Event('input'));
|
|
98143
|
+
// Focus search input after a brief delay to ensure modal is visible
|
|
98144
|
+
setTimeout(() => searchInput.focus(), 100);
|
|
98145
|
+
}
|
|
98146
|
+
this.util.showModal(iconModal);
|
|
98147
|
+
}
|
|
97183
98148
|
colorpicker(onPick, defaultcolor) {
|
|
97184
98149
|
// return new ColorPicker({
|
|
97185
98150
|
// onPick: onPick,
|