@innovastudio/contentbox 1.6.203 → 1.6.204
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) {
|
|
@@ -30004,7 +30067,7 @@ class PanelBox {
|
|
|
30004
30067
|
<button title="${out('Color')}" class="btn-color is-btn-color"></button>
|
|
30005
30068
|
</div>
|
|
30006
30069
|
</div>
|
|
30007
|
-
<div>
|
|
30070
|
+
<div class="div-box-gradient">
|
|
30008
30071
|
<div class="label mt-3">${out('Gradient')}:</div>
|
|
30009
30072
|
<div class="group">
|
|
30010
30073
|
<button title="${out('Gradient')}" class="btn-gradient is-btn-color"></button>
|
|
@@ -30074,10 +30137,9 @@ class PanelBox {
|
|
|
30074
30137
|
|
|
30075
30138
|
</div>
|
|
30076
30139
|
|
|
30077
|
-
<div class="submain box-
|
|
30078
|
-
|
|
30079
|
-
<
|
|
30080
|
-
|
|
30140
|
+
<div class="submain div-box-plugins" style="display:none">
|
|
30141
|
+
<div class="label mt-3">${out('In this box')}:</div>
|
|
30142
|
+
<div class="overlay-list mt-1"></div>
|
|
30081
30143
|
</div>
|
|
30082
30144
|
|
|
30083
30145
|
<button class="accordion-item btn-accordion-content" aria-expanded="false" aria-controls="boxcontent1">
|
|
@@ -31134,6 +31196,13 @@ class PanelBox {
|
|
|
31134
31196
|
|
|
31135
31197
|
static css() {
|
|
31136
31198
|
return `
|
|
31199
|
+
.cb-navbar-box .boxedit,
|
|
31200
|
+
.cb-navbar-box .div-box-gradient,
|
|
31201
|
+
.cb-navbar-box .box-bg-image,
|
|
31202
|
+
.cb-navbar-box > .accordion-item,
|
|
31203
|
+
.cb-navbar-box > .accordion-content {
|
|
31204
|
+
display: none !important;
|
|
31205
|
+
}
|
|
31137
31206
|
[data-cb-ui] .cb-contentpos .cb-fieldhead {
|
|
31138
31207
|
display: flex;
|
|
31139
31208
|
align-items: baseline;
|
|
@@ -31445,7 +31514,10 @@ class PanelBox {
|
|
|
31445
31514
|
const overlayBg = activeBox.querySelector('.is-overlay-bg');
|
|
31446
31515
|
const overlayContent = activeBox.querySelector('.is-overlay-content');
|
|
31447
31516
|
const container = this.box.contentContainer(activeBox);
|
|
31448
|
-
const panel = this.panel;
|
|
31517
|
+
const panel = this.panel; // A navigation bar keeps only its background colour (the bar's colour once the
|
|
31518
|
+
// header has scrolled by): no duplicate, extra box, gradient, image or box tabs
|
|
31519
|
+
|
|
31520
|
+
panel.classList.toggle('cb-navbar-box', !!activeBox.closest('.is-section-navbar'));
|
|
31449
31521
|
const divEditModule = panel.querySelector('.group.editmodule');
|
|
31450
31522
|
|
|
31451
31523
|
if (overlayContent && overlayContent.getAttribute('data-module')) {
|
|
@@ -31789,12 +31861,12 @@ class PanelBox {
|
|
|
31789
31861
|
chkAutoLayout.checked = true;
|
|
31790
31862
|
} else {
|
|
31791
31863
|
chkAutoLayout.checked = false;
|
|
31792
|
-
} //
|
|
31864
|
+
} // PLUGINS
|
|
31865
|
+
// A plugin in the box overlay has its own panel: a click on it opens that.
|
|
31866
|
+
// Here the box lists them, and each can hide box settings it has no use for.
|
|
31793
31867
|
|
|
31794
31868
|
|
|
31795
|
-
const
|
|
31796
|
-
this.divPluginSettings = divPluginSettings;
|
|
31797
|
-
const runtime = this.builder.win.builderRuntime; // Reset (can be changed by renderPluginSettings)
|
|
31869
|
+
const runtime = this.builder.win.builderRuntime; // Reset (can be changed by applyPluginOptions)
|
|
31798
31870
|
|
|
31799
31871
|
const divBgColor = this.panel.querySelector('.box-bg-color');
|
|
31800
31872
|
divBgColor.style.display = '';
|
|
@@ -31805,39 +31877,55 @@ class PanelBox {
|
|
|
31805
31877
|
const divPanelTitle = this.panel.parentNode.querySelector('.part-title');
|
|
31806
31878
|
divPanelTitle.style.display = '';
|
|
31807
31879
|
this.divPanelTitle = divPanelTitle;
|
|
31880
|
+
const divPlugins = panel.querySelector('.div-box-plugins');
|
|
31881
|
+
const list = divPlugins.querySelector('.overlay-list');
|
|
31882
|
+
list.innerHTML = ''; // The box's own plugins: not the ones inside its content areas, nor inside another plugin
|
|
31808
31883
|
|
|
31809
|
-
|
|
31810
|
-
const currentElement = overlayContent.querySelector('[data-cb-type]');
|
|
31884
|
+
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
31885
|
|
|
31812
|
-
|
|
31813
|
-
|
|
31814
|
-
|
|
31815
|
-
|
|
31816
|
-
|
|
31817
|
-
|
|
31818
|
-
|
|
31819
|
-
|
|
31820
|
-
|
|
31821
|
-
|
|
31886
|
+
const counts = {},
|
|
31887
|
+
seen = {};
|
|
31888
|
+
plugins.forEach(el => {
|
|
31889
|
+
const t = el.getAttribute('data-cb-type');
|
|
31890
|
+
counts[t] = (counts[t] || 0) + 1;
|
|
31891
|
+
});
|
|
31892
|
+
plugins.forEach(el => {
|
|
31893
|
+
const type = el.getAttribute('data-cb-type');
|
|
31894
|
+
const plugin = runtime.getPlugin(type);
|
|
31895
|
+
if (!plugin) return;
|
|
31896
|
+
seen[type] = (seen[type] || 0) + 1;
|
|
31897
|
+
this.applyPluginOptions(el, plugin);
|
|
31898
|
+
const row = this.panel.ownerDocument.createElement('button');
|
|
31899
|
+
row.type = 'button';
|
|
31900
|
+
row.className = 'ovl-row';
|
|
31901
|
+
const label = this.panel.ownerDocument.createElement('span');
|
|
31902
|
+
label.className = 'ovl-row-text';
|
|
31903
|
+
label.textContent = out(plugin.displayName || plugin.name) + (counts[type] > 1 ? ' ' + seen[type] : '');
|
|
31904
|
+
row.appendChild(label);
|
|
31905
|
+
row.addEventListener('click', () => {
|
|
31906
|
+
this.builder.controlpanel.markOverlayHover(null);
|
|
31907
|
+
this.builder.controlpanel.selectOverlayElement(el);
|
|
31908
|
+
});
|
|
31909
|
+
row.addEventListener('mouseenter', () => this.builder.controlpanel.markOverlayHover(el));
|
|
31910
|
+
row.addEventListener('mouseleave', () => this.builder.controlpanel.markOverlayHover(null));
|
|
31911
|
+
list.appendChild(row);
|
|
31912
|
+
});
|
|
31913
|
+
divPlugins.style.display = list.childElementCount ? '' : 'none';
|
|
31914
|
+
} // True when the plugin element fills (practically) the whole box overlay.
|
|
31822
31915
|
|
|
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
31916
|
|
|
31840
|
-
|
|
31917
|
+
pluginCoversBox(element) {
|
|
31918
|
+
const overlay = element.closest('.is-overlay');
|
|
31919
|
+
if (!overlay) return true;
|
|
31920
|
+
const box = overlay.getBoundingClientRect();
|
|
31921
|
+
const el = element.getBoundingClientRect();
|
|
31922
|
+
if (!box.width || !box.height) return true;
|
|
31923
|
+
return el.width >= box.width * 0.98 && el.height >= box.height * 0.98;
|
|
31924
|
+
} // The box settings a plugin in this box has no use for (its contentbox options)
|
|
31925
|
+
|
|
31926
|
+
|
|
31927
|
+
applyPluginOptions(currentElement, plugin) {
|
|
31928
|
+
if (plugin.contentbox && currentElement.querySelector('.is-container')) {
|
|
31841
31929
|
// if plugin element has editable content
|
|
31842
31930
|
plugin.contentbox.boxTabText = false;
|
|
31843
31931
|
plugin.contentbox.boxTabContent = false;
|
|
@@ -31860,16 +31948,21 @@ class PanelBox {
|
|
|
31860
31948
|
|
|
31861
31949
|
|
|
31862
31950
|
if (plugin.contentbox) {
|
|
31863
|
-
|
|
31951
|
+
// A plugin hides the box background (bg: false) because it covers it,
|
|
31952
|
+
// like a full-bleed video. When the plugin only fills part of the box
|
|
31953
|
+
// (a framed video, a card), the background is visible and stays editable.
|
|
31954
|
+
const coversBox = this.pluginCoversBox(currentElement);
|
|
31955
|
+
|
|
31956
|
+
if (coversBox && plugin.contentbox.bg !== undefined && plugin.contentbox.bg !== null && plugin.contentbox.bg === false) {
|
|
31864
31957
|
this.divBgColor.style.display = 'none';
|
|
31865
31958
|
this.divBgImage.style.display = 'none';
|
|
31866
31959
|
}
|
|
31867
31960
|
|
|
31868
|
-
if (plugin.contentbox.bgColor !== undefined && plugin.contentbox.bgColor !== null && plugin.contentbox.bgColor === false) {
|
|
31961
|
+
if (coversBox && plugin.contentbox.bgColor !== undefined && plugin.contentbox.bgColor !== null && plugin.contentbox.bgColor === false) {
|
|
31869
31962
|
this.divBgColor.style.display = 'none';
|
|
31870
31963
|
}
|
|
31871
31964
|
|
|
31872
|
-
if (plugin.contentbox.bgImage !== undefined && plugin.contentbox.bgImage !== null && plugin.contentbox.bgImage === false) {
|
|
31965
|
+
if (coversBox && plugin.contentbox.bgImage !== undefined && plugin.contentbox.bgImage !== null && plugin.contentbox.bgImage === false) {
|
|
31873
31966
|
this.divBgImage.style.display = 'none';
|
|
31874
31967
|
}
|
|
31875
31968
|
|
|
@@ -31941,111 +32034,6 @@ class PanelBox {
|
|
|
31941
32034
|
});
|
|
31942
32035
|
}
|
|
31943
32036
|
}
|
|
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
32037
|
}
|
|
32050
32038
|
|
|
32051
32039
|
getStateTargeted() {
|
|
@@ -33713,6 +33701,31 @@ class PanelSection {
|
|
|
33713
33701
|
|
|
33714
33702
|
</div>
|
|
33715
33703
|
|
|
33704
|
+
<button class="accordion-item btn-accordion-bar" aria-expanded="false" aria-controls="sectioncontent5">
|
|
33705
|
+
${out('Navigation Bar')}
|
|
33706
|
+
<span><svg><use xlink:href="#icon-chevron-down"></use></svg></span>
|
|
33707
|
+
</button>
|
|
33708
|
+
<div class="accordion-content" id="sectioncontent5" aria-hidden="true">
|
|
33709
|
+
|
|
33710
|
+
<div class="subpanel">
|
|
33711
|
+
<div class="label mt-2">${out('Bar over this section')}:</div>
|
|
33712
|
+
<div role="group" aria-label="${out('Bar over this section')}" class="group sectionbar">
|
|
33713
|
+
<button title="${out('Default')}" data-bar="">${out('Default')}</button>
|
|
33714
|
+
<button title="${out('Light')}" data-bar="light">${out('Light')}</button>
|
|
33715
|
+
<button title="${out('Dark')}" data-bar="dark">${out('Dark')}</button>
|
|
33716
|
+
<button title="${out('Light glass')}" data-bar="light-glass">${out('Light glass')}</button>
|
|
33717
|
+
<button title="${out('Dark glass')}" data-bar="dark-glass">${out('Dark glass')}</button>
|
|
33718
|
+
</div>
|
|
33719
|
+
|
|
33720
|
+
<div class="label mt-3">${out('Text Color')}:</div>
|
|
33721
|
+
<div class="group">
|
|
33722
|
+
<button title="${out('Text Color')}" class="btn-barink is-btn-color"></button>
|
|
33723
|
+
<button title="${out('Clear')}" class="btn-clear btn-barink-clear"><svg><use xlink:href="#icon-eraser"></use></svg></button>
|
|
33724
|
+
</div>
|
|
33725
|
+
</div>
|
|
33726
|
+
|
|
33727
|
+
</div>
|
|
33728
|
+
|
|
33716
33729
|
<button class="accordion-item" aria-expanded="false" aria-controls="sectioncontent3">
|
|
33717
33730
|
${out('Responsive')}
|
|
33718
33731
|
<span><svg><use xlink:href="#icon-chevron-down"></use></svg></span>
|
|
@@ -33904,6 +33917,40 @@ class PanelSection {
|
|
|
33904
33917
|
btnScrollPreset.forEach(btn => btn.classList.remove('on'));
|
|
33905
33918
|
if (s !== '') btn.classList.add('on');
|
|
33906
33919
|
});
|
|
33920
|
+
}); // Navigation Bar: how the page's navigation bar looks while it passes over this
|
|
33921
|
+
// section. The section only says it (data-bar, data-bar-ink); the menu in the bar
|
|
33922
|
+
// does the work.
|
|
33923
|
+
|
|
33924
|
+
const btnBarLooks = panel.querySelectorAll('[data-bar]');
|
|
33925
|
+
btnBarLooks.forEach(btn => {
|
|
33926
|
+
btn.addEventListener('click', () => {
|
|
33927
|
+
this.builder.editor.saveForUndo();
|
|
33928
|
+
const section = this.builder.activeSection;
|
|
33929
|
+
const s = btn.getAttribute('data-bar');
|
|
33930
|
+
if (s) section.setAttribute('data-bar', s);else section.removeAttribute('data-bar');
|
|
33931
|
+
btnBarLooks.forEach(btn => btn.classList.remove('on'));
|
|
33932
|
+
btn.classList.add('on');
|
|
33933
|
+
this.builder.onChange();
|
|
33934
|
+
});
|
|
33935
|
+
});
|
|
33936
|
+
const btnBarInk = panel.querySelector('.btn-barink');
|
|
33937
|
+
btnBarInk.addEventListener('click', () => {
|
|
33938
|
+
this.builder.editor.saveForUndo(true); // checkLater = true
|
|
33939
|
+
|
|
33940
|
+
const section = this.builder.activeSection;
|
|
33941
|
+
const colorpicker = this.builder.editor.colorpicker();
|
|
33942
|
+
colorpicker.open(s => {
|
|
33943
|
+
if (s) section.setAttribute('data-bar-ink', s);else section.removeAttribute('data-bar-ink');
|
|
33944
|
+
btnBarInk.style.backgroundColor = s; // preview
|
|
33945
|
+
|
|
33946
|
+
this.builder.onChange();
|
|
33947
|
+
}, btnBarInk.style.backgroundColor, () => {}, btnBarInk, true);
|
|
33948
|
+
});
|
|
33949
|
+
panel.querySelector('.btn-barink-clear').addEventListener('click', () => {
|
|
33950
|
+
this.builder.editor.saveForUndo();
|
|
33951
|
+
this.builder.activeSection.removeAttribute('data-bar-ink');
|
|
33952
|
+
btnBarInk.style.backgroundColor = '';
|
|
33953
|
+
this.builder.onChange();
|
|
33907
33954
|
}); // Responsive
|
|
33908
33955
|
|
|
33909
33956
|
bindDeviceRow(panel.querySelector('.div-target'), this.builder);
|
|
@@ -34142,6 +34189,26 @@ class PanelSection {
|
|
|
34142
34189
|
btnGradient.style.backgroundImage = gradient; //preview
|
|
34143
34190
|
} else {
|
|
34144
34191
|
btnGradient.style.backgroundImage = '';
|
|
34192
|
+
} // A navigation bar section: no height presets and no section tabs
|
|
34193
|
+
|
|
34194
|
+
|
|
34195
|
+
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
|
|
34196
|
+
// (one that says barLooks: true)
|
|
34197
|
+
|
|
34198
|
+
const btnAccordionBar = panel.querySelector('.btn-accordion-bar');
|
|
34199
|
+
const runtime = this.builder.win.builderRuntime;
|
|
34200
|
+
const barPlugins = this.builder.wrapperEl ? Array.from(this.builder.wrapperEl.querySelectorAll('.is-section-navbar [data-cb-type]')) : [];
|
|
34201
|
+
const pageBar = !!runtime && barPlugins.some(el => {
|
|
34202
|
+
const plugin = runtime.getPlugin(el.getAttribute('data-cb-type'));
|
|
34203
|
+
return !!(plugin && plugin.barLooks);
|
|
34204
|
+
});
|
|
34205
|
+
const showBar = pageBar && !activeSection.classList.contains('is-section-navbar');
|
|
34206
|
+
btnAccordionBar.classList.toggle('is-bar-off', !showBar);
|
|
34207
|
+
|
|
34208
|
+
if (showBar) {
|
|
34209
|
+
const look = activeSection.getAttribute('data-bar') || '';
|
|
34210
|
+
panel.querySelectorAll('[data-bar]').forEach(btn => btn.classList.toggle('on', btn.getAttribute('data-bar') === look));
|
|
34211
|
+
panel.querySelector('.btn-barink').style.backgroundColor = activeSection.getAttribute('data-bar-ink') || '';
|
|
34145
34212
|
}
|
|
34146
34213
|
|
|
34147
34214
|
this.realtimeVisibility(activeSection, true);
|
|
@@ -34246,6 +34313,13 @@ class PanelSection {
|
|
|
34246
34313
|
|
|
34247
34314
|
static css() {
|
|
34248
34315
|
return `
|
|
34316
|
+
.btn-accordion-bar.is-bar-off,
|
|
34317
|
+
.btn-accordion-bar.is-bar-off + .accordion-content ,
|
|
34318
|
+
.cb-navbar-section .div-section-height,
|
|
34319
|
+
.cb-navbar-section > .accordion-item,
|
|
34320
|
+
.cb-navbar-section > .accordion-content {
|
|
34321
|
+
display: none !important;
|
|
34322
|
+
}
|
|
34249
34323
|
.cb-sectionanim .cb-anim-note {
|
|
34250
34324
|
font-size: 11.5px;
|
|
34251
34325
|
line-height: 1.5;
|
|
@@ -43492,6 +43566,15 @@ class ControlPanel {
|
|
|
43492
43566
|
// elmTool, whose duplicate/delete/move actions would let a user
|
|
43493
43567
|
// restructure a generated layer.
|
|
43494
43568
|
this.builder.editor.inspectedElement = overlayTarget.element;
|
|
43569
|
+
} else {
|
|
43570
|
+
// A plugin in the box overlay opens its own panel (Section > Box > the
|
|
43571
|
+
// plugin), like overlay text. Its editable parts and content areas are
|
|
43572
|
+
// edited where they are.
|
|
43573
|
+
const plugin = e.target.closest ? e.target.closest('.is-overlay-content [data-cb-type]') : null;
|
|
43574
|
+
|
|
43575
|
+
if (plugin && !e.target.closest('.is-container') && !e.target.closest('.is-subblock')) {
|
|
43576
|
+
this.builder.editor.inspectedElement = plugin;
|
|
43577
|
+
}
|
|
43495
43578
|
}
|
|
43496
43579
|
|
|
43497
43580
|
if (old) old.call(this);
|
|
@@ -43636,8 +43719,92 @@ class ControlPanel {
|
|
|
43636
43719
|
this.builder.editor.controlpanel = this; // This overide ContentBuilder controlpanel object
|
|
43637
43720
|
|
|
43638
43721
|
this.bindEscape();
|
|
43722
|
+
this.bindLayerPassThrough();
|
|
43639
43723
|
this.showTooltip();
|
|
43640
43724
|
}
|
|
43725
|
+
/**
|
|
43726
|
+
* Clicks go through overlay layers that are faded out.
|
|
43727
|
+
*
|
|
43728
|
+
* A crossfade keeps every layer stacked at full size and only turns the
|
|
43729
|
+
* hidden ones transparent, so a click on the text you can see lands on an
|
|
43730
|
+
* invisible layer above it: an empty wrapper gets selected, or the caret
|
|
43731
|
+
* goes into text that is not on screen. hitTest() already sees through
|
|
43732
|
+
* these for the overlay panel; this does the same for the browser's own
|
|
43733
|
+
* hit test, which is what places the caret in a .is-container.
|
|
43734
|
+
*
|
|
43735
|
+
* Marks only exist under the pointer and are recomputed on every move and
|
|
43736
|
+
* scroll, so a layer that fades back in is clickable again. A faded layer
|
|
43737
|
+
* is only passed through when something visible lies below it; a stack
|
|
43738
|
+
* scrubbed to a moment where everything is faded stays selectable.
|
|
43739
|
+
* The marker is stripped from the saved HTML in html().
|
|
43740
|
+
*/
|
|
43741
|
+
|
|
43742
|
+
|
|
43743
|
+
bindLayerPassThrough() {
|
|
43744
|
+
if (!this.passThroughHandler) {
|
|
43745
|
+
let frame = 0;
|
|
43746
|
+
let x = -1,
|
|
43747
|
+
y = -1;
|
|
43748
|
+
|
|
43749
|
+
const update = () => {
|
|
43750
|
+
frame = 0;
|
|
43751
|
+
this.updateLayerPassThrough(x, y);
|
|
43752
|
+
};
|
|
43753
|
+
|
|
43754
|
+
this.passThroughHandler = e => {
|
|
43755
|
+
if (typeof e.clientX === 'number') {
|
|
43756
|
+
x = e.clientX;
|
|
43757
|
+
y = e.clientY;
|
|
43758
|
+
}
|
|
43759
|
+
|
|
43760
|
+
if (e.type === 'pointerdown') {
|
|
43761
|
+
// a touch has no move before it
|
|
43762
|
+
update();
|
|
43763
|
+
return;
|
|
43764
|
+
}
|
|
43765
|
+
|
|
43766
|
+
if (!frame) frame = this.builder.win.requestAnimationFrame(update);
|
|
43767
|
+
};
|
|
43768
|
+
}
|
|
43769
|
+
|
|
43770
|
+
const types = ['pointermove', 'pointerdown', 'scroll'];
|
|
43771
|
+
if (this.passThroughDoc) types.forEach(type => this.passThroughDoc.removeEventListener(type, this.passThroughHandler, true));
|
|
43772
|
+
this.passThroughDoc = this.builder.doc;
|
|
43773
|
+
if (this.passThroughDoc) types.forEach(type => this.passThroughDoc.addEventListener(type, this.passThroughHandler, true));
|
|
43774
|
+
}
|
|
43775
|
+
|
|
43776
|
+
updateLayerPassThrough(x, y) {
|
|
43777
|
+
const doc = this.builder.doc;
|
|
43778
|
+
if (!doc || !doc.elementsFromPoint) return;
|
|
43779
|
+
doc.querySelectorAll('[data-overlay-passthrough]').forEach(elm => {
|
|
43780
|
+
elm.removeAttribute('data-overlay-passthrough');
|
|
43781
|
+
});
|
|
43782
|
+
if (x < 0) return;
|
|
43783
|
+
const selector = this.builder.overlayEditableSelector;
|
|
43784
|
+
const stack = doc.elementsFromPoint(x, y);
|
|
43785
|
+
stack.forEach((el, i) => {
|
|
43786
|
+
const overlay = el.closest(selector); // Faded counts through the ancestors: text in a layer that is
|
|
43787
|
+
// faded out as a whole is painted itself, yet invisible.
|
|
43788
|
+
|
|
43789
|
+
if (!overlay || el === overlay || this.isVisibleIn(el, overlay)) return;
|
|
43790
|
+
const covered = stack.slice(i + 1).some(below => overlay.contains(below) && below !== overlay && !el.contains(below) && this.isLayerContent(below) && this.isVisibleIn(below, overlay));
|
|
43791
|
+
if (covered) el.setAttribute('data-overlay-passthrough', '');
|
|
43792
|
+
});
|
|
43793
|
+
} // Something a click is meant for: text, a picture, or a media element.
|
|
43794
|
+
|
|
43795
|
+
|
|
43796
|
+
isLayerContent(el) {
|
|
43797
|
+
return this.builder.hasOwnText(el) || el.matches('img,video,iframe,svg,a,button') || this.hasBackgroundImage(el);
|
|
43798
|
+
} // Painted itself and through every ancestor up to the overlay.
|
|
43799
|
+
|
|
43800
|
+
|
|
43801
|
+
isVisibleIn(el, overlay) {
|
|
43802
|
+
for (let node = el; node && node !== overlay; node = node.parentElement) {
|
|
43803
|
+
if (!this.isPainted(node)) return false;
|
|
43804
|
+
}
|
|
43805
|
+
|
|
43806
|
+
return true;
|
|
43807
|
+
}
|
|
43641
43808
|
/**
|
|
43642
43809
|
* Escape closes the open dialog (Element, Icons, Shadow, Image Adjust,
|
|
43643
43810
|
* Group, Block Options, Symbols, Settings).
|
|
@@ -44177,7 +44344,8 @@ class ControlPanel {
|
|
|
44177
44344
|
const el = stack[i];
|
|
44178
44345
|
|
|
44179
44346
|
if (match(el)) {
|
|
44180
|
-
|
|
44347
|
+
const overlay = el.closest(this.builder.overlayEditableSelector);
|
|
44348
|
+
if (overlay ? this.isVisibleIn(el, overlay) : this.isPainted(el)) return el; // Crossfade stacks (three images or three captions at the same
|
|
44181
44349
|
// spot, cycled by opacity keyframes) put a FULLY TRANSPARENT
|
|
44182
44350
|
// layer on top for most of the scene. Stacking order would hand
|
|
44183
44351
|
// back that one every time, so the user edits a layer they
|
|
@@ -44424,6 +44592,8 @@ class ControlPanel {
|
|
|
44424
44592
|
sectionHeightAllowed() {
|
|
44425
44593
|
const section = this.builder.activeSection;
|
|
44426
44594
|
if (!section || !section.classList.contains('is-box')) return true;
|
|
44595
|
+
if (section.classList.contains('is-section-navbar')) return false; // a navigation bar has no height presets
|
|
44596
|
+
|
|
44427
44597
|
const el = section.querySelector('.is-overlay-content [data-cb-type]');
|
|
44428
44598
|
if (!el) return true;
|
|
44429
44599
|
const runtime = this.builder.win.builderRuntime;
|
|
@@ -50657,6 +50827,10 @@ class ToolbarKeys {
|
|
|
50657
50827
|
*/
|
|
50658
50828
|
_hold(container, control) {
|
|
50659
50829
|
if (!control) return;
|
|
50830
|
+
|
|
50831
|
+
// Where the control was: if the command removes what it sat on, the caret
|
|
50832
|
+
// goes to what took its place
|
|
50833
|
+
const spot = (control.closest('.row, [data-cb-block]') || control).getBoundingClientRect();
|
|
50660
50834
|
setTimeout(() => {
|
|
50661
50835
|
// Not bailing when the container has gone: deleting the last column
|
|
50662
50836
|
// takes the strip with it, and that is exactly when the focus needs
|
|
@@ -50714,8 +50888,10 @@ class ToolbarKeys {
|
|
|
50714
50888
|
// A tick later again: emptying an area makes the editor put its
|
|
50715
50889
|
// own button there, and that has not happened yet.
|
|
50716
50890
|
setTimeout(() => {
|
|
50717
|
-
const somewhere = this._somewhereUsable();
|
|
50718
|
-
if (somewhere) somewhere.focus(
|
|
50891
|
+
const somewhere = this._somewhereUsable(spot);
|
|
50892
|
+
if (somewhere) somewhere.focus({
|
|
50893
|
+
preventScroll: true
|
|
50894
|
+
});
|
|
50719
50895
|
}, 30);
|
|
50720
50896
|
}
|
|
50721
50897
|
}, 0);
|
|
@@ -50786,7 +50962,9 @@ class ToolbarKeys {
|
|
|
50786
50962
|
// the trip to the toolbar after one button. Stay on it, so the next format
|
|
50787
50963
|
// is a key away, and leave deliberately with Escape. Anything that claims
|
|
50788
50964
|
// the focus for itself, a panel or a dialog, is left to do so.
|
|
50965
|
+
// A mouse click (detail > 0) is left alone, so the mouse works as it always did.
|
|
50789
50966
|
tool.addEventListener('click', e => {
|
|
50967
|
+
if (e.detail > 0) return;
|
|
50790
50968
|
const control = this._controlFrom(e, tool, BUTTONS);
|
|
50791
50969
|
if (control) this._hold(tool, control);
|
|
50792
50970
|
});
|
|
@@ -50928,13 +51106,26 @@ class ToolbarKeys {
|
|
|
50928
51106
|
* go dead with the focus stuck on a strip. Falls back through what the editor
|
|
50929
51107
|
* still calls active, then to the first column on the page.
|
|
50930
51108
|
*/
|
|
50931
|
-
currentColumn() {
|
|
51109
|
+
currentColumn(near) {
|
|
50932
51110
|
// It has to be able to hold the focus. A locked column is still the active
|
|
50933
51111
|
// one but cannot take it, and focusing it silently does nothing.
|
|
50934
51112
|
const usable = el => el && el.isConnected && (el.isContentEditable || el.hasAttribute('tabindex')) ? el : null;
|
|
50935
51113
|
const active = usable(this.builder.util.cellSelected()) || usable(this.builder.activeCol);
|
|
50936
51114
|
if (active) return active;
|
|
50937
51115
|
const rows = this.builder.doc.querySelectorAll(`${this.builder.opts.container} > *`);
|
|
51116
|
+
|
|
51117
|
+
// Near a place on screen (where a removed row was): the column that took its
|
|
51118
|
+
// place, or the last one before it
|
|
51119
|
+
if (near) {
|
|
51120
|
+
let before = null;
|
|
51121
|
+
for (const row of rows) {
|
|
51122
|
+
const cell = ToolbarKeys.cells(row).find(usable);
|
|
51123
|
+
if (!cell) continue;
|
|
51124
|
+
if (cell.getBoundingClientRect().top >= near.top - 1) return cell;
|
|
51125
|
+
before = cell;
|
|
51126
|
+
}
|
|
51127
|
+
if (before) return before;
|
|
51128
|
+
}
|
|
50938
51129
|
for (const row of rows) {
|
|
50939
51130
|
const cell = ToolbarKeys.cells(row).find(usable);
|
|
50940
51131
|
if (cell) return cell;
|
|
@@ -50948,8 +51139,8 @@ class ToolbarKeys {
|
|
|
50948
51139
|
* editing away: in both the focus would otherwise land on the body, where no
|
|
50949
51140
|
* key reaches the editor at all.
|
|
50950
51141
|
*/
|
|
50951
|
-
_somewhereUsable() {
|
|
50952
|
-
const column = this.currentColumn();
|
|
51142
|
+
_somewhereUsable(near) {
|
|
51143
|
+
const column = this.currentColumn(near);
|
|
50953
51144
|
if (column) return column;
|
|
50954
51145
|
const doc = this.builder.doc;
|
|
50955
51146
|
|
|
@@ -52122,6 +52313,18 @@ class Util$1 {
|
|
|
52122
52313
|
// A toolbar button that opens a dialog often moves the focus first, so the
|
|
52123
52314
|
// strip fills this in from the button actually used (toolbarkeys.js).
|
|
52124
52315
|
|
|
52316
|
+
// Opened from a control inside the content iframe: this page never saw
|
|
52317
|
+
// that mouse click, so the browser would ring the dialog's first control
|
|
52318
|
+
// as if the keyboard had been used. Carry the opener's own state over.
|
|
52319
|
+
let focusOpts;
|
|
52320
|
+
if (opener && opener.tagName === 'IFRAME') {
|
|
52321
|
+
try {
|
|
52322
|
+
const inner = opener.contentDocument.activeElement;
|
|
52323
|
+
if (!(inner && inner.matches(':focus-visible'))) focusOpts = {
|
|
52324
|
+
focusVisible: false
|
|
52325
|
+
};
|
|
52326
|
+
} catch (e) {/* another origin */}
|
|
52327
|
+
}
|
|
52125
52328
|
if (overflowHidden) document.body.style.overflow = 'hidden';
|
|
52126
52329
|
const handleKeyDown = e => {
|
|
52127
52330
|
if (e.keyCode === 27) {
|
|
@@ -52139,11 +52342,11 @@ class Util$1 {
|
|
|
52139
52342
|
modal.addEventListener('keydown', handleKeyDown);
|
|
52140
52343
|
let content = modal.querySelector('.is-tab-content.active');
|
|
52141
52344
|
if (content) {
|
|
52142
|
-
content.focus();
|
|
52345
|
+
content.focus(focusOpts);
|
|
52143
52346
|
this.builder.tabs.setupTabKey(content);
|
|
52144
52347
|
} else {
|
|
52145
52348
|
this.setupTabKeys(modal);
|
|
52146
|
-
modal.focus();
|
|
52349
|
+
modal.focus(focusOpts);
|
|
52147
52350
|
}
|
|
52148
52351
|
|
|
52149
52352
|
// Land on a control rather than on the dialog itself: focus on the dialog
|
|
@@ -52163,7 +52366,7 @@ class Util$1 {
|
|
|
52163
52366
|
const settled = modal.contains(held) && held !== modal && held.tagName !== 'IFRAME' && !held.classList.contains('is-tab-content');
|
|
52164
52367
|
if (settled) return;
|
|
52165
52368
|
const first = ToolbarKeys.firstControl(modal);
|
|
52166
|
-
if (first) first.focus();
|
|
52369
|
+
if (first) first.focus(focusOpts);
|
|
52167
52370
|
}, 0);
|
|
52168
52371
|
|
|
52169
52372
|
// A dialog whose content is an iframe, the asset picker, is a trap without
|
|
@@ -54192,6 +54395,7 @@ class Util$1 {
|
|
|
54192
54395
|
* Placeholders substituted by export():
|
|
54193
54396
|
* [%CONTENT%] the content HTML
|
|
54194
54397
|
* [%PLUGINS%] runtime plugin registry (see getPluginRegistry)
|
|
54398
|
+
* [%PLUGINFOLDER%] runtime plugin folder (see getPluginFolder)
|
|
54195
54399
|
* [%PATH%] base URL for runtime/asset references
|
|
54196
54400
|
*/
|
|
54197
54401
|
getPageTemplate(framework) {
|
|
@@ -54229,7 +54433,8 @@ class Util$1 {
|
|
|
54229
54433
|
// Registers available plugins (not yet loaded).
|
|
54230
54434
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
54231
54435
|
const runtime = new ContentBuilderRuntime({
|
|
54232
|
-
plugins: [%PLUGINS%]
|
|
54436
|
+
plugins: [%PLUGINS%],
|
|
54437
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
54233
54438
|
});
|
|
54234
54439
|
runtime.init();
|
|
54235
54440
|
runtime.renderPageCss('.container');
|
|
@@ -54279,7 +54484,8 @@ class Util$1 {
|
|
|
54279
54484
|
// Registers available plugins (not yet loaded).
|
|
54280
54485
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
54281
54486
|
const runtime = new ContentBuilderRuntime({
|
|
54282
|
-
plugins: [%PLUGINS%]
|
|
54487
|
+
plugins: [%PLUGINS%],
|
|
54488
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
54283
54489
|
});
|
|
54284
54490
|
runtime.init();
|
|
54285
54491
|
</script>
|
|
@@ -54332,7 +54538,8 @@ class Util$1 {
|
|
|
54332
54538
|
// Registers available plugins (not yet loaded).
|
|
54333
54539
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
54334
54540
|
const runtime = new ContentBuilderRuntime({
|
|
54335
|
-
plugins: [%PLUGINS%]
|
|
54541
|
+
plugins: [%PLUGINS%],
|
|
54542
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
54336
54543
|
});
|
|
54337
54544
|
runtime.init();
|
|
54338
54545
|
</script>
|
|
@@ -54390,7 +54597,8 @@ class Util$1 {
|
|
|
54390
54597
|
// Registers available plugins (not yet loaded).
|
|
54391
54598
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
54392
54599
|
const runtime = new ContentBuilderRuntime({
|
|
54393
|
-
plugins: [%PLUGINS%]
|
|
54600
|
+
plugins: [%PLUGINS%],
|
|
54601
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
54394
54602
|
});
|
|
54395
54603
|
runtime.init();
|
|
54396
54604
|
</script>
|
|
@@ -54442,7 +54650,8 @@ class Util$1 {
|
|
|
54442
54650
|
// Registers available plugins (not yet loaded).
|
|
54443
54651
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
54444
54652
|
const runtime = new ContentBuilderRuntime({
|
|
54445
|
-
plugins: [%PLUGINS%]
|
|
54653
|
+
plugins: [%PLUGINS%],
|
|
54654
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
54446
54655
|
});
|
|
54447
54656
|
runtime.init();
|
|
54448
54657
|
</script>
|
|
@@ -54496,6 +54705,27 @@ class Util$1 {
|
|
|
54496
54705
|
});
|
|
54497
54706
|
return JSON.stringify(registry, null, 4);
|
|
54498
54707
|
}
|
|
54708
|
+
|
|
54709
|
+
/*
|
|
54710
|
+
* The runtime's plugin folder (pluginFolder in blank.html), as a JS string.
|
|
54711
|
+
* Plugins used in the content load from it without being registered, so an
|
|
54712
|
+
* exported page keeps them. A URL under the content frame's base becomes a
|
|
54713
|
+
* [%PATH%] token, like the registry's URLs.
|
|
54714
|
+
*/
|
|
54715
|
+
getPluginFolder() {
|
|
54716
|
+
const win = this.builder.win;
|
|
54717
|
+
let folder = '';
|
|
54718
|
+
try {
|
|
54719
|
+
folder = win.builderRuntime.settings.pluginFolder || '';
|
|
54720
|
+
} catch (e) {/* no runtime in the content frame */}
|
|
54721
|
+
let base = '';
|
|
54722
|
+
try {
|
|
54723
|
+
const href = win.location.href;
|
|
54724
|
+
base = href.substring(0, href.lastIndexOf('/'));
|
|
54725
|
+
} catch (e) {/* cross-origin content frame */}
|
|
54726
|
+
if (base && folder.startsWith(base + '/')) folder = '[%PATH%]' + folder.substring(base.length);
|
|
54727
|
+
return JSON.stringify(folder);
|
|
54728
|
+
}
|
|
54499
54729
|
getElementPosition(element) {
|
|
54500
54730
|
const iframe = this.builder.iframe;
|
|
54501
54731
|
let top, left;
|
|
@@ -54707,6 +54937,46 @@ class Util$1 {
|
|
|
54707
54937
|
}
|
|
54708
54938
|
`;
|
|
54709
54939
|
}
|
|
54940
|
+
|
|
54941
|
+
// Place a small tool (link/button tool) above its target. When the target sits at the
|
|
54942
|
+
// top edge with no room above, put the tool beside it (right, then left), so it never
|
|
54943
|
+
// lands on the element tool below the element. Below is the last resort.
|
|
54944
|
+
// vTarget (optional, e.g. a selected icon inside a link) sets the vertical position.
|
|
54945
|
+
// The tool must be displayed before this (its size is measured).
|
|
54946
|
+
placeToolAbove(tool, target, vTarget) {
|
|
54947
|
+
const hr = target.getBoundingClientRect();
|
|
54948
|
+
const vr = (vTarget || target).getBoundingClientRect();
|
|
54949
|
+
tool.style.top = vr.top + this.builder.win.pageYOffset - tool.offsetHeight - 3 + 'px';
|
|
54950
|
+
tool.style.left = hr.left + target.offsetWidth * this.builder.opts.zoom - tool.offsetWidth + 'px';
|
|
54951
|
+
|
|
54952
|
+
// Measured, not computed: the tool can carry margins and a scale.
|
|
54953
|
+
const t = tool.getBoundingClientRect();
|
|
54954
|
+
if (t.top >= 0) return;
|
|
54955
|
+
const moveTo = (left, top) => {
|
|
54956
|
+
const c = tool.getBoundingClientRect();
|
|
54957
|
+
tool.style.left = parseFloat(tool.style.left) + left - c.left + 'px';
|
|
54958
|
+
tool.style.top = parseFloat(tool.style.top) + top - c.top + 'px';
|
|
54959
|
+
};
|
|
54960
|
+
|
|
54961
|
+
// Horizontal room, kept clear of the row tool when it sits beside the target.
|
|
54962
|
+
let minX = 0;
|
|
54963
|
+
let maxX = this.builder.doc.documentElement.clientWidth;
|
|
54964
|
+
const row = target.closest('.row');
|
|
54965
|
+
const rowTool = row && row.querySelector(':scope > .is-row-tool');
|
|
54966
|
+
if (rowTool && rowTool.offsetParent) {
|
|
54967
|
+
const r = rowTool.getBoundingClientRect();
|
|
54968
|
+
if (r.left >= hr.right) maxX = Math.min(maxX, r.left);else if (r.right <= hr.left) minX = Math.max(minX, r.right);
|
|
54969
|
+
}
|
|
54970
|
+
const gap = 8;
|
|
54971
|
+
const sideTop = vr.top + vr.height / 2 - t.height / 2;
|
|
54972
|
+
if (hr.right + gap + t.width <= maxX) {
|
|
54973
|
+
moveTo(hr.right + gap, sideTop);
|
|
54974
|
+
} else if (hr.left - gap - t.width >= minX) {
|
|
54975
|
+
moveTo(hr.left - gap - t.width, sideTop);
|
|
54976
|
+
} else {
|
|
54977
|
+
moveTo(t.left, vr.bottom + gap);
|
|
54978
|
+
}
|
|
54979
|
+
}
|
|
54710
54980
|
}
|
|
54711
54981
|
class Dom {
|
|
54712
54982
|
constructor(builder) {
|
|
@@ -77568,15 +77838,8 @@ class Hyperlink {
|
|
|
77568
77838
|
}
|
|
77569
77839
|
// /Button Stuff
|
|
77570
77840
|
|
|
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';
|
|
77841
|
+
linkTool.style.display = 'flex'; //to get the size, the tool must not be hidden (display:none)
|
|
77842
|
+
this.builder.util.placeToolAbove(linkTool, link, this.builder.activeIcon);
|
|
77580
77843
|
const handleLinkButtonClick = e => {
|
|
77581
77844
|
const clrPicker = document.querySelector('.pop-picker.active') || document.querySelector('.pickgradientcolor.active');
|
|
77582
77845
|
let elm = e.target;
|
|
@@ -77875,13 +78138,8 @@ class Button {
|
|
|
77875
78138
|
// selecting the button, so the pencil is a second route to the same
|
|
77876
78139
|
// panel and is left out.
|
|
77877
78140
|
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';
|
|
78141
|
+
buttonTool.style.display = 'flex'; //to get the size, the tool must not be hidden (display:none)
|
|
78142
|
+
this.builder.util.placeToolAbove(buttonTool, activeButton);
|
|
77885
78143
|
}
|
|
77886
78144
|
hideTool() {
|
|
77887
78145
|
if (this.buttonTool) this.buttonTool.style.display = '';
|
|
@@ -79078,7 +79336,10 @@ class Plugin {
|
|
|
79078
79336
|
const handlePluginClick = e => {
|
|
79079
79337
|
const clrPicker = document.querySelector('.pop-picker.active') || document.querySelector('.pickgradientcolor.active');
|
|
79080
79338
|
let elm = e.target;
|
|
79081
|
-
|
|
79339
|
+
|
|
79340
|
+
// isConnected, not document.contains(): in iframe mode the click is in
|
|
79341
|
+
// the content document, which this document never contains.
|
|
79342
|
+
let elmRemoved = !elm.isConnected; // e.g. when deleting a card in card list plugin
|
|
79082
79343
|
|
|
79083
79344
|
if (this.builder.doc.activeElement) {
|
|
79084
79345
|
if (this.builder.doc.activeElement.closest('.is-modal')) {
|
|
@@ -79276,7 +79537,9 @@ class Plugin {
|
|
|
79276
79537
|
const plugin = e.target.closest('[data-cb-type]');
|
|
79277
79538
|
if (plugin) {
|
|
79278
79539
|
this.builder.activePlugin = plugin;
|
|
79279
|
-
|
|
79540
|
+
|
|
79541
|
+
// With a control panel, plugin settings open there instead.
|
|
79542
|
+
if (!this.builder.controlPanel) {
|
|
79280
79543
|
this.renderTool();
|
|
79281
79544
|
this.pluginTool.style.display = 'flex';
|
|
79282
79545
|
let _toolwidth = this.pluginTool.offsetWidth; //to get value, element must not hidden (display:none). So set display:flex before this.
|
|
@@ -81552,6 +81815,10 @@ class Element {
|
|
|
81552
81815
|
customcode = true;
|
|
81553
81816
|
subblock = elm.closest('.is-subblock');
|
|
81554
81817
|
}
|
|
81818
|
+
if (elm.closest('[data-cb-code]')) {
|
|
81819
|
+
// Independent code element: the whole block is the element
|
|
81820
|
+
customcode = true;
|
|
81821
|
+
}
|
|
81555
81822
|
if (!customcode && !noedit && !_protected || subblock) {
|
|
81556
81823
|
//previously this is commented: && !noedit && !_protected
|
|
81557
81824
|
|
|
@@ -103213,6 +103480,136 @@ function coverValue(cover, def) {
|
|
|
103213
103480
|
return true;
|
|
103214
103481
|
}
|
|
103215
103482
|
|
|
103483
|
+
/*
|
|
103484
|
+
normalizer.js — the clean-output guarantee.
|
|
103485
|
+
|
|
103486
|
+
Runs after every mutation. It never changes the visible text; it only
|
|
103487
|
+
reduces markup to the minimal equivalent form:
|
|
103488
|
+
|
|
103489
|
+
<strong>a</strong><strong>b</strong> -> <strong>ab</strong>
|
|
103490
|
+
<strong><strong>x</strong></strong> -> <strong>x</strong>
|
|
103491
|
+
<span style="color:red"><span style="font-size:18px">x</span></span>
|
|
103492
|
+
-> one span, two properties
|
|
103493
|
+
<span>x</span> -> x
|
|
103494
|
+
<em></em> -> (removed)
|
|
103495
|
+
|
|
103496
|
+
Because it preserves text content exactly, character-offset selection
|
|
103497
|
+
bookmarks taken before normalization remain valid after it.
|
|
103498
|
+
*/
|
|
103499
|
+
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']);
|
|
103500
|
+
|
|
103501
|
+
/* Identity signature: two inline elements with equal signatures are
|
|
103502
|
+
interchangeable and can be merged. */
|
|
103503
|
+
function sig(el) {
|
|
103504
|
+
const attrs = [];
|
|
103505
|
+
for (const a of el.attributes) {
|
|
103506
|
+
attrs.push(a.name === 'style' ? 'style=' + el.style.cssText : a.name + '=' + a.value);
|
|
103507
|
+
}
|
|
103508
|
+
return el.tagName + '|' + attrs.sort().join(';');
|
|
103509
|
+
}
|
|
103510
|
+
function isEmptyInline(el) {
|
|
103511
|
+
if (el.textContent !== '' || el.querySelector(KEEPERS)) return false;
|
|
103512
|
+
// An empty inline that carries a class typically renders its content via CSS
|
|
103513
|
+
// rather than text — an icon font (<i class="bi bi-alarm">, <span
|
|
103514
|
+
// class="material-icons">…</span>, fa-*, glyphicon). It is NOT truly empty,
|
|
103515
|
+
// so never drop it. Bare leftover marks (<em></em>, <i></i>) have no class
|
|
103516
|
+
// and are still removed.
|
|
103517
|
+
if (el.getAttribute('class')) return false;
|
|
103518
|
+
return true;
|
|
103519
|
+
}
|
|
103520
|
+
|
|
103521
|
+
/* Does the page's CSS style this attribute-less span? A design can colour or
|
|
103522
|
+
size "the span in this heading" from a stylesheet (.hero h1 span {...});
|
|
103523
|
+
unwrapping it then changes how the text looks, which this file promises
|
|
103524
|
+
never to do. Compared against the parent for inherited properties and
|
|
103525
|
+
against the initial value for the rest. Without a live view (detached, or
|
|
103526
|
+
a headless test) it is treated as unstyled, as before. */
|
|
103527
|
+
const INHERITED = ['color', 'fontSize', 'fontWeight', 'fontStyle', 'fontFamily', 'letterSpacing', 'textTransform', 'lineHeight'];
|
|
103528
|
+
function styledByCss(el) {
|
|
103529
|
+
const parent = el.parentElement;
|
|
103530
|
+
const win = el.ownerDocument && el.ownerDocument.defaultView;
|
|
103531
|
+
if (!parent || !win || !el.isConnected || typeof win.getComputedStyle !== 'function') return false;
|
|
103532
|
+
const cs = win.getComputedStyle(el);
|
|
103533
|
+
const ps = win.getComputedStyle(parent);
|
|
103534
|
+
for (const p of INHERITED) if (cs[p] !== ps[p]) return true;
|
|
103535
|
+
if (cs.display && cs.display !== 'inline') return true;
|
|
103536
|
+
if (cs.backgroundImage && cs.backgroundImage !== 'none') return true;
|
|
103537
|
+
if (cs.backgroundColor && !/^(transparent|rgba\(0, 0, 0, 0\))$/.test(cs.backgroundColor)) return true;
|
|
103538
|
+
if (cs.textDecorationLine && cs.textDecorationLine !== 'none') return true;
|
|
103539
|
+
return false;
|
|
103540
|
+
}
|
|
103541
|
+
|
|
103542
|
+
/* One bottom-up cleanup pass. Returns true if anything changed. */
|
|
103543
|
+
function pass(scope) {
|
|
103544
|
+
let changed = false;
|
|
103545
|
+
const els = Array.prototype.slice.call(scope.querySelectorAll('*')).reverse();
|
|
103546
|
+
for (const el of els) {
|
|
103547
|
+
if (!el.isConnected && !scope.contains(el)) continue;
|
|
103548
|
+
if (el.closest && el.closest('[contenteditable="false"]')) continue;
|
|
103549
|
+
if (!INLINE_TAGS.has(el.tagName)) continue;
|
|
103550
|
+
|
|
103551
|
+
/* 1. Drop empty inline elements */
|
|
103552
|
+
if (isEmptyInline(el)) {
|
|
103553
|
+
el.remove();
|
|
103554
|
+
changed = true;
|
|
103555
|
+
continue;
|
|
103556
|
+
}
|
|
103557
|
+
|
|
103558
|
+
/* 2. Unwrap attribute-less spans (unless the page's CSS styles them) */
|
|
103559
|
+
if (el.tagName === 'SPAN' && el.attributes.length === 0 && !styledByCss(el)) {
|
|
103560
|
+
unwrap(el);
|
|
103561
|
+
changed = true;
|
|
103562
|
+
continue;
|
|
103563
|
+
}
|
|
103564
|
+
|
|
103565
|
+
/* 3. Unwrap when an identical ancestor already covers this element */
|
|
103566
|
+
let anc = el.parentNode;
|
|
103567
|
+
let redundant = false;
|
|
103568
|
+
while (isEl$1(anc) && anc !== scope && INLINE_TAGS.has(anc.tagName)) {
|
|
103569
|
+
if (sig(anc) === sig(el)) {
|
|
103570
|
+
redundant = true;
|
|
103571
|
+
break;
|
|
103572
|
+
}
|
|
103573
|
+
anc = anc.parentNode;
|
|
103574
|
+
}
|
|
103575
|
+
if (redundant) {
|
|
103576
|
+
unwrap(el);
|
|
103577
|
+
changed = true;
|
|
103578
|
+
continue;
|
|
103579
|
+
}
|
|
103580
|
+
|
|
103581
|
+
/* 4. Merge with an identical previous sibling */
|
|
103582
|
+
const prev = el.previousSibling;
|
|
103583
|
+
if (isEl$1(prev) && sig(prev) === sig(el)) {
|
|
103584
|
+
while (el.firstChild) prev.appendChild(el.firstChild);
|
|
103585
|
+
el.remove();
|
|
103586
|
+
changed = true;
|
|
103587
|
+
continue;
|
|
103588
|
+
}
|
|
103589
|
+
|
|
103590
|
+
/* 5. Collapse span-in-span single-child chains into one span.
|
|
103591
|
+
The inner (more specific) properties win on conflict. */
|
|
103592
|
+
if (el.tagName === 'SPAN' && el.childNodes.length === 1 && isEl$1(el.firstChild) && el.firstChild.tagName === 'SPAN') {
|
|
103593
|
+
const inner = el.firstChild;
|
|
103594
|
+
for (let i = 0; i < inner.style.length; i++) {
|
|
103595
|
+
const prop = inner.style[i];
|
|
103596
|
+
el.style.setProperty(prop, inner.style.getPropertyValue(prop));
|
|
103597
|
+
}
|
|
103598
|
+
inner.classList.forEach(c => el.classList.add(c));
|
|
103599
|
+
if (!el.getAttribute('style')) el.removeAttribute('style');
|
|
103600
|
+
unwrap(inner);
|
|
103601
|
+
changed = true;
|
|
103602
|
+
}
|
|
103603
|
+
}
|
|
103604
|
+
return changed;
|
|
103605
|
+
}
|
|
103606
|
+
function normalize(scope) {
|
|
103607
|
+
let guard = 0;
|
|
103608
|
+
while (pass(scope) && guard++ < 6) {/* repeat until stable */}
|
|
103609
|
+
scope.normalize(); /* merge adjacent text nodes */
|
|
103610
|
+
return scope;
|
|
103611
|
+
}
|
|
103612
|
+
|
|
103216
103613
|
/*
|
|
103217
103614
|
formatter.js — inline formatting engine.
|
|
103218
103615
|
|
|
@@ -103479,7 +103876,9 @@ function strip(el, def) {
|
|
|
103479
103876
|
unwrap(el);
|
|
103480
103877
|
return;
|
|
103481
103878
|
}
|
|
103482
|
-
|
|
103879
|
+
/* Left bare: unwrap, unless the page's CSS styles it (a design's
|
|
103880
|
+
".hero h1 span" colour), which must survive clearing a size from it. */
|
|
103881
|
+
if (el.tagName === 'SPAN' && el.attributes.length === 0 && !styledByCss(el)) unwrap(el);
|
|
103483
103882
|
}
|
|
103484
103883
|
|
|
103485
103884
|
/* Apply a style across a (non-collapsed) range. */
|
|
@@ -103544,115 +103943,6 @@ function removeStyle(root, range, def, opts) {
|
|
|
103544
103943
|
return texts;
|
|
103545
103944
|
}
|
|
103546
103945
|
|
|
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
103946
|
/*
|
|
103657
103947
|
probe.js — non-mutating state inspection (queryCommandState done right).
|
|
103658
103948
|
|
|
@@ -144123,11 +144413,14 @@ class ContentBuilder {
|
|
|
144123
144413
|
this.shortenOutput = false;
|
|
144124
144414
|
output = pageTemplate.replace('[%CONTENT%]', html);
|
|
144125
144415
|
|
|
144126
|
-
// [%PLUGINS%] - runtime plugin registry (must run
|
|
144127
|
-
//
|
|
144416
|
+
// [%PLUGINS%], [%PLUGINFOLDER%] - runtime plugin registry and folder (must run
|
|
144417
|
+
// before [%PATH%], their URLs are emitted as [%PATH%] tokens)
|
|
144128
144418
|
if (output.indexOf('[%PLUGINS%]') !== -1) {
|
|
144129
144419
|
output = output.replaceAll('[%PLUGINS%]', this.util.getPluginRegistry());
|
|
144130
144420
|
}
|
|
144421
|
+
if (output.indexOf('[%PLUGINFOLDER%]') !== -1) {
|
|
144422
|
+
output = output.replaceAll('[%PLUGINFOLDER%]', this.util.getPluginFolder());
|
|
144423
|
+
}
|
|
144131
144424
|
|
|
144132
144425
|
// Custom
|
|
144133
144426
|
if (options.replace) {
|
|
@@ -146867,6 +147160,9 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
|
|
|
146867
147160
|
});
|
|
146868
147161
|
tmp.querySelectorAll('*').forEach(el => {
|
|
146869
147162
|
el.removeAttribute('style');
|
|
147163
|
+
// Text direction is the destination's too: Word marks every
|
|
147164
|
+
// paragraph and run dir="RTL"/"LTR" in right-to-left documents.
|
|
147165
|
+
el.removeAttribute('dir');
|
|
146870
147166
|
if (el.classList && el.classList.length) {
|
|
146871
147167
|
const kept = Array.prototype.filter.call(el.classList, c => keep.has(c));
|
|
146872
147168
|
if (kept.length) el.setAttribute('class', kept.join(' '));else el.removeAttribute('class');
|
|
@@ -151889,6 +152185,7 @@ class Util {
|
|
|
151889
152185
|
* [%MAINCSS%] page-level <style>
|
|
151890
152186
|
* [%SECTIONCSS%] per-section <style>
|
|
151891
152187
|
* [%PLUGINS%] runtime plugin registry (see getPluginRegistry)
|
|
152188
|
+
* [%PLUGINFOLDER%] runtime plugin folder (see getPluginFolder)
|
|
151892
152189
|
* [%PATH%] base URL for runtime/asset references
|
|
151893
152190
|
*/
|
|
151894
152191
|
|
|
@@ -151923,7 +152220,8 @@ class Util {
|
|
|
151923
152220
|
// Registers available plugins (not yet loaded).
|
|
151924
152221
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
151925
152222
|
const libraryInstance = new ContentBoxRuntime({
|
|
151926
|
-
plugins: [%PLUGINS%]
|
|
152223
|
+
plugins: [%PLUGINS%],
|
|
152224
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
151927
152225
|
});
|
|
151928
152226
|
libraryInstance.init();
|
|
151929
152227
|
</script>
|
|
@@ -151962,7 +152260,8 @@ class Util {
|
|
|
151962
152260
|
// Registers available plugins (not yet loaded).
|
|
151963
152261
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
151964
152262
|
const libraryInstance = new ContentBoxRuntime({
|
|
151965
|
-
plugins: [%PLUGINS%]
|
|
152263
|
+
plugins: [%PLUGINS%],
|
|
152264
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
151966
152265
|
});
|
|
151967
152266
|
libraryInstance.init();
|
|
151968
152267
|
</script>
|
|
@@ -151999,7 +152298,8 @@ class Util {
|
|
|
151999
152298
|
// Registers available plugins (not yet loaded).
|
|
152000
152299
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
152001
152300
|
const libraryInstance = new ContentBoxRuntime({
|
|
152002
|
-
plugins: [%PLUGINS%]
|
|
152301
|
+
plugins: [%PLUGINS%],
|
|
152302
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
152003
152303
|
});
|
|
152004
152304
|
libraryInstance.init();
|
|
152005
152305
|
</script>
|
|
@@ -152037,7 +152337,8 @@ class Util {
|
|
|
152037
152337
|
// Registers available plugins (not yet loaded).
|
|
152038
152338
|
// Scripts and styles are fetched only when the plugin is actually used in content.
|
|
152039
152339
|
const libraryInstance = new ContentBoxRuntime({
|
|
152040
|
-
plugins: [%PLUGINS%]
|
|
152340
|
+
plugins: [%PLUGINS%],
|
|
152341
|
+
pluginFolder: [%PLUGINFOLDER%]
|
|
152041
152342
|
});
|
|
152042
152343
|
libraryInstance.init();
|
|
152043
152344
|
</script>
|
|
@@ -152102,6 +152403,36 @@ class Util {
|
|
|
152102
152403
|
});
|
|
152103
152404
|
return JSON.stringify(registry, null, 4);
|
|
152104
152405
|
}
|
|
152406
|
+
/*
|
|
152407
|
+
* The runtime's plugin folder (pluginFolder in blank.html), as a JS string.
|
|
152408
|
+
* Plugins used in the content load from it without being registered, so an
|
|
152409
|
+
* exported page keeps them. A URL under the content frame's base becomes a
|
|
152410
|
+
* [%PATH%] token, like the registry's URLs.
|
|
152411
|
+
*/
|
|
152412
|
+
|
|
152413
|
+
|
|
152414
|
+
getPluginFolder() {
|
|
152415
|
+
const win = this.builder.win;
|
|
152416
|
+
let folder = '';
|
|
152417
|
+
|
|
152418
|
+
try {
|
|
152419
|
+
folder = win.builderRuntime.settings.pluginFolder || '';
|
|
152420
|
+
} catch (e) {
|
|
152421
|
+
/* no runtime in the content frame */
|
|
152422
|
+
}
|
|
152423
|
+
|
|
152424
|
+
let base = '';
|
|
152425
|
+
|
|
152426
|
+
try {
|
|
152427
|
+
const href = win.location.href;
|
|
152428
|
+
base = href.substring(0, href.lastIndexOf('/'));
|
|
152429
|
+
} catch (e) {
|
|
152430
|
+
/* cross-origin content frame */
|
|
152431
|
+
}
|
|
152432
|
+
|
|
152433
|
+
if (base && folder.startsWith(base + '/')) folder = '[%PATH%]' + folder.substring(base.length);
|
|
152434
|
+
return JSON.stringify(folder);
|
|
152435
|
+
}
|
|
152105
152436
|
|
|
152106
152437
|
}
|
|
152107
152438
|
|
|
@@ -166555,10 +166886,6 @@ class IframePanel {
|
|
|
166555
166886
|
let screenMode = localStorage.getItem('_screenmode');
|
|
166556
166887
|
|
|
166557
166888
|
if (localStorage.getItem('_screenmode')) {
|
|
166558
|
-
if (!this.builder.controlPanel && this.builder.iframeSrc) {
|
|
166559
|
-
if (screenMode === 'fullview') screenMode = 'desktop';
|
|
166560
|
-
}
|
|
166561
|
-
|
|
166562
166889
|
this.builder.screenMode = screenMode;
|
|
166563
166890
|
contentView.classList.add(screenMode);
|
|
166564
166891
|
} else {
|
|
@@ -166646,8 +166973,22 @@ class IframePanel {
|
|
|
166646
166973
|
// addBreakpoint) and authored tablet values while the user believed they were
|
|
166647
166974
|
// authoring desktop. Scaling fullview the same way the device modes do puts the
|
|
166648
166975
|
// viewport back in the desktop band, so no control needs to know about this.
|
|
166976
|
+
// The topOffset setting in pixels. It is a CSS length ('55px', '3rem', 48), the same
|
|
166977
|
+
// value the topspace CSS uses, so a unit other than px is resolved, not read as px.
|
|
166649
166978
|
|
|
166650
166979
|
|
|
166980
|
+
topOffsetPx() {
|
|
166981
|
+
const value = this.builder.topOffset;
|
|
166982
|
+
if (value === undefined || value === null || value === '') return 55;
|
|
166983
|
+
if (typeof value === 'number') return value;
|
|
166984
|
+
const probe = document.createElement('div');
|
|
166985
|
+
probe.style.cssText = 'position:absolute;visibility:hidden;pointer-events:none;height:' + value;
|
|
166986
|
+
document.body.appendChild(probe);
|
|
166987
|
+
const px = probe.getBoundingClientRect().height;
|
|
166988
|
+
probe.remove();
|
|
166989
|
+
return px || parseInt(value) || 55;
|
|
166990
|
+
}
|
|
166991
|
+
|
|
166651
166992
|
applyFullviewScale() {
|
|
166652
166993
|
const contentView = this.contentView;
|
|
166653
166994
|
if (!contentView || !this.editPanel) return;
|
|
@@ -166683,7 +167024,7 @@ class IframePanel {
|
|
|
166683
167024
|
|
|
166684
167025
|
|
|
166685
167026
|
const hasTopSpace = this.builder.topSpace || document.body.classList.contains('topspace');
|
|
166686
|
-
const topOffset = hasTopSpace ?
|
|
167027
|
+
const topOffset = hasTopSpace ? this.topOffsetPx() : 0;
|
|
166687
167028
|
const scale = availableWidth / logicalWidth;
|
|
166688
167029
|
const availableHeight = this.editPanel.clientHeight - topOffset;
|
|
166689
167030
|
contentView.style.setProperty('--fullview-top', `${topOffset}px`);
|
|
@@ -166758,7 +167099,7 @@ class IframePanel {
|
|
|
166758
167099
|
|
|
166759
167100
|
|
|
166760
167101
|
const hasTopSpace = this.builder.topSpace || document.body.classList.contains('topspace');
|
|
166761
|
-
const topOffset = hasTopSpace ?
|
|
167102
|
+
const topOffset = hasTopSpace ? this.topOffsetPx() : 0; // A manual width drag writes an inline width and leaves the mode class alone, so
|
|
166762
167103
|
// that width is the frame the user asked for. Reading it is safe: it is an input
|
|
166763
167104
|
// here, never something this method writes.
|
|
166764
167105
|
|
|
@@ -175921,6 +176262,12 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
175921
176262
|
outline-offset: -1px;
|
|
175922
176263
|
}
|
|
175923
176264
|
|
|
176265
|
+
/* A faded-out overlay layer covering visible content, under the pointer.
|
|
176266
|
+
Clicks go through it. See ControlPanel.bindLayerPassThrough. */
|
|
176267
|
+
.is-wrapper [data-overlay-passthrough] {
|
|
176268
|
+
pointer-events: none !important;
|
|
176269
|
+
}
|
|
176270
|
+
|
|
175924
176271
|
/* Hovering a row of the panel's layer list. Locates the element while
|
|
175925
176272
|
leaving the selection outline alone. */
|
|
175926
176273
|
.is-wrapper [data-overlay-hover] {
|
|
@@ -175931,13 +176278,20 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
175931
176278
|
/* An overlay layer is edited through the control panel, not in place, so
|
|
175932
176279
|
a text I-beam here would promise a caret that never arrives. Regions
|
|
175933
176280
|
that ARE directly editable inside an overlay (a plugin's .is-subblock,
|
|
175934
|
-
which enableEditableRegions makes contentEditable) keep it.
|
|
175935
|
-
|
|
175936
|
-
|
|
176281
|
+
which enableEditableRegions makes contentEditable) keep it. Tool
|
|
176282
|
+
buttons and .is-container islands (normal in-place content) are left
|
|
176283
|
+
alone. :where() keeps this rule at the lowest specificity so it never
|
|
176284
|
+
overrides another cursor rule. */
|
|
176285
|
+
.is-wrapper :where(${this.overlayEditableSelector},
|
|
176286
|
+
${this.overlayEditableSelector} *:not(.is-tool, .is-tool *, .is-container, .is-container *)) {
|
|
175937
176287
|
cursor: default;
|
|
175938
176288
|
}
|
|
175939
|
-
|
|
175940
|
-
.is-wrapper ${this.overlayEditableSelector}
|
|
176289
|
+
/* cursor is inherited: give the containers back the normal behaviour */
|
|
176290
|
+
.is-wrapper :where(${this.overlayEditableSelector} .is-container) {
|
|
176291
|
+
cursor: auto;
|
|
176292
|
+
}
|
|
176293
|
+
.is-wrapper :where(${this.overlayEditableSelector} [contenteditable="true"]:not(.is-container, .is-container *),
|
|
176294
|
+
${this.overlayEditableSelector} [contenteditable="true"]:not(.is-container, .is-container *) *) {
|
|
175941
176295
|
cursor: text;
|
|
175942
176296
|
}
|
|
175943
176297
|
|
|
@@ -178086,12 +178440,7 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
178086
178440
|
}
|
|
178087
178441
|
e.stopImmediatePropagation();
|
|
178088
178442
|
*/
|
|
178089
|
-
|
|
178090
|
-
|
|
178091
|
-
if (answer) {
|
|
178092
|
-
window.open(url, '_blank');
|
|
178093
|
-
}
|
|
178094
|
-
|
|
178443
|
+
// Editing: a click selects the link, it never leaves the page
|
|
178095
178444
|
e.preventDefault();
|
|
178096
178445
|
}
|
|
178097
178446
|
}
|
|
@@ -178700,15 +179049,70 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
178700
179049
|
newArea = doc.body.innerHTML;
|
|
178701
179050
|
this.editor.saveForUndo();
|
|
178702
179051
|
const d = new Date();
|
|
178703
|
-
newArea = newArea.replace('[%YEAR%]', d.getFullYear());
|
|
179052
|
+
newArea = newArea.replace('[%YEAR%]', d.getFullYear()); // A navigation bar is a section that IS one, not any HTML that mentions the
|
|
179053
|
+
// class (a section's own CSS may style itself relative to a bar above it).
|
|
179054
|
+
|
|
179055
|
+
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,
|
|
179056
|
+
// locked): it is the page's navigation, so a design's own bar is left out
|
|
179057
|
+
// and the rest of the design (a header) goes in first, under the menu.
|
|
179058
|
+
|
|
179059
|
+
|
|
179060
|
+
const lockedTop = Array.from(this.wrapperEl.children).filter(el => el.hasAttribute('data-top-locked'));
|
|
179061
|
+
|
|
179062
|
+
if (lockedTop.length && Array.from(doc.body.children).some(isNavbar)) {
|
|
179063
|
+
const tpl = document.createElement('template');
|
|
179064
|
+
tpl.innerHTML = newArea; // The bar's text colour (the colour its design gives it over the header).
|
|
179065
|
+
|
|
179066
|
+
let barInk = '';
|
|
179067
|
+
Array.from(tpl.content.children).filter(isNavbar).forEach(el => {
|
|
179068
|
+
const m = /--bar-color:\s*var\(--bar-ink,\s*(rgba?\([^)]*\)|#[0-9a-fA-F]{3,8})\s*\)/.exec(el.outerHTML);
|
|
179069
|
+
if (m && !barInk) barInk = m[1];
|
|
179070
|
+
el.remove();
|
|
179071
|
+
}); // The header was made with a bar floating over its top: say so, so the
|
|
179072
|
+
// site's menu can float over it the same way, in the colour the header's
|
|
179073
|
+
// own bar used there.
|
|
178704
179074
|
|
|
178705
|
-
|
|
179075
|
+
const header = Array.from(tpl.content.children).find(el => el.classList.contains('is-section'));
|
|
179076
|
+
|
|
179077
|
+
if (header) {
|
|
179078
|
+
header.setAttribute('data-under-bar', '');
|
|
179079
|
+
if (barInk && !header.hasAttribute('data-bar-rest-ink')) header.setAttribute('data-bar-rest-ink', barInk);
|
|
179080
|
+
}
|
|
179081
|
+
|
|
179082
|
+
if (!tpl.content.children.length) {
|
|
179083
|
+
this.editor.util.confirm(out('This site\'s menu is managed in the menu settings.'), () => {}, false, out('OK'), true);
|
|
179084
|
+
return;
|
|
179085
|
+
}
|
|
179086
|
+
|
|
179087
|
+
dom.appendHtml(lockedTop[lockedTop.length - 1], tpl.innerHTML, 'afterend');
|
|
179088
|
+
} else if (Array.from(doc.body.children).some(isNavbar)) {
|
|
178706
179089
|
// Insert first
|
|
178707
179090
|
const sectionNavbar = this.wrapperEl.querySelector('.is-section-navbar');
|
|
178708
179091
|
|
|
178709
179092
|
if (sectionNavbar) {
|
|
178710
|
-
//
|
|
179093
|
+
// A design that brings its own bar together with other sections (a header):
|
|
179094
|
+
// keep the page's bar and insert the rest, or replace the bar too.
|
|
179095
|
+
const tpl = document.createElement('template');
|
|
179096
|
+
tpl.innerHTML = newArea;
|
|
179097
|
+
const newBar = Array.from(tpl.content.children).find(isNavbar);
|
|
179098
|
+
|
|
179099
|
+
if (newBar && tpl.content.children.length > 1) {
|
|
179100
|
+
this.editor.util.confirm(out('This page already has a navigation bar.'), (keep, dismissed) => {
|
|
179101
|
+
if (dismissed) return; // closed without choosing
|
|
179102
|
+
|
|
179103
|
+
if (keep) {
|
|
179104
|
+
newBar.remove();
|
|
179105
|
+
this.addIdea(tpl.innerHTML);
|
|
179106
|
+
} else {
|
|
179107
|
+
sectionNavbar.parentNode.removeChild(sectionNavbar);
|
|
179108
|
+
this.addIdea(newArea);
|
|
179109
|
+
}
|
|
179110
|
+
}, true, out('Keep current'), true, 'Replace');
|
|
179111
|
+
return;
|
|
179112
|
+
} // let delConf = this.deleteConfirm;
|
|
178711
179113
|
// this.editor.deleteConfirm = true;
|
|
179114
|
+
|
|
179115
|
+
|
|
178712
179116
|
this.editor.util.confirm(out('Do you want to replace the current navigation bar?'), ok => {
|
|
178713
179117
|
if (ok) {
|
|
178714
179118
|
sectionNavbar.parentNode.removeChild(sectionNavbar);
|
|
@@ -178957,7 +179361,10 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
178957
179361
|
// }
|
|
178958
179362
|
|
|
178959
179363
|
if (lastNewSection) {
|
|
178960
|
-
|
|
179364
|
+
// A template can bring several sections; a pinned one among them (not
|
|
179365
|
+
// only the last) needs the full re-render that sets up its pin.
|
|
179366
|
+
const anyPinned = Array.from(newSections).some(s => s.classList.contains('section-pin') || !!s.querySelector('.section-pin'));
|
|
179367
|
+
this.refreshSectionAnim(lastNewSection, anyPinned);
|
|
178961
179368
|
this.scrollTo(lastNewSection, 100);
|
|
178962
179369
|
}
|
|
178963
179370
|
|
|
@@ -179223,15 +179630,19 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
179223
179630
|
const btnSectionUp = sectionTool.querySelector('.btn-move-section-up');
|
|
179224
179631
|
const btnSectionDown = sectionTool.querySelector('.btn-move-section-down');
|
|
179225
179632
|
const btnSectionRemove = sectionTool.querySelector('.btn-section-remove');
|
|
179633
|
+
const btnSectionEdit = sectionTool.querySelector('.btn-section-edit');
|
|
179226
179634
|
|
|
179227
179635
|
if (box.offsetHeight < 160) {
|
|
179636
|
+
// Short section (e.g. a nav bar): room for two buttons, HTML and Remove
|
|
179228
179637
|
btnSectionUp.style.display = 'none';
|
|
179229
179638
|
btnSectionDown.style.display = 'none';
|
|
179230
|
-
|
|
179639
|
+
if (btnSectionEdit) btnSectionEdit.style.display = 'none';
|
|
179640
|
+
btnSectionRemove.style.display = '';
|
|
179231
179641
|
sectionTool.style.top = '17px'; //'calc(50% - 17px)';
|
|
179232
179642
|
} else {
|
|
179233
179643
|
btnSectionUp.style.display = '';
|
|
179234
179644
|
btnSectionDown.style.display = '';
|
|
179645
|
+
if (btnSectionEdit) btnSectionEdit.style.display = '';
|
|
179235
179646
|
btnSectionRemove.style.display = '';
|
|
179236
179647
|
}
|
|
179237
179648
|
} //new box tool
|
|
@@ -179914,7 +180325,16 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
179914
180325
|
|
|
179915
180326
|
this.activeBox = null;
|
|
179916
180327
|
if (this.editor) this.editor.activeBox = null;
|
|
179917
|
-
this.activeSection = null; //
|
|
180328
|
+
this.activeSection = null; // New content: whatever the control panel was showing belongs to the
|
|
180329
|
+
// old page (e.g. after Clear Content), so return it to its empty state.
|
|
180330
|
+
|
|
180331
|
+
if (this.controlPanel && this.controlpanel) this.controlpanel.select(''); // ...and so do the floating tools on the page (the element tool).
|
|
180332
|
+
|
|
180333
|
+
if (this.editor && this.editor.util && this.editor.doc) {
|
|
180334
|
+
this.editor.util.clearActiveElement();
|
|
180335
|
+
this.editor.util.hideControls();
|
|
180336
|
+
} // Wait for IFRAME loaded
|
|
180337
|
+
|
|
179918
180338
|
|
|
179919
180339
|
const wrapper = this.wrapperEl;
|
|
179920
180340
|
|
|
@@ -180350,9 +180770,10 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
180350
180770
|
// html.js attribute sweep: the attributes are ContentBox's, so the
|
|
180351
180771
|
// cleanup belongs on this side.
|
|
180352
180772
|
|
|
180353
|
-
clone.querySelectorAll('[data-overlay-select], [data-overlay-hover]').forEach(elm => {
|
|
180773
|
+
clone.querySelectorAll('[data-overlay-select], [data-overlay-hover], [data-overlay-passthrough]').forEach(elm => {
|
|
180354
180774
|
elm.removeAttribute('data-overlay-select');
|
|
180355
180775
|
elm.removeAttribute('data-overlay-hover');
|
|
180776
|
+
elm.removeAttribute('data-overlay-passthrough');
|
|
180356
180777
|
});
|
|
180357
180778
|
}
|
|
180358
180779
|
|
|
@@ -180823,11 +181244,15 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
|
|
|
180823
181244
|
let mainCss = this.mainCss(true);
|
|
180824
181245
|
let sectionCss = this.sectionCss(true);
|
|
180825
181246
|
output = output.replaceAll('[%MAINCSS%]', mainCss);
|
|
180826
|
-
output = output.replaceAll('[%SECTIONCSS%]', sectionCss); // [%PLUGINS%] - runtime plugin registry (must run
|
|
180827
|
-
//
|
|
181247
|
+
output = output.replaceAll('[%SECTIONCSS%]', sectionCss); // [%PLUGINS%], [%PLUGINFOLDER%] - runtime plugin registry and folder (must run
|
|
181248
|
+
// before [%PATH%], their URLs are emitted as [%PATH%] tokens)
|
|
180828
181249
|
|
|
180829
181250
|
if (output.indexOf('[%PLUGINS%]') !== -1) {
|
|
180830
181251
|
output = output.replaceAll('[%PLUGINS%]', this.util.getPluginRegistry());
|
|
181252
|
+
}
|
|
181253
|
+
|
|
181254
|
+
if (output.indexOf('[%PLUGINFOLDER%]') !== -1) {
|
|
181255
|
+
output = output.replaceAll('[%PLUGINFOLDER%]', this.util.getPluginFolder());
|
|
180831
181256
|
} // Custom
|
|
180832
181257
|
|
|
180833
181258
|
|