@innovastudio/contentbox 1.6.203 → 1.6.205
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.
|
@@ -627,7 +627,14 @@ class CustomSelect$1 {
|
|
|
627
627
|
this.selectStyled.addEventListener('keydown', this.handleKeydown.bind(this));
|
|
628
628
|
this.selectStyled.addEventListener('click', this.toggleOptions.bind(this));
|
|
629
629
|
this.selectOptions.addEventListener('click', this.handleOptionClick.bind(this));
|
|
630
|
-
this.selectSearch.addEventListener('input', this.handleFilterInput.bind(this)); //
|
|
630
|
+
this.selectSearch.addEventListener('input', this.handleFilterInput.bind(this)); // Keep the content's selection: without an iframe the content shares this
|
|
631
|
+
// document, so a press here would move the selection into the panel and
|
|
632
|
+
// the chosen value would have no text to apply to.
|
|
633
|
+
|
|
634
|
+
const keepSelection = e => e.preventDefault();
|
|
635
|
+
|
|
636
|
+
this.selectStyled.addEventListener('mousedown', keepSelection);
|
|
637
|
+
this.selectOptions.addEventListener('mousedown', keepSelection); // this.selectStyled.addEventListener('mousedown', ()=>{
|
|
631
638
|
// this.select.classList.add('clicked');
|
|
632
639
|
// });
|
|
633
640
|
}
|
|
@@ -11471,7 +11478,7 @@ class Text {
|
|
|
11471
11478
|
if (!anchorNode) return false;
|
|
11472
11479
|
let container = anchorNode.nodeType !== Node.TEXT_NODE && anchorNode.nodeType !== Node.COMMENT_NODE ? anchorNode : anchorNode.parentElement;
|
|
11473
11480
|
|
|
11474
|
-
if (container && container.closest('.is-overlay') && this.builder.controlpanel.activeElement && !container.closest('[data-cb-type]')) {
|
|
11481
|
+
if (container && container.closest('.is-overlay') && this.builder.controlpanel.activeElement && this.builder.controlpanel.activeElement.isConnected && !container.closest('[data-cb-type]')) {
|
|
11475
11482
|
return this.builder.controlpanel.activeElement; // container => is-overlay when drag & drop bloxk
|
|
11476
11483
|
}
|
|
11477
11484
|
/*
|
|
@@ -11495,9 +11502,13 @@ class Text {
|
|
|
11495
11502
|
getSelection() works only if element selectable. If not, it uses previous selected element which is incorrect.
|
|
11496
11503
|
So we check if activeElement is different with getSelection() => means incorrect
|
|
11497
11504
|
*/
|
|
11505
|
+
// An edit can take the active element off the page (clearing a size
|
|
11506
|
+
// unwraps its span); the selection is then the right answer.
|
|
11498
11507
|
|
|
11499
11508
|
|
|
11500
|
-
|
|
11509
|
+
const activeElement = this.builder.controlpanel.activeElement;
|
|
11510
|
+
|
|
11511
|
+
if (!activeElement || !activeElement.isConnected || activeElement.contains(container)) ; else {
|
|
11501
11512
|
container = this.builder.controlpanel.activeElement; // If incorrect, uses activeElement
|
|
11502
11513
|
}
|
|
11503
11514
|
|
|
@@ -18781,6 +18792,11 @@ class PanelOverlay {
|
|
|
18781
18792
|
|
|
18782
18793
|
<div class="inp-text" role="textbox"></div>
|
|
18783
18794
|
|
|
18795
|
+
<div class="label mt-4">${out('Color')}:</div>
|
|
18796
|
+
<div class="group">
|
|
18797
|
+
<button type="button" title="${out('Color')}" class="btn-ovl-color cb-colorbtn"><span></span></button>
|
|
18798
|
+
</div>
|
|
18799
|
+
|
|
18784
18800
|
<div class="label-note mt-4 note-motion">${out('This text is part of an animated layer. Change the wording here; its movement lives on the Motion tab above.')}</div>
|
|
18785
18801
|
|
|
18786
18802
|
<div class="label-note mt-4 note-plain" style="display:none">${out('Change the wording here. Its placement and styling come from the section design.')}</div>
|
|
@@ -18954,6 +18970,38 @@ class PanelOverlay {
|
|
|
18954
18970
|
attrs: ['alt']
|
|
18955
18971
|
});
|
|
18956
18972
|
this.builder.editor.onChange();
|
|
18973
|
+
}); // --- Text colour ---
|
|
18974
|
+
//
|
|
18975
|
+
// Written inline on the selected element, so it overrides the design's
|
|
18976
|
+
// colour for this text only; clearing it in the picker returns the
|
|
18977
|
+
// colour it had when selected. One undo step per picker session.
|
|
18978
|
+
|
|
18979
|
+
this.origColors = new WeakMap();
|
|
18980
|
+
const btnColor = panel.querySelector('.btn-ovl-color');
|
|
18981
|
+
btnColor.addEventListener('click', () => {
|
|
18982
|
+
const el = this.target();
|
|
18983
|
+
if (!el) return;
|
|
18984
|
+
let fresh = true;
|
|
18985
|
+
const current = el.style.color || this.builder.win.getComputedStyle(el).color;
|
|
18986
|
+
const colorpicker = this.builder.editor.colorpicker();
|
|
18987
|
+
colorpicker.open(color => {
|
|
18988
|
+
const el = this.target();
|
|
18989
|
+
if (!el) return;
|
|
18990
|
+
|
|
18991
|
+
if (fresh) {
|
|
18992
|
+
this.builder.editor.saveForUndo();
|
|
18993
|
+
fresh = false;
|
|
18994
|
+
} // Cleared: back to the colour the text had when it was selected,
|
|
18995
|
+
// which may itself be inline (set by the design) or none.
|
|
18996
|
+
|
|
18997
|
+
|
|
18998
|
+
el.style.color = color || this.origColors.get(el) || '';
|
|
18999
|
+
syncToCodeSource(el, {
|
|
19000
|
+
styles: ['color']
|
|
19001
|
+
});
|
|
19002
|
+
this.paintColor(el);
|
|
19003
|
+
this.builder.editor.onChange();
|
|
19004
|
+
}, current, () => {}, btnColor, true); // overlay=true
|
|
18957
19005
|
}); // --- Text ---
|
|
18958
19006
|
|
|
18959
19007
|
this.textEditor = panel.querySelector('.inp-text'); // No formatting UI, deliberately: this panel edits AI-generated overlay
|
|
@@ -18996,6 +19044,16 @@ class PanelOverlay {
|
|
|
18996
19044
|
sanitize: false
|
|
18997
19045
|
});
|
|
18998
19046
|
});
|
|
19047
|
+
} // The swatch shows the colour the text has now: its own if set, otherwise
|
|
19048
|
+
// the one it gets from the design.
|
|
19049
|
+
|
|
19050
|
+
|
|
19051
|
+
paintColor(el) {
|
|
19052
|
+
const btn = this.panel.querySelector('.btn-ovl-color');
|
|
19053
|
+
if (!btn || !el) return;
|
|
19054
|
+
const color = el.style.color || this.builder.win.getComputedStyle(el).color;
|
|
19055
|
+
const editor = this.builder.editor;
|
|
19056
|
+
if (editor && typeof editor.paintColorButton === 'function') editor.paintColorButton(btn, color);else btn.style.backgroundColor = color;
|
|
18999
19057
|
}
|
|
19000
19058
|
|
|
19001
19059
|
escapeHtml(text) {
|
|
@@ -19327,6 +19385,9 @@ class PanelOverlay {
|
|
|
19327
19385
|
// it (which would move their caret to the end on every keystroke).
|
|
19328
19386
|
|
|
19329
19387
|
if (panel.ownerDocument.activeElement !== this.textEditor) {
|
|
19388
|
+
// Opening a plugin's panel tears down every panel editor
|
|
19389
|
+
// (destroyPanelEditors), this field included: bring it back.
|
|
19390
|
+
if (!this.ed || !this.textEditor.isContentEditable) this.setupTextEditor();
|
|
19330
19391
|
this._loadingText = true;
|
|
19331
19392
|
if (this.ed) this.ed.setHTML(el.innerHTML);else this.textEditor.innerHTML = el.innerHTML;
|
|
19332
19393
|
this._loadingText = false;
|
|
@@ -19342,6 +19403,8 @@ class PanelOverlay {
|
|
|
19342
19403
|
this.textEditor.style.fontWeight = computed.fontWeight;
|
|
19343
19404
|
this.textEditor.style.fontStyle = computed.fontStyle;
|
|
19344
19405
|
this.textEditor.style.textTransform = computed.textTransform;
|
|
19406
|
+
if (!this.origColors.has(el)) this.origColors.set(el, el.style.color);
|
|
19407
|
+
this.paintColor(el);
|
|
19345
19408
|
}
|
|
19346
19409
|
|
|
19347
19410
|
getStateSource(el) {
|
|
@@ -19454,7 +19517,7 @@ class PanelIcon {
|
|
|
19454
19517
|
<button title="${out('Color')}" class="btn-color is-btn-color"></button>
|
|
19455
19518
|
</div>
|
|
19456
19519
|
|
|
19457
|
-
<div class="label">${out('Icon Size')}:</div>
|
|
19520
|
+
<div class="label label-iconsize">${out('Icon Size')}:</div>
|
|
19458
19521
|
|
|
19459
19522
|
<div role="group" aria-label="${out('Font Size')}" class="group fontsizes">
|
|
19460
19523
|
${fontSizeHtml}
|
|
@@ -19517,20 +19580,17 @@ class PanelIcon {
|
|
|
19517
19580
|
btnSizes.forEach(btn => {
|
|
19518
19581
|
btn.addEventListener('click', () => {
|
|
19519
19582
|
this.builder.editor.saveForUndo();
|
|
19520
|
-
const num = btn.getAttribute('data-value');
|
|
19521
|
-
let currentFontSize = 1;
|
|
19522
|
-
let container = this.getElm();
|
|
19583
|
+
const num = btn.getAttribute('data-value'); // Size the icon itself, as ContentBuilder's own size buttons do for an icon.
|
|
19523
19584
|
|
|
19524
|
-
|
|
19525
|
-
|
|
19585
|
+
const icon = this.builder.controlpanel.activeElement;
|
|
19586
|
+
const rte = this.builder.editor.rte;
|
|
19587
|
+
|
|
19588
|
+
if ((this.builder.editor.opts.fontSizeClassValues || []).length > 0) {
|
|
19589
|
+
rte.applyClassFontSize(icon, num);
|
|
19590
|
+
} else {
|
|
19591
|
+
rte.applyInlineFontSize(icon, num);
|
|
19526
19592
|
}
|
|
19527
19593
|
|
|
19528
|
-
this.builder.editor.dom.execCommand('font-size', currentFontSize + 1 + 'px', (ok, container) => {
|
|
19529
|
-
if (ok) {
|
|
19530
|
-
this.builder.editor.rte.applyClassFontSize(container, num);
|
|
19531
|
-
container.style.fontSize = '';
|
|
19532
|
-
}
|
|
19533
|
-
});
|
|
19534
19594
|
this.getState();
|
|
19535
19595
|
this.builder.editor.onChange();
|
|
19536
19596
|
});
|
|
@@ -19568,7 +19628,13 @@ class PanelIcon {
|
|
|
19568
19628
|
getState() {
|
|
19569
19629
|
const panel = this.panel;
|
|
19570
19630
|
let icon = this.builder.controlpanel.activeElement;
|
|
19571
|
-
const div = icon.parentNode;
|
|
19631
|
+
const div = icon.parentNode; // An icon in overlay text: its link and alignment belong to the line (the Text panel),
|
|
19632
|
+
// and its size follows the design, like the text's. Only Change icon and Color are shown.
|
|
19633
|
+
|
|
19634
|
+
const inOverlayText = !!this.builder.controlpanel.overlayIcon(icon);
|
|
19635
|
+
panel.querySelectorAll('.btn-link, .label-iconsize, .group.fontsizes, .alignicon').forEach(elm => {
|
|
19636
|
+
elm.style.display = inOverlayText ? 'none' : '';
|
|
19637
|
+
});
|
|
19572
19638
|
const btnAligns = panel.querySelectorAll('[data-align]');
|
|
19573
19639
|
btnAligns.forEach(btn => btn.classList.remove('on'));
|
|
19574
19640
|
if (div.classList.contains('text-left')) panel.querySelector('[data-align="left"]').classList.add('on');
|
|
@@ -19619,14 +19685,6 @@ class PanelIcon {
|
|
|
19619
19685
|
}
|
|
19620
19686
|
}
|
|
19621
19687
|
|
|
19622
|
-
getElm() {
|
|
19623
|
-
const selection = this.builder.win.getSelection();
|
|
19624
|
-
const anchorNode = selection.anchorNode;
|
|
19625
|
-
if (!anchorNode) return false;
|
|
19626
|
-
const container = anchorNode.nodeType !== Node.TEXT_NODE && anchorNode.nodeType !== Node.COMMENT_NODE ? anchorNode : anchorNode.parentElement;
|
|
19627
|
-
return container;
|
|
19628
|
-
}
|
|
19629
|
-
|
|
19630
19688
|
}
|
|
19631
19689
|
|
|
19632
19690
|
class PanelSvg {
|
|
@@ -30004,7 +30062,7 @@ class PanelBox {
|
|
|
30004
30062
|
<button title="${out('Color')}" class="btn-color is-btn-color"></button>
|
|
30005
30063
|
</div>
|
|
30006
30064
|
</div>
|
|
30007
|
-
<div>
|
|
30065
|
+
<div class="div-box-gradient">
|
|
30008
30066
|
<div class="label mt-3">${out('Gradient')}:</div>
|
|
30009
30067
|
<div class="group">
|
|
30010
30068
|
<button title="${out('Gradient')}" class="btn-gradient is-btn-color"></button>
|
|
@@ -30074,10 +30132,9 @@ class PanelBox {
|
|
|
30074
30132
|
|
|
30075
30133
|
</div>
|
|
30076
30134
|
|
|
30077
|
-
<div class="submain box-
|
|
30078
|
-
|
|
30079
|
-
<
|
|
30080
|
-
|
|
30135
|
+
<div class="submain div-box-plugins" style="display:none">
|
|
30136
|
+
<div class="label mt-3">${out('In this box')}:</div>
|
|
30137
|
+
<div class="overlay-list mt-1"></div>
|
|
30081
30138
|
</div>
|
|
30082
30139
|
|
|
30083
30140
|
<button class="accordion-item btn-accordion-content" aria-expanded="false" aria-controls="boxcontent1">
|
|
@@ -31134,6 +31191,13 @@ class PanelBox {
|
|
|
31134
31191
|
|
|
31135
31192
|
static css() {
|
|
31136
31193
|
return `
|
|
31194
|
+
.cb-navbar-box .boxedit,
|
|
31195
|
+
.cb-navbar-box .div-box-gradient,
|
|
31196
|
+
.cb-navbar-box .box-bg-image,
|
|
31197
|
+
.cb-navbar-box > .accordion-item,
|
|
31198
|
+
.cb-navbar-box > .accordion-content {
|
|
31199
|
+
display: none !important;
|
|
31200
|
+
}
|
|
31137
31201
|
[data-cb-ui] .cb-contentpos .cb-fieldhead {
|
|
31138
31202
|
display: flex;
|
|
31139
31203
|
align-items: baseline;
|
|
@@ -31445,7 +31509,10 @@ class PanelBox {
|
|
|
31445
31509
|
const overlayBg = activeBox.querySelector('.is-overlay-bg');
|
|
31446
31510
|
const overlayContent = activeBox.querySelector('.is-overlay-content');
|
|
31447
31511
|
const container = this.box.contentContainer(activeBox);
|
|
31448
|
-
const panel = this.panel;
|
|
31512
|
+
const panel = this.panel; // A navigation bar keeps only its background colour (the bar's colour once the
|
|
31513
|
+
// header has scrolled by): no duplicate, extra box, gradient, image or box tabs
|
|
31514
|
+
|
|
31515
|
+
panel.classList.toggle('cb-navbar-box', !!activeBox.closest('.is-section-navbar'));
|
|
31449
31516
|
const divEditModule = panel.querySelector('.group.editmodule');
|
|
31450
31517
|
|
|
31451
31518
|
if (overlayContent && overlayContent.getAttribute('data-module')) {
|
|
@@ -31789,12 +31856,12 @@ class PanelBox {
|
|
|
31789
31856
|
chkAutoLayout.checked = true;
|
|
31790
31857
|
} else {
|
|
31791
31858
|
chkAutoLayout.checked = false;
|
|
31792
|
-
} //
|
|
31859
|
+
} // PLUGINS
|
|
31860
|
+
// A plugin in the box overlay has its own panel: a click on it opens that.
|
|
31861
|
+
// Here the box lists them, and each can hide box settings it has no use for.
|
|
31793
31862
|
|
|
31794
31863
|
|
|
31795
|
-
const
|
|
31796
|
-
this.divPluginSettings = divPluginSettings;
|
|
31797
|
-
const runtime = this.builder.win.builderRuntime; // Reset (can be changed by renderPluginSettings)
|
|
31864
|
+
const runtime = this.builder.win.builderRuntime; // Reset (can be changed by applyPluginOptions)
|
|
31798
31865
|
|
|
31799
31866
|
const divBgColor = this.panel.querySelector('.box-bg-color');
|
|
31800
31867
|
divBgColor.style.display = '';
|
|
@@ -31805,39 +31872,55 @@ class PanelBox {
|
|
|
31805
31872
|
const divPanelTitle = this.panel.parentNode.querySelector('.part-title');
|
|
31806
31873
|
divPanelTitle.style.display = '';
|
|
31807
31874
|
this.divPanelTitle = divPanelTitle;
|
|
31875
|
+
const divPlugins = panel.querySelector('.div-box-plugins');
|
|
31876
|
+
const list = divPlugins.querySelector('.overlay-list');
|
|
31877
|
+
list.innerHTML = ''; // The box's own plugins: not the ones inside its content areas, nor inside another plugin
|
|
31808
31878
|
|
|
31809
|
-
|
|
31810
|
-
const currentElement = overlayContent.querySelector('[data-cb-type]');
|
|
31879
|
+
const plugins = overlayContent && runtime ? Array.from(overlayContent.querySelectorAll('[data-cb-type]')).filter(el => !el.closest('.is-container') && !(el.parentElement && el.parentElement.closest('[data-cb-type]'))) : []; // Several of the same plugin are numbered, so their rows can be told apart
|
|
31811
31880
|
|
|
31812
|
-
|
|
31813
|
-
|
|
31814
|
-
|
|
31815
|
-
|
|
31816
|
-
|
|
31817
|
-
|
|
31818
|
-
|
|
31819
|
-
|
|
31820
|
-
|
|
31821
|
-
|
|
31881
|
+
const counts = {},
|
|
31882
|
+
seen = {};
|
|
31883
|
+
plugins.forEach(el => {
|
|
31884
|
+
const t = el.getAttribute('data-cb-type');
|
|
31885
|
+
counts[t] = (counts[t] || 0) + 1;
|
|
31886
|
+
});
|
|
31887
|
+
plugins.forEach(el => {
|
|
31888
|
+
const type = el.getAttribute('data-cb-type');
|
|
31889
|
+
const plugin = runtime.getPlugin(type);
|
|
31890
|
+
if (!plugin) return;
|
|
31891
|
+
seen[type] = (seen[type] || 0) + 1;
|
|
31892
|
+
this.applyPluginOptions(el, plugin);
|
|
31893
|
+
const row = this.panel.ownerDocument.createElement('button');
|
|
31894
|
+
row.type = 'button';
|
|
31895
|
+
row.className = 'ovl-row';
|
|
31896
|
+
const label = this.panel.ownerDocument.createElement('span');
|
|
31897
|
+
label.className = 'ovl-row-text';
|
|
31898
|
+
label.textContent = out(plugin.displayName || plugin.name) + (counts[type] > 1 ? ' ' + seen[type] : '');
|
|
31899
|
+
row.appendChild(label);
|
|
31900
|
+
row.addEventListener('click', () => {
|
|
31901
|
+
this.builder.controlpanel.markOverlayHover(null);
|
|
31902
|
+
this.builder.controlpanel.selectOverlayElement(el);
|
|
31903
|
+
});
|
|
31904
|
+
row.addEventListener('mouseenter', () => this.builder.controlpanel.markOverlayHover(el));
|
|
31905
|
+
row.addEventListener('mouseleave', () => this.builder.controlpanel.markOverlayHover(null));
|
|
31906
|
+
list.appendChild(row);
|
|
31907
|
+
});
|
|
31908
|
+
divPlugins.style.display = list.childElementCount ? '' : 'none';
|
|
31909
|
+
} // True when the plugin element fills (practically) the whole box overlay.
|
|
31822
31910
|
|
|
31823
|
-
renderPluginSettings(currentElement) {
|
|
31824
|
-
// Same panel lifecycle as panel-plugin.js / ContentBuilder's
|
|
31825
|
-
// elementplugin.js: destroy the previous render's panel editors before
|
|
31826
|
-
// their elements go away, and publish the plugin being edited.
|
|
31827
|
-
this.builder.destroyPanelEditors();
|
|
31828
|
-
this.builder.activePlugin = currentElement;
|
|
31829
|
-
this.currentElement = currentElement;
|
|
31830
|
-
this.generator = null;
|
|
31831
|
-
this.currentForm = null;
|
|
31832
|
-
const divPluginSettings = this.divPluginSettings;
|
|
31833
|
-
divPluginSettings.innerHTML = '';
|
|
31834
|
-
const runtime = this.builder.win.builderRuntime;
|
|
31835
|
-
const pluginName = currentElement.getAttribute('data-cb-type');
|
|
31836
|
-
const plugin = runtime.getPlugin(pluginName);
|
|
31837
|
-
if (!plugin) return;
|
|
31838
|
-
let displayName = out(plugin.displayName || plugin.name);
|
|
31839
31911
|
|
|
31840
|
-
|
|
31912
|
+
pluginCoversBox(element) {
|
|
31913
|
+
const overlay = element.closest('.is-overlay');
|
|
31914
|
+
if (!overlay) return true;
|
|
31915
|
+
const box = overlay.getBoundingClientRect();
|
|
31916
|
+
const el = element.getBoundingClientRect();
|
|
31917
|
+
if (!box.width || !box.height) return true;
|
|
31918
|
+
return el.width >= box.width * 0.98 && el.height >= box.height * 0.98;
|
|
31919
|
+
} // The box settings a plugin in this box has no use for (its contentbox options)
|
|
31920
|
+
|
|
31921
|
+
|
|
31922
|
+
applyPluginOptions(currentElement, plugin) {
|
|
31923
|
+
if (plugin.contentbox && currentElement.querySelector('.is-container')) {
|
|
31841
31924
|
// if plugin element has editable content
|
|
31842
31925
|
plugin.contentbox.boxTabText = false;
|
|
31843
31926
|
plugin.contentbox.boxTabContent = false;
|
|
@@ -31860,16 +31943,21 @@ class PanelBox {
|
|
|
31860
31943
|
|
|
31861
31944
|
|
|
31862
31945
|
if (plugin.contentbox) {
|
|
31863
|
-
|
|
31946
|
+
// A plugin hides the box background (bg: false) because it covers it,
|
|
31947
|
+
// like a full-bleed video. When the plugin only fills part of the box
|
|
31948
|
+
// (a framed video, a card), the background is visible and stays editable.
|
|
31949
|
+
const coversBox = this.pluginCoversBox(currentElement);
|
|
31950
|
+
|
|
31951
|
+
if (coversBox && plugin.contentbox.bg !== undefined && plugin.contentbox.bg !== null && plugin.contentbox.bg === false) {
|
|
31864
31952
|
this.divBgColor.style.display = 'none';
|
|
31865
31953
|
this.divBgImage.style.display = 'none';
|
|
31866
31954
|
}
|
|
31867
31955
|
|
|
31868
|
-
if (plugin.contentbox.bgColor !== undefined && plugin.contentbox.bgColor !== null && plugin.contentbox.bgColor === false) {
|
|
31956
|
+
if (coversBox && plugin.contentbox.bgColor !== undefined && plugin.contentbox.bgColor !== null && plugin.contentbox.bgColor === false) {
|
|
31869
31957
|
this.divBgColor.style.display = 'none';
|
|
31870
31958
|
}
|
|
31871
31959
|
|
|
31872
|
-
if (plugin.contentbox.bgImage !== undefined && plugin.contentbox.bgImage !== null && plugin.contentbox.bgImage === false) {
|
|
31960
|
+
if (coversBox && plugin.contentbox.bgImage !== undefined && plugin.contentbox.bgImage !== null && plugin.contentbox.bgImage === false) {
|
|
31873
31961
|
this.divBgImage.style.display = 'none';
|
|
31874
31962
|
}
|
|
31875
31963
|
|
|
@@ -31941,111 +32029,6 @@ class PanelBox {
|
|
|
31941
32029
|
});
|
|
31942
32030
|
}
|
|
31943
32031
|
}
|
|
31944
|
-
|
|
31945
|
-
const divTitle = document.createElement('h3');
|
|
31946
|
-
divTitle.style.cssText = 'font-family: sans-serif;font-weight: 300;font-size: 20px;margin: 0 0 12px;';
|
|
31947
|
-
divTitle.innerText = displayName;
|
|
31948
|
-
divPluginSettings.appendChild(divTitle);
|
|
31949
|
-
this.generator = new SettingsUIGenerator$1(runtime, this.builder);
|
|
31950
|
-
const hasContentEditor = plugin && plugin.editor && plugin.editor.openContentEditor;
|
|
31951
|
-
|
|
31952
|
-
if (hasContentEditor) {
|
|
31953
|
-
// Get original content for the editor to work with
|
|
31954
|
-
const originalContent = currentElement.getAttribute('data-cb-original-content'); // Create a temporary element for editing (so editor can manipulate it)
|
|
31955
|
-
|
|
31956
|
-
let editableClone = document.querySelector('.editable-clone');
|
|
31957
|
-
if (editableClone) editableClone.remove(); // editableClone = document.createElement('div');
|
|
31958
|
-
|
|
31959
|
-
editableClone = currentElement.cloneNode(false);
|
|
31960
|
-
editableClone.innerHTML = originalContent;
|
|
31961
|
-
editableClone.className = 'editable-clone';
|
|
31962
|
-
editableClone.style.display = 'none'; // Hidden, just for editing
|
|
31963
|
-
|
|
31964
|
-
document.body.appendChild(editableClone);
|
|
31965
|
-
const originalElement = editableClone.cloneNode(false); // Let plugin handle everything - pass the editable clone
|
|
31966
|
-
|
|
31967
|
-
const editorUI = plugin.editor.openContentEditor(editableClone, this.builder, async () => {
|
|
31968
|
-
this.builder.editor.saveForUndo(); // Do the same process like reinitialize, needed for newly added item (which requires data-cb-original-content update)
|
|
31969
|
-
// Add index for sortable grid (for easy reorder)
|
|
31970
|
-
|
|
31971
|
-
let grid = editableClone.querySelector('.grid-sortable');
|
|
31972
|
-
|
|
31973
|
-
if (!grid) {
|
|
31974
|
-
if (currentElement.classList.contains('grid-sortable')) {
|
|
31975
|
-
grid = editableClone;
|
|
31976
|
-
}
|
|
31977
|
-
}
|
|
31978
|
-
|
|
31979
|
-
if (grid) {
|
|
31980
|
-
Array.from(grid.children).forEach((child, index) => {
|
|
31981
|
-
if (child.nodeType === 1 && child.tagName !== 'STYLE' && child.tagName !== 'SCRIPT') child.setAttribute('data-index', index);
|
|
31982
|
-
});
|
|
31983
|
-
} //----
|
|
31984
|
-
// Add data-edit-id for editable area
|
|
31985
|
-
|
|
31986
|
-
|
|
31987
|
-
let editables = editableClone.querySelectorAll('.edit');
|
|
31988
|
-
editables.forEach((area, index) => {
|
|
31989
|
-
const id = `content-${Date.now()}-${index}`;
|
|
31990
|
-
area.setAttribute('data-edit-id', id);
|
|
31991
|
-
}); //-----
|
|
31992
|
-
// Store edited content from clone
|
|
31993
|
-
|
|
31994
|
-
currentElement.setAttribute('data-cb-original-content', editableClone.innerHTML); // Also performs similar value updates like the applyChanges()
|
|
31995
|
-
// currentElement.setAttribute('data-cb-content', editableClone.getAttribute('data-cb-content'));
|
|
31996
|
-
|
|
31997
|
-
const getChangedDataAttributes = (el1, el2) => {
|
|
31998
|
-
const changed = [];
|
|
31999
|
-
|
|
32000
|
-
for (const key of Object.keys(el1.dataset)) {
|
|
32001
|
-
if (key in el2.dataset && el1.dataset[key] !== el2.dataset[key]) {
|
|
32002
|
-
changed.push(key);
|
|
32003
|
-
}
|
|
32004
|
-
}
|
|
32005
|
-
|
|
32006
|
-
return changed;
|
|
32007
|
-
};
|
|
32008
|
-
|
|
32009
|
-
const changedAttributes = getChangedDataAttributes(originalElement, editableClone);
|
|
32010
|
-
changedAttributes.forEach(attrName => {
|
|
32011
|
-
// console.log(attrName)
|
|
32012
|
-
// console.log(editableClone.dataset[attrName])
|
|
32013
|
-
currentElement.dataset[attrName] = editableClone.dataset[attrName];
|
|
32014
|
-
}); // -------
|
|
32015
|
-
|
|
32016
|
-
currentElement.innerHTML = editableClone.innerHTML; // Update current content
|
|
32017
|
-
// Remove temporary clone
|
|
32018
|
-
|
|
32019
|
-
editableClone.remove(); // Reinitialize plugin with new content
|
|
32020
|
-
|
|
32021
|
-
await runtime.reinitialize(currentElement.parentElement);
|
|
32022
|
-
this.builder.onChange();
|
|
32023
|
-
this.builder.editor.hideElementTools();
|
|
32024
|
-
this.builder.makeSortable(currentElement.parentElement);
|
|
32025
|
-
});
|
|
32026
|
-
divPluginSettings.appendChild(editorUI);
|
|
32027
|
-
} // Generate form
|
|
32028
|
-
|
|
32029
|
-
|
|
32030
|
-
this.currentForm = this.generator.generateForm(pluginName, currentElement, () => {
|
|
32031
|
-
this.builder.editor.saveForUndo();
|
|
32032
|
-
this.applyChanges(runtime);
|
|
32033
|
-
});
|
|
32034
|
-
divPluginSettings.appendChild(this.currentForm);
|
|
32035
|
-
}
|
|
32036
|
-
|
|
32037
|
-
async applyChanges(runtime) {
|
|
32038
|
-
if (!this.currentElement || !this.currentForm) return; // 1. Get form values
|
|
32039
|
-
|
|
32040
|
-
const values = this.generator.getFormValues(this.currentForm); // 2. Apply to element attributes
|
|
32041
|
-
|
|
32042
|
-
this.generator.applyValues(this.currentElement, values); // 3. Reinitialize component
|
|
32043
|
-
|
|
32044
|
-
const container = this.currentElement.parentElement || this.currentElement.closest('.is-wrapper');
|
|
32045
|
-
await runtime.reinitialize(container);
|
|
32046
|
-
this.builder.onChange(); // console.log('Settings applied and component reinitialized');
|
|
32047
|
-
|
|
32048
|
-
this.builder.makeSortable(container);
|
|
32049
32032
|
}
|
|
32050
32033
|
|
|
32051
32034
|
getStateTargeted() {
|
|
@@ -33713,6 +33696,40 @@ class PanelSection {
|
|
|
33713
33696
|
|
|
33714
33697
|
</div>
|
|
33715
33698
|
|
|
33699
|
+
<button class="accordion-item btn-accordion-bar" aria-expanded="false" aria-controls="sectioncontent5">
|
|
33700
|
+
${out('Navigation Bar')}
|
|
33701
|
+
<span><svg><use xlink:href="#icon-chevron-down"></use></svg></span>
|
|
33702
|
+
</button>
|
|
33703
|
+
<div class="accordion-content" id="sectioncontent5" aria-hidden="true">
|
|
33704
|
+
|
|
33705
|
+
<div class="subpanel">
|
|
33706
|
+
<div class="barstart-row">
|
|
33707
|
+
<div class="label mt-2">${out('Bar at the start')}:</div>
|
|
33708
|
+
<div role="group" aria-label="${out('Bar at the start')}" class="group sectionbar barstart">
|
|
33709
|
+
<button title="${out('Auto')}" data-barstart="">${out('Auto')}</button>
|
|
33710
|
+
<button title="${out('Light')}" data-barstart="light">${out('Light')}</button>
|
|
33711
|
+
<button title="${out('Dark')}" data-barstart="dark">${out('Dark')}</button>
|
|
33712
|
+
</div>
|
|
33713
|
+
</div>
|
|
33714
|
+
|
|
33715
|
+
<div class="label mt-2">${out('Bar over this section')}:</div>
|
|
33716
|
+
<div role="group" aria-label="${out('Bar over this section')}" class="group sectionbar">
|
|
33717
|
+
<button title="${out('Default')}" data-bar="">${out('Default')}</button>
|
|
33718
|
+
<button title="${out('Light')}" data-bar="light">${out('Light')}</button>
|
|
33719
|
+
<button title="${out('Dark')}" data-bar="dark">${out('Dark')}</button>
|
|
33720
|
+
<button title="${out('Light glass')}" data-bar="light-glass">${out('Light glass')}</button>
|
|
33721
|
+
<button title="${out('Dark glass')}" data-bar="dark-glass">${out('Dark glass')}</button>
|
|
33722
|
+
</div>
|
|
33723
|
+
|
|
33724
|
+
<div class="label mt-3">${out('Text Color')}:</div>
|
|
33725
|
+
<div class="group">
|
|
33726
|
+
<button title="${out('Text Color')}" class="btn-barink is-btn-color"></button>
|
|
33727
|
+
<button title="${out('Clear')}" class="btn-clear btn-barink-clear"><svg><use xlink:href="#icon-eraser"></use></svg></button>
|
|
33728
|
+
</div>
|
|
33729
|
+
</div>
|
|
33730
|
+
|
|
33731
|
+
</div>
|
|
33732
|
+
|
|
33716
33733
|
<button class="accordion-item" aria-expanded="false" aria-controls="sectioncontent3">
|
|
33717
33734
|
${out('Responsive')}
|
|
33718
33735
|
<span><svg><use xlink:href="#icon-chevron-down"></use></svg></span>
|
|
@@ -33904,6 +33921,54 @@ class PanelSection {
|
|
|
33904
33921
|
btnScrollPreset.forEach(btn => btn.classList.remove('on'));
|
|
33905
33922
|
if (s !== '') btn.classList.add('on');
|
|
33906
33923
|
});
|
|
33924
|
+
}); // Navigation Bar: how the page's navigation bar looks while it passes over this
|
|
33925
|
+
// section. The section only says it (data-bar, data-bar-ink); the menu in the bar
|
|
33926
|
+
// does the work.
|
|
33927
|
+
|
|
33928
|
+
const btnBarLooks = panel.querySelectorAll('[data-bar]');
|
|
33929
|
+
btnBarLooks.forEach(btn => {
|
|
33930
|
+
btn.addEventListener('click', () => {
|
|
33931
|
+
this.builder.editor.saveForUndo();
|
|
33932
|
+
const section = this.builder.activeSection;
|
|
33933
|
+
const s = btn.getAttribute('data-bar');
|
|
33934
|
+
if (s) section.setAttribute('data-bar', s);else section.removeAttribute('data-bar');
|
|
33935
|
+
btnBarLooks.forEach(btn => btn.classList.remove('on'));
|
|
33936
|
+
btn.classList.add('on');
|
|
33937
|
+
this.builder.onChange();
|
|
33938
|
+
});
|
|
33939
|
+
}); // Bar at the start: the bar's text while it rests over the top section, at the top of
|
|
33940
|
+
// the page (data-bar-start). Only the top section has it. Auto: no attribute.
|
|
33941
|
+
|
|
33942
|
+
const btnBarStarts = panel.querySelectorAll('[data-barstart]');
|
|
33943
|
+
btnBarStarts.forEach(btn => {
|
|
33944
|
+
btn.addEventListener('click', () => {
|
|
33945
|
+
this.builder.editor.saveForUndo();
|
|
33946
|
+
const section = this.builder.activeSection;
|
|
33947
|
+
const s = btn.getAttribute('data-barstart');
|
|
33948
|
+
if (s) section.setAttribute('data-bar-start', s);else section.removeAttribute('data-bar-start');
|
|
33949
|
+
btnBarStarts.forEach(btn => btn.classList.remove('on'));
|
|
33950
|
+
btn.classList.add('on');
|
|
33951
|
+
this.builder.onChange();
|
|
33952
|
+
});
|
|
33953
|
+
});
|
|
33954
|
+
const btnBarInk = panel.querySelector('.btn-barink');
|
|
33955
|
+
btnBarInk.addEventListener('click', () => {
|
|
33956
|
+
this.builder.editor.saveForUndo(true); // checkLater = true
|
|
33957
|
+
|
|
33958
|
+
const section = this.builder.activeSection;
|
|
33959
|
+
const colorpicker = this.builder.editor.colorpicker();
|
|
33960
|
+
colorpicker.open(s => {
|
|
33961
|
+
if (s) section.setAttribute('data-bar-ink', s);else section.removeAttribute('data-bar-ink');
|
|
33962
|
+
btnBarInk.style.backgroundColor = s; // preview
|
|
33963
|
+
|
|
33964
|
+
this.builder.onChange();
|
|
33965
|
+
}, btnBarInk.style.backgroundColor, () => {}, btnBarInk, true);
|
|
33966
|
+
});
|
|
33967
|
+
panel.querySelector('.btn-barink-clear').addEventListener('click', () => {
|
|
33968
|
+
this.builder.editor.saveForUndo();
|
|
33969
|
+
this.builder.activeSection.removeAttribute('data-bar-ink');
|
|
33970
|
+
btnBarInk.style.backgroundColor = '';
|
|
33971
|
+
this.builder.onChange();
|
|
33907
33972
|
}); // Responsive
|
|
33908
33973
|
|
|
33909
33974
|
bindDeviceRow(panel.querySelector('.div-target'), this.builder);
|
|
@@ -34142,6 +34207,35 @@ class PanelSection {
|
|
|
34142
34207
|
btnGradient.style.backgroundImage = gradient; //preview
|
|
34143
34208
|
} else {
|
|
34144
34209
|
btnGradient.style.backgroundImage = '';
|
|
34210
|
+
} // A navigation bar section: no height presets and no section tabs
|
|
34211
|
+
|
|
34212
|
+
|
|
34213
|
+
panel.classList.toggle('cb-navbar-section', activeSection.classList.contains('is-section-navbar')); // Navigation Bar: only when the page's bar holds a plugin that reads the looks
|
|
34214
|
+
// (one that says barLooks: true)
|
|
34215
|
+
|
|
34216
|
+
const btnAccordionBar = panel.querySelector('.btn-accordion-bar');
|
|
34217
|
+
const runtime = this.builder.win.builderRuntime;
|
|
34218
|
+
const barPlugins = this.builder.wrapperEl ? Array.from(this.builder.wrapperEl.querySelectorAll('.is-section-navbar [data-cb-type]')) : [];
|
|
34219
|
+
const pageBar = !!runtime && barPlugins.some(el => {
|
|
34220
|
+
const plugin = runtime.getPlugin(el.getAttribute('data-cb-type'));
|
|
34221
|
+
return !!(plugin && plugin.barLooks);
|
|
34222
|
+
});
|
|
34223
|
+
const showBar = pageBar && !activeSection.classList.contains('is-section-navbar');
|
|
34224
|
+
btnAccordionBar.classList.toggle('is-bar-off', !showBar);
|
|
34225
|
+
|
|
34226
|
+
if (showBar) {
|
|
34227
|
+
const look = activeSection.getAttribute('data-bar') || '';
|
|
34228
|
+
panel.querySelectorAll('[data-bar]').forEach(btn => btn.classList.toggle('on', btn.getAttribute('data-bar') === look));
|
|
34229
|
+
panel.querySelector('.btn-barink').style.backgroundColor = activeSection.getAttribute('data-bar-ink') || ''; // Bar at the start: the top section only (the first section under the bar)
|
|
34230
|
+
|
|
34231
|
+
const top = Array.from(this.builder.wrapperEl.querySelectorAll('.is-section')).find(s => !s.closest('.is-section-navbar'));
|
|
34232
|
+
const isTop = top === activeSection;
|
|
34233
|
+
panel.querySelector('.barstart-row').style.display = isTop ? '' : 'none';
|
|
34234
|
+
|
|
34235
|
+
if (isTop) {
|
|
34236
|
+
const start = activeSection.getAttribute('data-bar-start') || '';
|
|
34237
|
+
panel.querySelectorAll('[data-barstart]').forEach(btn => btn.classList.toggle('on', btn.getAttribute('data-barstart') === start));
|
|
34238
|
+
}
|
|
34145
34239
|
}
|
|
34146
34240
|
|
|
34147
34241
|
this.realtimeVisibility(activeSection, true);
|
|
@@ -34246,6 +34340,13 @@ class PanelSection {
|
|
|
34246
34340
|
|
|
34247
34341
|
static css() {
|
|
34248
34342
|
return `
|
|
34343
|
+
.btn-accordion-bar.is-bar-off,
|
|
34344
|
+
.btn-accordion-bar.is-bar-off + .accordion-content ,
|
|
34345
|
+
.cb-navbar-section .div-section-height,
|
|
34346
|
+
.cb-navbar-section > .accordion-item,
|
|
34347
|
+
.cb-navbar-section > .accordion-content {
|
|
34348
|
+
display: none !important;
|
|
34349
|
+
}
|
|
34249
34350
|
.cb-sectionanim .cb-anim-note {
|
|
34250
34351
|
font-size: 11.5px;
|
|
34251
34352
|
line-height: 1.5;
|
|
@@ -43492,6 +43593,18 @@ class ControlPanel {
|
|
|
43492
43593
|
// elmTool, whose duplicate/delete/move actions would let a user
|
|
43493
43594
|
// restructure a generated layer.
|
|
43494
43595
|
this.builder.editor.inspectedElement = overlayTarget.element;
|
|
43596
|
+
} else if (this.overlayIcon(e.target)) {
|
|
43597
|
+
// An icon in overlay text opens the Icon panel.
|
|
43598
|
+
this.builder.editor.inspectedElement = e.target;
|
|
43599
|
+
} else {
|
|
43600
|
+
// A plugin in the box overlay opens its own panel (Section > Box > the
|
|
43601
|
+
// plugin), like overlay text. Its editable parts and content areas are
|
|
43602
|
+
// edited where they are.
|
|
43603
|
+
const plugin = e.target.closest ? e.target.closest('.is-overlay-content [data-cb-type]') : null;
|
|
43604
|
+
|
|
43605
|
+
if (plugin && !e.target.closest('.is-container') && !e.target.closest('.is-subblock')) {
|
|
43606
|
+
this.builder.editor.inspectedElement = plugin;
|
|
43607
|
+
}
|
|
43495
43608
|
}
|
|
43496
43609
|
|
|
43497
43610
|
if (old) old.call(this);
|
|
@@ -43636,8 +43749,92 @@ class ControlPanel {
|
|
|
43636
43749
|
this.builder.editor.controlpanel = this; // This overide ContentBuilder controlpanel object
|
|
43637
43750
|
|
|
43638
43751
|
this.bindEscape();
|
|
43752
|
+
this.bindLayerPassThrough();
|
|
43639
43753
|
this.showTooltip();
|
|
43640
43754
|
}
|
|
43755
|
+
/**
|
|
43756
|
+
* Clicks go through overlay layers that are faded out.
|
|
43757
|
+
*
|
|
43758
|
+
* A crossfade keeps every layer stacked at full size and only turns the
|
|
43759
|
+
* hidden ones transparent, so a click on the text you can see lands on an
|
|
43760
|
+
* invisible layer above it: an empty wrapper gets selected, or the caret
|
|
43761
|
+
* goes into text that is not on screen. hitTest() already sees through
|
|
43762
|
+
* these for the overlay panel; this does the same for the browser's own
|
|
43763
|
+
* hit test, which is what places the caret in a .is-container.
|
|
43764
|
+
*
|
|
43765
|
+
* Marks only exist under the pointer and are recomputed on every move and
|
|
43766
|
+
* scroll, so a layer that fades back in is clickable again. A faded layer
|
|
43767
|
+
* is only passed through when something visible lies below it; a stack
|
|
43768
|
+
* scrubbed to a moment where everything is faded stays selectable.
|
|
43769
|
+
* The marker is stripped from the saved HTML in html().
|
|
43770
|
+
*/
|
|
43771
|
+
|
|
43772
|
+
|
|
43773
|
+
bindLayerPassThrough() {
|
|
43774
|
+
if (!this.passThroughHandler) {
|
|
43775
|
+
let frame = 0;
|
|
43776
|
+
let x = -1,
|
|
43777
|
+
y = -1;
|
|
43778
|
+
|
|
43779
|
+
const update = () => {
|
|
43780
|
+
frame = 0;
|
|
43781
|
+
this.updateLayerPassThrough(x, y);
|
|
43782
|
+
};
|
|
43783
|
+
|
|
43784
|
+
this.passThroughHandler = e => {
|
|
43785
|
+
if (typeof e.clientX === 'number') {
|
|
43786
|
+
x = e.clientX;
|
|
43787
|
+
y = e.clientY;
|
|
43788
|
+
}
|
|
43789
|
+
|
|
43790
|
+
if (e.type === 'pointerdown') {
|
|
43791
|
+
// a touch has no move before it
|
|
43792
|
+
update();
|
|
43793
|
+
return;
|
|
43794
|
+
}
|
|
43795
|
+
|
|
43796
|
+
if (!frame) frame = this.builder.win.requestAnimationFrame(update);
|
|
43797
|
+
};
|
|
43798
|
+
}
|
|
43799
|
+
|
|
43800
|
+
const types = ['pointermove', 'pointerdown', 'scroll'];
|
|
43801
|
+
if (this.passThroughDoc) types.forEach(type => this.passThroughDoc.removeEventListener(type, this.passThroughHandler, true));
|
|
43802
|
+
this.passThroughDoc = this.builder.doc;
|
|
43803
|
+
if (this.passThroughDoc) types.forEach(type => this.passThroughDoc.addEventListener(type, this.passThroughHandler, true));
|
|
43804
|
+
}
|
|
43805
|
+
|
|
43806
|
+
updateLayerPassThrough(x, y) {
|
|
43807
|
+
const doc = this.builder.doc;
|
|
43808
|
+
if (!doc || !doc.elementsFromPoint) return;
|
|
43809
|
+
doc.querySelectorAll('[data-overlay-passthrough]').forEach(elm => {
|
|
43810
|
+
elm.removeAttribute('data-overlay-passthrough');
|
|
43811
|
+
});
|
|
43812
|
+
if (x < 0) return;
|
|
43813
|
+
const selector = this.builder.overlayEditableSelector;
|
|
43814
|
+
const stack = doc.elementsFromPoint(x, y);
|
|
43815
|
+
stack.forEach((el, i) => {
|
|
43816
|
+
const overlay = el.closest(selector); // Faded counts through the ancestors: text in a layer that is
|
|
43817
|
+
// faded out as a whole is painted itself, yet invisible.
|
|
43818
|
+
|
|
43819
|
+
if (!overlay || el === overlay || this.isVisibleIn(el, overlay)) return;
|
|
43820
|
+
const covered = stack.slice(i + 1).some(below => overlay.contains(below) && below !== overlay && !el.contains(below) && this.isLayerContent(below) && this.isVisibleIn(below, overlay));
|
|
43821
|
+
if (covered) el.setAttribute('data-overlay-passthrough', '');
|
|
43822
|
+
});
|
|
43823
|
+
} // Something a click is meant for: text, a picture, or a media element.
|
|
43824
|
+
|
|
43825
|
+
|
|
43826
|
+
isLayerContent(el) {
|
|
43827
|
+
return this.builder.hasOwnText(el) || el.matches('img,video,iframe,svg,a,button') || this.hasBackgroundImage(el);
|
|
43828
|
+
} // Painted itself and through every ancestor up to the overlay.
|
|
43829
|
+
|
|
43830
|
+
|
|
43831
|
+
isVisibleIn(el, overlay) {
|
|
43832
|
+
for (let node = el; node && node !== overlay; node = node.parentElement) {
|
|
43833
|
+
if (!this.isPainted(node)) return false;
|
|
43834
|
+
}
|
|
43835
|
+
|
|
43836
|
+
return true;
|
|
43837
|
+
}
|
|
43641
43838
|
/**
|
|
43642
43839
|
* Escape closes the open dialog (Element, Icons, Shadow, Image Adjust,
|
|
43643
43840
|
* Group, Block Options, Symbols, Settings).
|
|
@@ -43987,6 +44184,8 @@ class ControlPanel {
|
|
|
43987
44184
|
if (el.closest('[data-html]')) return null; // code/module block — has its own editor
|
|
43988
44185
|
|
|
43989
44186
|
if (el.closest('[data-cb-type]')) return null; // plugin — panel-plugin owns it
|
|
44187
|
+
|
|
44188
|
+
if (this.overlayIcon(el)) return null; // icon in a text — the Icon panel owns it
|
|
43990
44189
|
// Text of an overlay layer. Resolve upward: a click can land on a
|
|
43991
44190
|
// <b>/<span> inside the region, which is not itself a region.
|
|
43992
44191
|
|
|
@@ -44177,7 +44376,8 @@ class ControlPanel {
|
|
|
44177
44376
|
const el = stack[i];
|
|
44178
44377
|
|
|
44179
44378
|
if (match(el)) {
|
|
44180
|
-
|
|
44379
|
+
const overlay = el.closest(this.builder.overlayEditableSelector);
|
|
44380
|
+
if (overlay ? this.isVisibleIn(el, overlay) : this.isPainted(el)) return el; // Crossfade stacks (three images or three captions at the same
|
|
44181
44381
|
// spot, cycled by opacity keyframes) put a FULLY TRANSPARENT
|
|
44182
44382
|
// layer on top for most of the scene. Stacking order would hand
|
|
44183
44383
|
// back that one every time, so the user edits a layer they
|
|
@@ -44226,7 +44426,7 @@ class ControlPanel {
|
|
|
44226
44426
|
activeElement = this.resolvedImage;
|
|
44227
44427
|
}
|
|
44228
44428
|
|
|
44229
|
-
if (activeElement && this.overlayTarget(activeElement)) {
|
|
44429
|
+
if (activeElement && (this.overlayTarget(activeElement) || this.overlayIcon(activeElement))) {
|
|
44230
44430
|
this.activeElement = activeElement;
|
|
44231
44431
|
this.activeColumn = null;
|
|
44232
44432
|
this.activeRow = null;
|
|
@@ -44350,7 +44550,7 @@ class ControlPanel {
|
|
|
44350
44550
|
} // Overlay content has no row/column to walk back up to.
|
|
44351
44551
|
|
|
44352
44552
|
|
|
44353
|
-
if (selection === 'element' && (this.overlayTarget(this.activeElement) || this.activeElement.closest('[data-cb-type]') && this.activeElement.closest('.is-overlay-content'))) {
|
|
44553
|
+
if (selection === 'element' && (this.overlayTarget(this.activeElement) || this.overlayIcon(this.activeElement) || this.activeElement.closest('[data-cb-type]') && this.activeElement.closest('.is-overlay-content'))) {
|
|
44354
44554
|
breadcrumbHtml = `<div>${section} ${sep} ${box} ${sep}</div>`;
|
|
44355
44555
|
}
|
|
44356
44556
|
|
|
@@ -44424,6 +44624,8 @@ class ControlPanel {
|
|
|
44424
44624
|
sectionHeightAllowed() {
|
|
44425
44625
|
const section = this.builder.activeSection;
|
|
44426
44626
|
if (!section || !section.classList.contains('is-box')) return true;
|
|
44627
|
+
if (section.classList.contains('is-section-navbar')) return false; // a navigation bar has no height presets
|
|
44628
|
+
|
|
44427
44629
|
const el = section.querySelector('.is-overlay-content [data-cb-type]');
|
|
44428
44630
|
if (!el) return true;
|
|
44429
44631
|
const runtime = this.builder.win.builderRuntime;
|
|
@@ -44601,6 +44803,25 @@ class ControlPanel {
|
|
|
44601
44803
|
elm.removeAttribute('data-overlay-select');
|
|
44602
44804
|
});
|
|
44603
44805
|
if (el) el.setAttribute('data-overlay-select', '');
|
|
44806
|
+
} // A plugin placed in an overlay (eg. a header bar's Site Menu or Site Logo).
|
|
44807
|
+
// Like an overlay target it has no cell/row outline, so it gets the same mark.
|
|
44808
|
+
|
|
44809
|
+
|
|
44810
|
+
overlayPlugin(el) {
|
|
44811
|
+
if (!el || !el.closest) return null;
|
|
44812
|
+
const plugin = el.closest('[data-cb-type]');
|
|
44813
|
+
if (!plugin || !plugin.closest(this.builder.overlayEditableSelector)) return null;
|
|
44814
|
+
if (plugin.closest('.is-container')) return null;
|
|
44815
|
+
return plugin;
|
|
44816
|
+
} // An icon (<i class="bi ...">) in an overlay, eg. at the start of a chip's text. It opens the
|
|
44817
|
+
// Icon panel, so it can be changed, while a click on the words still opens the Text panel.
|
|
44818
|
+
|
|
44819
|
+
|
|
44820
|
+
overlayIcon(el) {
|
|
44821
|
+
if (!el || !el.matches || !el.matches('i.bi, i.icon') || el.innerHTML !== '') return null;
|
|
44822
|
+
if (!el.closest(this.builder.overlayEditableSelector)) return null;
|
|
44823
|
+
if (el.closest('.is-container, [data-html], [data-cb-type]')) return null;
|
|
44824
|
+
return el;
|
|
44604
44825
|
} // Is `el` (or an ancestor of it) a button?
|
|
44605
44826
|
//
|
|
44606
44827
|
// A <button> always is. An <a> is a button when it lays itself out as a
|
|
@@ -44683,7 +44904,16 @@ class ControlPanel {
|
|
|
44683
44904
|
|
|
44684
44905
|
if (what === 'element' && this.activeElement) {
|
|
44685
44906
|
const overlayNow = this.overlayTarget(this.activeElement);
|
|
44686
|
-
if (overlayNow) marked = overlayNow.element;else if (this.pictureOnlyElement(this.activeElement)) marked = this.activeElement;
|
|
44907
|
+
if (overlayNow) marked = overlayNow.element;else if (this.pictureOnlyElement(this.activeElement)) marked = this.activeElement;else marked = this.overlayPlugin(this.activeElement) || this.overlayIcon(this.activeElement);
|
|
44908
|
+
} // An overlay icon stays the editor's active icon only while it is selected; otherwise
|
|
44909
|
+
// a later icon insert would replace it.
|
|
44910
|
+
|
|
44911
|
+
|
|
44912
|
+
const activeIcon = this.builder.editor.activeIcon;
|
|
44913
|
+
|
|
44914
|
+
if (activeIcon && activeIcon !== marked && this.overlayIcon(activeIcon)) {
|
|
44915
|
+
activeIcon.classList.remove('icon-active');
|
|
44916
|
+
this.builder.editor.activeIcon = null;
|
|
44687
44917
|
}
|
|
44688
44918
|
|
|
44689
44919
|
this.markOverlaySelection(marked);
|
|
@@ -44834,6 +45064,9 @@ class ControlPanel {
|
|
|
44834
45064
|
this.panelIframe.classList.add('active');
|
|
44835
45065
|
this.objPanelIframe.getState();
|
|
44836
45066
|
} else if (element.tagName.toLowerCase() === 'i' && (element.classList.contains('icon') || element.classList.contains('bi'))) {
|
|
45067
|
+
// In a container ContentBuilder's click makes it the active icon; an overlay
|
|
45068
|
+
// icon gets no such click, so name it here (Change icon replaces this one).
|
|
45069
|
+
if (this.overlayIcon(element)) this.builder.editor.activeIcon = element;
|
|
44837
45070
|
let titleHtml = out('Icon');
|
|
44838
45071
|
this.title.innerHTML = titleHtml;
|
|
44839
45072
|
this.panelIcon.classList.add('active');
|
|
@@ -44966,8 +45199,10 @@ class ControlPanel {
|
|
|
44966
45199
|
// should animate exposes that as its own setting.
|
|
44967
45200
|
|
|
44968
45201
|
|
|
44969
|
-
const isPlugin = what === 'element' && !!pluginOf(this.activeElement);
|
|
44970
|
-
|
|
45202
|
+
const isPlugin = what === 'element' && !!pluginOf(this.activeElement); // An icon in overlay text moves with its line; only the line has Motion.
|
|
45203
|
+
|
|
45204
|
+
const isOverlayIcon = what === 'element' && !!this.overlayIcon(this.activeElement);
|
|
45205
|
+
const canMotion = !isPlugin && !isOverlayIcon && (what === 'element' || what === 'column' || what === 'row' || what === 'container' || what === 'block' || what === 'box' || what === 'section');
|
|
44971
45206
|
|
|
44972
45207
|
if (canMotion) {
|
|
44973
45208
|
if (this.tab === 'motion') {
|
|
@@ -50657,6 +50892,10 @@ class ToolbarKeys {
|
|
|
50657
50892
|
*/
|
|
50658
50893
|
_hold(container, control) {
|
|
50659
50894
|
if (!control) return;
|
|
50895
|
+
|
|
50896
|
+
// Where the control was: if the command removes what it sat on, the caret
|
|
50897
|
+
// goes to what took its place
|
|
50898
|
+
const spot = (control.closest('.row, [data-cb-block]') || control).getBoundingClientRect();
|
|
50660
50899
|
setTimeout(() => {
|
|
50661
50900
|
// Not bailing when the container has gone: deleting the last column
|
|
50662
50901
|
// takes the strip with it, and that is exactly when the focus needs
|
|
@@ -50714,8 +50953,10 @@ class ToolbarKeys {
|
|
|
50714
50953
|
// A tick later again: emptying an area makes the editor put its
|
|
50715
50954
|
// own button there, and that has not happened yet.
|
|
50716
50955
|
setTimeout(() => {
|
|
50717
|
-
const somewhere = this._somewhereUsable();
|
|
50718
|
-
if (somewhere) somewhere.focus(
|
|
50956
|
+
const somewhere = this._somewhereUsable(spot);
|
|
50957
|
+
if (somewhere) somewhere.focus({
|
|
50958
|
+
preventScroll: true
|
|
50959
|
+
});
|
|
50719
50960
|
}, 30);
|
|
50720
50961
|
}
|
|
50721
50962
|
}, 0);
|
|
@@ -50786,7 +51027,9 @@ class ToolbarKeys {
|
|
|
50786
51027
|
// the trip to the toolbar after one button. Stay on it, so the next format
|
|
50787
51028
|
// is a key away, and leave deliberately with Escape. Anything that claims
|
|
50788
51029
|
// the focus for itself, a panel or a dialog, is left to do so.
|
|
51030
|
+
// A mouse click (detail > 0) is left alone, so the mouse works as it always did.
|
|
50789
51031
|
tool.addEventListener('click', e => {
|
|
51032
|
+
if (e.detail > 0) return;
|
|
50790
51033
|
const control = this._controlFrom(e, tool, BUTTONS);
|
|
50791
51034
|
if (control) this._hold(tool, control);
|
|
50792
51035
|
});
|
|
@@ -50928,13 +51171,26 @@ class ToolbarKeys {
|
|
|
50928
51171
|
* go dead with the focus stuck on a strip. Falls back through what the editor
|
|
50929
51172
|
* still calls active, then to the first column on the page.
|
|
50930
51173
|
*/
|
|
50931
|
-
currentColumn() {
|
|
51174
|
+
currentColumn(near) {
|
|
50932
51175
|
// It has to be able to hold the focus. A locked column is still the active
|
|
50933
51176
|
// one but cannot take it, and focusing it silently does nothing.
|
|
50934
51177
|
const usable = el => el && el.isConnected && (el.isContentEditable || el.hasAttribute('tabindex')) ? el : null;
|
|
50935
51178
|
const active = usable(this.builder.util.cellSelected()) || usable(this.builder.activeCol);
|
|
50936
51179
|
if (active) return active;
|
|
50937
51180
|
const rows = this.builder.doc.querySelectorAll(`${this.builder.opts.container} > *`);
|
|
51181
|
+
|
|
51182
|
+
// Near a place on screen (where a removed row was): the column that took its
|
|
51183
|
+
// place, or the last one before it
|
|
51184
|
+
if (near) {
|
|
51185
|
+
let before = null;
|
|
51186
|
+
for (const row of rows) {
|
|
51187
|
+
const cell = ToolbarKeys.cells(row).find(usable);
|
|
51188
|
+
if (!cell) continue;
|
|
51189
|
+
if (cell.getBoundingClientRect().top >= near.top - 1) return cell;
|
|
51190
|
+
before = cell;
|
|
51191
|
+
}
|
|
51192
|
+
if (before) return before;
|
|
51193
|
+
}
|
|
50938
51194
|
for (const row of rows) {
|
|
50939
51195
|
const cell = ToolbarKeys.cells(row).find(usable);
|
|
50940
51196
|
if (cell) return cell;
|
|
@@ -50948,8 +51204,8 @@ class ToolbarKeys {
|
|
|
50948
51204
|
* editing away: in both the focus would otherwise land on the body, where no
|
|
50949
51205
|
* key reaches the editor at all.
|
|
50950
51206
|
*/
|
|
50951
|
-
_somewhereUsable() {
|
|
50952
|
-
const column = this.currentColumn();
|
|
51207
|
+
_somewhereUsable(near) {
|
|
51208
|
+
const column = this.currentColumn(near);
|
|
50953
51209
|
if (column) return column;
|
|
50954
51210
|
const doc = this.builder.doc;
|
|
50955
51211
|
|
|
@@ -52122,6 +52378,18 @@ class Util$1 {
|
|
|
52122
52378
|
// A toolbar button that opens a dialog often moves the focus first, so the
|
|
52123
52379
|
// strip fills this in from the button actually used (toolbarkeys.js).
|
|
52124
52380
|
|
|
52381
|
+
// Opened from a control inside the content iframe: this page never saw
|
|
52382
|
+
// that mouse click, so the browser would ring the dialog's first control
|
|
52383
|
+
// as if the keyboard had been used. Carry the opener's own state over.
|
|
52384
|
+
let focusOpts;
|
|
52385
|
+
if (opener && opener.tagName === 'IFRAME') {
|
|
52386
|
+
try {
|
|
52387
|
+
const inner = opener.contentDocument.activeElement;
|
|
52388
|
+
if (!(inner && inner.matches(':focus-visible'))) focusOpts = {
|
|
52389
|
+
focusVisible: false
|
|
52390
|
+
};
|
|
52391
|
+
} catch (e) {/* another origin */}
|
|
52392
|
+
}
|
|
52125
52393
|
if (overflowHidden) document.body.style.overflow = 'hidden';
|
|
52126
52394
|
const handleKeyDown = e => {
|
|
52127
52395
|
if (e.keyCode === 27) {
|
|
@@ -52139,11 +52407,11 @@ class Util$1 {
|
|
|
52139
52407
|
modal.addEventListener('keydown', handleKeyDown);
|
|
52140
52408
|
let content = modal.querySelector('.is-tab-content.active');
|
|
52141
52409
|
if (content) {
|
|
52142
|
-
content.focus();
|
|
52410
|
+
content.focus(focusOpts);
|
|
52143
52411
|
this.builder.tabs.setupTabKey(content);
|
|
52144
52412
|
} else {
|
|
52145
52413
|
this.setupTabKeys(modal);
|
|
52146
|
-
modal.focus();
|
|
52414
|
+
modal.focus(focusOpts);
|
|
52147
52415
|
}
|
|
52148
52416
|
|
|
52149
52417
|
// Land on a control rather than on the dialog itself: focus on the dialog
|
|
@@ -52163,7 +52431,7 @@ class Util$1 {
|
|
|
52163
52431
|
const settled = modal.contains(held) && held !== modal && held.tagName !== 'IFRAME' && !held.classList.contains('is-tab-content');
|
|
52164
52432
|
if (settled) return;
|
|
52165
52433
|
const first = ToolbarKeys.firstControl(modal);
|
|
52166
|
-
if (first) first.focus();
|
|
52434
|
+
if (first) first.focus(focusOpts);
|
|
52167
52435
|
}, 0);
|
|
52168
52436
|
|
|
52169
52437
|
// A dialog whose content is an iframe, the asset picker, is a trap without
|
|
@@ -54192,6 +54460,7 @@ class Util$1 {
|
|
|
54192
54460
|
* Placeholders substituted by export():
|
|
54193
54461
|
* [%CONTENT%] the content HTML
|
|
54194
54462
|
* [%PLUGINS%] runtime plugin registry (see getPluginRegistry)
|
|
54463
|
+
* [%PLUGINFOLDER%] runtime plugin folder (see getPluginFolder)
|
|
54195
54464
|
* [%PATH%] base URL for runtime/asset references
|
|
54196
54465
|
*/
|
|
54197
54466
|
getPageTemplate(framework) {
|
|
@@ -54229,7 +54498,8 @@ class Util$1 {
|
|
|
54229
54498
|
// Registers available plugins (not yet loaded).
|
|
54230
54499
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
54231
54500
|
const runtime = new ContentBuilderRuntime({
|
|
54232
|
-
plugins: [%PLUGINS%]
|
|
54501
|
+
plugins: [%PLUGINS%],
|
|
54502
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
54233
54503
|
});
|
|
54234
54504
|
runtime.init();
|
|
54235
54505
|
runtime.renderPageCss('.container');
|
|
@@ -54279,7 +54549,8 @@ class Util$1 {
|
|
|
54279
54549
|
// Registers available plugins (not yet loaded).
|
|
54280
54550
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
54281
54551
|
const runtime = new ContentBuilderRuntime({
|
|
54282
|
-
plugins: [%PLUGINS%]
|
|
54552
|
+
plugins: [%PLUGINS%],
|
|
54553
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
54283
54554
|
});
|
|
54284
54555
|
runtime.init();
|
|
54285
54556
|
</script>
|
|
@@ -54332,7 +54603,8 @@ class Util$1 {
|
|
|
54332
54603
|
// Registers available plugins (not yet loaded).
|
|
54333
54604
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
54334
54605
|
const runtime = new ContentBuilderRuntime({
|
|
54335
|
-
plugins: [%PLUGINS%]
|
|
54606
|
+
plugins: [%PLUGINS%],
|
|
54607
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
54336
54608
|
});
|
|
54337
54609
|
runtime.init();
|
|
54338
54610
|
</script>
|
|
@@ -54390,7 +54662,8 @@ class Util$1 {
|
|
|
54390
54662
|
// Registers available plugins (not yet loaded).
|
|
54391
54663
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
54392
54664
|
const runtime = new ContentBuilderRuntime({
|
|
54393
|
-
plugins: [%PLUGINS%]
|
|
54665
|
+
plugins: [%PLUGINS%],
|
|
54666
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
54394
54667
|
});
|
|
54395
54668
|
runtime.init();
|
|
54396
54669
|
</script>
|
|
@@ -54442,7 +54715,8 @@ class Util$1 {
|
|
|
54442
54715
|
// Registers available plugins (not yet loaded).
|
|
54443
54716
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
54444
54717
|
const runtime = new ContentBuilderRuntime({
|
|
54445
|
-
plugins: [%PLUGINS%]
|
|
54718
|
+
plugins: [%PLUGINS%],
|
|
54719
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
54446
54720
|
});
|
|
54447
54721
|
runtime.init();
|
|
54448
54722
|
</script>
|
|
@@ -54496,6 +54770,27 @@ class Util$1 {
|
|
|
54496
54770
|
});
|
|
54497
54771
|
return JSON.stringify(registry, null, 4);
|
|
54498
54772
|
}
|
|
54773
|
+
|
|
54774
|
+
/*
|
|
54775
|
+
* The runtime's plugin folder (pluginFolder in blank.html), as a JS string.
|
|
54776
|
+
* Plugins used in the content load from it without being registered, so an
|
|
54777
|
+
* exported page keeps them. A URL under the content frame's base becomes a
|
|
54778
|
+
* [%PATH%] token, like the registry's URLs.
|
|
54779
|
+
*/
|
|
54780
|
+
getPluginFolder() {
|
|
54781
|
+
const win = this.builder.win;
|
|
54782
|
+
let folder = '';
|
|
54783
|
+
try {
|
|
54784
|
+
folder = win.builderRuntime.settings.pluginFolder || '';
|
|
54785
|
+
} catch (e) {/* no runtime in the content frame */}
|
|
54786
|
+
let base = '';
|
|
54787
|
+
try {
|
|
54788
|
+
const href = win.location.href;
|
|
54789
|
+
base = href.substring(0, href.lastIndexOf('/'));
|
|
54790
|
+
} catch (e) {/* cross-origin content frame */}
|
|
54791
|
+
if (base && folder.startsWith(base + '/')) folder = '[%PATH%]' + folder.substring(base.length);
|
|
54792
|
+
return JSON.stringify(folder);
|
|
54793
|
+
}
|
|
54499
54794
|
getElementPosition(element) {
|
|
54500
54795
|
const iframe = this.builder.iframe;
|
|
54501
54796
|
let top, left;
|
|
@@ -54707,6 +55002,46 @@ class Util$1 {
|
|
|
54707
55002
|
}
|
|
54708
55003
|
`;
|
|
54709
55004
|
}
|
|
55005
|
+
|
|
55006
|
+
// Place a small tool (link/button tool) above its target. When the target sits at the
|
|
55007
|
+
// top edge with no room above, put the tool beside it (right, then left), so it never
|
|
55008
|
+
// lands on the element tool below the element. Below is the last resort.
|
|
55009
|
+
// vTarget (optional, e.g. a selected icon inside a link) sets the vertical position.
|
|
55010
|
+
// The tool must be displayed before this (its size is measured).
|
|
55011
|
+
placeToolAbove(tool, target, vTarget) {
|
|
55012
|
+
const hr = target.getBoundingClientRect();
|
|
55013
|
+
const vr = (vTarget || target).getBoundingClientRect();
|
|
55014
|
+
tool.style.top = vr.top + this.builder.win.pageYOffset - tool.offsetHeight - 3 + 'px';
|
|
55015
|
+
tool.style.left = hr.left + target.offsetWidth * this.builder.opts.zoom - tool.offsetWidth + 'px';
|
|
55016
|
+
|
|
55017
|
+
// Measured, not computed: the tool can carry margins and a scale.
|
|
55018
|
+
const t = tool.getBoundingClientRect();
|
|
55019
|
+
if (t.top >= 0) return;
|
|
55020
|
+
const moveTo = (left, top) => {
|
|
55021
|
+
const c = tool.getBoundingClientRect();
|
|
55022
|
+
tool.style.left = parseFloat(tool.style.left) + left - c.left + 'px';
|
|
55023
|
+
tool.style.top = parseFloat(tool.style.top) + top - c.top + 'px';
|
|
55024
|
+
};
|
|
55025
|
+
|
|
55026
|
+
// Horizontal room, kept clear of the row tool when it sits beside the target.
|
|
55027
|
+
let minX = 0;
|
|
55028
|
+
let maxX = this.builder.doc.documentElement.clientWidth;
|
|
55029
|
+
const row = target.closest('.row');
|
|
55030
|
+
const rowTool = row && row.querySelector(':scope > .is-row-tool');
|
|
55031
|
+
if (rowTool && rowTool.offsetParent) {
|
|
55032
|
+
const r = rowTool.getBoundingClientRect();
|
|
55033
|
+
if (r.left >= hr.right) maxX = Math.min(maxX, r.left);else if (r.right <= hr.left) minX = Math.max(minX, r.right);
|
|
55034
|
+
}
|
|
55035
|
+
const gap = 8;
|
|
55036
|
+
const sideTop = vr.top + vr.height / 2 - t.height / 2;
|
|
55037
|
+
if (hr.right + gap + t.width <= maxX) {
|
|
55038
|
+
moveTo(hr.right + gap, sideTop);
|
|
55039
|
+
} else if (hr.left - gap - t.width >= minX) {
|
|
55040
|
+
moveTo(hr.left - gap - t.width, sideTop);
|
|
55041
|
+
} else {
|
|
55042
|
+
moveTo(t.left, vr.bottom + gap);
|
|
55043
|
+
}
|
|
55044
|
+
}
|
|
54710
55045
|
}
|
|
54711
55046
|
class Dom {
|
|
54712
55047
|
constructor(builder) {
|
|
@@ -77568,15 +77903,8 @@ class Hyperlink {
|
|
|
77568
77903
|
}
|
|
77569
77904
|
// /Button Stuff
|
|
77570
77905
|
|
|
77571
|
-
|
|
77572
|
-
|
|
77573
|
-
if (this.builder.activeIcon) top = this.builder.activeIcon.getBoundingClientRect().top + this.builder.win.pageYOffset;
|
|
77574
|
-
left = link.getBoundingClientRect().left;
|
|
77575
|
-
linkTool.style.display = 'flex';
|
|
77576
|
-
const w = linkTool.offsetWidth; //to get value, element must not hidden (display:none). So set display:flex before this.
|
|
77577
|
-
const h = linkTool.offsetHeight;
|
|
77578
|
-
linkTool.style.top = top - h - 3 + 'px';
|
|
77579
|
-
linkTool.style.left = left + link.offsetWidth * this.builder.opts.zoom - w + 'px';
|
|
77906
|
+
linkTool.style.display = 'flex'; //to get the size, the tool must not be hidden (display:none)
|
|
77907
|
+
this.builder.util.placeToolAbove(linkTool, link, this.builder.activeIcon);
|
|
77580
77908
|
const handleLinkButtonClick = e => {
|
|
77581
77909
|
const clrPicker = document.querySelector('.pop-picker.active') || document.querySelector('.pickgradientcolor.active');
|
|
77582
77910
|
let elm = e.target;
|
|
@@ -77875,13 +78203,8 @@ class Button {
|
|
|
77875
78203
|
// selecting the button, so the pencil is a second route to the same
|
|
77876
78204
|
// panel and is left out.
|
|
77877
78205
|
buttonTool.querySelector('.button-edit').style.display = this.builder.opts.onButtonEdit ? 'none' : '';
|
|
77878
|
-
|
|
77879
|
-
|
|
77880
|
-
buttonTool.style.display = 'flex';
|
|
77881
|
-
const w = buttonTool.offsetWidth; //to get value, element must not hidden (display:none). So set display:flex before this.
|
|
77882
|
-
const h = buttonTool.offsetHeight;
|
|
77883
|
-
buttonTool.style.top = top - h - 3 + 'px';
|
|
77884
|
-
buttonTool.style.left = left + activeButton.offsetWidth * this.builder.opts.zoom - w + 'px';
|
|
78206
|
+
buttonTool.style.display = 'flex'; //to get the size, the tool must not be hidden (display:none)
|
|
78207
|
+
this.builder.util.placeToolAbove(buttonTool, activeButton);
|
|
77885
78208
|
}
|
|
77886
78209
|
hideTool() {
|
|
77887
78210
|
if (this.buttonTool) this.buttonTool.style.display = '';
|
|
@@ -79078,7 +79401,10 @@ class Plugin {
|
|
|
79078
79401
|
const handlePluginClick = e => {
|
|
79079
79402
|
const clrPicker = document.querySelector('.pop-picker.active') || document.querySelector('.pickgradientcolor.active');
|
|
79080
79403
|
let elm = e.target;
|
|
79081
|
-
|
|
79404
|
+
|
|
79405
|
+
// isConnected, not document.contains(): in iframe mode the click is in
|
|
79406
|
+
// the content document, which this document never contains.
|
|
79407
|
+
let elmRemoved = !elm.isConnected; // e.g. when deleting a card in card list plugin
|
|
79082
79408
|
|
|
79083
79409
|
if (this.builder.doc.activeElement) {
|
|
79084
79410
|
if (this.builder.doc.activeElement.closest('.is-modal')) {
|
|
@@ -79276,7 +79602,9 @@ class Plugin {
|
|
|
79276
79602
|
const plugin = e.target.closest('[data-cb-type]');
|
|
79277
79603
|
if (plugin) {
|
|
79278
79604
|
this.builder.activePlugin = plugin;
|
|
79279
|
-
|
|
79605
|
+
|
|
79606
|
+
// With a control panel, plugin settings open there instead.
|
|
79607
|
+
if (!this.builder.controlPanel) {
|
|
79280
79608
|
this.renderTool();
|
|
79281
79609
|
this.pluginTool.style.display = 'flex';
|
|
79282
79610
|
let _toolwidth = this.pluginTool.offsetWidth; //to get value, element must not hidden (display:none). So set display:flex before this.
|
|
@@ -81552,6 +81880,10 @@ class Element {
|
|
|
81552
81880
|
customcode = true;
|
|
81553
81881
|
subblock = elm.closest('.is-subblock');
|
|
81554
81882
|
}
|
|
81883
|
+
if (elm.closest('[data-cb-code]')) {
|
|
81884
|
+
// Independent code element: the whole block is the element
|
|
81885
|
+
customcode = true;
|
|
81886
|
+
}
|
|
81555
81887
|
if (!customcode && !noedit && !_protected || subblock) {
|
|
81556
81888
|
//previously this is commented: && !noedit && !_protected
|
|
81557
81889
|
|
|
@@ -103213,6 +103545,136 @@ function coverValue(cover, def) {
|
|
|
103213
103545
|
return true;
|
|
103214
103546
|
}
|
|
103215
103547
|
|
|
103548
|
+
/*
|
|
103549
|
+
normalizer.js — the clean-output guarantee.
|
|
103550
|
+
|
|
103551
|
+
Runs after every mutation. It never changes the visible text; it only
|
|
103552
|
+
reduces markup to the minimal equivalent form:
|
|
103553
|
+
|
|
103554
|
+
<strong>a</strong><strong>b</strong> -> <strong>ab</strong>
|
|
103555
|
+
<strong><strong>x</strong></strong> -> <strong>x</strong>
|
|
103556
|
+
<span style="color:red"><span style="font-size:18px">x</span></span>
|
|
103557
|
+
-> one span, two properties
|
|
103558
|
+
<span>x</span> -> x
|
|
103559
|
+
<em></em> -> (removed)
|
|
103560
|
+
|
|
103561
|
+
Because it preserves text content exactly, character-offset selection
|
|
103562
|
+
bookmarks taken before normalization remain valid after it.
|
|
103563
|
+
*/
|
|
103564
|
+
const INLINE_TAGS = new Set(['SPAN', 'STRONG', 'B', 'EM', 'I', 'U', 'S', 'STRIKE', 'DEL', 'INS', 'SUB', 'SUP', 'CODE', 'MARK', 'A', 'SMALL', 'BIG', 'FONT', 'ABBR', 'Q', 'CITE', 'DFN', 'KBD', 'SAMP', 'VAR', 'TIME']);
|
|
103565
|
+
|
|
103566
|
+
/* Identity signature: two inline elements with equal signatures are
|
|
103567
|
+
interchangeable and can be merged. */
|
|
103568
|
+
function sig(el) {
|
|
103569
|
+
const attrs = [];
|
|
103570
|
+
for (const a of el.attributes) {
|
|
103571
|
+
attrs.push(a.name === 'style' ? 'style=' + el.style.cssText : a.name + '=' + a.value);
|
|
103572
|
+
}
|
|
103573
|
+
return el.tagName + '|' + attrs.sort().join(';');
|
|
103574
|
+
}
|
|
103575
|
+
function isEmptyInline(el) {
|
|
103576
|
+
if (el.textContent !== '' || el.querySelector(KEEPERS)) return false;
|
|
103577
|
+
// An empty inline that carries a class typically renders its content via CSS
|
|
103578
|
+
// rather than text — an icon font (<i class="bi bi-alarm">, <span
|
|
103579
|
+
// class="material-icons">…</span>, fa-*, glyphicon). It is NOT truly empty,
|
|
103580
|
+
// so never drop it. Bare leftover marks (<em></em>, <i></i>) have no class
|
|
103581
|
+
// and are still removed.
|
|
103582
|
+
if (el.getAttribute('class')) return false;
|
|
103583
|
+
return true;
|
|
103584
|
+
}
|
|
103585
|
+
|
|
103586
|
+
/* Does the page's CSS style this attribute-less span? A design can colour or
|
|
103587
|
+
size "the span in this heading" from a stylesheet (.hero h1 span {...});
|
|
103588
|
+
unwrapping it then changes how the text looks, which this file promises
|
|
103589
|
+
never to do. Compared against the parent for inherited properties and
|
|
103590
|
+
against the initial value for the rest. Without a live view (detached, or
|
|
103591
|
+
a headless test) it is treated as unstyled, as before. */
|
|
103592
|
+
const INHERITED = ['color', 'fontSize', 'fontWeight', 'fontStyle', 'fontFamily', 'letterSpacing', 'textTransform', 'lineHeight'];
|
|
103593
|
+
function styledByCss(el) {
|
|
103594
|
+
const parent = el.parentElement;
|
|
103595
|
+
const win = el.ownerDocument && el.ownerDocument.defaultView;
|
|
103596
|
+
if (!parent || !win || !el.isConnected || typeof win.getComputedStyle !== 'function') return false;
|
|
103597
|
+
const cs = win.getComputedStyle(el);
|
|
103598
|
+
const ps = win.getComputedStyle(parent);
|
|
103599
|
+
for (const p of INHERITED) if (cs[p] !== ps[p]) return true;
|
|
103600
|
+
if (cs.display && cs.display !== 'inline') return true;
|
|
103601
|
+
if (cs.backgroundImage && cs.backgroundImage !== 'none') return true;
|
|
103602
|
+
if (cs.backgroundColor && !/^(transparent|rgba\(0, 0, 0, 0\))$/.test(cs.backgroundColor)) return true;
|
|
103603
|
+
if (cs.textDecorationLine && cs.textDecorationLine !== 'none') return true;
|
|
103604
|
+
return false;
|
|
103605
|
+
}
|
|
103606
|
+
|
|
103607
|
+
/* One bottom-up cleanup pass. Returns true if anything changed. */
|
|
103608
|
+
function pass(scope) {
|
|
103609
|
+
let changed = false;
|
|
103610
|
+
const els = Array.prototype.slice.call(scope.querySelectorAll('*')).reverse();
|
|
103611
|
+
for (const el of els) {
|
|
103612
|
+
if (!el.isConnected && !scope.contains(el)) continue;
|
|
103613
|
+
if (el.closest && el.closest('[contenteditable="false"]')) continue;
|
|
103614
|
+
if (!INLINE_TAGS.has(el.tagName)) continue;
|
|
103615
|
+
|
|
103616
|
+
/* 1. Drop empty inline elements */
|
|
103617
|
+
if (isEmptyInline(el)) {
|
|
103618
|
+
el.remove();
|
|
103619
|
+
changed = true;
|
|
103620
|
+
continue;
|
|
103621
|
+
}
|
|
103622
|
+
|
|
103623
|
+
/* 2. Unwrap attribute-less spans (unless the page's CSS styles them) */
|
|
103624
|
+
if (el.tagName === 'SPAN' && el.attributes.length === 0 && !styledByCss(el)) {
|
|
103625
|
+
unwrap(el);
|
|
103626
|
+
changed = true;
|
|
103627
|
+
continue;
|
|
103628
|
+
}
|
|
103629
|
+
|
|
103630
|
+
/* 3. Unwrap when an identical ancestor already covers this element */
|
|
103631
|
+
let anc = el.parentNode;
|
|
103632
|
+
let redundant = false;
|
|
103633
|
+
while (isEl$1(anc) && anc !== scope && INLINE_TAGS.has(anc.tagName)) {
|
|
103634
|
+
if (sig(anc) === sig(el)) {
|
|
103635
|
+
redundant = true;
|
|
103636
|
+
break;
|
|
103637
|
+
}
|
|
103638
|
+
anc = anc.parentNode;
|
|
103639
|
+
}
|
|
103640
|
+
if (redundant) {
|
|
103641
|
+
unwrap(el);
|
|
103642
|
+
changed = true;
|
|
103643
|
+
continue;
|
|
103644
|
+
}
|
|
103645
|
+
|
|
103646
|
+
/* 4. Merge with an identical previous sibling */
|
|
103647
|
+
const prev = el.previousSibling;
|
|
103648
|
+
if (isEl$1(prev) && sig(prev) === sig(el)) {
|
|
103649
|
+
while (el.firstChild) prev.appendChild(el.firstChild);
|
|
103650
|
+
el.remove();
|
|
103651
|
+
changed = true;
|
|
103652
|
+
continue;
|
|
103653
|
+
}
|
|
103654
|
+
|
|
103655
|
+
/* 5. Collapse span-in-span single-child chains into one span.
|
|
103656
|
+
The inner (more specific) properties win on conflict. */
|
|
103657
|
+
if (el.tagName === 'SPAN' && el.childNodes.length === 1 && isEl$1(el.firstChild) && el.firstChild.tagName === 'SPAN') {
|
|
103658
|
+
const inner = el.firstChild;
|
|
103659
|
+
for (let i = 0; i < inner.style.length; i++) {
|
|
103660
|
+
const prop = inner.style[i];
|
|
103661
|
+
el.style.setProperty(prop, inner.style.getPropertyValue(prop));
|
|
103662
|
+
}
|
|
103663
|
+
inner.classList.forEach(c => el.classList.add(c));
|
|
103664
|
+
if (!el.getAttribute('style')) el.removeAttribute('style');
|
|
103665
|
+
unwrap(inner);
|
|
103666
|
+
changed = true;
|
|
103667
|
+
}
|
|
103668
|
+
}
|
|
103669
|
+
return changed;
|
|
103670
|
+
}
|
|
103671
|
+
function normalize(scope) {
|
|
103672
|
+
let guard = 0;
|
|
103673
|
+
while (pass(scope) && guard++ < 6) {/* repeat until stable */}
|
|
103674
|
+
scope.normalize(); /* merge adjacent text nodes */
|
|
103675
|
+
return scope;
|
|
103676
|
+
}
|
|
103677
|
+
|
|
103216
103678
|
/*
|
|
103217
103679
|
formatter.js — inline formatting engine.
|
|
103218
103680
|
|
|
@@ -103479,7 +103941,9 @@ function strip(el, def) {
|
|
|
103479
103941
|
unwrap(el);
|
|
103480
103942
|
return;
|
|
103481
103943
|
}
|
|
103482
|
-
|
|
103944
|
+
/* Left bare: unwrap, unless the page's CSS styles it (a design's
|
|
103945
|
+
".hero h1 span" colour), which must survive clearing a size from it. */
|
|
103946
|
+
if (el.tagName === 'SPAN' && el.attributes.length === 0 && !styledByCss(el)) unwrap(el);
|
|
103483
103947
|
}
|
|
103484
103948
|
|
|
103485
103949
|
/* Apply a style across a (non-collapsed) range. */
|
|
@@ -103544,115 +104008,6 @@ function removeStyle(root, range, def, opts) {
|
|
|
103544
104008
|
return texts;
|
|
103545
104009
|
}
|
|
103546
104010
|
|
|
103547
|
-
/*
|
|
103548
|
-
normalizer.js — the clean-output guarantee.
|
|
103549
|
-
|
|
103550
|
-
Runs after every mutation. It never changes the visible text; it only
|
|
103551
|
-
reduces markup to the minimal equivalent form:
|
|
103552
|
-
|
|
103553
|
-
<strong>a</strong><strong>b</strong> -> <strong>ab</strong>
|
|
103554
|
-
<strong><strong>x</strong></strong> -> <strong>x</strong>
|
|
103555
|
-
<span style="color:red"><span style="font-size:18px">x</span></span>
|
|
103556
|
-
-> one span, two properties
|
|
103557
|
-
<span>x</span> -> x
|
|
103558
|
-
<em></em> -> (removed)
|
|
103559
|
-
|
|
103560
|
-
Because it preserves text content exactly, character-offset selection
|
|
103561
|
-
bookmarks taken before normalization remain valid after it.
|
|
103562
|
-
*/
|
|
103563
|
-
const INLINE_TAGS = new Set(['SPAN', 'STRONG', 'B', 'EM', 'I', 'U', 'S', 'STRIKE', 'DEL', 'INS', 'SUB', 'SUP', 'CODE', 'MARK', 'A', 'SMALL', 'BIG', 'FONT', 'ABBR', 'Q', 'CITE', 'DFN', 'KBD', 'SAMP', 'VAR', 'TIME']);
|
|
103564
|
-
|
|
103565
|
-
/* Identity signature: two inline elements with equal signatures are
|
|
103566
|
-
interchangeable and can be merged. */
|
|
103567
|
-
function sig(el) {
|
|
103568
|
-
const attrs = [];
|
|
103569
|
-
for (const a of el.attributes) {
|
|
103570
|
-
attrs.push(a.name === 'style' ? 'style=' + el.style.cssText : a.name + '=' + a.value);
|
|
103571
|
-
}
|
|
103572
|
-
return el.tagName + '|' + attrs.sort().join(';');
|
|
103573
|
-
}
|
|
103574
|
-
function isEmptyInline(el) {
|
|
103575
|
-
if (el.textContent !== '' || el.querySelector(KEEPERS)) return false;
|
|
103576
|
-
// An empty inline that carries a class typically renders its content via CSS
|
|
103577
|
-
// rather than text — an icon font (<i class="bi bi-alarm">, <span
|
|
103578
|
-
// class="material-icons">…</span>, fa-*, glyphicon). It is NOT truly empty,
|
|
103579
|
-
// so never drop it. Bare leftover marks (<em></em>, <i></i>) have no class
|
|
103580
|
-
// and are still removed.
|
|
103581
|
-
if (el.getAttribute('class')) return false;
|
|
103582
|
-
return true;
|
|
103583
|
-
}
|
|
103584
|
-
|
|
103585
|
-
/* One bottom-up cleanup pass. Returns true if anything changed. */
|
|
103586
|
-
function pass(scope) {
|
|
103587
|
-
let changed = false;
|
|
103588
|
-
const els = Array.prototype.slice.call(scope.querySelectorAll('*')).reverse();
|
|
103589
|
-
for (const el of els) {
|
|
103590
|
-
if (!el.isConnected && !scope.contains(el)) continue;
|
|
103591
|
-
if (el.closest && el.closest('[contenteditable="false"]')) continue;
|
|
103592
|
-
if (!INLINE_TAGS.has(el.tagName)) continue;
|
|
103593
|
-
|
|
103594
|
-
/* 1. Drop empty inline elements */
|
|
103595
|
-
if (isEmptyInline(el)) {
|
|
103596
|
-
el.remove();
|
|
103597
|
-
changed = true;
|
|
103598
|
-
continue;
|
|
103599
|
-
}
|
|
103600
|
-
|
|
103601
|
-
/* 2. Unwrap attribute-less spans */
|
|
103602
|
-
if (el.tagName === 'SPAN' && el.attributes.length === 0) {
|
|
103603
|
-
unwrap(el);
|
|
103604
|
-
changed = true;
|
|
103605
|
-
continue;
|
|
103606
|
-
}
|
|
103607
|
-
|
|
103608
|
-
/* 3. Unwrap when an identical ancestor already covers this element */
|
|
103609
|
-
let anc = el.parentNode;
|
|
103610
|
-
let redundant = false;
|
|
103611
|
-
while (isEl$1(anc) && anc !== scope && INLINE_TAGS.has(anc.tagName)) {
|
|
103612
|
-
if (sig(anc) === sig(el)) {
|
|
103613
|
-
redundant = true;
|
|
103614
|
-
break;
|
|
103615
|
-
}
|
|
103616
|
-
anc = anc.parentNode;
|
|
103617
|
-
}
|
|
103618
|
-
if (redundant) {
|
|
103619
|
-
unwrap(el);
|
|
103620
|
-
changed = true;
|
|
103621
|
-
continue;
|
|
103622
|
-
}
|
|
103623
|
-
|
|
103624
|
-
/* 4. Merge with an identical previous sibling */
|
|
103625
|
-
const prev = el.previousSibling;
|
|
103626
|
-
if (isEl$1(prev) && sig(prev) === sig(el)) {
|
|
103627
|
-
while (el.firstChild) prev.appendChild(el.firstChild);
|
|
103628
|
-
el.remove();
|
|
103629
|
-
changed = true;
|
|
103630
|
-
continue;
|
|
103631
|
-
}
|
|
103632
|
-
|
|
103633
|
-
/* 5. Collapse span-in-span single-child chains into one span.
|
|
103634
|
-
The inner (more specific) properties win on conflict. */
|
|
103635
|
-
if (el.tagName === 'SPAN' && el.childNodes.length === 1 && isEl$1(el.firstChild) && el.firstChild.tagName === 'SPAN') {
|
|
103636
|
-
const inner = el.firstChild;
|
|
103637
|
-
for (let i = 0; i < inner.style.length; i++) {
|
|
103638
|
-
const prop = inner.style[i];
|
|
103639
|
-
el.style.setProperty(prop, inner.style.getPropertyValue(prop));
|
|
103640
|
-
}
|
|
103641
|
-
inner.classList.forEach(c => el.classList.add(c));
|
|
103642
|
-
if (!el.getAttribute('style')) el.removeAttribute('style');
|
|
103643
|
-
unwrap(inner);
|
|
103644
|
-
changed = true;
|
|
103645
|
-
}
|
|
103646
|
-
}
|
|
103647
|
-
return changed;
|
|
103648
|
-
}
|
|
103649
|
-
function normalize(scope) {
|
|
103650
|
-
let guard = 0;
|
|
103651
|
-
while (pass(scope) && guard++ < 6) {/* repeat until stable */}
|
|
103652
|
-
scope.normalize(); /* merge adjacent text nodes */
|
|
103653
|
-
return scope;
|
|
103654
|
-
}
|
|
103655
|
-
|
|
103656
104011
|
/*
|
|
103657
104012
|
probe.js — non-mutating state inspection (queryCommandState done right).
|
|
103658
104013
|
|
|
@@ -144123,11 +144478,14 @@ class ContentBuilder {
|
|
|
144123
144478
|
this.shortenOutput = false;
|
|
144124
144479
|
output = pageTemplate.replace('[%CONTENT%]', html);
|
|
144125
144480
|
|
|
144126
|
-
// [%PLUGINS%] - runtime plugin registry (must run
|
|
144127
|
-
//
|
|
144481
|
+
// [%PLUGINS%], [%PLUGINFOLDER%] - runtime plugin registry and folder (must run
|
|
144482
|
+
// before [%PATH%], their URLs are emitted as [%PATH%] tokens)
|
|
144128
144483
|
if (output.indexOf('[%PLUGINS%]') !== -1) {
|
|
144129
144484
|
output = output.replaceAll('[%PLUGINS%]', this.util.getPluginRegistry());
|
|
144130
144485
|
}
|
|
144486
|
+
if (output.indexOf('[%PLUGINFOLDER%]') !== -1) {
|
|
144487
|
+
output = output.replaceAll('[%PLUGINFOLDER%]', this.util.getPluginFolder());
|
|
144488
|
+
}
|
|
144131
144489
|
|
|
144132
144490
|
// Custom
|
|
144133
144491
|
if (options.replace) {
|
|
@@ -146867,6 +147225,9 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
|
|
|
146867
147225
|
});
|
|
146868
147226
|
tmp.querySelectorAll('*').forEach(el => {
|
|
146869
147227
|
el.removeAttribute('style');
|
|
147228
|
+
// Text direction is the destination's too: Word marks every
|
|
147229
|
+
// paragraph and run dir="RTL"/"LTR" in right-to-left documents.
|
|
147230
|
+
el.removeAttribute('dir');
|
|
146870
147231
|
if (el.classList && el.classList.length) {
|
|
146871
147232
|
const kept = Array.prototype.filter.call(el.classList, c => keep.has(c));
|
|
146872
147233
|
if (kept.length) el.setAttribute('class', kept.join(' '));else el.removeAttribute('class');
|
|
@@ -151889,6 +152250,7 @@ class Util {
|
|
|
151889
152250
|
* [%MAINCSS%] page-level <style>
|
|
151890
152251
|
* [%SECTIONCSS%] per-section <style>
|
|
151891
152252
|
* [%PLUGINS%] runtime plugin registry (see getPluginRegistry)
|
|
152253
|
+
* [%PLUGINFOLDER%] runtime plugin folder (see getPluginFolder)
|
|
151892
152254
|
* [%PATH%] base URL for runtime/asset references
|
|
151893
152255
|
*/
|
|
151894
152256
|
|
|
@@ -151923,7 +152285,8 @@ class Util {
|
|
|
151923
152285
|
// Registers available plugins (not yet loaded).
|
|
151924
152286
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
151925
152287
|
const libraryInstance = new ContentBoxRuntime({
|
|
151926
|
-
plugins: [%PLUGINS%]
|
|
152288
|
+
plugins: [%PLUGINS%],
|
|
152289
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
151927
152290
|
});
|
|
151928
152291
|
libraryInstance.init();
|
|
151929
152292
|
</script>
|
|
@@ -151962,7 +152325,8 @@ class Util {
|
|
|
151962
152325
|
// Registers available plugins (not yet loaded).
|
|
151963
152326
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
151964
152327
|
const libraryInstance = new ContentBoxRuntime({
|
|
151965
|
-
plugins: [%PLUGINS%]
|
|
152328
|
+
plugins: [%PLUGINS%],
|
|
152329
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
151966
152330
|
});
|
|
151967
152331
|
libraryInstance.init();
|
|
151968
152332
|
</script>
|
|
@@ -151999,7 +152363,8 @@ class Util {
|
|
|
151999
152363
|
// Registers available plugins (not yet loaded).
|
|
152000
152364
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
152001
152365
|
const libraryInstance = new ContentBoxRuntime({
|
|
152002
|
-
plugins: [%PLUGINS%]
|
|
152366
|
+
plugins: [%PLUGINS%],
|
|
152367
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
152003
152368
|
});
|
|
152004
152369
|
libraryInstance.init();
|
|
152005
152370
|
</script>
|
|
@@ -152037,7 +152402,8 @@ class Util {
|
|
|
152037
152402
|
// Registers available plugins (not yet loaded).
|
|
152038
152403
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
152039
152404
|
const libraryInstance = new ContentBoxRuntime({
|
|
152040
|
-
plugins: [%PLUGINS%]
|
|
152405
|
+
plugins: [%PLUGINS%],
|
|
152406
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
152041
152407
|
});
|
|
152042
152408
|
libraryInstance.init();
|
|
152043
152409
|
</script>
|
|
@@ -152102,6 +152468,36 @@ class Util {
|
|
|
152102
152468
|
});
|
|
152103
152469
|
return JSON.stringify(registry, null, 4);
|
|
152104
152470
|
}
|
|
152471
|
+
/*
|
|
152472
|
+
* The runtime's plugin folder (pluginFolder in blank.html), as a JS string.
|
|
152473
|
+
* Plugins used in the content load from it without being registered, so an
|
|
152474
|
+
* exported page keeps them. A URL under the content frame's base becomes a
|
|
152475
|
+
* [%PATH%] token, like the registry's URLs.
|
|
152476
|
+
*/
|
|
152477
|
+
|
|
152478
|
+
|
|
152479
|
+
getPluginFolder() {
|
|
152480
|
+
const win = this.builder.win;
|
|
152481
|
+
let folder = '';
|
|
152482
|
+
|
|
152483
|
+
try {
|
|
152484
|
+
folder = win.builderRuntime.settings.pluginFolder || '';
|
|
152485
|
+
} catch (e) {
|
|
152486
|
+
/* no runtime in the content frame */
|
|
152487
|
+
}
|
|
152488
|
+
|
|
152489
|
+
let base = '';
|
|
152490
|
+
|
|
152491
|
+
try {
|
|
152492
|
+
const href = win.location.href;
|
|
152493
|
+
base = href.substring(0, href.lastIndexOf('/'));
|
|
152494
|
+
} catch (e) {
|
|
152495
|
+
/* cross-origin content frame */
|
|
152496
|
+
}
|
|
152497
|
+
|
|
152498
|
+
if (base && folder.startsWith(base + '/')) folder = '[%PATH%]' + folder.substring(base.length);
|
|
152499
|
+
return JSON.stringify(folder);
|
|
152500
|
+
}
|
|
152105
152501
|
|
|
152106
152502
|
}
|
|
152107
152503
|
|
|
@@ -166555,10 +166951,6 @@ class IframePanel {
|
|
|
166555
166951
|
let screenMode = localStorage.getItem('_screenmode');
|
|
166556
166952
|
|
|
166557
166953
|
if (localStorage.getItem('_screenmode')) {
|
|
166558
|
-
if (!this.builder.controlPanel && this.builder.iframeSrc) {
|
|
166559
|
-
if (screenMode === 'fullview') screenMode = 'desktop';
|
|
166560
|
-
}
|
|
166561
|
-
|
|
166562
166954
|
this.builder.screenMode = screenMode;
|
|
166563
166955
|
contentView.classList.add(screenMode);
|
|
166564
166956
|
} else {
|
|
@@ -166646,8 +167038,22 @@ class IframePanel {
|
|
|
166646
167038
|
// addBreakpoint) and authored tablet values while the user believed they were
|
|
166647
167039
|
// authoring desktop. Scaling fullview the same way the device modes do puts the
|
|
166648
167040
|
// viewport back in the desktop band, so no control needs to know about this.
|
|
167041
|
+
// The topOffset setting in pixels. It is a CSS length ('55px', '3rem', 48), the same
|
|
167042
|
+
// value the topspace CSS uses, so a unit other than px is resolved, not read as px.
|
|
166649
167043
|
|
|
166650
167044
|
|
|
167045
|
+
topOffsetPx() {
|
|
167046
|
+
const value = this.builder.topOffset;
|
|
167047
|
+
if (value === undefined || value === null || value === '') return 55;
|
|
167048
|
+
if (typeof value === 'number') return value;
|
|
167049
|
+
const probe = document.createElement('div');
|
|
167050
|
+
probe.style.cssText = 'position:absolute;visibility:hidden;pointer-events:none;height:' + value;
|
|
167051
|
+
document.body.appendChild(probe);
|
|
167052
|
+
const px = probe.getBoundingClientRect().height;
|
|
167053
|
+
probe.remove();
|
|
167054
|
+
return px || parseInt(value) || 55;
|
|
167055
|
+
}
|
|
167056
|
+
|
|
166651
167057
|
applyFullviewScale() {
|
|
166652
167058
|
const contentView = this.contentView;
|
|
166653
167059
|
if (!contentView || !this.editPanel) return;
|
|
@@ -166683,7 +167089,7 @@ class IframePanel {
|
|
|
166683
167089
|
|
|
166684
167090
|
|
|
166685
167091
|
const hasTopSpace = this.builder.topSpace || document.body.classList.contains('topspace');
|
|
166686
|
-
const topOffset = hasTopSpace ?
|
|
167092
|
+
const topOffset = hasTopSpace ? this.topOffsetPx() : 0;
|
|
166687
167093
|
const scale = availableWidth / logicalWidth;
|
|
166688
167094
|
const availableHeight = this.editPanel.clientHeight - topOffset;
|
|
166689
167095
|
contentView.style.setProperty('--fullview-top', `${topOffset}px`);
|
|
@@ -166758,7 +167164,7 @@ class IframePanel {
|
|
|
166758
167164
|
|
|
166759
167165
|
|
|
166760
167166
|
const hasTopSpace = this.builder.topSpace || document.body.classList.contains('topspace');
|
|
166761
|
-
const topOffset = hasTopSpace ?
|
|
167167
|
+
const topOffset = hasTopSpace ? this.topOffsetPx() : 0; // A manual width drag writes an inline width and leaves the mode class alone, so
|
|
166762
167168
|
// that width is the frame the user asked for. Reading it is safe: it is an input
|
|
166763
167169
|
// here, never something this method writes.
|
|
166764
167170
|
|
|
@@ -175921,6 +176327,12 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
175921
176327
|
outline-offset: -1px;
|
|
175922
176328
|
}
|
|
175923
176329
|
|
|
176330
|
+
/* A faded-out overlay layer covering visible content, under the pointer.
|
|
176331
|
+
Clicks go through it. See ControlPanel.bindLayerPassThrough. */
|
|
176332
|
+
.is-wrapper [data-overlay-passthrough] {
|
|
176333
|
+
pointer-events: none !important;
|
|
176334
|
+
}
|
|
176335
|
+
|
|
175924
176336
|
/* Hovering a row of the panel's layer list. Locates the element while
|
|
175925
176337
|
leaving the selection outline alone. */
|
|
175926
176338
|
.is-wrapper [data-overlay-hover] {
|
|
@@ -175931,13 +176343,20 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
175931
176343
|
/* An overlay layer is edited through the control panel, not in place, so
|
|
175932
176344
|
a text I-beam here would promise a caret that never arrives. Regions
|
|
175933
176345
|
that ARE directly editable inside an overlay (a plugin's .is-subblock,
|
|
175934
|
-
which enableEditableRegions makes contentEditable) keep it.
|
|
175935
|
-
|
|
175936
|
-
|
|
176346
|
+
which enableEditableRegions makes contentEditable) keep it. Tool
|
|
176347
|
+
buttons and .is-container islands (normal in-place content) are left
|
|
176348
|
+
alone. :where() keeps this rule at the lowest specificity so it never
|
|
176349
|
+
overrides another cursor rule. */
|
|
176350
|
+
.is-wrapper :where(${this.overlayEditableSelector},
|
|
176351
|
+
${this.overlayEditableSelector} *:not(.is-tool, .is-tool *, .is-container, .is-container *)) {
|
|
175937
176352
|
cursor: default;
|
|
175938
176353
|
}
|
|
175939
|
-
|
|
175940
|
-
.is-wrapper ${this.overlayEditableSelector}
|
|
176354
|
+
/* cursor is inherited: give the containers back the normal behaviour */
|
|
176355
|
+
.is-wrapper :where(${this.overlayEditableSelector} .is-container) {
|
|
176356
|
+
cursor: auto;
|
|
176357
|
+
}
|
|
176358
|
+
.is-wrapper :where(${this.overlayEditableSelector} [contenteditable="true"]:not(.is-container, .is-container *),
|
|
176359
|
+
${this.overlayEditableSelector} [contenteditable="true"]:not(.is-container, .is-container *) *) {
|
|
175941
176360
|
cursor: text;
|
|
175942
176361
|
}
|
|
175943
176362
|
|
|
@@ -176542,6 +176961,9 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
176542
176961
|
}
|
|
176543
176962
|
|
|
176544
176963
|
i.bi, i.icon { display: inline-flex; }
|
|
176964
|
+
/* As a flex box an icon ignores Bootstrap Icons' own vertical-align (on its ::before) and
|
|
176965
|
+
sits higher than the text beside it. Put the same offset on the icon itself. */
|
|
176966
|
+
i.bi { vertical-align: -.125em; }
|
|
176545
176967
|
|
|
176546
176968
|
</style>`); // i.bi, i.icon { display: inline-flex; } => this makes icons more cursor friendly
|
|
176547
176969
|
|
|
@@ -178086,12 +178508,7 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
178086
178508
|
}
|
|
178087
178509
|
e.stopImmediatePropagation();
|
|
178088
178510
|
*/
|
|
178089
|
-
|
|
178090
|
-
|
|
178091
|
-
if (answer) {
|
|
178092
|
-
window.open(url, '_blank');
|
|
178093
|
-
}
|
|
178094
|
-
|
|
178511
|
+
// Editing: a click selects the link, it never leaves the page
|
|
178095
178512
|
e.preventDefault();
|
|
178096
178513
|
}
|
|
178097
178514
|
}
|
|
@@ -178700,15 +179117,70 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
178700
179117
|
newArea = doc.body.innerHTML;
|
|
178701
179118
|
this.editor.saveForUndo();
|
|
178702
179119
|
const d = new Date();
|
|
178703
|
-
newArea = newArea.replace('[%YEAR%]', d.getFullYear());
|
|
179120
|
+
newArea = newArea.replace('[%YEAR%]', d.getFullYear()); // A navigation bar is a section that IS one, not any HTML that mentions the
|
|
179121
|
+
// class (a section's own CSS may style itself relative to a bar above it).
|
|
179122
|
+
|
|
179123
|
+
const isNavbar = el => el.matches('.is-section-navbar, [data-cb-type="nav-menu"]') || !!el.querySelector('[data-cb-type="nav-menu"]'); // The site supplies its own menu above the content (setTopContentProvider,
|
|
179124
|
+
// locked): it is the page's navigation, so a design's own bar is left out
|
|
179125
|
+
// and the rest of the design (a header) goes in first, under the menu.
|
|
179126
|
+
|
|
179127
|
+
|
|
179128
|
+
const lockedTop = Array.from(this.wrapperEl.children).filter(el => el.hasAttribute('data-top-locked'));
|
|
178704
179129
|
|
|
178705
|
-
if (
|
|
179130
|
+
if (lockedTop.length && Array.from(doc.body.children).some(isNavbar)) {
|
|
179131
|
+
const tpl = document.createElement('template');
|
|
179132
|
+
tpl.innerHTML = newArea; // The bar's text colour (the colour its design gives it over the header).
|
|
179133
|
+
|
|
179134
|
+
let barInk = '';
|
|
179135
|
+
Array.from(tpl.content.children).filter(isNavbar).forEach(el => {
|
|
179136
|
+
const m = /--bar-color:\s*var\(--bar-ink,\s*(rgba?\([^)]*\)|#[0-9a-fA-F]{3,8})\s*\)/.exec(el.outerHTML);
|
|
179137
|
+
if (m && !barInk) barInk = m[1];
|
|
179138
|
+
el.remove();
|
|
179139
|
+
}); // The header was made with a bar floating over its top: say so, so the
|
|
179140
|
+
// site's menu can float over it the same way, in the colour the header's
|
|
179141
|
+
// own bar used there.
|
|
179142
|
+
|
|
179143
|
+
const header = Array.from(tpl.content.children).find(el => el.classList.contains('is-section'));
|
|
179144
|
+
|
|
179145
|
+
if (header) {
|
|
179146
|
+
header.setAttribute('data-under-bar', '');
|
|
179147
|
+
if (barInk && !header.hasAttribute('data-bar-rest-ink')) header.setAttribute('data-bar-rest-ink', barInk);
|
|
179148
|
+
}
|
|
179149
|
+
|
|
179150
|
+
if (!tpl.content.children.length) {
|
|
179151
|
+
this.editor.util.confirm(out('This site\'s menu is managed in the menu settings.'), () => {}, false, out('OK'), true);
|
|
179152
|
+
return;
|
|
179153
|
+
}
|
|
179154
|
+
|
|
179155
|
+
dom.appendHtml(lockedTop[lockedTop.length - 1], tpl.innerHTML, 'afterend');
|
|
179156
|
+
} else if (Array.from(doc.body.children).some(isNavbar)) {
|
|
178706
179157
|
// Insert first
|
|
178707
179158
|
const sectionNavbar = this.wrapperEl.querySelector('.is-section-navbar');
|
|
178708
179159
|
|
|
178709
179160
|
if (sectionNavbar) {
|
|
178710
|
-
//
|
|
179161
|
+
// A design that brings its own bar together with other sections (a header):
|
|
179162
|
+
// keep the page's bar and insert the rest, or replace the bar too.
|
|
179163
|
+
const tpl = document.createElement('template');
|
|
179164
|
+
tpl.innerHTML = newArea;
|
|
179165
|
+
const newBar = Array.from(tpl.content.children).find(isNavbar);
|
|
179166
|
+
|
|
179167
|
+
if (newBar && tpl.content.children.length > 1) {
|
|
179168
|
+
this.editor.util.confirm(out('This page already has a navigation bar.'), (keep, dismissed) => {
|
|
179169
|
+
if (dismissed) return; // closed without choosing
|
|
179170
|
+
|
|
179171
|
+
if (keep) {
|
|
179172
|
+
newBar.remove();
|
|
179173
|
+
this.addIdea(tpl.innerHTML);
|
|
179174
|
+
} else {
|
|
179175
|
+
sectionNavbar.parentNode.removeChild(sectionNavbar);
|
|
179176
|
+
this.addIdea(newArea);
|
|
179177
|
+
}
|
|
179178
|
+
}, true, out('Keep current'), true, 'Replace');
|
|
179179
|
+
return;
|
|
179180
|
+
} // let delConf = this.deleteConfirm;
|
|
178711
179181
|
// this.editor.deleteConfirm = true;
|
|
179182
|
+
|
|
179183
|
+
|
|
178712
179184
|
this.editor.util.confirm(out('Do you want to replace the current navigation bar?'), ok => {
|
|
178713
179185
|
if (ok) {
|
|
178714
179186
|
sectionNavbar.parentNode.removeChild(sectionNavbar);
|
|
@@ -178957,7 +179429,10 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
178957
179429
|
// }
|
|
178958
179430
|
|
|
178959
179431
|
if (lastNewSection) {
|
|
178960
|
-
|
|
179432
|
+
// A template can bring several sections; a pinned one among them (not
|
|
179433
|
+
// only the last) needs the full re-render that sets up its pin.
|
|
179434
|
+
const anyPinned = Array.from(newSections).some(s => s.classList.contains('section-pin') || !!s.querySelector('.section-pin'));
|
|
179435
|
+
this.refreshSectionAnim(lastNewSection, anyPinned);
|
|
178961
179436
|
this.scrollTo(lastNewSection, 100);
|
|
178962
179437
|
}
|
|
178963
179438
|
|
|
@@ -179223,15 +179698,19 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
179223
179698
|
const btnSectionUp = sectionTool.querySelector('.btn-move-section-up');
|
|
179224
179699
|
const btnSectionDown = sectionTool.querySelector('.btn-move-section-down');
|
|
179225
179700
|
const btnSectionRemove = sectionTool.querySelector('.btn-section-remove');
|
|
179701
|
+
const btnSectionEdit = sectionTool.querySelector('.btn-section-edit');
|
|
179226
179702
|
|
|
179227
179703
|
if (box.offsetHeight < 160) {
|
|
179704
|
+
// Short section (e.g. a nav bar): room for two buttons, HTML and Remove
|
|
179228
179705
|
btnSectionUp.style.display = 'none';
|
|
179229
179706
|
btnSectionDown.style.display = 'none';
|
|
179230
|
-
|
|
179707
|
+
if (btnSectionEdit) btnSectionEdit.style.display = 'none';
|
|
179708
|
+
btnSectionRemove.style.display = '';
|
|
179231
179709
|
sectionTool.style.top = '17px'; //'calc(50% - 17px)';
|
|
179232
179710
|
} else {
|
|
179233
179711
|
btnSectionUp.style.display = '';
|
|
179234
179712
|
btnSectionDown.style.display = '';
|
|
179713
|
+
if (btnSectionEdit) btnSectionEdit.style.display = '';
|
|
179235
179714
|
btnSectionRemove.style.display = '';
|
|
179236
179715
|
}
|
|
179237
179716
|
} //new box tool
|
|
@@ -179914,7 +180393,16 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
179914
180393
|
|
|
179915
180394
|
this.activeBox = null;
|
|
179916
180395
|
if (this.editor) this.editor.activeBox = null;
|
|
179917
|
-
this.activeSection = null; //
|
|
180396
|
+
this.activeSection = null; // New content: whatever the control panel was showing belongs to the
|
|
180397
|
+
// old page (e.g. after Clear Content), so return it to its empty state.
|
|
180398
|
+
|
|
180399
|
+
if (this.controlPanel && this.controlpanel) this.controlpanel.select(''); // ...and so do the floating tools on the page (the element tool).
|
|
180400
|
+
|
|
180401
|
+
if (this.editor && this.editor.util && this.editor.doc) {
|
|
180402
|
+
this.editor.util.clearActiveElement();
|
|
180403
|
+
this.editor.util.hideControls();
|
|
180404
|
+
} // Wait for IFRAME loaded
|
|
180405
|
+
|
|
179918
180406
|
|
|
179919
180407
|
const wrapper = this.wrapperEl;
|
|
179920
180408
|
|
|
@@ -180350,9 +180838,10 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
180350
180838
|
// html.js attribute sweep: the attributes are ContentBox's, so the
|
|
180351
180839
|
// cleanup belongs on this side.
|
|
180352
180840
|
|
|
180353
|
-
clone.querySelectorAll('[data-overlay-select], [data-overlay-hover]').forEach(elm => {
|
|
180841
|
+
clone.querySelectorAll('[data-overlay-select], [data-overlay-hover], [data-overlay-passthrough]').forEach(elm => {
|
|
180354
180842
|
elm.removeAttribute('data-overlay-select');
|
|
180355
180843
|
elm.removeAttribute('data-overlay-hover');
|
|
180844
|
+
elm.removeAttribute('data-overlay-passthrough');
|
|
180356
180845
|
});
|
|
180357
180846
|
}
|
|
180358
180847
|
|
|
@@ -180823,11 +181312,15 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
180823
181312
|
let mainCss = this.mainCss(true);
|
|
180824
181313
|
let sectionCss = this.sectionCss(true);
|
|
180825
181314
|
output = output.replaceAll('[%MAINCSS%]', mainCss);
|
|
180826
|
-
output = output.replaceAll('[%SECTIONCSS%]', sectionCss); // [%PLUGINS%] - runtime plugin registry (must run
|
|
180827
|
-
//
|
|
181315
|
+
output = output.replaceAll('[%SECTIONCSS%]', sectionCss); // [%PLUGINS%], [%PLUGINFOLDER%] - runtime plugin registry and folder (must run
|
|
181316
|
+
// before [%PATH%], their URLs are emitted as [%PATH%] tokens)
|
|
180828
181317
|
|
|
180829
181318
|
if (output.indexOf('[%PLUGINS%]') !== -1) {
|
|
180830
181319
|
output = output.replaceAll('[%PLUGINS%]', this.util.getPluginRegistry());
|
|
181320
|
+
}
|
|
181321
|
+
|
|
181322
|
+
if (output.indexOf('[%PLUGINFOLDER%]') !== -1) {
|
|
181323
|
+
output = output.replaceAll('[%PLUGINFOLDER%]', this.util.getPluginFolder());
|
|
180831
181324
|
} // Custom
|
|
180832
181325
|
|
|
180833
181326
|
|