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