tinymce-rails 8.8.1 → 8.9.0
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.
- checksums.yaml +4 -4
- data/app/assets/source/tinymce/tinymce.js +270 -68
- data/lib/tinymce/rails/version.rb +2 -2
- data/vendor/assets/javascripts/tinymce/plugins/accordion/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/advlist/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/anchor/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/autolink/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/autoresize/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/autosave/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/charmap/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/code/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/codesample/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/directionality/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/emoticons/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/fullscreen/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/help/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/image/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/importcss/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/insertdatetime/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/link/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/lists/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/media/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/nonbreaking/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/pagebreak/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/preview/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/quickbars/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/save/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/searchreplace/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/table/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/visualblocks/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/visualchars/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/plugins/wordcount/plugin.js +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.css +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.js +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.min.css +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.css +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.js +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.min.css +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.css +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.js +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.min.css +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.css +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.js +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.min.css +1 -1
- data/vendor/assets/javascripts/tinymce/themes/silver/theme.js +3 -3
- data/vendor/assets/javascripts/tinymce/tinymce.d.ts +32 -6
- data/vendor/assets/javascripts/tinymce/tinymce.js +4 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac0f6b7996563391a41c7a79784540a1a7e4d6e4c3df8172f45886f909320b38
|
|
4
|
+
data.tar.gz: '018e0b48877e87639b2312a6ba0b673d275e9dfb905c3986e6d93ab8d1d14eec'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9fa2ae87f866744fb26965a51de6193bf53fef8afbce8baebf7ab2b8d2a19e63923c89b8044e3fe843cdbeb05d580352ca831fc47fec780d51d0e165f19326c0
|
|
7
|
+
data.tar.gz: 66d4cbe492f6247fbc37d64f050863766162ae84403845e953bd3573302c4357378e701fd2153d67fad8bb8810d76159e4633e8e0d080002d50d42a36ad66104
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TinyMCE version 8.
|
|
2
|
+
* TinyMCE version 8.9.0 (2026-08-27)
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
(function () {
|
|
@@ -12153,6 +12153,13 @@
|
|
|
12153
12153
|
const selector = isEmpty$5(formatNoneditableSelector) ? baseDataSelector : `${baseDataSelector},${formatNoneditableSelector}`;
|
|
12154
12154
|
return is$2(SugarElement.fromDom(node), selector);
|
|
12155
12155
|
};
|
|
12156
|
+
const hasEditableDescendants = (dom, node) => dom.select('[contenteditable="true"]', node).length > 0;
|
|
12157
|
+
const getEditableDescendants = (dom, parent) => foldl(from(parent.childNodes), (editableDescendants, child) => [
|
|
12158
|
+
...editableDescendants,
|
|
12159
|
+
...(isElement$7(child) && dom.getContentEditable(child) === 'true'
|
|
12160
|
+
? [child]
|
|
12161
|
+
: getEditableDescendants(dom, child))
|
|
12162
|
+
], []);
|
|
12156
12163
|
// A noneditable element is wrappable if it:
|
|
12157
12164
|
// - is valid target (has data-mce-cef-wrappable attribute or matches selector from option)
|
|
12158
12165
|
// - has no editable descendants - removing formats in the editable region can result in the wrapped noneditable being split which is undesirable
|
|
@@ -12161,7 +12168,7 @@
|
|
|
12161
12168
|
return (isElementNode$1(node) &&
|
|
12162
12169
|
dom.getContentEditable(node) === 'false' &&
|
|
12163
12170
|
isWrapNoneditableTarget(editor, node) &&
|
|
12164
|
-
dom
|
|
12171
|
+
!hasEditableDescendants(dom, node));
|
|
12165
12172
|
};
|
|
12166
12173
|
/**
|
|
12167
12174
|
* Replaces variables in the value. The variable format is %var.
|
|
@@ -13399,7 +13406,11 @@
|
|
|
13399
13406
|
editor.selection.normalize();
|
|
13400
13407
|
}, (caretPos) => editor.selection.setRng(caretPos.toRange()));
|
|
13401
13408
|
};
|
|
13402
|
-
const focusBody = (body) => {
|
|
13409
|
+
const focusBody = (body, preventScroll = false) => {
|
|
13410
|
+
if (preventScroll) {
|
|
13411
|
+
body.focus({ preventScroll: true });
|
|
13412
|
+
return;
|
|
13413
|
+
}
|
|
13403
13414
|
if (body.setActive) {
|
|
13404
13415
|
// IE 11 sometimes throws "Invalid function" then fallback to focus
|
|
13405
13416
|
// setActive is better since it doesn't scroll to the element being focused
|
|
@@ -13430,7 +13441,7 @@
|
|
|
13430
13441
|
};
|
|
13431
13442
|
const hasFocus = (editor) => editor.inline ? hasInlineFocus(editor) : hasIframeFocus(editor);
|
|
13432
13443
|
const hasEditorOrUiFocus = (editor) => hasFocus(editor) || hasUiFocus(editor);
|
|
13433
|
-
const focusEditor = (editor) => {
|
|
13444
|
+
const focusEditor = (editor, preventScroll) => {
|
|
13434
13445
|
const selection = editor.selection;
|
|
13435
13446
|
const body = editor.getBody();
|
|
13436
13447
|
let rng = selection.getRng();
|
|
@@ -13448,9 +13459,9 @@
|
|
|
13448
13459
|
const contentEditableHost = getContentEditableHost(editor, selection.getNode());
|
|
13449
13460
|
if (contentEditableHost && editor.dom.isChildOf(contentEditableHost, body)) {
|
|
13450
13461
|
if (!hasContentEditableFalseParent$1(editor, contentEditableHost)) {
|
|
13451
|
-
focusBody(body);
|
|
13462
|
+
focusBody(body, preventScroll);
|
|
13452
13463
|
}
|
|
13453
|
-
focusBody(contentEditableHost);
|
|
13464
|
+
focusBody(contentEditableHost, preventScroll);
|
|
13454
13465
|
if (!editor.hasEditableRoot()) {
|
|
13455
13466
|
restoreBookmark(editor);
|
|
13456
13467
|
}
|
|
@@ -13461,30 +13472,40 @@
|
|
|
13461
13472
|
// Focus the window iframe
|
|
13462
13473
|
if (!editor.inline) {
|
|
13463
13474
|
// WebKit needs this call to fire focusin event properly see #5948
|
|
13464
|
-
|
|
13465
|
-
if (
|
|
13466
|
-
|
|
13475
|
+
focusBody(body, preventScroll);
|
|
13476
|
+
if (preventScroll) {
|
|
13477
|
+
if (isNonNullable(editor.iframeElement)) {
|
|
13478
|
+
editor.iframeElement.focus({ preventScroll: true });
|
|
13479
|
+
}
|
|
13480
|
+
}
|
|
13481
|
+
else {
|
|
13482
|
+
editor.getWin().focus();
|
|
13467
13483
|
}
|
|
13468
|
-
editor.getWin().focus();
|
|
13469
13484
|
}
|
|
13470
13485
|
// Focus the body as well since it's contentEditable
|
|
13471
13486
|
if (Env.browser.isFirefox() || editor.inline) {
|
|
13472
|
-
focusBody(body);
|
|
13487
|
+
focusBody(body, preventScroll);
|
|
13473
13488
|
normalizeSelection(editor, rng);
|
|
13474
13489
|
}
|
|
13475
13490
|
activateEditor(editor);
|
|
13476
13491
|
};
|
|
13477
13492
|
const activateEditor = (editor) => editor.editorManager.setActive(editor);
|
|
13478
|
-
const
|
|
13493
|
+
const normalizeFocusOptions = (args) => {
|
|
13494
|
+
if (isNonNullable(args) && !isBoolean(args)) {
|
|
13495
|
+
return args;
|
|
13496
|
+
}
|
|
13497
|
+
return { scrollToSelection: true };
|
|
13498
|
+
};
|
|
13499
|
+
const focus = (editor, args) => {
|
|
13479
13500
|
if (editor.removed) {
|
|
13480
13501
|
return;
|
|
13481
13502
|
}
|
|
13482
|
-
if (
|
|
13503
|
+
if (args === true) {
|
|
13483
13504
|
activateEditor(editor);
|
|
13505
|
+
return;
|
|
13484
13506
|
}
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
}
|
|
13507
|
+
const options = normalizeFocusOptions(args);
|
|
13508
|
+
focusEditor(editor, options.scrollToSelection === false);
|
|
13488
13509
|
};
|
|
13489
13510
|
|
|
13490
13511
|
/**
|
|
@@ -16769,14 +16790,23 @@
|
|
|
16769
16790
|
}
|
|
16770
16791
|
return undefined;
|
|
16771
16792
|
};
|
|
16793
|
+
const containsFormatDeep = (editor, root, name, vars, similar) => isNonNullable(matchNode$1(editor, root, name, vars, similar)) ||
|
|
16794
|
+
exists(from(root.childNodes), (child) => containsFormatDeep(editor, child, name, vars, similar));
|
|
16795
|
+
const matchEditableDescendants = (editor, node, name, vars, similar) => {
|
|
16796
|
+
const dom = editor.dom;
|
|
16797
|
+
if (!isElement$7(node) || dom.getContentEditable(node) !== 'false' || isWrappableNoneditable(editor, node)) {
|
|
16798
|
+
return false;
|
|
16799
|
+
}
|
|
16800
|
+
return exists(getEditableDescendants(dom, node), (descendant) => containsFormatDeep(editor, descendant, name, vars, similar));
|
|
16801
|
+
};
|
|
16772
16802
|
const match$2 = (editor, name, vars, node, similar) => {
|
|
16773
16803
|
// Check specified node
|
|
16774
16804
|
if (node) {
|
|
16775
|
-
return matchParents(editor, node, name, vars, similar);
|
|
16805
|
+
return matchParents(editor, node, name, vars, similar) || matchEditableDescendants(editor, node, name, vars, similar);
|
|
16776
16806
|
}
|
|
16777
16807
|
// Check selected node
|
|
16778
16808
|
node = editor.selection.getNode();
|
|
16779
|
-
if (matchParents(editor, node, name, vars, similar)) {
|
|
16809
|
+
if (matchParents(editor, node, name, vars, similar) || matchEditableDescendants(editor, node, name, vars, similar)) {
|
|
16780
16810
|
return true;
|
|
16781
16811
|
}
|
|
16782
16812
|
// Check start node if it's different
|
|
@@ -18647,7 +18677,7 @@
|
|
|
18647
18677
|
}
|
|
18648
18678
|
};
|
|
18649
18679
|
|
|
18650
|
-
/*! @license DOMPurify 3.4.
|
|
18680
|
+
/*! @license DOMPurify 3.4.12 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.12/LICENSE */
|
|
18651
18681
|
|
|
18652
18682
|
function _arrayLikeToArray(r, a) {
|
|
18653
18683
|
(null == a || a > r.length) && (a = r.length);
|
|
@@ -18960,7 +18990,7 @@
|
|
|
18960
18990
|
const text = freeze(['#text']);
|
|
18961
18991
|
|
|
18962
18992
|
const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'command', 'commandfor', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns']);
|
|
18963
|
-
const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
|
|
18993
|
+
const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dominant-baseline', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-orientation', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
|
|
18964
18994
|
const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lquote', 'lspace', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
|
|
18965
18995
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
18966
18996
|
|
|
@@ -19073,7 +19103,7 @@
|
|
|
19073
19103
|
function createDOMPurify() {
|
|
19074
19104
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
19075
19105
|
const DOMPurify = root => createDOMPurify(root);
|
|
19076
|
-
DOMPurify.version = '3.4.
|
|
19106
|
+
DOMPurify.version = '3.4.12';
|
|
19077
19107
|
DOMPurify.removed = [];
|
|
19078
19108
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
19079
19109
|
// Not running in a browser, provide a factory function
|
|
@@ -19755,6 +19785,13 @@
|
|
|
19755
19785
|
* @param root the in-place root to empty
|
|
19756
19786
|
*/
|
|
19757
19787
|
const _neutralizeRoot = function _neutralizeRoot(root) {
|
|
19788
|
+
/* Strip every disallowed attribute (on* handlers included) off the whole
|
|
19789
|
+
subtree BEFORE detaching anything. Detaching first would hand back
|
|
19790
|
+
handler-bearing originals (e.g. an already-loading `<img onerror>`)
|
|
19791
|
+
whose queued resource event still fires in page scope after we throw.
|
|
19792
|
+
Clobber-safe reads; a doomed clobbered node's own attributes are
|
|
19793
|
+
irrelevant while its non-clobbered descendants are reached and scrubbed. */
|
|
19794
|
+
_neutralizeSubtree(root);
|
|
19758
19795
|
const childNodes = getChildNodes(root);
|
|
19759
19796
|
if (childNodes) {
|
|
19760
19797
|
const snapshot = [];
|
|
@@ -19882,6 +19919,82 @@
|
|
|
19882
19919
|
}
|
|
19883
19920
|
}
|
|
19884
19921
|
};
|
|
19922
|
+
/**
|
|
19923
|
+
* _neutralizePatchLinkage
|
|
19924
|
+
*
|
|
19925
|
+
* IN_PLACE entry pre-pass (declarative-partial-updates / streaming
|
|
19926
|
+
* hardening, https://github.com/WICG/declarative-partial-updates).
|
|
19927
|
+
*
|
|
19928
|
+
* The main walk strips patch linkage (`for`/`patchsrc`) and removes range
|
|
19929
|
+
* markers (PIs / markup comments) node-by-node, in document order, AS it
|
|
19930
|
+
* reaches each node. On a live in-place root that leaves a window: from the
|
|
19931
|
+
* moment the root is connected until the walk arrives at a given node, that
|
|
19932
|
+
* node's linkage is live. A patch applied on connection/stream can fire as
|
|
19933
|
+
* a microtask during the walk and inject or teleport an unsanitized DOM
|
|
19934
|
+
* range into a region the iterator has already passed and will not revisit,
|
|
19935
|
+
* so the post-return "tree is sanitized" contract is violated. Sweep the
|
|
19936
|
+
* whole tree once up front and sever every linkage before the walk begins,
|
|
19937
|
+
* closing that window.
|
|
19938
|
+
*
|
|
19939
|
+
* This CANNOT undo a patch that already fired before sanitize ran — that is
|
|
19940
|
+
* the irreducible "do not IN_PLACE a live-connected attacker tree" caveat —
|
|
19941
|
+
* but it closes everything from sanitize-start onward. Gated on SAFE_FOR_XML
|
|
19942
|
+
* to group with the rest of the declarative-partial-updates handling and
|
|
19943
|
+
* stay overridable, consistent with the codebase.
|
|
19944
|
+
*
|
|
19945
|
+
* Clobber-safe traversal (cached childNodes getter); per-node try/catch so a
|
|
19946
|
+
* clobbered root cannot defeat the sweep of its non-clobbered descendants.
|
|
19947
|
+
*
|
|
19948
|
+
* NOTE (pending real-Chrome confirmation, see test/declarative-patch-probe
|
|
19949
|
+
* .html Q1): this mirrors the existing policy of keeping `for` on
|
|
19950
|
+
* <label>/<output>. If the shipping feature can drive a patch through a
|
|
19951
|
+
* surviving `for`-on-label/output + `id` pair, this pre-pass and the
|
|
19952
|
+
* attribute check at _isBasicCustomElement's caller must additionally drop
|
|
19953
|
+
* that pair on the IN_PLACE path. Left as-is until the taxonomy is verified.
|
|
19954
|
+
*
|
|
19955
|
+
* @param root the in-place root to sweep
|
|
19956
|
+
*/
|
|
19957
|
+
const _neutralizePatchLinkage = function _neutralizePatchLinkage(root) {
|
|
19958
|
+
if (!SAFE_FOR_XML) {
|
|
19959
|
+
return;
|
|
19960
|
+
}
|
|
19961
|
+
const stack = [root];
|
|
19962
|
+
while (stack.length > 0) {
|
|
19963
|
+
const node = stack.pop();
|
|
19964
|
+
const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
|
|
19965
|
+
/* Remove range markers (the target side of a patch linkage): every
|
|
19966
|
+
processing instruction, and any markup-bearing comment. */
|
|
19967
|
+
if (nodeType === NODE_TYPE.processingInstruction || nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, node.data)) {
|
|
19968
|
+
try {
|
|
19969
|
+
remove(node);
|
|
19970
|
+
} catch (_) {
|
|
19971
|
+
/* Best-effort */
|
|
19972
|
+
}
|
|
19973
|
+
continue;
|
|
19974
|
+
}
|
|
19975
|
+
/* Strip patch-source attributes (the source side) off elements. */
|
|
19976
|
+
if (nodeType === NODE_TYPE.element) {
|
|
19977
|
+
const element = node;
|
|
19978
|
+
const lcTag = transformCaseFunc(getNodeName ? getNodeName(node) : node.nodeName);
|
|
19979
|
+
try {
|
|
19980
|
+
if (element.hasAttribute && element.hasAttribute('patchsrc')) {
|
|
19981
|
+
element.removeAttribute('patchsrc');
|
|
19982
|
+
}
|
|
19983
|
+
if (element.hasAttribute && element.hasAttribute('for') && lcTag !== 'label' && lcTag !== 'output') {
|
|
19984
|
+
element.removeAttribute('for');
|
|
19985
|
+
}
|
|
19986
|
+
} catch (_) {
|
|
19987
|
+
/* Clobbered removeAttribute/hasAttribute on a doomed node — ignore */
|
|
19988
|
+
}
|
|
19989
|
+
}
|
|
19990
|
+
const childNodes = getChildNodes(node);
|
|
19991
|
+
if (childNodes) {
|
|
19992
|
+
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
19993
|
+
stack.push(childNodes[i]);
|
|
19994
|
+
}
|
|
19995
|
+
}
|
|
19996
|
+
}
|
|
19997
|
+
};
|
|
19885
19998
|
/**
|
|
19886
19999
|
* _initDocument
|
|
19887
20000
|
*
|
|
@@ -20132,9 +20245,15 @@
|
|
|
20132
20245
|
/**
|
|
20133
20246
|
* Handle a node whose tag is forbidden or not allowlisted: keep
|
|
20134
20247
|
* allowed custom elements (false return exits _sanitizeElements
|
|
20135
|
-
* early - namespace
|
|
20136
|
-
*
|
|
20137
|
-
*
|
|
20248
|
+
* early - the namespace and fallback-tag removal checks are
|
|
20249
|
+
* intentionally skipped for kept custom elements), else hoist
|
|
20250
|
+
* content per KEEP_CONTENT and remove.
|
|
20251
|
+
*
|
|
20252
|
+
* A kept custom element is the ONLY case in which this function
|
|
20253
|
+
* returns false, so the caller uses that return value to run the
|
|
20254
|
+
* afterSanitizeElements hook on the kept element and keep the
|
|
20255
|
+
* element-hook lifecycle consistent with normal allowlisted
|
|
20256
|
+
* elements (GHSA-c2j3-45gr-mqc4).
|
|
20138
20257
|
*
|
|
20139
20258
|
* @param currentNode the disallowed node
|
|
20140
20259
|
* @param tagName the node's transformCaseFunc'd tag name
|
|
@@ -20200,9 +20319,15 @@
|
|
|
20200
20319
|
* @param currentNode to check for permission to exist
|
|
20201
20320
|
* @return true if node was killed, false if left alive
|
|
20202
20321
|
*/
|
|
20203
|
-
|
|
20322
|
+
// eslint-disable-next-line complexity
|
|
20323
|
+
const _sanitizeElements = function _sanitizeElements(currentNode, root) {
|
|
20204
20324
|
/* Execute a hook if present */
|
|
20205
20325
|
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
20326
|
+
/* A hook may have detached the node — treat it as removed (see the
|
|
20327
|
+
detached-node comment after the uponSanitizeElement hook below). */
|
|
20328
|
+
if (currentNode !== root && getParentNode(currentNode) === null) {
|
|
20329
|
+
return true;
|
|
20330
|
+
}
|
|
20206
20331
|
/* Check if element is clobbered or can clobber */
|
|
20207
20332
|
if (_isClobbered(currentNode)) {
|
|
20208
20333
|
_forceRemove(currentNode);
|
|
@@ -20215,6 +20340,24 @@
|
|
|
20215
20340
|
tagName,
|
|
20216
20341
|
allowedTags: ALLOWED_TAGS
|
|
20217
20342
|
});
|
|
20343
|
+
/* A hook may have detached the node from the tree — a long-standing
|
|
20344
|
+
user pattern (issue #469; draw.io-style foreignObject filtering).
|
|
20345
|
+
Per the cached, unclobberable parentNode getter the node is
|
|
20346
|
+
genuinely out of the tree, so it can reach neither the serialized
|
|
20347
|
+
output nor an IN_PLACE live tree; treat it as removed and stop
|
|
20348
|
+
processing it. Without this guard, the unsafe-node / namespace
|
|
20349
|
+
checks below would call _forceRemove on a parentless node and hit
|
|
20350
|
+
the REPORT-3 fail-closed throw — which exists for nodes DOMPurify
|
|
20351
|
+
wants gone but *cannot* detach (clobbered / parentless roots), the
|
|
20352
|
+
opposite of a node that is already safely gone. The walk root is
|
|
20353
|
+
exempt: a detached IN_PLACE root is legitimate input and must still
|
|
20354
|
+
be fully sanitized, and a kill-decision on it must keep hitting the
|
|
20355
|
+
REPORT-3 throw. Nodes detached by hooks are the hook's
|
|
20356
|
+
responsibility: they are not recorded in DOMPurify.removed and are
|
|
20357
|
+
not neutralized by the post-walk IN_PLACE pass. */
|
|
20358
|
+
if (currentNode !== root && getParentNode(currentNode) === null) {
|
|
20359
|
+
return true;
|
|
20360
|
+
}
|
|
20218
20361
|
/* Remove mXSS vectors, processing instructions and risky comments */
|
|
20219
20362
|
if (_isUnsafeNode(currentNode, tagName)) {
|
|
20220
20363
|
_forceRemove(currentNode);
|
|
@@ -20222,7 +20365,22 @@
|
|
|
20222
20365
|
}
|
|
20223
20366
|
/* Remove element if anything forbids its presence */
|
|
20224
20367
|
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
|
|
20225
|
-
|
|
20368
|
+
const removed = _sanitizeDisallowedNode(currentNode, tagName);
|
|
20369
|
+
/* A false return means the node is a custom element kept via
|
|
20370
|
+
CUSTOM_ELEMENT_HANDLING - the only keep path through
|
|
20371
|
+
_sanitizeDisallowedNode. Run afterSanitizeElements on it so the
|
|
20372
|
+
element-hook lifecycle matches normal allowlisted elements: a
|
|
20373
|
+
security policy applied in this hook (e.g. stripping an attribute
|
|
20374
|
+
from every surviving element) must not silently skip kept custom
|
|
20375
|
+
elements (GHSA-c2j3-45gr-mqc4). This mirrors the normal-element
|
|
20376
|
+
tail below - the hook runs, then the walker's subsequent
|
|
20377
|
+
_sanitizeAttributes pass sanitizes the element's attributes. The
|
|
20378
|
+
deliberately skipped namespace and fallback-tag removal checks stay
|
|
20379
|
+
skipped; they are removal decisions, not the hook contract. */
|
|
20380
|
+
if (removed === false) {
|
|
20381
|
+
_executeHooks(hooks.afterSanitizeElements, currentNode, null);
|
|
20382
|
+
}
|
|
20383
|
+
return removed;
|
|
20226
20384
|
}
|
|
20227
20385
|
/* Check whether element has a valid namespace.
|
|
20228
20386
|
Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
|
|
@@ -20269,6 +20427,33 @@
|
|
|
20269
20427
|
if (FORBID_ATTR[lcName]) {
|
|
20270
20428
|
return false;
|
|
20271
20429
|
}
|
|
20430
|
+
/* Reject declarative-partial-updates patch-linkage attributes
|
|
20431
|
+
(https://github.com/WICG/declarative-partial-updates).
|
|
20432
|
+
Empirical note (Chrome 150, verified — see
|
|
20433
|
+
test/declarative-patch-probe-v3.html): expansion is NOT applied after
|
|
20434
|
+
sanitization. For the string path it fires during sanitize()'s own
|
|
20435
|
+
parse, so the walk sees and sanitizes the fully materialized expanded
|
|
20436
|
+
tree — teleports into MathML/SVG integration points included; a
|
|
20437
|
+
weaponized `<template for>`->`<img onerror>` comes back with the handler
|
|
20438
|
+
stripped. For the IN_PLACE path it fires on connection, before the walk.
|
|
20439
|
+
Either way DOMPurify is NOT blind to the patch.
|
|
20440
|
+
This removal is therefore defense-in-depth rather than the sole barrier:
|
|
20441
|
+
it prevents live linkage from surviving into the OUTPUT and re-expanding
|
|
20442
|
+
in the caller's context, and keeps behaviour deterministic if a future
|
|
20443
|
+
engine defers expansion. `for` is legitimate only on <label>/<output>;
|
|
20444
|
+
anywhere else (notably <template for>) it links the element to a patch
|
|
20445
|
+
target and teleports or removes an arbitrary DOM range by id/marker name.
|
|
20446
|
+
`patchsrc` fetches remote markup and is treated as a script-loading
|
|
20447
|
+
mechanism (CSP). Gated on SAFE_FOR_XML so the removal groups with the
|
|
20448
|
+
other structural-threat checks and stays overridable, consistent with
|
|
20449
|
+
the rest of the codebase. PI range markers are already removed by
|
|
20450
|
+
_isUnsafeNode. */
|
|
20451
|
+
if (SAFE_FOR_XML && lcName === 'patchsrc') {
|
|
20452
|
+
return false;
|
|
20453
|
+
}
|
|
20454
|
+
if (SAFE_FOR_XML && lcName === 'for' && lcTag !== 'label' && lcTag !== 'output') {
|
|
20455
|
+
return false;
|
|
20456
|
+
}
|
|
20272
20457
|
/* Make sure attribute cannot clobber */
|
|
20273
20458
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
20274
20459
|
return false;
|
|
@@ -20478,7 +20663,7 @@
|
|
|
20478
20663
|
/* Execute a hook if present */
|
|
20479
20664
|
_executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
|
|
20480
20665
|
/* Sanitize tags and elements */
|
|
20481
|
-
_sanitizeElements(shadowNode);
|
|
20666
|
+
_sanitizeElements(shadowNode, fragment);
|
|
20482
20667
|
/* Check attributes next */
|
|
20483
20668
|
_sanitizeAttributes(shadowNode);
|
|
20484
20669
|
/* Deep shadow DOM detected.
|
|
@@ -20669,6 +20854,11 @@
|
|
|
20669
20854
|
keep using and whose return value they ignore — unsanitized. REPORT-2. */
|
|
20670
20855
|
const inPlace = IN_PLACE && typeof dirty !== 'string' && _isNode(dirty);
|
|
20671
20856
|
if (inPlace) {
|
|
20857
|
+
/* Declarative-partial-updates / streaming pre-pass: sever every patch
|
|
20858
|
+
linkage across the live tree BEFORE the walk, so no patch can fire
|
|
20859
|
+
mid-walk and inject into an already-processed region. Runs first, so
|
|
20860
|
+
it also covers the forbidden/clobbered roots that throw below. */
|
|
20861
|
+
_neutralizePatchLinkage(dirty);
|
|
20672
20862
|
/* Do some early pre-sanitization to avoid unsafe root nodes.
|
|
20673
20863
|
Read nodeName through the cached prototype getter — a clobbering
|
|
20674
20864
|
child named "nodeName" on the form root would otherwise shadow
|
|
@@ -20678,6 +20868,9 @@
|
|
|
20678
20868
|
if (typeof nn === 'string') {
|
|
20679
20869
|
const tagName = transformCaseFunc(nn);
|
|
20680
20870
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
20871
|
+
/* Fail closed on a live root: neutralize handlers/children before
|
|
20872
|
+
throwing, exactly as the mid-walk abort path does. */
|
|
20873
|
+
_neutralizeRoot(dirty);
|
|
20681
20874
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
20682
20875
|
}
|
|
20683
20876
|
}
|
|
@@ -20692,6 +20885,10 @@
|
|
|
20692
20885
|
the application unsanitized. Refuse to sanitize such a root
|
|
20693
20886
|
the same way we refuse a forbidden tag. GHSA-r47g-fvhr-h676. */
|
|
20694
20887
|
if (_isClobbered(dirty)) {
|
|
20888
|
+
/* Fail closed on a live clobbered root before throwing.
|
|
20889
|
+
_neutralizeRoot's reads are clobber-safe (cached getters); the
|
|
20890
|
+
form's non-clobbered descendants, e.g. an armed <img>, are scrubbed. */
|
|
20891
|
+
_neutralizeRoot(dirty);
|
|
20695
20892
|
throw typeErrorCreate('root node is clobbered and cannot be sanitized in-place');
|
|
20696
20893
|
}
|
|
20697
20894
|
/* Sanitize attached shadow roots before the main iterator runs.
|
|
@@ -20744,7 +20941,8 @@
|
|
|
20744
20941
|
_forceRemove(body.firstChild);
|
|
20745
20942
|
}
|
|
20746
20943
|
/* Get node iterator */
|
|
20747
|
-
const
|
|
20944
|
+
const walkRoot = inPlace ? dirty : body;
|
|
20945
|
+
const nodeIterator = _createNodeIterator(walkRoot);
|
|
20748
20946
|
/* Now start iterating over the created document.
|
|
20749
20947
|
The walk runs inside an exception barrier (campaign-3 F2): a re-entrant
|
|
20750
20948
|
engine/custom-element mutation can detach a node mid-walk so
|
|
@@ -20757,7 +20955,7 @@
|
|
|
20757
20955
|
try {
|
|
20758
20956
|
while (currentNode = nodeIterator.nextNode()) {
|
|
20759
20957
|
/* Sanitize tags and elements */
|
|
20760
|
-
_sanitizeElements(currentNode);
|
|
20958
|
+
_sanitizeElements(currentNode, walkRoot);
|
|
20761
20959
|
/* Check attributes next */
|
|
20762
20960
|
_sanitizeAttributes(currentNode);
|
|
20763
20961
|
/* Shadow DOM detected, sanitize it.
|
|
@@ -20771,6 +20969,14 @@
|
|
|
20771
20969
|
} catch (error) {
|
|
20772
20970
|
if (inPlace) {
|
|
20773
20971
|
_neutralizeRoot(dirty);
|
|
20972
|
+
/* Nodes _forceRemove'd earlier in the aborted walk are already
|
|
20973
|
+
detached from the root, so _neutralizeRoot's subtree pass does not
|
|
20974
|
+
reach them. Defuse them too, mirroring the success-path loop below. */
|
|
20975
|
+
arrayForEach(DOMPurify.removed, entry => {
|
|
20976
|
+
if (entry.element) {
|
|
20977
|
+
_neutralizeSubtree(entry.element);
|
|
20978
|
+
}
|
|
20979
|
+
});
|
|
20774
20980
|
}
|
|
20775
20981
|
throw error;
|
|
20776
20982
|
}
|
|
@@ -21368,44 +21574,22 @@
|
|
|
21368
21574
|
const bogus = get$a(element, 'data-mce-bogus');
|
|
21369
21575
|
if (!isInternalElement && isString(bogus)) {
|
|
21370
21576
|
if (bogus === 'all') {
|
|
21371
|
-
// Empty before removing so a detached raw-text element (e.g. script/style) can't carry
|
|
21372
|
-
// markup-like content into DOMPurify's unsafe-node check, which would then throw trying
|
|
21373
|
-
// to _forceRemove the now-parentless node.
|
|
21374
|
-
empty(element);
|
|
21375
21577
|
remove$8(element);
|
|
21376
21578
|
}
|
|
21377
21579
|
else {
|
|
21378
21580
|
unwrap(element);
|
|
21379
21581
|
}
|
|
21380
|
-
// We detach the element above; DOMPurify throws if it then tries to _forceRemove the
|
|
21381
|
-
// now-parentless node, so mark the tag allowed to stop that second removal.
|
|
21382
|
-
if (isNonNullable(evt)) {
|
|
21383
|
-
evt.allowedTags[lcTagName] = true;
|
|
21384
|
-
}
|
|
21385
21582
|
return;
|
|
21386
21583
|
}
|
|
21387
21584
|
// Determine if the schema allows the element and either add it or remove it
|
|
21388
21585
|
const rule = schema.getElementRule(lcTagName);
|
|
21389
21586
|
if (validate && !rule) {
|
|
21390
|
-
//
|
|
21391
|
-
|
|
21392
|
-
|
|
21393
|
-
if (settings.sanitize && (has$2(specialElements, lcTagName) || scope !== 'html')) {
|
|
21394
|
-
empty(element);
|
|
21587
|
+
// If a special element is invalid, then remove the entire element instead of unwrapping
|
|
21588
|
+
if (has$2(specialElements, lcTagName)) {
|
|
21589
|
+
remove$8(element);
|
|
21395
21590
|
}
|
|
21396
21591
|
else {
|
|
21397
|
-
|
|
21398
|
-
if (has$2(specialElements, lcTagName)) {
|
|
21399
|
-
remove$8(element);
|
|
21400
|
-
}
|
|
21401
|
-
else {
|
|
21402
|
-
unwrap(element);
|
|
21403
|
-
}
|
|
21404
|
-
// The element is now detached; mark the tag allowed so DOMPurify won't _forceRemove the
|
|
21405
|
-
// parentless node and throw. Safe here: it's empty and HTML-namespaced, so no earlier check fires.
|
|
21406
|
-
if (isNonNullable(evt)) {
|
|
21407
|
-
evt.allowedTags[lcTagName] = true;
|
|
21408
|
-
}
|
|
21592
|
+
unwrap(element);
|
|
21409
21593
|
}
|
|
21410
21594
|
return;
|
|
21411
21595
|
}
|
|
@@ -21588,9 +21772,7 @@
|
|
|
21588
21772
|
evt.allowedTags[lcTagName] = keepElement;
|
|
21589
21773
|
if (!keepElement && settings.sanitize) {
|
|
21590
21774
|
if (isElement$7(node)) {
|
|
21591
|
-
|
|
21592
|
-
// Detaching it would make DOMPurify throw when it _forceRemoves the parentless node.
|
|
21593
|
-
empty(SugarElement.fromDom(node));
|
|
21775
|
+
node.remove();
|
|
21594
21776
|
}
|
|
21595
21777
|
}
|
|
21596
21778
|
});
|
|
@@ -23212,14 +23394,21 @@
|
|
|
23212
23394
|
}
|
|
23213
23395
|
});
|
|
23214
23396
|
};
|
|
23215
|
-
// TODO: TINY-9142: Remove this to make nested noneditable formatting work
|
|
23216
23397
|
const targetNode = isNode(node) ? node : selection.getNode();
|
|
23217
23398
|
if (dom.getContentEditable(targetNode) === 'false' && !isWrappableNoneditable(ed, targetNode)) {
|
|
23218
23399
|
// node variable is used by other functions above in the same scope so need to set it here
|
|
23219
23400
|
node = targetNode;
|
|
23220
23401
|
applyNodeStyle(formatList, node);
|
|
23221
|
-
if (
|
|
23222
|
-
|
|
23402
|
+
if (hasEditableDescendants(dom, node)) {
|
|
23403
|
+
each$e(getEditableDescendants(dom, node), (island) => {
|
|
23404
|
+
const rng = dom.createRng();
|
|
23405
|
+
rng.selectNodeContents(island);
|
|
23406
|
+
applyRngStyle(dom, rng, true);
|
|
23407
|
+
});
|
|
23408
|
+
return;
|
|
23409
|
+
}
|
|
23410
|
+
if (isBlockFormat(format) && !dom.isBlock(node)) {
|
|
23411
|
+
const parentBlock = dom.getParent(node, dom.isBlock);
|
|
23223
23412
|
if (dom.isEditable(parentBlock)) {
|
|
23224
23413
|
const wrapperElementName = format.block;
|
|
23225
23414
|
if (parentBlock.nodeName.toLowerCase() === wrapperElementName.toLowerCase()) {
|
|
@@ -23231,6 +23420,12 @@
|
|
|
23231
23420
|
}
|
|
23232
23421
|
}
|
|
23233
23422
|
}
|
|
23423
|
+
else {
|
|
23424
|
+
const selectorFormats = filter$5(formatList, isSelectorFormat);
|
|
23425
|
+
Optional.from(dom.getParent(node, (candidate) => exists(selectorFormats, (fmt) => dom.is(candidate, fmt.selector))))
|
|
23426
|
+
.filter(dom.isEditable)
|
|
23427
|
+
.each((parent) => applyNodeStyle(formatList, parent));
|
|
23428
|
+
}
|
|
23234
23429
|
fireFormatApply(ed, name, node, vars);
|
|
23235
23430
|
return;
|
|
23236
23431
|
}
|
|
@@ -38309,9 +38504,11 @@
|
|
|
38309
38504
|
const initTooltipClosing = (editor) => {
|
|
38310
38505
|
const closeTooltipsListener = (event) => {
|
|
38311
38506
|
if (event.keyCode === VK.ESC && !event.defaultPrevented) {
|
|
38312
|
-
|
|
38313
|
-
|
|
38314
|
-
|
|
38507
|
+
each$e(editor.editorManager.get(), (ed) => {
|
|
38508
|
+
if (fireCloseTooltips(ed).isDefaultPrevented()) {
|
|
38509
|
+
event.preventDefault();
|
|
38510
|
+
}
|
|
38511
|
+
});
|
|
38315
38512
|
}
|
|
38316
38513
|
};
|
|
38317
38514
|
document.addEventListener('keyup', closeTooltipsListener);
|
|
@@ -39479,7 +39676,7 @@
|
|
|
39479
39676
|
editor.getWin().print();
|
|
39480
39677
|
},
|
|
39481
39678
|
mceFocus: (_command, _ui, value) => {
|
|
39482
|
-
focus(editor, value
|
|
39679
|
+
focus(editor, value);
|
|
39483
39680
|
},
|
|
39484
39681
|
mceToggleVisualAid: () => {
|
|
39485
39682
|
editor.hasVisual = !editor.hasVisual;
|
|
@@ -41205,7 +41402,12 @@
|
|
|
41205
41402
|
* it will also place DOM focus inside the editor.
|
|
41206
41403
|
*
|
|
41207
41404
|
* @method focus
|
|
41208
|
-
* @param {Boolean} skipFocus
|
|
41405
|
+
* @param {Boolean/Object} skipFocus If true, skip DOM focus and only set this editor as the active editor. If an object, focus the editor and optionally control scrolling.
|
|
41406
|
+
* @param {Boolean} skipFocus.scrollToSelection If false, focuses the editor without scrolling the selection into view. Defaults to true.
|
|
41407
|
+
* @example
|
|
41408
|
+
* tinymce.activeEditor.focus();
|
|
41409
|
+
* tinymce.activeEditor.focus(true);
|
|
41410
|
+
* tinymce.activeEditor.focus({ scrollToSelection: false });
|
|
41209
41411
|
*/
|
|
41210
41412
|
focus(skipFocus) {
|
|
41211
41413
|
this.execCommand('mceFocus', false, skipFocus);
|
|
@@ -41901,14 +42103,14 @@
|
|
|
41901
42103
|
* @property minorVersion
|
|
41902
42104
|
* @type String
|
|
41903
42105
|
*/
|
|
41904
|
-
minorVersion: '
|
|
42106
|
+
minorVersion: '9.0',
|
|
41905
42107
|
/**
|
|
41906
42108
|
* Release date of TinyMCE build.
|
|
41907
42109
|
*
|
|
41908
42110
|
* @property releaseDate
|
|
41909
42111
|
* @type String
|
|
41910
42112
|
*/
|
|
41911
|
-
releaseDate: '2026-
|
|
42113
|
+
releaseDate: '2026-08-27',
|
|
41912
42114
|
/**
|
|
41913
42115
|
* Collection of language pack data.
|
|
41914
42116
|
*
|