@mgdis/mg-components 6.23.0 → 6.24.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.
- package/dist/cjs/mg-action-more_36.cjs.entry.js +20 -12
- package/dist/cjs/mg-input-combobox.cjs.entry.js +7 -6
- package/dist/cjs/mg-input-rich-text-editor.cjs.entry.js +808 -135
- package/dist/cjs/mg-progress.cjs.entry.js +1 -1
- package/dist/collection/components/molecules/inputs/mg-input-combobox/mg-input-combobox.js +7 -6
- package/dist/collection/components/molecules/mg-message/mg-message.js +2 -2
- package/dist/collection/components/molecules/mg-modal/mg-modal.js +2 -2
- package/dist/collection/components/molecules/mg-panel/mg-panel.js +1 -1
- package/dist/collection/components/molecules/mg-popover/mg-popover-content/mg-popover-content.js +1 -1
- package/dist/collection/components/molecules/mg-popover/mg-popover.js +10 -2
- package/dist/collection/components/molecules/mg-progress/mg-progress.js +1 -1
- package/dist/collection/components/molecules/mg-skip-links/mg-skip-links.js +1 -1
- package/dist/collection/components/molecules/mg-tabs/mg-tabs.js +1 -1
- package/dist/components/mg-illustrated-message.js +1 -1
- package/dist/components/mg-input-combobox.js +1 -1
- package/dist/components/mg-input-rich-text-editor.js +238 -239
- package/dist/components/mg-message2.js +1 -1
- package/dist/components/mg-modal.js +1 -1
- package/dist/components/mg-panel.js +1 -1
- package/dist/components/mg-popover-content2.js +1 -1
- package/dist/components/mg-popover2.js +1 -1
- package/dist/components/mg-progress.js +1 -1
- package/dist/components/mg-skip-links.js +1 -1
- package/dist/components/mg-tabs2.js +1 -1
- package/dist/esm/mg-action-more_36.entry.js +20 -12
- package/dist/esm/mg-input-combobox.entry.js +7 -6
- package/dist/esm/mg-input-rich-text-editor.entry.js +808 -135
- package/dist/esm/mg-progress.entry.js +1 -1
- package/dist/mg-components/mg-components.esm.js +1 -1
- package/dist/mg-components/{p-236bb1d2.entry.js → p-08d3d957.entry.js} +1 -1
- package/dist/mg-components/p-9953b835.entry.js +1 -0
- package/dist/mg-components/{p-d698fa67.entry.js → p-9c0a9fc7.entry.js} +203 -204
- package/dist/mg-components/p-a338088e.entry.js +1 -0
- package/ide/intellij/web-types.json +1 -1
- package/package.json +18 -18
- package/dist/mg-components/p-79265ad3.entry.js +0 -1
- package/dist/mg-components/p-a0e2c886.entry.js +0 -1
|
@@ -6865,10 +6865,10 @@ var e = Object.create, t = Object.defineProperty, n = Object.getOwnPropertyDescr
|
|
|
6865
6865
|
return;
|
|
6866
6866
|
}
|
|
6867
6867
|
}
|
|
6868
|
-
if (c === "srcset") try {
|
|
6868
|
+
if (c === "srcset" || c === "imagesrcset") try {
|
|
6869
6869
|
let e = o(n);
|
|
6870
6870
|
if (e.forEach(function(e) {
|
|
6871
|
-
H(
|
|
6871
|
+
H(c, e.url) && (e.evil = true);
|
|
6872
6872
|
}), e = p(e, function(e) {
|
|
6873
6873
|
return !e.evil;
|
|
6874
6874
|
}), e.length) n = h(p(e, function(e) {
|
|
@@ -6911,8 +6911,8 @@ var e = Object.create, t = Object.defineProperty, n = Object.getOwnPropertyDescr
|
|
|
6911
6911
|
if (I) return;
|
|
6912
6912
|
let n = N[N.length - 1], r;
|
|
6913
6913
|
if (n && (r = n.tag, e = n.innerText === void 0 ? e : n.innerText), t.disallowedTagsMode === "completelyDiscard" && !C(r)) e = "";
|
|
6914
|
-
else if ((t.disallowedTagsMode === "discard" || t.disallowedTagsMode === "completelyDiscard") && (r === "script" || r === "style")) b += e;
|
|
6915
|
-
else if ((t.disallowedTagsMode === "discard" || t.disallowedTagsMode === "completelyDiscard") && (r === "textarea" || r === "xmp")) b += e;
|
|
6914
|
+
else if (r && C(r) && (t.disallowedTagsMode === "discard" || t.disallowedTagsMode === "completelyDiscard") && (r === "script" || r === "style")) b += e;
|
|
6915
|
+
else if (r && C(r) && (t.disallowedTagsMode === "discard" || t.disallowedTagsMode === "completelyDiscard") && (r === "textarea" || r === "xmp")) b += e;
|
|
6916
6916
|
else if (!R) {
|
|
6917
6917
|
let n = V(e, false);
|
|
6918
6918
|
t.textFilter ? b += t.textFilter(n, r) : b += n;
|
|
@@ -7056,7 +7056,24 @@ var e = Object.create, t = Object.defineProperty, n = Object.getOwnPropertyDescr
|
|
|
7056
7056
|
allowedSchemesAppliedToAttributes: [
|
|
7057
7057
|
"href",
|
|
7058
7058
|
"src",
|
|
7059
|
-
"cite"
|
|
7059
|
+
"cite",
|
|
7060
|
+
"action",
|
|
7061
|
+
"formaction",
|
|
7062
|
+
"data",
|
|
7063
|
+
"xlink:href",
|
|
7064
|
+
"poster",
|
|
7065
|
+
"background",
|
|
7066
|
+
"ping",
|
|
7067
|
+
"longdesc",
|
|
7068
|
+
"usemap",
|
|
7069
|
+
"codebase",
|
|
7070
|
+
"classid",
|
|
7071
|
+
"archive",
|
|
7072
|
+
"profile",
|
|
7073
|
+
"manifest",
|
|
7074
|
+
"itemid",
|
|
7075
|
+
"dynsrc",
|
|
7076
|
+
"lowsrc"
|
|
7060
7077
|
],
|
|
7061
7078
|
allowProtocolRelative: true,
|
|
7062
7079
|
enforceHtmlBoundary: false,
|
|
@@ -7121,7 +7138,7 @@ var e = Object.create, t = Object.defineProperty, n = Object.getOwnPropertyDescr
|
|
|
7121
7138
|
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
7122
7139
|
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
7123
7140
|
*/
|
|
7124
|
-
const APP_VERSION = "4.12.
|
|
7141
|
+
const APP_VERSION = "4.12.25";
|
|
7125
7142
|
// prettier-ignore
|
|
7126
7143
|
const ES = "es2020";
|
|
7127
7144
|
const IS_ES_MODERN = true;
|
|
@@ -8568,12 +8585,12 @@ class Dom {
|
|
|
8568
8585
|
* Check if element is element node
|
|
8569
8586
|
*/
|
|
8570
8587
|
static isElement(node) {
|
|
8571
|
-
var _a;
|
|
8572
8588
|
if (!Dom.isNode(node)) {
|
|
8573
8589
|
return false;
|
|
8574
8590
|
}
|
|
8575
|
-
|
|
8576
|
-
|
|
8591
|
+
// no `defaultView` requirement — nodes of an inert document
|
|
8592
|
+
// (`DOMParser`, `implementation.createHTMLDocument`) are still elements
|
|
8593
|
+
return node.nodeType === Node.ELEMENT_NODE;
|
|
8577
8594
|
}
|
|
8578
8595
|
/**
|
|
8579
8596
|
* Check if element is document fragment
|
|
@@ -8594,8 +8611,10 @@ class Dom {
|
|
|
8594
8611
|
if (!Dom.isNode(node)) {
|
|
8595
8612
|
return false;
|
|
8596
8613
|
}
|
|
8614
|
+
// an inert document has no browsing context (`defaultView` is null),
|
|
8615
|
+
// but its nodes are same-realm HTMLElements
|
|
8597
8616
|
const win = (_a = node.ownerDocument) === null || _a === void 0 ? void 0 : _a.defaultView;
|
|
8598
|
-
return
|
|
8617
|
+
return node instanceof (win ? win.HTMLElement : HTMLElement);
|
|
8599
8618
|
}
|
|
8600
8619
|
/**
|
|
8601
8620
|
* Check element is inline block
|
|
@@ -9162,11 +9181,22 @@ function isFastEqual(a, b) {
|
|
|
9162
9181
|
* @module helpers/checker
|
|
9163
9182
|
*/
|
|
9164
9183
|
/**
|
|
9165
|
-
* Detect if string is HTML from MS Word or
|
|
9184
|
+
* Detect if string is HTML from MS Word, Excel or LibreOffice/OpenOffice
|
|
9166
9185
|
*/
|
|
9167
9186
|
function isHtmlFromWord(data) {
|
|
9168
9187
|
return (data.search(/<meta.*?Microsoft Excel\s[\d].*?>/) !== -1 ||
|
|
9169
9188
|
data.search(/<meta.*?Microsoft Word\s[\d].*?>/) !== -1 ||
|
|
9189
|
+
// `<meta name=ProgId content=Word.Document>` — attribute values are
|
|
9190
|
+
// unquoted in the raw Word clipboard fragment
|
|
9191
|
+
data.search(/<meta[^>]*?ProgId[^>]*?(Word|Excel)\./i) !== -1 ||
|
|
9192
|
+
// LibreOffice/OpenOffice Writer & Calc
|
|
9193
|
+
data.search(/<meta[^>]*?(LibreOffice|OpenOffice)/i) !== -1 ||
|
|
9194
|
+
// Office namespaces on the root element of the clipboard fragment
|
|
9195
|
+
data.search(/urn:schemas-microsoft-com:office:(word|excel)/) !== -1 ||
|
|
9196
|
+
// `class=MsoNormal` and friends (unquoted/quoted)
|
|
9197
|
+
data.search(/<\w[^>]*\sclass=("|')?Mso/) !== -1 ||
|
|
9198
|
+
// the raw Word clipboard uses SINGLE quotes: style='mso-…'
|
|
9199
|
+
data.search(/style='[^']*mso-/) !== -1 ||
|
|
9170
9200
|
(data.search(/style="[^"]*mso-/) !== -1 && data.search(/<font/) !== -1));
|
|
9171
9201
|
}
|
|
9172
9202
|
|
|
@@ -11235,6 +11265,17 @@ function applyStyles(html) {
|
|
|
11235
11265
|
iframeDoc.open();
|
|
11236
11266
|
iframeDoc.write(html);
|
|
11237
11267
|
iframeDoc.close();
|
|
11268
|
+
// Word marks its auto-generated list markers (the literal
|
|
11269
|
+
// bullet/number, e.g. `1.` or `·`) with `mso-list:Ignore`.
|
|
11270
|
+
// They are display-only and must not be imported, otherwise
|
|
11271
|
+
// the marker text leaks into the content. Drop them before any
|
|
11272
|
+
// style normalization strips the `mso-list` hint. See #948
|
|
11273
|
+
Dom.each(iframeDoc.body, (node) => {
|
|
11274
|
+
if (Dom.isElement(node) &&
|
|
11275
|
+
/mso-list:\s*ignore/i.test(node.getAttribute('style') || '')) {
|
|
11276
|
+
Dom.safeRemove(node);
|
|
11277
|
+
}
|
|
11278
|
+
});
|
|
11238
11279
|
try {
|
|
11239
11280
|
for (let i = 0; i < iframeDoc.styleSheets.length; i += 1) {
|
|
11240
11281
|
const rules = iframeDoc.styleSheets[i].cssRules;
|
|
@@ -11325,6 +11366,15 @@ function cleanFromWord(html) {
|
|
|
11325
11366
|
Dom.unwrap(node);
|
|
11326
11367
|
break;
|
|
11327
11368
|
default:
|
|
11369
|
+
// Word marks its auto-generated list markers
|
|
11370
|
+
// (the literal bullet/number, e.g. `1.` or `·`)
|
|
11371
|
+
// with `mso-list:Ignore`. They are display-only
|
|
11372
|
+
// and must not be imported, otherwise the marker
|
|
11373
|
+
// text leaks into the content. See #948
|
|
11374
|
+
if (/mso-list:\s*ignore/i.test(node.getAttribute('style') || '')) {
|
|
11375
|
+
marks.push(node);
|
|
11376
|
+
break;
|
|
11377
|
+
}
|
|
11328
11378
|
toArray(node.attributes).forEach((attr) => {
|
|
11329
11379
|
if ([
|
|
11330
11380
|
'src',
|
|
@@ -12591,6 +12641,15 @@ function fastClone(object) {
|
|
|
12591
12641
|
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
12592
12642
|
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
12593
12643
|
*/
|
|
12644
|
+
/**
|
|
12645
|
+
* Keys that must never be copied from a (potentially untrusted) config object —
|
|
12646
|
+
* assigning them during a recursive merge can reach and mutate
|
|
12647
|
+
* `Object.prototype` (prototype pollution, CWE-1321).
|
|
12648
|
+
*/
|
|
12649
|
+
const UNSAFE_PROTO_KEYS = ['__proto__', 'constructor', 'prototype'];
|
|
12650
|
+
function isUnsafeProtoKey(key) {
|
|
12651
|
+
return UNSAFE_PROTO_KEYS.indexOf(key) !== -1;
|
|
12652
|
+
}
|
|
12594
12653
|
/**
|
|
12595
12654
|
* @example
|
|
12596
12655
|
* ```js
|
|
@@ -12640,6 +12699,9 @@ function ConfigProto(options, proto, deep = 0) {
|
|
|
12640
12699
|
}
|
|
12641
12700
|
const newOpt = {};
|
|
12642
12701
|
Object.keys(options).forEach(key => {
|
|
12702
|
+
if (isUnsafeProtoKey(key)) {
|
|
12703
|
+
return;
|
|
12704
|
+
}
|
|
12643
12705
|
const opt = options[key], protoKey = proto ? proto[key] : null;
|
|
12644
12706
|
if (isPlainObject(opt) && isPlainObject(protoKey) && !isAtom(opt)) {
|
|
12645
12707
|
newOpt[key] = ConfigProto(opt, protoKey, deep + 1);
|
|
@@ -12700,6 +12762,9 @@ function ConfigFlatten(obj) {
|
|
|
12700
12762
|
*/
|
|
12701
12763
|
function ConfigMerge(target, source) {
|
|
12702
12764
|
Object.keys(source).forEach(key => {
|
|
12765
|
+
if (isUnsafeProtoKey(key)) {
|
|
12766
|
+
return;
|
|
12767
|
+
}
|
|
12703
12768
|
const srcVal = source[key];
|
|
12704
12769
|
const tgtVal = target[key];
|
|
12705
12770
|
if (isPlainObject(srcVal) && isPlainObject(tgtVal) && !isAtom(srcVal)) {
|
|
@@ -12752,7 +12817,6 @@ const convertMediaUrlToVideoEmbed = (url, { width = 400, height = 345 } = {}) =>
|
|
|
12752
12817
|
return url;
|
|
12753
12818
|
}
|
|
12754
12819
|
const parser = globalDocument.createElement('a');
|
|
12755
|
-
const pattern1 = /(?:http?s?:\/\/)?(?:www\.)?(?:vimeo\.com)\/?(.+)/g;
|
|
12756
12820
|
parser.href = url;
|
|
12757
12821
|
if (!width) {
|
|
12758
12822
|
width = 400;
|
|
@@ -12763,27 +12827,50 @@ const convertMediaUrlToVideoEmbed = (url, { width = 400, height = 345 } = {}) =>
|
|
|
12763
12827
|
const protocol = parser.protocol || '';
|
|
12764
12828
|
switch (parser.hostname) {
|
|
12765
12829
|
case 'www.vimeo.com':
|
|
12766
|
-
case 'vimeo.com':
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
12772
|
-
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12830
|
+
case 'vimeo.com': {
|
|
12831
|
+
// The numeric video id can be preceded by `channels/<name>/` or
|
|
12832
|
+
// `groups/<name>/videos/` and followed by tracking params (e.g.
|
|
12833
|
+
// `?share=copy`). Unlisted videos keep a hash right after the id
|
|
12834
|
+
// (`vimeo.com/<id>/<hash>`). Extract the id (+ hash) from the path
|
|
12835
|
+
// so all of those forms produce a valid embed. See #1209
|
|
12836
|
+
const segments = parser.pathname.split('/').filter(Boolean);
|
|
12837
|
+
const idIndex = segments.findIndex(s => /^\d+$/.test(s));
|
|
12838
|
+
if (idIndex === -1) {
|
|
12839
|
+
return url;
|
|
12840
|
+
}
|
|
12841
|
+
let path = segments[idIndex];
|
|
12842
|
+
const hash = segments[idIndex + 1];
|
|
12843
|
+
if (hash && idIndex === 0) {
|
|
12844
|
+
path += '/' + hash;
|
|
12845
|
+
}
|
|
12846
|
+
return ('<iframe width="' +
|
|
12847
|
+
width +
|
|
12848
|
+
'" height="' +
|
|
12849
|
+
height +
|
|
12850
|
+
'" src="' +
|
|
12851
|
+
protocol +
|
|
12852
|
+
'//player.vimeo.com/video/' +
|
|
12853
|
+
path +
|
|
12854
|
+
'" frameborder="0" allowfullscreen></iframe>');
|
|
12855
|
+
}
|
|
12776
12856
|
case 'youtube.com':
|
|
12777
12857
|
case 'www.youtube.com':
|
|
12858
|
+
case 'm.youtube.com':
|
|
12859
|
+
case 'music.youtube.com':
|
|
12778
12860
|
case 'youtu.be':
|
|
12779
12861
|
case 'www.youtu.be': {
|
|
12780
|
-
const query = parser.search
|
|
12781
|
-
|
|
12782
|
-
|
|
12783
|
-
|
|
12784
|
-
|
|
12785
|
-
|
|
12786
|
-
|
|
12862
|
+
const query = parser.search ? parseQuery(parser.search) : {};
|
|
12863
|
+
// `youtube.com/watch` keeps the video id in the `v` query
|
|
12864
|
+
// parameter, while the short `youtu.be/<id>` links and the
|
|
12865
|
+
// `/embed/`, `/shorts/`, `/live/` paths keep it in the pathname.
|
|
12866
|
+
// Modern share urls add tracking params (e.g. `?si=`, `?t=`), so
|
|
12867
|
+
// the pathname must still be used as a fallback when there is no
|
|
12868
|
+
// `v`. See #1209
|
|
12869
|
+
let v = query.v || parser.pathname.substring(1);
|
|
12870
|
+
v = v
|
|
12871
|
+
.replace(/^(watch|embed|shorts|live|v)\//, '')
|
|
12872
|
+
.replace(/\/$/, '');
|
|
12873
|
+
return v
|
|
12787
12874
|
? '<iframe width="' +
|
|
12788
12875
|
width +
|
|
12789
12876
|
'" height="' +
|
|
@@ -12791,7 +12878,7 @@ const convertMediaUrlToVideoEmbed = (url, { width = 400, height = 345 } = {}) =>
|
|
|
12791
12878
|
'" src="' +
|
|
12792
12879
|
protocol +
|
|
12793
12880
|
'//www.youtube.com/embed/' +
|
|
12794
|
-
|
|
12881
|
+
v +
|
|
12795
12882
|
'" frameborder="0" allowfullscreen></iframe>'
|
|
12796
12883
|
: url;
|
|
12797
12884
|
}
|
|
@@ -13177,21 +13264,22 @@ function safeHTML(box, options) {
|
|
|
13177
13264
|
return;
|
|
13178
13265
|
}
|
|
13179
13266
|
const removeEvents = (_a = options.removeEventAttributes) !== null && _a !== void 0 ? _a : options.removeOnError;
|
|
13180
|
-
|
|
13181
|
-
|
|
13182
|
-
|
|
13183
|
-
|
|
13184
|
-
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
|
|
13191
|
-
|
|
13192
|
-
|
|
13193
|
-
|
|
13194
|
-
|
|
13267
|
+
// Single synchronous traversal of the subtree. Besides removing event
|
|
13268
|
+
// handlers and `javascript:` links, `sanitizeHTMLElement` neutralises
|
|
13269
|
+
// executable `iframe[srcdoc]`, `data:text/html` / SVG `data:` document
|
|
13270
|
+
// sources and dangerous schemes in every URL-bearing attribute.
|
|
13271
|
+
const process = (node) => {
|
|
13272
|
+
if (!Dom.isElement(node)) {
|
|
13273
|
+
return;
|
|
13274
|
+
}
|
|
13275
|
+
if (removeEvents) {
|
|
13276
|
+
removeAllEventAttributes(node);
|
|
13277
|
+
}
|
|
13278
|
+
sanitizeHTMLElement(node, options);
|
|
13279
|
+
if (options.safeLinksTarget &&
|
|
13280
|
+
node.nodeName === 'A' &&
|
|
13281
|
+
node.getAttribute('target') === '_blank') {
|
|
13282
|
+
const rel = node.getAttribute('rel') || '';
|
|
13195
13283
|
const parts = rel.split(/\s+/).filter(Boolean);
|
|
13196
13284
|
if (!parts.includes('noopener')) {
|
|
13197
13285
|
parts.push('noopener');
|
|
@@ -13199,9 +13287,11 @@ function safeHTML(box, options) {
|
|
|
13199
13287
|
if (!parts.includes('noreferrer')) {
|
|
13200
13288
|
parts.push('noreferrer');
|
|
13201
13289
|
}
|
|
13202
|
-
attr(
|
|
13203
|
-
}
|
|
13204
|
-
}
|
|
13290
|
+
attr(node, 'rel', parts.join(' '));
|
|
13291
|
+
}
|
|
13292
|
+
};
|
|
13293
|
+
process(box);
|
|
13294
|
+
Dom.each(box, process);
|
|
13205
13295
|
}
|
|
13206
13296
|
/**
|
|
13207
13297
|
* Remove all on* event handler attributes from an element
|
|
@@ -13223,6 +13313,39 @@ function removeAllEventAttributes(elm) {
|
|
|
13223
13313
|
}
|
|
13224
13314
|
return effected;
|
|
13225
13315
|
}
|
|
13316
|
+
/**
|
|
13317
|
+
* URL-bearing attributes (besides `href`) that can load or execute content.
|
|
13318
|
+
*/
|
|
13319
|
+
const URL_ATTRIBUTES = [
|
|
13320
|
+
'src',
|
|
13321
|
+
'data',
|
|
13322
|
+
'action',
|
|
13323
|
+
'formaction',
|
|
13324
|
+
'poster',
|
|
13325
|
+
'background',
|
|
13326
|
+
'xlink:href'
|
|
13327
|
+
];
|
|
13328
|
+
/**
|
|
13329
|
+
* Tags that load their URL as a *document* (scripts inside run). An SVG data
|
|
13330
|
+
* URL is only an XSS vector here — as an `<img>` source it renders inertly.
|
|
13331
|
+
*/
|
|
13332
|
+
const DOCUMENT_EMBED_TAGS = new Set(['iframe', 'frame', 'object', 'embed']);
|
|
13333
|
+
/**
|
|
13334
|
+
* Detects executable / script-bearing URL schemes. The attribute value is
|
|
13335
|
+
* already HTML-entity-decoded by `getAttribute`, so only whitespace and
|
|
13336
|
+
* control characters (which browsers ignore inside a scheme) need stripping.
|
|
13337
|
+
*/
|
|
13338
|
+
function isDangerousUrl(value, tagName) {
|
|
13339
|
+
// eslint-disable-next-line no-control-regex
|
|
13340
|
+
const normalized = value.replace(/[\u0000-\u0020]+/g, '').toLowerCase();
|
|
13341
|
+
if (/^(?:javascript|vbscript|livescript|mocha):/.test(normalized)) {
|
|
13342
|
+
return true;
|
|
13343
|
+
}
|
|
13344
|
+
if (/^data:(?:text\/html|application\/xhtml)/.test(normalized)) {
|
|
13345
|
+
return true;
|
|
13346
|
+
}
|
|
13347
|
+
return (/^data:image\/svg/.test(normalized) && DOCUMENT_EMBED_TAGS.has(tagName));
|
|
13348
|
+
}
|
|
13226
13349
|
function sanitizeHTMLElement(elm, { safeJavaScriptLink, removeOnError } = {
|
|
13227
13350
|
safeJavaScriptLink: true,
|
|
13228
13351
|
removeOnError: true
|
|
@@ -13240,6 +13363,22 @@ function sanitizeHTMLElement(elm, { safeJavaScriptLink, removeOnError } = {
|
|
|
13240
13363
|
attr(elm, 'href', location.protocol + '//' + href);
|
|
13241
13364
|
effected = true;
|
|
13242
13365
|
}
|
|
13366
|
+
if (safeJavaScriptLink) {
|
|
13367
|
+
// `srcdoc` runs its content as a full HTML document — drop it entirely.
|
|
13368
|
+
if (elm.hasAttribute('srcdoc')) {
|
|
13369
|
+
attr(elm, 'srcdoc', null);
|
|
13370
|
+
effected = true;
|
|
13371
|
+
}
|
|
13372
|
+
// Strip executable schemes from any other URL-bearing attribute.
|
|
13373
|
+
const tagName = elm.nodeName.toLowerCase();
|
|
13374
|
+
for (const name of URL_ATTRIBUTES) {
|
|
13375
|
+
const value = elm.getAttribute(name);
|
|
13376
|
+
if (value && isDangerousUrl(value, tagName)) {
|
|
13377
|
+
attr(elm, name, null);
|
|
13378
|
+
effected = true;
|
|
13379
|
+
}
|
|
13380
|
+
}
|
|
13381
|
+
}
|
|
13243
13382
|
return effected;
|
|
13244
13383
|
}
|
|
13245
13384
|
|
|
@@ -13265,7 +13404,7 @@ const ALONE_TAGS = new Set(['br', 'hr', 'input']);
|
|
|
13265
13404
|
/**
|
|
13266
13405
|
* Extract plain text from HTML text
|
|
13267
13406
|
*/
|
|
13268
|
-
function stripTags(html, doc = document, exclude = null) {
|
|
13407
|
+
function stripTags(html, doc = document, exclude = null, blockBr = false) {
|
|
13269
13408
|
const tmp = doc.createElement('div');
|
|
13270
13409
|
if (isString(html)) {
|
|
13271
13410
|
tmp.innerHTML = html;
|
|
@@ -13281,7 +13420,7 @@ function stripTags(html, doc = document, exclude = null) {
|
|
|
13281
13420
|
if (exclude && Dom.isTag(p, exclude)) {
|
|
13282
13421
|
const tag = p.nodeName.toLowerCase();
|
|
13283
13422
|
const text = !Dom.isTag(p, ALONE_TAGS)
|
|
13284
|
-
? `%%%jodit-${tag}%%%${stripTags(p.innerHTML, doc, exclude)}%%%/jodit-${tag}%%%`
|
|
13423
|
+
? `%%%jodit-${tag}%%%${stripTags(p.innerHTML, doc, exclude, blockBr)}%%%/jodit-${tag}%%%`
|
|
13285
13424
|
: `%%%jodit-single-${tag}%%%`;
|
|
13286
13425
|
Dom.before(p, doc.createTextNode(text));
|
|
13287
13426
|
Dom.safeRemove(p);
|
|
@@ -13299,7 +13438,11 @@ function stripTags(html, doc = document, exclude = null) {
|
|
|
13299
13438
|
return;
|
|
13300
13439
|
}
|
|
13301
13440
|
if (nx) {
|
|
13302
|
-
|
|
13441
|
+
// By default blocks are joined with a single space (single-line
|
|
13442
|
+
// plain text). When `blockBr` is set, separate them with a line
|
|
13443
|
+
// break instead, so paragraph structure survives — e.g. the
|
|
13444
|
+
// "Insert only Text" paste option. See #1232
|
|
13445
|
+
pr.insertBefore(doc.createTextNode(blockBr ? '%%%jodit-single-br%%%' : ' '), nx);
|
|
13303
13446
|
}
|
|
13304
13447
|
});
|
|
13305
13448
|
return restoreTags(trim(tmp.innerText));
|
|
@@ -13506,6 +13649,53 @@ const getContentWidth = (element, win) => {
|
|
|
13506
13649
|
return width - paddingLeft - paddingRight;
|
|
13507
13650
|
};
|
|
13508
13651
|
|
|
13652
|
+
/*!
|
|
13653
|
+
* Jodit Editor (https://xdsoft.net/jodit/)
|
|
13654
|
+
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
13655
|
+
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
13656
|
+
*/
|
|
13657
|
+
/**
|
|
13658
|
+
* Whether the element establishes a containing block for its
|
|
13659
|
+
* `position: fixed` descendants, so their coordinates become relative to it
|
|
13660
|
+
* instead of the viewport.
|
|
13661
|
+
*/
|
|
13662
|
+
function isContainingBlockForFixed(style) {
|
|
13663
|
+
return ((style.transform !== '' && style.transform !== 'none') ||
|
|
13664
|
+
(style.perspective !== '' && style.perspective !== 'none') ||
|
|
13665
|
+
(style.filter !== '' && style.filter !== 'none') ||
|
|
13666
|
+
style.willChange === 'transform' ||
|
|
13667
|
+
style.willChange === 'perspective' ||
|
|
13668
|
+
style.willChange === 'filter' ||
|
|
13669
|
+
style.contain === 'paint' ||
|
|
13670
|
+
style.contain === 'layout' ||
|
|
13671
|
+
style.contain === 'strict' ||
|
|
13672
|
+
style.contain === 'content');
|
|
13673
|
+
}
|
|
13674
|
+
/**
|
|
13675
|
+
* Returns the viewport offset of the containing block of a `position: fixed`
|
|
13676
|
+
* descendant of `elm`. A fixed element is normally positioned relative to the
|
|
13677
|
+
* viewport, but an ancestor with `transform`, `filter`, `perspective`, etc.
|
|
13678
|
+
* establishes a new containing block, shifting the fixed element by that
|
|
13679
|
+
* ancestor's top-left corner. The returned offset should be subtracted from
|
|
13680
|
+
* the desired viewport coordinates before applying them.
|
|
13681
|
+
*
|
|
13682
|
+
* Returns `{ x: 0, y: 0 }` when no such ancestor exists (the common case), so
|
|
13683
|
+
* call sites keep their previous behaviour unchanged.
|
|
13684
|
+
*/
|
|
13685
|
+
function getFixedPositionOffset(elm) {
|
|
13686
|
+
var _a;
|
|
13687
|
+
const win = (_a = elm.ownerDocument) === null || _a === void 0 ? void 0 : _a.defaultView;
|
|
13688
|
+
let node = elm.parentElement;
|
|
13689
|
+
while (win && node) {
|
|
13690
|
+
if (isContainingBlockForFixed(win.getComputedStyle(node))) {
|
|
13691
|
+
const rect = node.getBoundingClientRect();
|
|
13692
|
+
return { x: rect.left, y: rect.top };
|
|
13693
|
+
}
|
|
13694
|
+
node = node.parentElement;
|
|
13695
|
+
}
|
|
13696
|
+
return { x: 0, y: 0 };
|
|
13697
|
+
}
|
|
13698
|
+
|
|
13509
13699
|
/*!
|
|
13510
13700
|
* Jodit Editor (https://xdsoft.net/jodit/)
|
|
13511
13701
|
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
@@ -13854,6 +14044,7 @@ var index = /*#__PURE__*/Object.freeze({
|
|
|
13854
14044
|
getClassName: getClassName,
|
|
13855
14045
|
getContentWidth: getContentWidth,
|
|
13856
14046
|
getDataTransfer: getDataTransfer,
|
|
14047
|
+
getFixedPositionOffset: getFixedPositionOffset,
|
|
13857
14048
|
getPropertyDescriptor: getPropertyDescriptor,
|
|
13858
14049
|
getScrollParent: getScrollParent,
|
|
13859
14050
|
getXPathByElement: getXPathByElement,
|
|
@@ -14148,6 +14339,14 @@ class ViewComponent extends Component {
|
|
|
14148
14339
|
*/
|
|
14149
14340
|
setParentView(jodit) {
|
|
14150
14341
|
this.jodit = jodit;
|
|
14342
|
+
// Inherit the owner window from the parent view — for an editor
|
|
14343
|
+
// created with a custom `ownerWindow` (e.g. inside an iframe) the
|
|
14344
|
+
// component default (the global `window`) is wrong: outside-click
|
|
14345
|
+
// handlers of dropdowns/popups listened to the wrong window. See
|
|
14346
|
+
// https://github.com/xdan/jodit/issues/965
|
|
14347
|
+
if (jodit.ow) {
|
|
14348
|
+
this.ownerWindow = jodit.ow;
|
|
14349
|
+
}
|
|
14151
14350
|
jodit.components.add(this);
|
|
14152
14351
|
return this;
|
|
14153
14352
|
}
|
|
@@ -15900,9 +16099,14 @@ let UITooltip = UITooltip_1 = class UITooltip extends UIElement {
|
|
|
15900
16099
|
this.setMod('above', false);
|
|
15901
16100
|
this.getElm('content').innerHTML = content;
|
|
15902
16101
|
const point = getPoint();
|
|
16102
|
+
// The tooltip is `position: fixed`. If it is rendered inside an ancestor
|
|
16103
|
+
// with a `transform` (e.g. an editor placed in a modal/flyout), that
|
|
16104
|
+
// ancestor becomes the containing block, so viewport coordinates must be
|
|
16105
|
+
// shifted by its offset. Returns `{0, 0}` when there is no such ancestor.
|
|
16106
|
+
const offset = getFixedPositionOffset(this.container);
|
|
15903
16107
|
css(this.container, {
|
|
15904
|
-
left: point.x,
|
|
15905
|
-
top: point.y
|
|
16108
|
+
left: point.x - offset.x,
|
|
16109
|
+
top: point.y - offset.y
|
|
15906
16110
|
});
|
|
15907
16111
|
const tooltipPos = position(this.container);
|
|
15908
16112
|
const viewHeight = this.j.ow.innerHeight;
|
|
@@ -15911,7 +16115,7 @@ let UITooltip = UITooltip_1 = class UITooltip extends UIElement {
|
|
|
15911
16115
|
const targetPos = position(this.__currentTarget);
|
|
15912
16116
|
this.setMod('above', true);
|
|
15913
16117
|
css(this.container, {
|
|
15914
|
-
top: targetPos.top - tooltipPos.height
|
|
16118
|
+
top: targetPos.top - tooltipPos.height - offset.y
|
|
15915
16119
|
});
|
|
15916
16120
|
}
|
|
15917
16121
|
}
|
|
@@ -17056,8 +17260,11 @@ class Popup extends UIGroup {
|
|
|
17056
17260
|
* Calculate static bound for point
|
|
17057
17261
|
*/
|
|
17058
17262
|
getKeepBound(getBound) {
|
|
17263
|
+
var _a;
|
|
17059
17264
|
const oldBound = getBound();
|
|
17060
|
-
|
|
17265
|
+
// Inside Shadow DOM `document.elementFromPoint` returns the shadow
|
|
17266
|
+
// host, so the lookup must start from the shadow root
|
|
17267
|
+
const elmUnderCursor = ((_a = this.j.o.shadowRoot) !== null && _a !== void 0 ? _a : this.od).elementFromPoint(oldBound.left, oldBound.top);
|
|
17061
17268
|
if (!elmUnderCursor) {
|
|
17062
17269
|
return getBound;
|
|
17063
17270
|
}
|
|
@@ -17084,9 +17291,13 @@ class Popup extends UIGroup {
|
|
|
17084
17291
|
}
|
|
17085
17292
|
const [pos, strategy] = this.__calculatePosition(this.__targetBound(), this.viewBound(), position(this.container, this.j));
|
|
17086
17293
|
this.setMod('strategy', strategy);
|
|
17294
|
+
// The popup is `position: fixed`; shift by the containing-block offset
|
|
17295
|
+
// when it is rendered inside a transformed ancestor (e.g. a modal),
|
|
17296
|
+
// otherwise `{0, 0}` keeps the viewport coordinates unchanged.
|
|
17297
|
+
const offset = getFixedPositionOffset(this.container);
|
|
17087
17298
|
css(this.container, {
|
|
17088
|
-
left: pos.left,
|
|
17089
|
-
top: pos.top
|
|
17299
|
+
left: pos.left - offset.x,
|
|
17300
|
+
top: pos.top - offset.y
|
|
17090
17301
|
});
|
|
17091
17302
|
this.__childrenPopups.forEach(popup => popup.updatePosition());
|
|
17092
17303
|
return this;
|
|
@@ -18689,7 +18900,7 @@ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar {
|
|
|
18689
18900
|
e.stopImmediatePropagation();
|
|
18690
18901
|
}
|
|
18691
18902
|
if (this.resizable && this.o.resizable) {
|
|
18692
|
-
this.setSize(this.startPoint.w + e.clientX - this.startX, this.startPoint.h + e.clientY - this.startY);
|
|
18903
|
+
this.setSize(Math.max(this.startPoint.w + e.clientX - this.startX, this.minSize.w), Math.max(this.startPoint.h + e.clientY - this.startY, this.minSize.h));
|
|
18693
18904
|
if (this.e) {
|
|
18694
18905
|
/**
|
|
18695
18906
|
* Fired when dialog box is resized
|
|
@@ -18716,11 +18927,29 @@ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar {
|
|
|
18716
18927
|
}
|
|
18717
18928
|
}
|
|
18718
18929
|
__onResizerMouseDown(e) {
|
|
18930
|
+
var _a, _b, _c;
|
|
18719
18931
|
this.resizable = true;
|
|
18720
18932
|
this.startX = e.clientX;
|
|
18721
18933
|
this.startY = e.clientY;
|
|
18722
18934
|
this.startPoint.w = this.dialog.offsetWidth;
|
|
18723
18935
|
this.startPoint.h = this.dialog.offsetHeight;
|
|
18936
|
+
const header = this.getElm('header');
|
|
18937
|
+
const footer = this.getElm('footer');
|
|
18938
|
+
const content = this.getElm('content');
|
|
18939
|
+
// the content area does not shrink below its CSS `min-height`,
|
|
18940
|
+
// so it is part of the smallest height the panel can take
|
|
18941
|
+
const contentMinHeight = content
|
|
18942
|
+
? parseFloat(this.ow.getComputedStyle(content).minHeight) || 0
|
|
18943
|
+
: 0;
|
|
18944
|
+
this.minSize.w = isNumber(this.o.minWidth)
|
|
18945
|
+
? this.o.minWidth
|
|
18946
|
+
: Math.max(100, (_a = footer === null || footer === void 0 ? void 0 : footer.scrollWidth) !== null && _a !== void 0 ? _a : 0);
|
|
18947
|
+
this.minSize.h = isNumber(this.o.minHeight)
|
|
18948
|
+
? this.o.minHeight
|
|
18949
|
+
: ((_b = header === null || header === void 0 ? void 0 : header.offsetHeight) !== null && _b !== void 0 ? _b : 0) +
|
|
18950
|
+
((_c = footer === null || footer === void 0 ? void 0 : footer.offsetHeight) !== null && _c !== void 0 ? _c : 0) +
|
|
18951
|
+
contentMinHeight +
|
|
18952
|
+
this.resizer.offsetHeight;
|
|
18724
18953
|
this.lockSelect();
|
|
18725
18954
|
this.__addGlobalResizeListeners();
|
|
18726
18955
|
if (this.e) {
|
|
@@ -19032,6 +19261,11 @@ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar {
|
|
|
19032
19261
|
this.setPosition();
|
|
19033
19262
|
}
|
|
19034
19263
|
};
|
|
19264
|
+
/**
|
|
19265
|
+
* Minimal size the dialog can be resized to — the header and the footer
|
|
19266
|
+
* (with its buttons) must always stay inside the panel
|
|
19267
|
+
*/
|
|
19268
|
+
this.minSize = { w: 0, h: 0 };
|
|
19035
19269
|
this.isModal = false;
|
|
19036
19270
|
/**
|
|
19037
19271
|
* True, if dialog was opened
|
|
@@ -20908,12 +21142,17 @@ let ImageEditor = ImageEditor_1 = class ImageEditor extends ViewComponent {
|
|
|
20908
21142
|
self.j.alert('The name should not be empty');
|
|
20909
21143
|
return false;
|
|
20910
21144
|
}
|
|
21145
|
+
self.j.e.fire('afterImageEditorSave', data, name);
|
|
20911
21146
|
self.onSave(name, data, self.hide, (e) => {
|
|
20912
21147
|
self.j.alert(e.message);
|
|
20913
21148
|
});
|
|
20914
21149
|
});
|
|
20915
21150
|
break;
|
|
20916
21151
|
case self.buttons.save:
|
|
21152
|
+
// Notify listeners that a crop/resize was applied,
|
|
21153
|
+
// passing the action box (action + box dimensions).
|
|
21154
|
+
// See https://github.com/xdan/jodit/issues/820
|
|
21155
|
+
self.j.e.fire('afterImageEditorSave', data);
|
|
20917
21156
|
self.onSave(undefined, data, self.hide, (e) => {
|
|
20918
21157
|
self.j.alert(e.message);
|
|
20919
21158
|
});
|
|
@@ -22776,8 +23015,22 @@ class Table extends ViewComponent {
|
|
|
22776
23015
|
}
|
|
22777
23016
|
const nextRow = Dom.next(cell.parentNode, elm => Dom.isTag(elm, 'tr'), table);
|
|
22778
23017
|
if (nextRow) {
|
|
22779
|
-
|
|
22780
|
-
|
|
23018
|
+
// `box[rowIndex + 1][nextCell]` is the cell that
|
|
23019
|
+
// logically follows the moved cell in the next row, but
|
|
23020
|
+
// it may physically belong to an earlier `<tr>` (when it
|
|
23021
|
+
// spans down from a row above). Inserting before such a
|
|
23022
|
+
// cell throws `NotFoundError`, so look for the first
|
|
23023
|
+
// following cell that is actually a child of `nextRow`.
|
|
23024
|
+
let referenceCell = null;
|
|
23025
|
+
for (let nextColumn = nextCell; nextColumn < box[rowIndex + 1].length; nextColumn += 1) {
|
|
23026
|
+
const candidate = box[rowIndex + 1][nextColumn];
|
|
23027
|
+
if (candidate && candidate.parentNode === nextRow) {
|
|
23028
|
+
referenceCell = candidate;
|
|
23029
|
+
break;
|
|
23030
|
+
}
|
|
23031
|
+
}
|
|
23032
|
+
if (referenceCell) {
|
|
23033
|
+
nextRow.insertBefore(cell, referenceCell);
|
|
22781
23034
|
}
|
|
22782
23035
|
else {
|
|
22783
23036
|
nextRow.appendChild(cell);
|
|
@@ -23682,13 +23935,38 @@ let ToolbarContent = class ToolbarContent extends UIButton {
|
|
|
23682
23935
|
}
|
|
23683
23936
|
/** @override */
|
|
23684
23937
|
update() {
|
|
23685
|
-
|
|
23938
|
+
var _a, _b, _c;
|
|
23939
|
+
const { control } = this;
|
|
23940
|
+
const content = control.getContent(this.j, this);
|
|
23686
23941
|
if (isString(content) || content.parentNode !== this.container) {
|
|
23687
23942
|
Dom.detach(this.container);
|
|
23688
23943
|
this.container.appendChild(isString(content) ? this.j.create.fromHTML(content) : content);
|
|
23689
23944
|
}
|
|
23945
|
+
// Content controls never went through the ToolbarButton status
|
|
23946
|
+
// calculation, so `isDisabled`/`isActive`/`update` declared on the
|
|
23947
|
+
// control were silently ignored (e.g. the FileBrowser Upload button
|
|
23948
|
+
// ignored the backend permissions). See
|
|
23949
|
+
// https://github.com/xdan/jodit/issues/1094
|
|
23950
|
+
this.state.disabled = Boolean((_a = control.isDisabled) === null || _a === void 0 ? void 0 : _a.call(control, this.j, this));
|
|
23951
|
+
this.state.activated = Boolean((_b = control.isActive) === null || _b === void 0 ? void 0 : _b.call(control, this.j, this));
|
|
23952
|
+
(_c = control.update) === null || _c === void 0 ? void 0 : _c.call(control, this.j, this);
|
|
23953
|
+
// The first update() runs before the state watchers are attached, so
|
|
23954
|
+
// apply the calculated state explicitly (the calls are idempotent)
|
|
23955
|
+
this.onChangeDisabled();
|
|
23956
|
+
this.onChangeActivated();
|
|
23690
23957
|
super.update();
|
|
23691
23958
|
}
|
|
23959
|
+
/**
|
|
23960
|
+
* The content is arbitrary HTML — propagate the disabled state to the
|
|
23961
|
+
* nested form controls (e.g. the file input of the Upload button),
|
|
23962
|
+
* otherwise they stay interactive.
|
|
23963
|
+
*/
|
|
23964
|
+
onChangeDisabled() {
|
|
23965
|
+
super.onChangeDisabled();
|
|
23966
|
+
this.container
|
|
23967
|
+
.querySelectorAll('input,button,select,textarea')
|
|
23968
|
+
.forEach(elm => attr(elm, 'disabled', this.state.disabled || null));
|
|
23969
|
+
}
|
|
23692
23970
|
/** @override */
|
|
23693
23971
|
createContainer() {
|
|
23694
23972
|
return this.j.c.span(this.componentName);
|
|
@@ -24127,6 +24405,15 @@ function sendFiles(uploader, files, handlerSuccess, handlerError, process) {
|
|
|
24127
24405
|
if (!fileList.length) {
|
|
24128
24406
|
return Promise.reject(error('Need files'));
|
|
24129
24407
|
}
|
|
24408
|
+
// Client-side validation hook — returning `false` aborts the upload.
|
|
24409
|
+
// See https://github.com/xdan/jodit/issues/1329
|
|
24410
|
+
if (isFunction(o.beforeUpload)) {
|
|
24411
|
+
if (o.beforeUpload.call(uploader, fileList) === false) {
|
|
24412
|
+
const err = error('Upload canceled');
|
|
24413
|
+
(handlerError || o.defaultHandlerError).call(uploader, err);
|
|
24414
|
+
return Promise.reject(err);
|
|
24415
|
+
}
|
|
24416
|
+
}
|
|
24130
24417
|
const promises = [];
|
|
24131
24418
|
if (o.insertImageAsBase64URI) {
|
|
24132
24419
|
readImagesWithReader(fileList, o.imagesExtensions, promises, uploader, handlerSuccess, o.defaultHandlerSuccess);
|
|
@@ -24246,7 +24533,17 @@ Config.prototype.enableDragAndDropFileToEditor = true;
|
|
|
24246
24533
|
Config.prototype.uploader = {
|
|
24247
24534
|
url: '',
|
|
24248
24535
|
insertImageAsBase64URI: false,
|
|
24249
|
-
imagesExtensions: [
|
|
24536
|
+
imagesExtensions: [
|
|
24537
|
+
'jpg',
|
|
24538
|
+
'jpeg',
|
|
24539
|
+
'png',
|
|
24540
|
+
'gif',
|
|
24541
|
+
'webp',
|
|
24542
|
+
'bmp',
|
|
24543
|
+
'svg',
|
|
24544
|
+
'tiff',
|
|
24545
|
+
'avif'
|
|
24546
|
+
],
|
|
24250
24547
|
headers: null,
|
|
24251
24548
|
data: null,
|
|
24252
24549
|
filesVariableName(i) {
|
|
@@ -25028,12 +25325,19 @@ function getShadowRoot(jodit) {
|
|
|
25028
25325
|
* `strong -> fontWeight 700`
|
|
25029
25326
|
*/
|
|
25030
25327
|
function getNativeCSSValue(jodit, elm, key) {
|
|
25328
|
+
const root = getShadowRoot(jodit);
|
|
25329
|
+
// Reproduce the editor's inherited text color inside the isolated probe so
|
|
25330
|
+
// that a value equal to the editor's *effective* default (not only the
|
|
25331
|
+
// browser default black) is recognised. Without this, applying black after
|
|
25332
|
+
// the default font color was changed was wrongly treated as a no-op (#1311).
|
|
25333
|
+
const wrapper = jodit.create.element('div');
|
|
25334
|
+
wrapper.style.color = css(jodit.editor, 'color');
|
|
25031
25335
|
const newElm = jodit.create.element(elm.tagName.toLowerCase());
|
|
25032
25336
|
newElm.style.cssText = elm.style.cssText;
|
|
25033
|
-
|
|
25034
|
-
root.appendChild(
|
|
25337
|
+
wrapper.appendChild(newElm);
|
|
25338
|
+
root.appendChild(wrapper);
|
|
25035
25339
|
const result = css(newElm, key);
|
|
25036
|
-
Dom.safeRemove(
|
|
25340
|
+
Dom.safeRemove(wrapper);
|
|
25037
25341
|
return result;
|
|
25038
25342
|
}
|
|
25039
25343
|
|
|
@@ -25247,9 +25551,23 @@ function wrapUnwrappedText(style, elm, jodit) {
|
|
|
25247
25551
|
*/
|
|
25248
25552
|
function wrap(commitStyle, font, jodit) {
|
|
25249
25553
|
const wrapper = findOrCreateWrapper(commitStyle, font, jodit);
|
|
25250
|
-
|
|
25251
|
-
|
|
25252
|
-
|
|
25554
|
+
if (commitStyle.elementIsList) {
|
|
25555
|
+
return wrapList(commitStyle, wrapper, jodit);
|
|
25556
|
+
}
|
|
25557
|
+
const newWrapper = Dom.replace(wrapper, commitStyle.element, jodit.createInside, true);
|
|
25558
|
+
if (commitStyle.elementIsBlock) {
|
|
25559
|
+
// Inline font styles left over from pasted content visually override
|
|
25560
|
+
// the new block format — e.g. an h2 with `font-weight: normal;
|
|
25561
|
+
// font-size: 24px` does not look like a heading at all, so the
|
|
25562
|
+
// command seems to do nothing. See
|
|
25563
|
+
// https://github.com/xdan/jodit/issues/1063
|
|
25564
|
+
css(newWrapper, 'fontSize', null);
|
|
25565
|
+
css(newWrapper, 'fontWeight', null);
|
|
25566
|
+
if (!attr(newWrapper, 'style')) {
|
|
25567
|
+
attr(newWrapper, 'style', null);
|
|
25568
|
+
}
|
|
25569
|
+
}
|
|
25570
|
+
return newWrapper;
|
|
25253
25571
|
}
|
|
25254
25572
|
const WRAP_NODES = new Set([
|
|
25255
25573
|
'td',
|
|
@@ -27930,6 +28248,18 @@ let Jodit$1 = Jodit_1 = class Jodit extends ViewWithToolbar {
|
|
|
27930
28248
|
(opt.uploader.url || opt.uploader.insertImageAsBase64URI)) {
|
|
27931
28249
|
this.uploader.bind(this.editor);
|
|
27932
28250
|
}
|
|
28251
|
+
else if (!opt.enableDragAndDropFileToEditor) {
|
|
28252
|
+
// Without a bound uploader nobody cancels a file drop, and
|
|
28253
|
+
// Firefox inserts the dropped image natively — the option
|
|
28254
|
+
// must mean "do nothing". See
|
|
28255
|
+
// https://github.com/xdan/jodit/issues/1077
|
|
28256
|
+
this.e.on(editor, 'drop', (e) => {
|
|
28257
|
+
var _a, _b;
|
|
28258
|
+
if ((_b = (_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b.length) {
|
|
28259
|
+
e.preventDefault();
|
|
28260
|
+
}
|
|
28261
|
+
});
|
|
28262
|
+
}
|
|
27933
28263
|
// in initEditor - the editor could change
|
|
27934
28264
|
if (!this.__elementToPlace.get(this.editor)) {
|
|
27935
28265
|
this.__elementToPlace.set(this.editor, currentPlace);
|
|
@@ -28067,6 +28397,26 @@ let Jodit$1 = Jodit_1 = class Jodit extends ViewWithToolbar {
|
|
|
28067
28397
|
}
|
|
28068
28398
|
this.synchronizeValues();
|
|
28069
28399
|
}
|
|
28400
|
+
})
|
|
28401
|
+
.on(this.ow, 'mouseup', (event) => {
|
|
28402
|
+
if (this.o.readonly || this.__isSilentChange) {
|
|
28403
|
+
return;
|
|
28404
|
+
}
|
|
28405
|
+
// When a selection is started inside the editor and the
|
|
28406
|
+
// mouse button is released outside of it, the editable
|
|
28407
|
+
// area never receives the `mouseup` event, so the toolbar
|
|
28408
|
+
// state (active buttons) is not recalculated. Re-fire the
|
|
28409
|
+
// event manually for that case while the selection still
|
|
28410
|
+
// belongs to the editor. See #1251
|
|
28411
|
+
const target = event.target;
|
|
28412
|
+
const insideEditor = Boolean(target &&
|
|
28413
|
+
isNumber(target.nodeType) &&
|
|
28414
|
+
editor.contains(target));
|
|
28415
|
+
if (insideEditor || !this.s.isInsideArea) {
|
|
28416
|
+
return;
|
|
28417
|
+
}
|
|
28418
|
+
this.e.fire('changeSelection');
|
|
28419
|
+
this.synchronizeValues();
|
|
28070
28420
|
});
|
|
28071
28421
|
}
|
|
28072
28422
|
fetch(url, options) {
|
|
@@ -32560,7 +32910,7 @@ var tr = {
|
|
|
32560
32910
|
'Insert as Text': 'Yazı olarak ekle',
|
|
32561
32911
|
'Word Paste Detected': 'Word biçiminde yapıştırma algılandı',
|
|
32562
32912
|
'The pasted content is coming from a Microsoft Word/Excel document. Do you want to keep the format or clean it up?':
|
|
32563
|
-
'
|
|
32913
|
+
'Yapıştırılan içerik bir Microsoft Word/Excel belgesinden geliyor. Formatı korumak mı yoksa temizlemek mi istiyorsunuz?',
|
|
32564
32914
|
'Insert only Text': 'Sadece yazıyı ekle',
|
|
32565
32915
|
'File Browser': 'Dosya Listeleyici',
|
|
32566
32916
|
'Error on load list': 'Liste yüklenirken hata oluştu',
|
|
@@ -33579,15 +33929,37 @@ function checkJoinNeighbors(jodit, fakeNode, backspace) {
|
|
|
33579
33929
|
if (Dom.isElement(mainClosestBox) &&
|
|
33580
33930
|
Dom.isContentEditable(mainClosestBox, jodit.editor)) {
|
|
33581
33931
|
const sibling = Dom.findNotEmptySibling(mainClosestBox, backspace);
|
|
33932
|
+
// A line break before the cursor is a single unit: remove it instead of
|
|
33933
|
+
// merging the current element into the (void) `<br>`, which used to move
|
|
33934
|
+
// the whole element's content into the `<br>` and delete it (#1282).
|
|
33935
|
+
if (Dom.isTag(sibling, 'br')) {
|
|
33936
|
+
Dom.safeRemove(sibling);
|
|
33937
|
+
jodit.s.setCursorBefore(fakeNode);
|
|
33938
|
+
return true;
|
|
33939
|
+
}
|
|
33582
33940
|
if (sibling &&
|
|
33583
33941
|
(checkMoveListContent(jodit, mainClosestBox, sibling, backspace) ||
|
|
33584
|
-
moveContentAndRemoveEmpty(jodit, mainClosestBox, sibling, backspace))) {
|
|
33942
|
+
moveContentAndRemoveEmpty(jodit, mainClosestBox, resolveTableSibling(sibling, backspace), backspace))) {
|
|
33585
33943
|
jodit.s.setCursorBefore(fakeNode);
|
|
33586
33944
|
return true;
|
|
33587
33945
|
}
|
|
33588
33946
|
}
|
|
33589
33947
|
return false;
|
|
33590
33948
|
}
|
|
33949
|
+
/**
|
|
33950
|
+
* Content cannot be merged into the `<table>` element itself — it would land
|
|
33951
|
+
* between the table sections (after `</tbody>`), which is invalid HTML and
|
|
33952
|
+
* gets foster-parented out of the table on the next parse. Merge into the
|
|
33953
|
+
* edge cell instead. See https://github.com/xdan/jodit/issues/1064
|
|
33954
|
+
* @private
|
|
33955
|
+
*/
|
|
33956
|
+
function resolveTableSibling(sibling, backspace) {
|
|
33957
|
+
if (!Dom.isTag(sibling, 'table')) {
|
|
33958
|
+
return sibling;
|
|
33959
|
+
}
|
|
33960
|
+
const cells = sibling.querySelectorAll('td,th');
|
|
33961
|
+
return cells.length ? cells[backspace ? cells.length - 1 : 0] : null;
|
|
33962
|
+
}
|
|
33591
33963
|
function checkMoveListContent(jodit, mainClosestBox, sibling, backspace) {
|
|
33592
33964
|
// Process UL/LI/OL cases
|
|
33593
33965
|
const siblingIsList = Dom.isTag(sibling, LIST_TAGS);
|
|
@@ -33733,6 +34105,14 @@ function getNextInlineSibling(sibling, backspace, root) {
|
|
|
33733
34105
|
let nextSibling = Dom.sibling(sibling, backspace);
|
|
33734
34106
|
if (!nextSibling && sibling.parentNode && sibling.parentNode !== root) {
|
|
33735
34107
|
nextSibling = findMostNestedNeighbor(sibling, !backspace, root, true);
|
|
34108
|
+
// Do not cross a list-item boundary. At the start of a list item the
|
|
34109
|
+
// neighbor can be the text of the parent/previous item; deleting a char
|
|
34110
|
+
// there is wrong — the list cases must handle the outdent/merge (#1277).
|
|
34111
|
+
if (nextSibling &&
|
|
34112
|
+
Dom.closest(sibling, 'li', root) !==
|
|
34113
|
+
Dom.closest(nextSibling, 'li', root)) {
|
|
34114
|
+
return null;
|
|
34115
|
+
}
|
|
33736
34116
|
}
|
|
33737
34117
|
return nextSibling;
|
|
33738
34118
|
}
|
|
@@ -34034,21 +34414,21 @@ function checkUnwrapFirstListItem(jodit, fakeNode, backspace) {
|
|
|
34034
34414
|
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
34035
34415
|
*/
|
|
34036
34416
|
/**
|
|
34037
|
-
*
|
|
34038
|
-
|
|
34039
|
-
|
|
34417
|
+
* Ordered delete/backspace cases with stable keys. The first one returning
|
|
34418
|
+
* `true` wins. The keys are stable across minified builds (function names are
|
|
34419
|
+
* mangled by terser) so they can be referenced by `delete.disableCases`.
|
|
34040
34420
|
* @private
|
|
34041
34421
|
*/
|
|
34042
|
-
const
|
|
34043
|
-
checkRemoveUnbreakableElement,
|
|
34044
|
-
checkRemoveContentNotEditable,
|
|
34045
|
-
checkRemoveChar,
|
|
34046
|
-
checkTableCell,
|
|
34047
|
-
checkRemoveEmptyParent,
|
|
34048
|
-
checkRemoveEmptyNeighbor,
|
|
34049
|
-
checkJoinTwoLists,
|
|
34050
|
-
checkJoinNeighbors,
|
|
34051
|
-
checkUnwrapFirstListItem
|
|
34422
|
+
const casesMap = [
|
|
34423
|
+
['remove-unbreakable', checkRemoveUnbreakableElement],
|
|
34424
|
+
['remove-not-editable', checkRemoveContentNotEditable],
|
|
34425
|
+
['remove-char', checkRemoveChar],
|
|
34426
|
+
['table-cell', checkTableCell],
|
|
34427
|
+
['remove-empty-parent', checkRemoveEmptyParent],
|
|
34428
|
+
['remove-empty-neighbor', checkRemoveEmptyNeighbor],
|
|
34429
|
+
['join-two-lists', checkJoinTwoLists],
|
|
34430
|
+
['join-neighbors', checkJoinNeighbors],
|
|
34431
|
+
['unwrap-first-list-item', checkUnwrapFirstListItem]
|
|
34052
34432
|
];
|
|
34053
34433
|
|
|
34054
34434
|
/*!
|
|
@@ -34115,7 +34495,11 @@ class backspace extends Plugin {
|
|
|
34115
34495
|
return false;
|
|
34116
34496
|
}
|
|
34117
34497
|
moveNodeInsideStart(jodit, fakeNode, backspace);
|
|
34118
|
-
|
|
34498
|
+
const disabled = jodit.o.delete.disableCases;
|
|
34499
|
+
if (casesMap.some(([key, func]) => {
|
|
34500
|
+
if (disabled && disabled.has(key)) {
|
|
34501
|
+
return;
|
|
34502
|
+
}
|
|
34119
34503
|
if (isFunction(func) &&
|
|
34120
34504
|
func(jodit, fakeNode, backspace, mode)) {
|
|
34121
34505
|
return true;
|
|
@@ -34798,8 +35182,9 @@ Config.prototype.controls.brush = {
|
|
|
34798
35182
|
const update = (key, value) => {
|
|
34799
35183
|
if (value && value !== css(editor.editor, key).toString()) {
|
|
34800
35184
|
button.state.icon.fill = value;
|
|
34801
|
-
return;
|
|
35185
|
+
return true;
|
|
34802
35186
|
}
|
|
35187
|
+
return false;
|
|
34803
35188
|
};
|
|
34804
35189
|
if (color) {
|
|
34805
35190
|
const mode = dataBind(button, 'color');
|
|
@@ -34809,8 +35194,16 @@ Config.prototype.controls.brush = {
|
|
|
34809
35194
|
const current = editor.s.current();
|
|
34810
35195
|
if (current && !button.state.disabled) {
|
|
34811
35196
|
const currentBpx = Dom.closest(current, Dom.isElement, editor.editor) || editor.editor;
|
|
34812
|
-
|
|
34813
|
-
|
|
35197
|
+
// The icon's fill mirrors the current text/background color so the
|
|
35198
|
+
// button reflects the formatting under the caret. Both calls run so
|
|
35199
|
+
// that a background color (the second call) wins over the text color
|
|
35200
|
+
// when both are set. Keep the computed fill instead of resetting it
|
|
35201
|
+
// below. See #195, #182
|
|
35202
|
+
const hasColor = update('color', css(currentBpx, 'color').toString());
|
|
35203
|
+
const hasBackground = update('background-color', css(currentBpx, 'background-color').toString());
|
|
35204
|
+
if (hasColor || hasBackground) {
|
|
35205
|
+
return;
|
|
35206
|
+
}
|
|
34814
35207
|
}
|
|
34815
35208
|
button.state.icon.fill = '';
|
|
34816
35209
|
button.state.activated = false;
|
|
@@ -34913,28 +35306,28 @@ function color(editor) {
|
|
|
34913
35306
|
group: 'color'
|
|
34914
35307
|
});
|
|
34915
35308
|
const callback = (command, second, third) => {
|
|
35309
|
+
var _a;
|
|
34916
35310
|
const colorHEX = normalizeColor(third);
|
|
34917
|
-
|
|
34918
|
-
|
|
34919
|
-
|
|
34920
|
-
|
|
34921
|
-
|
|
34922
|
-
|
|
34923
|
-
|
|
34924
|
-
|
|
34925
|
-
|
|
34926
|
-
|
|
34927
|
-
|
|
34928
|
-
|
|
34929
|
-
|
|
34930
|
-
editor.s.
|
|
34931
|
-
|
|
34932
|
-
|
|
34933
|
-
|
|
34934
|
-
|
|
34935
|
-
|
|
34936
|
-
|
|
34937
|
-
break;
|
|
35311
|
+
const value = !colorHEX ? '' : colorHEX;
|
|
35312
|
+
const style = command === 'background'
|
|
35313
|
+
? { backgroundColor: value }
|
|
35314
|
+
: { color: value };
|
|
35315
|
+
// Cells selected with the `select-cells` plugin drop or collapse the
|
|
35316
|
+
// native range, so `commitStyle` would paint a pending caret format
|
|
35317
|
+
// outside the table instead of the selection the user sees. Apply the
|
|
35318
|
+
// style to the content of every selected cell instead. See #1250
|
|
35319
|
+
const selectedCells = editor
|
|
35320
|
+
.getInstance('Table', editor.o)
|
|
35321
|
+
.getAllSelectedCells();
|
|
35322
|
+
if (selectedCells.length && editor.s.isCollapsed()) {
|
|
35323
|
+
selectedCells.forEach(cell => {
|
|
35324
|
+
editor.s.select(cell, true);
|
|
35325
|
+
editor.s.commitStyle({ attributes: { style } });
|
|
35326
|
+
});
|
|
35327
|
+
(_a = editor.s.sel) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
|
|
35328
|
+
}
|
|
35329
|
+
else {
|
|
35330
|
+
editor.s.commitStyle({ attributes: { style } });
|
|
34938
35331
|
}
|
|
34939
35332
|
editor.synchronizeValues();
|
|
34940
35333
|
return false;
|
|
@@ -35236,12 +35629,17 @@ class enter extends Plugin {
|
|
|
35236
35629
|
if (beforeEnter !== undefined) {
|
|
35237
35630
|
return beforeEnter;
|
|
35238
35631
|
}
|
|
35239
|
-
|
|
35240
|
-
|
|
35241
|
-
|
|
35242
|
-
editor.
|
|
35243
|
-
|
|
35244
|
-
|
|
35632
|
+
// Delete-of-selection + new block must be a single history step,
|
|
35633
|
+
// otherwise pressing Enter over a selection needs two Ctrl+Z to undo
|
|
35634
|
+
// (the first only reverts to the intermediate empty state). #1292
|
|
35635
|
+
editor.history.snapshot.transaction(() => {
|
|
35636
|
+
if (!editor.s.isCollapsed()) {
|
|
35637
|
+
editor.execCommand('Delete');
|
|
35638
|
+
}
|
|
35639
|
+
editor.s.focus();
|
|
35640
|
+
this.onEnter(event);
|
|
35641
|
+
editor.e.fire('afterEnter', event);
|
|
35642
|
+
});
|
|
35245
35643
|
editor.synchronizeValues(); // fire change
|
|
35246
35644
|
return false;
|
|
35247
35645
|
}
|
|
@@ -35411,6 +35809,25 @@ Config.prototype.controls.font = {
|
|
|
35411
35809
|
.replace(/[^a-z0-9-]+/g, ',');
|
|
35412
35810
|
}
|
|
35413
35811
|
},
|
|
35812
|
+
// When no font is explicitly set, the computed font-family equals the
|
|
35813
|
+
// editor's own default (e.g. `-apple-system, …`). Return '' so the
|
|
35814
|
+
// button shows the `Default` list entry instead of that raw stack.
|
|
35815
|
+
// See #1370
|
|
35816
|
+
value: (editor) => {
|
|
35817
|
+
const current = editor.s.current();
|
|
35818
|
+
if (!current) {
|
|
35819
|
+
return;
|
|
35820
|
+
}
|
|
35821
|
+
const box = Dom.closest(current, Dom.isElement, editor.editor);
|
|
35822
|
+
if (!box) {
|
|
35823
|
+
return;
|
|
35824
|
+
}
|
|
35825
|
+
const value = css(box, 'font-family').toString();
|
|
35826
|
+
if (value === css(editor.editor, 'font-family').toString()) {
|
|
35827
|
+
return '';
|
|
35828
|
+
}
|
|
35829
|
+
return value;
|
|
35830
|
+
},
|
|
35414
35831
|
tooltip: 'Font family'
|
|
35415
35832
|
};
|
|
35416
35833
|
|
|
@@ -35547,7 +35964,7 @@ pluginSystem.add('formatBlock', formatBlock);
|
|
|
35547
35964
|
Config.prototype.commandToHotkeys = {
|
|
35548
35965
|
removeFormat: ['ctrl+shift+m', 'cmd+shift+m'],
|
|
35549
35966
|
insertOrderedList: ['ctrl+shift+7', 'cmd+shift+7'],
|
|
35550
|
-
insertUnorderedList: ['ctrl+shift+8, cmd+shift+8'],
|
|
35967
|
+
insertUnorderedList: ['ctrl+shift+8', 'cmd+shift+8'],
|
|
35551
35968
|
selectall: ['ctrl+a', 'cmd+a']
|
|
35552
35969
|
};
|
|
35553
35970
|
|
|
@@ -36012,13 +36429,15 @@ pluginSystem.add('image', image);
|
|
|
36012
36429
|
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
36013
36430
|
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
36014
36431
|
*/
|
|
36432
|
+
const isSafeHref = (href) => /^(https?:|mailto:|tel:)/i.test(href.trim()) &&
|
|
36433
|
+
!/^(javascript:|data:|vbscript:)/i.test(href.trim());
|
|
36015
36434
|
var a = [
|
|
36016
36435
|
{
|
|
36017
36436
|
name: 'eye',
|
|
36018
36437
|
tooltip: 'Open link',
|
|
36019
36438
|
exec: (editor, current) => {
|
|
36020
36439
|
const href = attr(current, 'href');
|
|
36021
|
-
if (current && href) {
|
|
36440
|
+
if (current && href && isSafeHref(href)) {
|
|
36022
36441
|
editor.ow.open(href);
|
|
36023
36442
|
}
|
|
36024
36443
|
}
|
|
@@ -36345,6 +36764,12 @@ class inlinePopup extends Plugin {
|
|
|
36345
36764
|
constructor() {
|
|
36346
36765
|
super(...arguments);
|
|
36347
36766
|
this.type = null;
|
|
36767
|
+
/**
|
|
36768
|
+
* The user pressed a button inside the selection toolbar — after the
|
|
36769
|
+
* command fires `closeAllPopups`, the toolbar should be shown again
|
|
36770
|
+
* while the selection is still there. See #1238
|
|
36771
|
+
*/
|
|
36772
|
+
this.__reopenSelectionPopup = false;
|
|
36348
36773
|
this.snapRange = null;
|
|
36349
36774
|
this.elmsList = keys$2(this.j.o.popup, false).filter(s => !this.isExcludedTarget(s));
|
|
36350
36775
|
}
|
|
@@ -36460,9 +36885,52 @@ class inlinePopup extends Plugin {
|
|
|
36460
36885
|
this.previousTarget = undefined;
|
|
36461
36886
|
}
|
|
36462
36887
|
})
|
|
36463
|
-
.on([this.j.ew, this.j.ow], 'mouseup keyup', this.onSelectionEnd)
|
|
36888
|
+
.on([this.j.ew, this.j.ow], 'mouseup keyup', this.onSelectionEnd)
|
|
36889
|
+
.on([this.j.ew, this.j.ow], 'mousedown touchstart', this.__onDocumentMouseDown)
|
|
36890
|
+
.on('closeAllPopups', this.__onCloseAllPopups);
|
|
36464
36891
|
this.addListenersForElements();
|
|
36465
36892
|
}
|
|
36893
|
+
__onDocumentMouseDown(e) {
|
|
36894
|
+
if (this.popup.isOpened &&
|
|
36895
|
+
this.type === 'selection' &&
|
|
36896
|
+
e.target &&
|
|
36897
|
+
UIElement.closestElement(e.target, Popup)) {
|
|
36898
|
+
this.__reopenSelectionPopup = true;
|
|
36899
|
+
}
|
|
36900
|
+
}
|
|
36901
|
+
__onCloseAllPopups() {
|
|
36902
|
+
if (!this.__reopenSelectionPopup) {
|
|
36903
|
+
return;
|
|
36904
|
+
}
|
|
36905
|
+
this.__reopenSelectionPopup = false;
|
|
36906
|
+
if (!this.j.o.toolbarInlineForSelection) {
|
|
36907
|
+
return;
|
|
36908
|
+
}
|
|
36909
|
+
// a zero timeout would run synchronously — before the popup's own
|
|
36910
|
+
// `closeAllPopups` handler closes it; defer to the next macrotask
|
|
36911
|
+
this.j.async.setTimeout(() => {
|
|
36912
|
+
const sel = this.j.s.sel;
|
|
36913
|
+
if (sel && !sel.isCollapsed) {
|
|
36914
|
+
this.showPopup(() => this.__selectionBound(), 'selection');
|
|
36915
|
+
}
|
|
36916
|
+
}, 1);
|
|
36917
|
+
}
|
|
36918
|
+
/**
|
|
36919
|
+
* The selection rect comes from the editor document — in iframe mode its
|
|
36920
|
+
* coordinates are iframe-local, while the popup lives in the host
|
|
36921
|
+
* document, so the iframe offset must be added. See
|
|
36922
|
+
* https://github.com/xdan/jodit/issues/1058
|
|
36923
|
+
*/
|
|
36924
|
+
__selectionBound() {
|
|
36925
|
+
const rect = this.j.s.range.getBoundingClientRect();
|
|
36926
|
+
let { left, top } = rect;
|
|
36927
|
+
if (this.j.iframe) {
|
|
36928
|
+
const offset = position(this.j.iframe, this.j, true);
|
|
36929
|
+
left += offset.left;
|
|
36930
|
+
top += offset.top;
|
|
36931
|
+
}
|
|
36932
|
+
return { left, top, width: rect.width, height: rect.height };
|
|
36933
|
+
}
|
|
36466
36934
|
onSelectionStart() {
|
|
36467
36935
|
this.snapRange = this.j.s.range.cloneRange();
|
|
36468
36936
|
}
|
|
@@ -36502,7 +36970,7 @@ class inlinePopup extends Plugin {
|
|
|
36502
36970
|
if (!node) {
|
|
36503
36971
|
return;
|
|
36504
36972
|
}
|
|
36505
|
-
this.showPopup(() =>
|
|
36973
|
+
this.showPopup(() => this.__selectionBound(), type);
|
|
36506
36974
|
}
|
|
36507
36975
|
/**
|
|
36508
36976
|
* In not collapsed selection - only one image
|
|
@@ -36524,7 +36992,9 @@ class inlinePopup extends Plugin {
|
|
|
36524
36992
|
beforeDestruct(jodit) {
|
|
36525
36993
|
jodit.e
|
|
36526
36994
|
.off('showPopup')
|
|
36527
|
-
.off([this.j.ew, this.j.ow], 'mouseup keyup', this.onSelectionEnd)
|
|
36995
|
+
.off([this.j.ew, this.j.ow], 'mouseup keyup', this.onSelectionEnd)
|
|
36996
|
+
.off([this.j.ew, this.j.ow], 'mousedown touchstart', this.__onDocumentMouseDown)
|
|
36997
|
+
.off('closeAllPopups', this.__onCloseAllPopups);
|
|
36528
36998
|
this.removeListenersForElements();
|
|
36529
36999
|
}
|
|
36530
37000
|
_eventsList() {
|
|
@@ -36573,6 +37043,12 @@ __decorate$c([
|
|
|
36573
37043
|
__decorate$c([
|
|
36574
37044
|
watch(':outsideClick')
|
|
36575
37045
|
], inlinePopup.prototype, "onOutsideClick", null);
|
|
37046
|
+
__decorate$c([
|
|
37047
|
+
autobind
|
|
37048
|
+
], inlinePopup.prototype, "__onDocumentMouseDown", null);
|
|
37049
|
+
__decorate$c([
|
|
37050
|
+
autobind
|
|
37051
|
+
], inlinePopup.prototype, "__onCloseAllPopups", null);
|
|
36576
37052
|
__decorate$c([
|
|
36577
37053
|
autobind
|
|
36578
37054
|
], inlinePopup.prototype, "onSelectionStart", null);
|
|
@@ -36597,7 +37073,7 @@ var unlinkIcon = "<svg xmlns='http://www.w3.org/2000/svg' viewBox=\"0 0 1792 179
|
|
|
36597
37073
|
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
36598
37074
|
*/
|
|
36599
37075
|
const formTemplate = (editor) => {
|
|
36600
|
-
const { openInNewTabCheckbox, noFollowCheckbox, modeClassName, selectSizeClassName, selectMultipleClassName, selectOptionsClassName } = editor.o.link;
|
|
37076
|
+
const { openInNewTabCheckbox, noFollowCheckbox, ariaLabelInput, modeClassName, selectSizeClassName, selectMultipleClassName, selectOptionsClassName } = editor.o.link;
|
|
36601
37077
|
return new UIForm(editor, [
|
|
36602
37078
|
new UIBlock(editor, [
|
|
36603
37079
|
new UIInput(editor, {
|
|
@@ -36609,6 +37085,15 @@ const formTemplate = (editor) => {
|
|
|
36609
37085
|
required: true
|
|
36610
37086
|
})
|
|
36611
37087
|
]),
|
|
37088
|
+
ariaLabelInput
|
|
37089
|
+
? new UIBlock(editor, [
|
|
37090
|
+
new UIInput(editor, {
|
|
37091
|
+
name: 'ariaLabel',
|
|
37092
|
+
ref: 'aria_label_input',
|
|
37093
|
+
label: 'Aria label'
|
|
37094
|
+
})
|
|
37095
|
+
])
|
|
37096
|
+
: null,
|
|
36612
37097
|
new UIBlock(editor, [
|
|
36613
37098
|
new UIInput(editor, {
|
|
36614
37099
|
name: 'content',
|
|
@@ -36687,6 +37172,7 @@ Config.prototype.link = {
|
|
|
36687
37172
|
noFollowCheckbox: true,
|
|
36688
37173
|
openInNewTabCheckbox: true,
|
|
36689
37174
|
openInNewTabCheckboxDefaultChecked: false,
|
|
37175
|
+
ariaLabelInput: false,
|
|
36690
37176
|
modeClassName: 'input',
|
|
36691
37177
|
selectMultipleClassName: true,
|
|
36692
37178
|
preventReadOnlyNavigation: true,
|
|
@@ -36835,7 +37321,7 @@ class link extends Plugin {
|
|
|
36835
37321
|
const currentElement = current;
|
|
36836
37322
|
const isImageContent = Dom.isImage(currentElement);
|
|
36837
37323
|
let { content_input } = elements;
|
|
36838
|
-
const { className_input } = elements, { className_select } = elements;
|
|
37324
|
+
const { className_input } = elements, { className_select } = elements, { aria_label_input } = elements;
|
|
36839
37325
|
if (!content_input) {
|
|
36840
37326
|
content_input = jodit.c.element('input', {
|
|
36841
37327
|
type: 'hidden',
|
|
@@ -36861,6 +37347,9 @@ class link extends Plugin {
|
|
|
36861
37347
|
if (!isImageContent && current) {
|
|
36862
37348
|
content_input.value = getSelectionText();
|
|
36863
37349
|
}
|
|
37350
|
+
if (aria_label_input) {
|
|
37351
|
+
aria_label_input.value = link ? attr(link, 'aria-label') || '' : '';
|
|
37352
|
+
}
|
|
36864
37353
|
if (link) {
|
|
36865
37354
|
url_input.value = attr(link, 'href') || '';
|
|
36866
37355
|
if (modeClassName) {
|
|
@@ -36951,6 +37440,10 @@ class link extends Plugin {
|
|
|
36951
37440
|
}
|
|
36952
37441
|
attr(a, 'rel', relParts.length ? relParts.join(' ') : null);
|
|
36953
37442
|
}
|
|
37443
|
+
if (aria_label_input) {
|
|
37444
|
+
const ariaLabel = aria_label_input.value.trim();
|
|
37445
|
+
attr(a, 'aria-label', ariaLabel || null);
|
|
37446
|
+
}
|
|
36954
37447
|
jodit.e.fire('applyLink', jodit, a, form);
|
|
36955
37448
|
});
|
|
36956
37449
|
jodit.synchronizeValues();
|
|
@@ -38139,6 +38632,12 @@ function removeExtraBr(jodit, node) {
|
|
|
38139
38632
|
if (!Dom.isTag(br, 'br')) {
|
|
38140
38633
|
return;
|
|
38141
38634
|
}
|
|
38635
|
+
// Only a trailing `<br>` is "extra". If meaningful content follows it,
|
|
38636
|
+
// the `<br>` is a real line break and removing it would merge two lines
|
|
38637
|
+
// (e.g. toggling a style on an empty line), so keep it (#1302).
|
|
38638
|
+
if (Dom.findNotEmptySibling(br, false)) {
|
|
38639
|
+
return;
|
|
38640
|
+
}
|
|
38142
38641
|
jodit.s.setCursorBefore(br);
|
|
38143
38642
|
Dom.safeRemove(br);
|
|
38144
38643
|
}
|
|
@@ -38406,6 +38905,68 @@ class select extends Plugin {
|
|
|
38406
38905
|
s.expandSelection();
|
|
38407
38906
|
return false;
|
|
38408
38907
|
}
|
|
38908
|
+
/**
|
|
38909
|
+
* Fix caret position when clicking to the right of a list item that has a
|
|
38910
|
+
* nested list. Blink/WebKit place the caret at the start of the line instead
|
|
38911
|
+
* of the end (#1296); move it to the end of the item's own text.
|
|
38912
|
+
*/
|
|
38913
|
+
onClickRightOfNestedListItem(e) {
|
|
38914
|
+
const { s } = this.j;
|
|
38915
|
+
const range = s.range;
|
|
38916
|
+
if (!range.collapsed ||
|
|
38917
|
+
range.startOffset !== 0 ||
|
|
38918
|
+
!Dom.isText(range.startContainer)) {
|
|
38919
|
+
return;
|
|
38920
|
+
}
|
|
38921
|
+
const text = range.startContainer;
|
|
38922
|
+
const li = text.parentNode;
|
|
38923
|
+
// The text must be the direct content of a list item that has a nested
|
|
38924
|
+
// list (the last level has no nested list and behaves correctly).
|
|
38925
|
+
if (!Dom.isTag(li, 'li') ||
|
|
38926
|
+
!(li.querySelector('ul') || li.querySelector('ol'))) {
|
|
38927
|
+
return;
|
|
38928
|
+
}
|
|
38929
|
+
const measure = this.j.ed.createRange();
|
|
38930
|
+
measure.selectNodeContents(text);
|
|
38931
|
+
const rect = measure.getBoundingClientRect();
|
|
38932
|
+
// Only when the click happened to the right of the text.
|
|
38933
|
+
if (e.clientX > rect.right) {
|
|
38934
|
+
s.setCursorAfter(text);
|
|
38935
|
+
}
|
|
38936
|
+
}
|
|
38937
|
+
/**
|
|
38938
|
+
* Keep pending inline formatting after a click. Toggling Bold/Italic/etc. on
|
|
38939
|
+
* a collapsed cursor leaves empty marker elements with the caret inside; a
|
|
38940
|
+
* click puts the caret right before them (outside), so they get cleaned up
|
|
38941
|
+
* and the formatting is lost. Move the caret back into the innermost marker
|
|
38942
|
+
* so the next typed character keeps every pending format (#1291).
|
|
38943
|
+
*/
|
|
38944
|
+
onClickKeepPendingFormat() {
|
|
38945
|
+
var _a, _b;
|
|
38946
|
+
const { s } = this.j;
|
|
38947
|
+
const range = s.range;
|
|
38948
|
+
if (!range.collapsed || !Dom.isText(range.startContainer)) {
|
|
38949
|
+
return;
|
|
38950
|
+
}
|
|
38951
|
+
const text = range.startContainer;
|
|
38952
|
+
// Caret must sit at the very end of the text, right before the markers.
|
|
38953
|
+
if (range.startOffset !== ((_b = (_a = text.nodeValue) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0)) {
|
|
38954
|
+
return;
|
|
38955
|
+
}
|
|
38956
|
+
const marker = text.nextSibling;
|
|
38957
|
+
if (!Dom.isElement(marker) ||
|
|
38958
|
+
!marker.nodeName.match(IS_INLINE) ||
|
|
38959
|
+
!Dom.isEmpty(marker)) {
|
|
38960
|
+
return;
|
|
38961
|
+
}
|
|
38962
|
+
let inner = marker;
|
|
38963
|
+
// Descend into the innermost empty formatting marker.
|
|
38964
|
+
while (Dom.isElement(inner.firstElementChild) &&
|
|
38965
|
+
inner.firstElementChild.nodeName.match(IS_INLINE)) {
|
|
38966
|
+
inner = inner.firstElementChild;
|
|
38967
|
+
}
|
|
38968
|
+
s.setCursorIn(inner);
|
|
38969
|
+
}
|
|
38409
38970
|
/**
|
|
38410
38971
|
* Normalize selection after triple click
|
|
38411
38972
|
*/
|
|
@@ -38446,6 +39007,12 @@ __decorate$5([
|
|
|
38446
39007
|
__decorate$5([
|
|
38447
39008
|
watch([':beforeCommandSelectall'])
|
|
38448
39009
|
], select.prototype, "beforeCommandSelectAll", null);
|
|
39010
|
+
__decorate$5([
|
|
39011
|
+
watch([':click'])
|
|
39012
|
+
], select.prototype, "onClickRightOfNestedListItem", null);
|
|
39013
|
+
__decorate$5([
|
|
39014
|
+
watch([':click'])
|
|
39015
|
+
], select.prototype, "onClickKeepPendingFormat", null);
|
|
38449
39016
|
__decorate$5([
|
|
38450
39017
|
watch([':click'])
|
|
38451
39018
|
], select.prototype, "onTripleClickNormalizeSelection", null);
|
|
@@ -38665,7 +39232,11 @@ class resizer extends Plugin {
|
|
|
38665
39232
|
this.pointerX = e.clientX;
|
|
38666
39233
|
this.pointerY = e.clientY;
|
|
38667
39234
|
let diff_x, diff_y;
|
|
38668
|
-
if (this.j.options.iframe) {
|
|
39235
|
+
if (this.j.options.iframe && e.view === this.j.ew) {
|
|
39236
|
+
// the event was proxied from the editor's iframe — its client
|
|
39237
|
+
// coordinates are relative to the iframe viewport, while
|
|
39238
|
+
// `startX/startY` were captured on a host-document handle;
|
|
39239
|
+
// shift them into the host coordinate space
|
|
38669
39240
|
const workplacePosition = this.getWorkplacePosition();
|
|
38670
39241
|
diff_x = e.clientX + workplacePosition.left - this.startX;
|
|
38671
39242
|
diff_y = e.clientY + workplacePosition.top - this.startY;
|
|
@@ -39005,15 +39576,42 @@ class selectCells extends Plugin {
|
|
|
39005
39576
|
/**
|
|
39006
39577
|
* Mouse click inside the table
|
|
39007
39578
|
*/
|
|
39008
|
-
onStartSelection(cell) {
|
|
39579
|
+
onStartSelection(cell, event) {
|
|
39580
|
+
var _a;
|
|
39009
39581
|
if (this.j.o.readonly) {
|
|
39010
39582
|
return;
|
|
39011
39583
|
}
|
|
39584
|
+
const table = Dom.closest(cell, 'table', this.j.editor);
|
|
39585
|
+
// Ctrl/Cmd + click toggles a single cell into the existing selection
|
|
39586
|
+
// instead of resetting it — non-contiguous multi-cell selection.
|
|
39587
|
+
// See https://github.com/xdan/jodit/issues/1163
|
|
39588
|
+
if (((event === null || event === void 0 ? void 0 : event.ctrlKey) || (event === null || event === void 0 ? void 0 : event.metaKey)) &&
|
|
39589
|
+
cell !== this.j.editor &&
|
|
39590
|
+
table &&
|
|
39591
|
+
Dom.isCell(cell)) {
|
|
39592
|
+
if (this.__tableModule.getAllSelectedCells().includes(cell)) {
|
|
39593
|
+
this.__tableModule.removeSelection(cell);
|
|
39594
|
+
}
|
|
39595
|
+
else {
|
|
39596
|
+
if (!cell.firstChild) {
|
|
39597
|
+
cell.appendChild(this.j.createInside.element('br'));
|
|
39598
|
+
}
|
|
39599
|
+
this.__tableModule.addSelection(cell);
|
|
39600
|
+
}
|
|
39601
|
+
this.__selectedCell = cell;
|
|
39602
|
+
(_a = this.j.s.sel) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
|
|
39603
|
+
if (this.__tableModule.getAllSelectedCells().length) {
|
|
39604
|
+
this.j.e.fire('showPopup', table, () => position(cell, this.j), 'cells');
|
|
39605
|
+
}
|
|
39606
|
+
else {
|
|
39607
|
+
this.j.e.fire('hidePopup', 'cells');
|
|
39608
|
+
}
|
|
39609
|
+
return false;
|
|
39610
|
+
}
|
|
39012
39611
|
this.unselectCells();
|
|
39013
39612
|
if (cell === this.j.editor) {
|
|
39014
39613
|
return;
|
|
39015
39614
|
}
|
|
39016
|
-
const table = Dom.closest(cell, 'table', this.j.editor);
|
|
39017
39615
|
if (!cell || !table) {
|
|
39018
39616
|
return;
|
|
39019
39617
|
}
|
|
@@ -39046,14 +39644,16 @@ class selectCells extends Plugin {
|
|
|
39046
39644
|
* Mouse move inside the table
|
|
39047
39645
|
*/
|
|
39048
39646
|
__onMove(table, e) {
|
|
39049
|
-
var _a;
|
|
39647
|
+
var _a, _b;
|
|
39050
39648
|
if (this.j.o.readonly && !this.j.isLocked) {
|
|
39051
39649
|
return;
|
|
39052
39650
|
}
|
|
39053
39651
|
if (this.j.isLockedNotBy(key$1)) {
|
|
39054
39652
|
return;
|
|
39055
39653
|
}
|
|
39056
|
-
|
|
39654
|
+
// Inside Shadow DOM `document.elementFromPoint` returns the shadow
|
|
39655
|
+
// host, so the lookup must start from the shadow root
|
|
39656
|
+
const node = ((_a = this.j.o.shadowRoot) !== null && _a !== void 0 ? _a : this.j.ed).elementFromPoint(e.clientX, e.clientY);
|
|
39057
39657
|
if (!node) {
|
|
39058
39658
|
return;
|
|
39059
39659
|
}
|
|
@@ -39076,7 +39676,7 @@ class selectCells extends Plugin {
|
|
|
39076
39676
|
}
|
|
39077
39677
|
const cellsCount = this.__tableModule.getAllSelectedCells().length;
|
|
39078
39678
|
if (cellsCount > 1) {
|
|
39079
|
-
(
|
|
39679
|
+
(_b = this.j.s.sel) === null || _b === void 0 ? void 0 : _b.removeAllRanges();
|
|
39080
39680
|
}
|
|
39081
39681
|
this.j.e.fire('hidePopup');
|
|
39082
39682
|
e.stopPropagation();
|
|
@@ -39110,12 +39710,15 @@ class selectCells extends Plugin {
|
|
|
39110
39710
|
* Stop a selection process
|
|
39111
39711
|
*/
|
|
39112
39712
|
__onStopSelection(table, e) {
|
|
39713
|
+
var _a, _b, _c;
|
|
39113
39714
|
if (!this.__selectedCell) {
|
|
39114
39715
|
return;
|
|
39115
39716
|
}
|
|
39116
39717
|
this.__isSelectionMode = false;
|
|
39117
39718
|
this.j.unlock();
|
|
39118
|
-
|
|
39719
|
+
// Inside Shadow DOM `document.elementFromPoint` returns the shadow
|
|
39720
|
+
// host, so the lookup must start from the shadow root
|
|
39721
|
+
const node = ((_a = this.j.o.shadowRoot) !== null && _a !== void 0 ? _a : this.j.ed).elementFromPoint(e.clientX, e.clientY);
|
|
39119
39722
|
if (!node) {
|
|
39120
39723
|
return;
|
|
39121
39724
|
}
|
|
@@ -39131,7 +39734,16 @@ class selectCells extends Plugin {
|
|
|
39131
39734
|
cell,
|
|
39132
39735
|
this.__selectedCell
|
|
39133
39736
|
]), box = this.__tableModule.formalMatrix(table);
|
|
39134
|
-
const max = box[bound[1][0]][bound[1][1]], min = box[bound[0][0]][bound[0][1]];
|
|
39737
|
+
const max = (_b = box[bound[1][0]]) === null || _b === void 0 ? void 0 : _b[bound[1][1]], min = (_c = box[bound[0][0]]) === null || _c === void 0 ? void 0 : _c[bound[0][1]];
|
|
39738
|
+
// `getSelectedBound` keeps its `Infinity` sentinel when none of the
|
|
39739
|
+
// selected cells belong to this table's matrix — e.g. after a
|
|
39740
|
+
// drag-and-drop that moved/removed the cells, leaving a stale anchor and
|
|
39741
|
+
// a drop target outside the table. Bail out instead of dereferencing an
|
|
39742
|
+
// out-of-range matrix slot (which threw `Cannot read properties of
|
|
39743
|
+
// undefined`).
|
|
39744
|
+
if (!min || !max) {
|
|
39745
|
+
return;
|
|
39746
|
+
}
|
|
39135
39747
|
this.j.e.fire('showPopup', table, () => {
|
|
39136
39748
|
const minOffset = position(min, this.j), maxOffset = position(max, this.j);
|
|
39137
39749
|
return {
|
|
@@ -39164,6 +39776,21 @@ class selectCells extends Plugin {
|
|
|
39164
39776
|
* Execute custom commands for table
|
|
39165
39777
|
*/
|
|
39166
39778
|
onExecCommand(command) {
|
|
39779
|
+
// When several cells are selected, Backspace/Delete must clear the
|
|
39780
|
+
// selected cells instead of running the normal delete (whose collapsed
|
|
39781
|
+
// document-level range threw `insertNode ... #document`). See #1273.
|
|
39782
|
+
if (/^(delete|backspace)(word|sentence)?button$/.test(command) &&
|
|
39783
|
+
this.__tableModule.getAllSelectedCells().length > 1) {
|
|
39784
|
+
const cells = this.__tableModule.getAllSelectedCells();
|
|
39785
|
+
cells.forEach(td => {
|
|
39786
|
+
Dom.detach(td);
|
|
39787
|
+
td.appendChild(this.j.createInside.element('br'));
|
|
39788
|
+
});
|
|
39789
|
+
this.unselectCells();
|
|
39790
|
+
this.j.s.setCursorIn(cells[0]);
|
|
39791
|
+
this.j.synchronizeValues();
|
|
39792
|
+
return false;
|
|
39793
|
+
}
|
|
39167
39794
|
if (/table(splitv|splitg|merge|empty|bin|binrow|bincolumn|addcolumn|addrow)/.test(command)) {
|
|
39168
39795
|
command = command.replace('table', '');
|
|
39169
39796
|
const cells = this.__tableModule.getAllSelectedCells();
|
|
@@ -39710,6 +40337,7 @@ Config.prototype.cleanHTML = {
|
|
|
39710
40337
|
timeout: 300,
|
|
39711
40338
|
removeEmptyElements: true,
|
|
39712
40339
|
fillEmptyParagraph: true,
|
|
40340
|
+
collapseEmptyValueToEmptyString: false,
|
|
39713
40341
|
replaceNBSP: true,
|
|
39714
40342
|
replaceOldTags: {
|
|
39715
40343
|
i: 'em',
|
|
@@ -39885,12 +40513,19 @@ function removeFormatForSelection(jodit) {
|
|
|
39885
40513
|
* @private
|
|
39886
40514
|
*/
|
|
39887
40515
|
function allowAttributes(jodit, nodeElm, hadEffect, allow) {
|
|
39888
|
-
|
|
40516
|
+
const allowedForTag = allow && Dom.isElement(nodeElm) && allow[nodeElm.nodeName];
|
|
40517
|
+
if (allow && Dom.isElement(nodeElm) && allowedForTag !== true) {
|
|
40518
|
+
// the tag is not in the allow list at all — attributes do not matter,
|
|
40519
|
+
// the element itself will be removed by the tags filter. Without this
|
|
40520
|
+
// check `allow[nodeName][attr]` threw on e.g. `<meta charset>`. See #1224
|
|
40521
|
+
if (!allowedForTag) {
|
|
40522
|
+
return hadEffect;
|
|
40523
|
+
}
|
|
39889
40524
|
const attrs = nodeElm.attributes;
|
|
39890
40525
|
if (attrs && attrs.length) {
|
|
39891
40526
|
const removeAttrs = [];
|
|
39892
40527
|
for (let i = 0; i < attrs.length; i += 1) {
|
|
39893
|
-
const attr =
|
|
40528
|
+
const attr = allowedForTag[attrs[i].name];
|
|
39894
40529
|
if (!attr || (attr !== true && attr !== attrs[i].value)) {
|
|
39895
40530
|
removeAttrs.push(attrs[i].name);
|
|
39896
40531
|
}
|
|
@@ -40228,11 +40863,18 @@ function isRemovableNode(jodit, node, current, allow, deny) {
|
|
|
40228
40863
|
if (!jodit.o.cleanHTML.removeEmptyElements) {
|
|
40229
40864
|
return false;
|
|
40230
40865
|
}
|
|
40866
|
+
// Never drop an empty inline element that currently holds the caret — it is
|
|
40867
|
+
// a pending-format marker the user is about to type into (#1291). `current`
|
|
40868
|
+
// is captured before a click moves the caret, so also check the live caret.
|
|
40869
|
+
const liveCaret = jodit.s.isCollapsed()
|
|
40870
|
+
? jodit.s.range.startContainer
|
|
40871
|
+
: null;
|
|
40231
40872
|
return (Dom.isElement(node) &&
|
|
40232
40873
|
node.nodeName.match(IS_INLINE) != null &&
|
|
40233
40874
|
!Dom.isTemporary(node) &&
|
|
40234
40875
|
trimInv(node.innerHTML).length === 0 &&
|
|
40235
|
-
(current == null || !Dom.isOrContains(node, current))
|
|
40876
|
+
(current == null || !Dom.isOrContains(node, current)) &&
|
|
40877
|
+
(liveCaret == null || !Dom.isOrContains(node, liveCaret)));
|
|
40236
40878
|
}
|
|
40237
40879
|
|
|
40238
40880
|
/*!
|
|
@@ -40367,14 +41009,33 @@ class cleanHtml extends Plugin {
|
|
|
40367
41009
|
onBeforeSetNativeEditorValue(data) {
|
|
40368
41010
|
const [sandBox, iframe] = this.j.o.cleanHTML.useIframeSandbox
|
|
40369
41011
|
? this.j.createInside.sandbox()
|
|
40370
|
-
: [
|
|
41012
|
+
: [
|
|
41013
|
+
// an inert document never loads sub-resources, so the
|
|
41014
|
+
// images in the value are not re-requested from the
|
|
41015
|
+
// server on every assignment (e.g. on each change in
|
|
41016
|
+
// jodit-react). See #1237
|
|
41017
|
+
this.j.od.implementation.createHTMLDocument('').body
|
|
41018
|
+
];
|
|
40371
41019
|
sandBox.innerHTML = data.value;
|
|
40372
|
-
this.
|
|
41020
|
+
this.j.e.fire('safeHTML', sandBox);
|
|
40373
41021
|
data.value = sandBox.innerHTML;
|
|
40374
41022
|
safeHTML(sandBox, { safeJavaScriptLink: true, removeOnError: true });
|
|
40375
41023
|
Dom.safeRemove(iframe);
|
|
40376
41024
|
return false;
|
|
40377
41025
|
}
|
|
41026
|
+
/**
|
|
41027
|
+
* Collapse a value that holds only a single empty block (e.g.
|
|
41028
|
+
* `<p><br></p>` left after deleting all content) to an empty string —
|
|
41029
|
+
* opt-in via `cleanHTML.collapseEmptyValueToEmptyString`. See #1149
|
|
41030
|
+
*/
|
|
41031
|
+
onAfterGetValueFromEditor(data) {
|
|
41032
|
+
if (!this.j.o.cleanHTML.collapseEmptyValueToEmptyString) {
|
|
41033
|
+
return;
|
|
41034
|
+
}
|
|
41035
|
+
if (/^<([a-z][a-z0-9]*)\b[^>]*>(?:<br\/?>)?<\/\1>$/i.test(data.value.trim())) {
|
|
41036
|
+
data.value = '';
|
|
41037
|
+
}
|
|
41038
|
+
}
|
|
40378
41039
|
onSafeHTML(sandBox) {
|
|
40379
41040
|
const sanitizer = this.j.o.cleanHTML.sanitizer;
|
|
40380
41041
|
if (sanitizer) {
|
|
@@ -40399,6 +41060,9 @@ __decorate$1([
|
|
|
40399
41060
|
__decorate$1([
|
|
40400
41061
|
watch(':beforeSetNativeEditorValue')
|
|
40401
41062
|
], cleanHtml.prototype, "onBeforeSetNativeEditorValue", null);
|
|
41063
|
+
__decorate$1([
|
|
41064
|
+
watch(':afterGetValueFromEditor')
|
|
41065
|
+
], cleanHtml.prototype, "onAfterGetValueFromEditor", null);
|
|
40402
41066
|
__decorate$1([
|
|
40403
41067
|
watch(':safeHTML')
|
|
40404
41068
|
], cleanHtml.prototype, "onSafeHTML", null);
|
|
@@ -40649,7 +41313,7 @@ Config.prototype.sourceEditorNativeOptions = {
|
|
|
40649
41313
|
highlightActiveLine: true
|
|
40650
41314
|
};
|
|
40651
41315
|
Config.prototype.sourceEditorCDNUrlsJS = [
|
|
40652
|
-
'https://cdnjs.cloudflare.com/ajax/libs/ace/1.
|
|
41316
|
+
'https://cdnjs.cloudflare.com/ajax/libs/ace/1.43.3/ace.js'
|
|
40653
41317
|
];
|
|
40654
41318
|
Config.prototype.beautifyHTMLCDNUrlsJS = [
|
|
40655
41319
|
'https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.14.4/beautify.min.js',
|
|
@@ -41171,6 +41835,15 @@ class source extends Plugin {
|
|
|
41171
41835
|
}
|
|
41172
41836
|
setFocusToMirror() {
|
|
41173
41837
|
var _a;
|
|
41838
|
+
const active = this.j.od.activeElement;
|
|
41839
|
+
// do not steal focus from another editor or control — e.g. when the
|
|
41840
|
+
// mode is switched programmatically (a Vue/React wrapper re-render)
|
|
41841
|
+
// while the user is already typing elsewhere. See #1356
|
|
41842
|
+
if (active &&
|
|
41843
|
+
active !== this.j.od.body &&
|
|
41844
|
+
!this.j.container.contains(active)) {
|
|
41845
|
+
return;
|
|
41846
|
+
}
|
|
41174
41847
|
(_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.focus();
|
|
41175
41848
|
}
|
|
41176
41849
|
saveSelection() {
|