jodit 4.12.21 → 4.12.23
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/CHANGELOG.md +46 -0
- package/es2015/jodit.css +1 -1
- package/es2015/jodit.fat.min.js +11 -11
- package/es2015/jodit.js +362 -101
- package/es2015/jodit.min.js +11 -11
- package/es2015/plugins/debug/debug.css +1 -1
- package/es2015/plugins/debug/debug.js +1 -1
- package/es2015/plugins/debug/debug.min.js +1 -1
- package/es2015/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es2015/plugins/speech-recognize/speech-recognize.js +1 -1
- package/es2015/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es2018/jodit.fat.min.js +11 -11
- package/es2018/jodit.min.js +25 -25
- package/es2018/plugins/debug/debug.min.js +1 -1
- package/es2018/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es2021/jodit.css +1 -1
- package/es2021/jodit.fat.min.js +13 -13
- package/es2021/jodit.js +352 -99
- package/es2021/jodit.min.js +23 -23
- package/es2021/plugins/debug/debug.css +1 -1
- package/es2021/plugins/debug/debug.js +1 -1
- package/es2021/plugins/debug/debug.min.js +1 -1
- package/es2021/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es2021/plugins/speech-recognize/speech-recognize.js +1 -1
- package/es2021/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es2021.en/jodit.css +1 -1
- package/es2021.en/jodit.fat.min.js +14 -14
- package/es2021.en/jodit.js +330 -77
- package/es2021.en/jodit.min.js +14 -14
- package/es2021.en/plugins/debug/debug.css +1 -1
- package/es2021.en/plugins/debug/debug.js +1 -1
- package/es2021.en/plugins/debug/debug.min.js +1 -1
- package/es2021.en/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es2021.en/plugins/speech-recognize/speech-recognize.js +1 -1
- package/es2021.en/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es5/jodit.css +2 -2
- package/es5/jodit.fat.min.js +2 -2
- package/es5/jodit.js +415 -136
- package/es5/jodit.min.css +2 -2
- package/es5/jodit.min.js +2 -2
- package/es5/plugins/debug/debug.css +1 -1
- package/es5/plugins/debug/debug.js +1 -1
- package/es5/plugins/debug/debug.min.js +1 -1
- package/es5/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es5/plugins/speech-recognize/speech-recognize.js +1 -1
- package/es5/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es5/polyfills.fat.min.js +1 -1
- package/es5/polyfills.js +1 -1
- package/es5/polyfills.min.js +1 -1
- package/esm/core/component/view-component.js +8 -0
- package/esm/core/constants.js +1 -1
- package/esm/core/dom/dom.js +6 -4
- package/esm/core/helpers/checker/is-html-from-word.d.ts +1 -1
- package/esm/core/helpers/checker/is-html-from-word.js +12 -1
- package/esm/core/selection/style/api/wrap.js +18 -3
- package/esm/core/ui/popup/popup.js +4 -1
- package/esm/jodit.js +12 -0
- package/esm/langs/tr.js +1 -1
- package/esm/modules/dialog/dialog.d.ts +5 -0
- package/esm/modules/dialog/dialog.js +25 -2
- package/esm/modules/toolbar/button/content.d.ts +6 -0
- package/esm/modules/toolbar/button/content.js +26 -1
- package/esm/plugins/add-new-line/add-new-line.js +4 -1
- package/esm/plugins/ai-assistant/ai-assistant.js +7 -1
- package/esm/plugins/backspace/cases/check-join-neighbors.js +15 -1
- package/esm/plugins/clean-html/clean-html.js +8 -2
- package/esm/plugins/clean-html/helpers/visitor/filters/allow-attributes.js +9 -2
- package/esm/plugins/clipboard/clipboard.js +29 -1
- package/esm/plugins/color/color.js +21 -21
- package/esm/plugins/inline-popup/inline-popup.d.ts +15 -0
- package/esm/plugins/inline-popup/inline-popup.js +60 -3
- package/esm/plugins/limit/limit.js +7 -1
- package/esm/plugins/paste/config.js +11 -2
- package/esm/plugins/paste/paste.d.ts +1 -1
- package/esm/plugins/paste/paste.js +7 -5
- package/esm/plugins/resize-handler/resize-handler.js +13 -2
- package/esm/plugins/resizer/resizer.js +5 -1
- package/esm/plugins/search/ui/search.js +22 -12
- package/esm/plugins/select-cells/select-cells.js +10 -6
- package/esm/plugins/source/source.js +9 -0
- package/package.json +1 -1
- package/types/core/helpers/checker/is-html-from-word.d.ts +1 -1
- package/types/modules/dialog/dialog.d.ts +5 -0
- package/types/modules/toolbar/button/content.d.ts +6 -0
- package/types/plugins/inline-popup/inline-popup.d.ts +15 -0
- package/types/plugins/paste/paste.d.ts +1 -1
package/es5/polyfills.fat.min.js
CHANGED
package/es5/polyfills.js
CHANGED
package/es5/polyfills.min.js
CHANGED
|
@@ -22,6 +22,14 @@ export class ViewComponent extends Component {
|
|
|
22
22
|
*/
|
|
23
23
|
setParentView(jodit) {
|
|
24
24
|
this.jodit = jodit;
|
|
25
|
+
// Inherit the owner window from the parent view — for an editor
|
|
26
|
+
// created with a custom `ownerWindow` (e.g. inside an iframe) the
|
|
27
|
+
// component default (the global `window`) is wrong: outside-click
|
|
28
|
+
// handlers of dropdowns/popups listened to the wrong window. See
|
|
29
|
+
// https://github.com/xdan/jodit/issues/965
|
|
30
|
+
if (jodit.ow) {
|
|
31
|
+
this.ownerWindow = jodit.ow;
|
|
32
|
+
}
|
|
25
33
|
jodit.components.add(this);
|
|
26
34
|
return this;
|
|
27
35
|
}
|
package/esm/core/constants.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
4
4
|
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
5
5
|
*/
|
|
6
|
-
export const APP_VERSION = "4.12.
|
|
6
|
+
export const APP_VERSION = "4.12.23";
|
|
7
7
|
// prettier-ignore
|
|
8
8
|
export const ES = "es2020";
|
|
9
9
|
export const IS_ES_MODERN = true;
|
package/esm/core/dom/dom.js
CHANGED
|
@@ -283,12 +283,12 @@ export class Dom {
|
|
|
283
283
|
* Check if element is element node
|
|
284
284
|
*/
|
|
285
285
|
static isElement(node) {
|
|
286
|
-
var _a;
|
|
287
286
|
if (!Dom.isNode(node)) {
|
|
288
287
|
return false;
|
|
289
288
|
}
|
|
290
|
-
|
|
291
|
-
|
|
289
|
+
// no `defaultView` requirement — nodes of an inert document
|
|
290
|
+
// (`DOMParser`, `implementation.createHTMLDocument`) are still elements
|
|
291
|
+
return node.nodeType === Node.ELEMENT_NODE;
|
|
292
292
|
}
|
|
293
293
|
/**
|
|
294
294
|
* Check if element is document fragment
|
|
@@ -309,8 +309,10 @@ export class Dom {
|
|
|
309
309
|
if (!Dom.isNode(node)) {
|
|
310
310
|
return false;
|
|
311
311
|
}
|
|
312
|
+
// an inert document has no browsing context (`defaultView` is null),
|
|
313
|
+
// but its nodes are same-realm HTMLElements
|
|
312
314
|
const win = (_a = node.ownerDocument) === null || _a === void 0 ? void 0 : _a.defaultView;
|
|
313
|
-
return
|
|
315
|
+
return node instanceof (win ? win.HTMLElement : HTMLElement);
|
|
314
316
|
}
|
|
315
317
|
/**
|
|
316
318
|
* Check element is inline block
|
|
@@ -7,10 +7,21 @@
|
|
|
7
7
|
* @module helpers/checker
|
|
8
8
|
*/
|
|
9
9
|
/**
|
|
10
|
-
* Detect if string is HTML from MS Word or
|
|
10
|
+
* Detect if string is HTML from MS Word, Excel or LibreOffice/OpenOffice
|
|
11
11
|
*/
|
|
12
12
|
export function isHtmlFromWord(data) {
|
|
13
13
|
return (data.search(/<meta.*?Microsoft Excel\s[\d].*?>/) !== -1 ||
|
|
14
14
|
data.search(/<meta.*?Microsoft Word\s[\d].*?>/) !== -1 ||
|
|
15
|
+
// `<meta name=ProgId content=Word.Document>` — attribute values are
|
|
16
|
+
// unquoted in the raw Word clipboard fragment
|
|
17
|
+
data.search(/<meta[^>]*?ProgId[^>]*?(Word|Excel)\./i) !== -1 ||
|
|
18
|
+
// LibreOffice/OpenOffice Writer & Calc
|
|
19
|
+
data.search(/<meta[^>]*?(LibreOffice|OpenOffice)/i) !== -1 ||
|
|
20
|
+
// Office namespaces on the root element of the clipboard fragment
|
|
21
|
+
data.search(/urn:schemas-microsoft-com:office:(word|excel)/) !== -1 ||
|
|
22
|
+
// `class=MsoNormal` and friends (unquoted/quoted)
|
|
23
|
+
data.search(/<\w[^>]*\sclass=("|')?Mso/) !== -1 ||
|
|
24
|
+
// the raw Word clipboard uses SINGLE quotes: style='mso-…'
|
|
25
|
+
data.search(/style='[^']*mso-/) !== -1 ||
|
|
15
26
|
(data.search(/style="[^"]*mso-/) !== -1 && data.search(/<font/) !== -1));
|
|
16
27
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Dom } from "../../../dom/index.js";
|
|
7
7
|
import { attr } from "../../../helpers/utils/attr.js";
|
|
8
|
+
import { css } from "../../../helpers/utils/css.js";
|
|
8
9
|
import { wrapList } from "./list/wrap-list.js";
|
|
9
10
|
import { wrapUnwrappedText } from "./wrap-unwrapped-text.js";
|
|
10
11
|
/**
|
|
@@ -13,9 +14,23 @@ import { wrapUnwrappedText } from "./wrap-unwrapped-text.js";
|
|
|
13
14
|
*/
|
|
14
15
|
export function wrap(commitStyle, font, jodit) {
|
|
15
16
|
const wrapper = findOrCreateWrapper(commitStyle, font, jodit);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
if (commitStyle.elementIsList) {
|
|
18
|
+
return wrapList(commitStyle, wrapper, jodit);
|
|
19
|
+
}
|
|
20
|
+
const newWrapper = Dom.replace(wrapper, commitStyle.element, jodit.createInside, true);
|
|
21
|
+
if (commitStyle.elementIsBlock) {
|
|
22
|
+
// Inline font styles left over from pasted content visually override
|
|
23
|
+
// the new block format — e.g. an h2 with `font-weight: normal;
|
|
24
|
+
// font-size: 24px` does not look like a heading at all, so the
|
|
25
|
+
// command seems to do nothing. See
|
|
26
|
+
// https://github.com/xdan/jodit/issues/1063
|
|
27
|
+
css(newWrapper, 'fontSize', null);
|
|
28
|
+
css(newWrapper, 'fontWeight', null);
|
|
29
|
+
if (!attr(newWrapper, 'style')) {
|
|
30
|
+
attr(newWrapper, 'style', null);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return newWrapper;
|
|
19
34
|
}
|
|
20
35
|
const WRAP_NODES = new Set([
|
|
21
36
|
'td',
|
|
@@ -137,8 +137,11 @@ export class Popup extends UIGroup {
|
|
|
137
137
|
* Calculate static bound for point
|
|
138
138
|
*/
|
|
139
139
|
getKeepBound(getBound) {
|
|
140
|
+
var _a;
|
|
140
141
|
const oldBound = getBound();
|
|
141
|
-
|
|
142
|
+
// Inside Shadow DOM `document.elementFromPoint` returns the shadow
|
|
143
|
+
// host, so the lookup must start from the shadow root
|
|
144
|
+
const elmUnderCursor = ((_a = this.j.o.shadowRoot) !== null && _a !== void 0 ? _a : this.od).elementFromPoint(oldBound.left, oldBound.top);
|
|
142
145
|
if (!elmUnderCursor) {
|
|
143
146
|
return getBound;
|
|
144
147
|
}
|
package/esm/jodit.js
CHANGED
|
@@ -1073,6 +1073,18 @@ let Jodit = Jodit_1 = class Jodit extends ViewWithToolbar {
|
|
|
1073
1073
|
(opt.uploader.url || opt.uploader.insertImageAsBase64URI)) {
|
|
1074
1074
|
this.uploader.bind(this.editor);
|
|
1075
1075
|
}
|
|
1076
|
+
else if (!opt.enableDragAndDropFileToEditor) {
|
|
1077
|
+
// Without a bound uploader nobody cancels a file drop, and
|
|
1078
|
+
// Firefox inserts the dropped image natively — the option
|
|
1079
|
+
// must mean "do nothing". See
|
|
1080
|
+
// https://github.com/xdan/jodit/issues/1077
|
|
1081
|
+
this.e.on(editor, 'drop', (e) => {
|
|
1082
|
+
var _a, _b;
|
|
1083
|
+
if ((_b = (_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b.length) {
|
|
1084
|
+
e.preventDefault();
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1076
1088
|
// in initEditor - the editor could change
|
|
1077
1089
|
if (!this.__elementToPlace.get(this.editor)) {
|
|
1078
1090
|
this.__elementToPlace.set(this.editor, currentPlace);
|
package/esm/langs/tr.js
CHANGED
|
@@ -85,7 +85,7 @@ export default {
|
|
|
85
85
|
'Insert as Text': 'Yazı olarak ekle',
|
|
86
86
|
'Word Paste Detected': 'Word biçiminde yapıştırma algılandı',
|
|
87
87
|
'The pasted content is coming from a Microsoft Word/Excel document. Do you want to keep the format or clean it up?':
|
|
88
|
-
'
|
|
88
|
+
'Yapıştırılan içerik bir Microsoft Word/Excel belgesinden geliyor. Formatı korumak mı yoksa temizlemek mi istiyorsunuz?',
|
|
89
89
|
'Insert only Text': 'Sadece yazıyı ekle',
|
|
90
90
|
'File Browser': 'Dosya Listeleyici',
|
|
91
91
|
'Error on load list': 'Liste yüklenirken hata oluştu',
|
|
@@ -42,6 +42,11 @@ export declare class Dialog extends ViewWithToolbar implements IDialog {
|
|
|
42
42
|
private __onMouseMove;
|
|
43
43
|
private __onEsc;
|
|
44
44
|
private __onResize;
|
|
45
|
+
/**
|
|
46
|
+
* Minimal size the dialog can be resized to — the header and the footer
|
|
47
|
+
* (with its buttons) must always stay inside the panel
|
|
48
|
+
*/
|
|
49
|
+
private minSize;
|
|
45
50
|
private __onResizerMouseDown;
|
|
46
51
|
private __addGlobalResizeListeners;
|
|
47
52
|
private __removeGlobalResizeListeners;
|
|
@@ -20,7 +20,7 @@ import { autobind, component, hook } from "../../core/decorators/index.js";
|
|
|
20
20
|
import { Dom } from "../../core/dom/dom.js";
|
|
21
21
|
import { eventEmitter, pluginSystem } from "../../core/global.js";
|
|
22
22
|
import { asArray, splitArray, toArray } from "../../core/helpers/array/index.js";
|
|
23
|
-
import { hasContainer, isArray, isBoolean, isFunction, isString, isVoid } from "../../core/helpers/checker/index.js";
|
|
23
|
+
import { hasContainer, isArray, isBoolean, isFunction, isNumber, isString, isVoid } from "../../core/helpers/checker/index.js";
|
|
24
24
|
import { $$, attr, ConfigProto, css } from "../../core/helpers/utils/index.js";
|
|
25
25
|
import { assert } from "../../core/helpers/utils/assert.js";
|
|
26
26
|
import { Icon } from "../../core/ui/index.js";
|
|
@@ -151,7 +151,7 @@ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar {
|
|
|
151
151
|
e.stopImmediatePropagation();
|
|
152
152
|
}
|
|
153
153
|
if (this.resizable && this.o.resizable) {
|
|
154
|
-
this.setSize(this.startPoint.w + e.clientX - this.startX, this.startPoint.h + e.clientY - this.startY);
|
|
154
|
+
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));
|
|
155
155
|
if (this.e) {
|
|
156
156
|
/**
|
|
157
157
|
* Fired when dialog box is resized
|
|
@@ -178,11 +178,29 @@ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar {
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
__onResizerMouseDown(e) {
|
|
181
|
+
var _a, _b, _c;
|
|
181
182
|
this.resizable = true;
|
|
182
183
|
this.startX = e.clientX;
|
|
183
184
|
this.startY = e.clientY;
|
|
184
185
|
this.startPoint.w = this.dialog.offsetWidth;
|
|
185
186
|
this.startPoint.h = this.dialog.offsetHeight;
|
|
187
|
+
const header = this.getElm('header');
|
|
188
|
+
const footer = this.getElm('footer');
|
|
189
|
+
const content = this.getElm('content');
|
|
190
|
+
// the content area does not shrink below its CSS `min-height`,
|
|
191
|
+
// so it is part of the smallest height the panel can take
|
|
192
|
+
const contentMinHeight = content
|
|
193
|
+
? parseFloat(this.ow.getComputedStyle(content).minHeight) || 0
|
|
194
|
+
: 0;
|
|
195
|
+
this.minSize.w = isNumber(this.o.minWidth)
|
|
196
|
+
? this.o.minWidth
|
|
197
|
+
: Math.max(100, (_a = footer === null || footer === void 0 ? void 0 : footer.scrollWidth) !== null && _a !== void 0 ? _a : 0);
|
|
198
|
+
this.minSize.h = isNumber(this.o.minHeight)
|
|
199
|
+
? this.o.minHeight
|
|
200
|
+
: ((_b = header === null || header === void 0 ? void 0 : header.offsetHeight) !== null && _b !== void 0 ? _b : 0) +
|
|
201
|
+
((_c = footer === null || footer === void 0 ? void 0 : footer.offsetHeight) !== null && _c !== void 0 ? _c : 0) +
|
|
202
|
+
contentMinHeight +
|
|
203
|
+
this.resizer.offsetHeight;
|
|
186
204
|
this.lockSelect();
|
|
187
205
|
this.__addGlobalResizeListeners();
|
|
188
206
|
if (this.e) {
|
|
@@ -494,6 +512,11 @@ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar {
|
|
|
494
512
|
this.setPosition();
|
|
495
513
|
}
|
|
496
514
|
};
|
|
515
|
+
/**
|
|
516
|
+
* Minimal size the dialog can be resized to — the header and the footer
|
|
517
|
+
* (with its buttons) must always stay inside the panel
|
|
518
|
+
*/
|
|
519
|
+
this.minSize = { w: 0, h: 0 };
|
|
497
520
|
this.isModal = false;
|
|
498
521
|
/**
|
|
499
522
|
* True, if dialog was opened
|
|
@@ -15,6 +15,12 @@ export declare class ToolbarContent<T extends IViewBased = IViewBased> extends U
|
|
|
15
15
|
className(): string;
|
|
16
16
|
/** @override */
|
|
17
17
|
update(): void;
|
|
18
|
+
/**
|
|
19
|
+
* The content is arbitrary HTML — propagate the disabled state to the
|
|
20
|
+
* nested form controls (e.g. the file input of the Upload button),
|
|
21
|
+
* otherwise they stay interactive.
|
|
22
|
+
*/
|
|
23
|
+
protected onChangeDisabled(): void;
|
|
18
24
|
/** @override */
|
|
19
25
|
protected createContainer(): HTMLElement;
|
|
20
26
|
constructor(jodit: T, control: IControlTypeContent, target?: Nullable<HTMLElement>);
|
|
@@ -24,13 +24,38 @@ let ToolbarContent = class ToolbarContent extends UIButton {
|
|
|
24
24
|
}
|
|
25
25
|
/** @override */
|
|
26
26
|
update() {
|
|
27
|
-
|
|
27
|
+
var _a, _b, _c;
|
|
28
|
+
const { control } = this;
|
|
29
|
+
const content = control.getContent(this.j, this);
|
|
28
30
|
if (isString(content) || content.parentNode !== this.container) {
|
|
29
31
|
Dom.detach(this.container);
|
|
30
32
|
this.container.appendChild(isString(content) ? this.j.create.fromHTML(content) : content);
|
|
31
33
|
}
|
|
34
|
+
// Content controls never went through the ToolbarButton status
|
|
35
|
+
// calculation, so `isDisabled`/`isActive`/`update` declared on the
|
|
36
|
+
// control were silently ignored (e.g. the FileBrowser Upload button
|
|
37
|
+
// ignored the backend permissions). See
|
|
38
|
+
// https://github.com/xdan/jodit/issues/1094
|
|
39
|
+
this.state.disabled = Boolean((_a = control.isDisabled) === null || _a === void 0 ? void 0 : _a.call(control, this.j, this));
|
|
40
|
+
this.state.activated = Boolean((_b = control.isActive) === null || _b === void 0 ? void 0 : _b.call(control, this.j, this));
|
|
41
|
+
(_c = control.update) === null || _c === void 0 ? void 0 : _c.call(control, this.j, this);
|
|
42
|
+
// The first update() runs before the state watchers are attached, so
|
|
43
|
+
// apply the calculated state explicitly (the calls are idempotent)
|
|
44
|
+
this.onChangeDisabled();
|
|
45
|
+
this.onChangeActivated();
|
|
32
46
|
super.update();
|
|
33
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* The content is arbitrary HTML — propagate the disabled state to the
|
|
50
|
+
* nested form controls (e.g. the file input of the Upload button),
|
|
51
|
+
* otherwise they stay interactive.
|
|
52
|
+
*/
|
|
53
|
+
onChangeDisabled() {
|
|
54
|
+
super.onChangeDisabled();
|
|
55
|
+
this.container
|
|
56
|
+
.querySelectorAll('input,button,select,textarea')
|
|
57
|
+
.forEach(elm => attr(elm, 'disabled', this.state.disabled || null));
|
|
58
|
+
}
|
|
34
59
|
/** @override */
|
|
35
60
|
createContainer() {
|
|
36
61
|
return this.j.c.span(this.componentName);
|
|
@@ -151,8 +151,11 @@ export class addNewLine extends Plugin {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
__onMouseMove(e) {
|
|
154
|
+
var _a;
|
|
154
155
|
const editor = this.j;
|
|
155
|
-
|
|
156
|
+
// Inside Shadow DOM `document.elementFromPoint` returns the shadow
|
|
157
|
+
// host, so the lookup must start from the shadow root
|
|
158
|
+
let currentElement = ((_a = editor.o.shadowRoot) !== null && _a !== void 0 ? _a : editor.ed).elementFromPoint(e.clientX, e.clientY);
|
|
156
159
|
if (!Dom.isHTMLElement(currentElement) ||
|
|
157
160
|
!Dom.isOrContains(editor.editor, currentElement)) {
|
|
158
161
|
return;
|
|
@@ -40,7 +40,13 @@ export class aiAssistant extends Plugin {
|
|
|
40
40
|
return new UiAiAssistant(jodit, {
|
|
41
41
|
onInsertAfter(html) {
|
|
42
42
|
jodit.s.focus();
|
|
43
|
-
|
|
43
|
+
// `current()` returns a node of the selection START, so with
|
|
44
|
+
// several selected paragraphs the result landed after the
|
|
45
|
+
// first one — collapse the range to the END of the selection
|
|
46
|
+
// instead. See https://github.com/xdan/jodit/issues/1263
|
|
47
|
+
const range = jodit.s.range;
|
|
48
|
+
range.collapse(false);
|
|
49
|
+
jodit.s.selectRange(range);
|
|
44
50
|
jodit.s.insertHTML(html);
|
|
45
51
|
__dialog.close();
|
|
46
52
|
},
|
|
@@ -32,13 +32,27 @@ export function checkJoinNeighbors(jodit, fakeNode, backspace) {
|
|
|
32
32
|
}
|
|
33
33
|
if (sibling &&
|
|
34
34
|
(checkMoveListContent(jodit, mainClosestBox, sibling, backspace) ||
|
|
35
|
-
moveContentAndRemoveEmpty(jodit, mainClosestBox, sibling, backspace))) {
|
|
35
|
+
moveContentAndRemoveEmpty(jodit, mainClosestBox, resolveTableSibling(sibling, backspace), backspace))) {
|
|
36
36
|
jodit.s.setCursorBefore(fakeNode);
|
|
37
37
|
return true;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
return false;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Content cannot be merged into the `<table>` element itself — it would land
|
|
44
|
+
* between the table sections (after `</tbody>`), which is invalid HTML and
|
|
45
|
+
* gets foster-parented out of the table on the next parse. Merge into the
|
|
46
|
+
* edge cell instead. See https://github.com/xdan/jodit/issues/1064
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
function resolveTableSibling(sibling, backspace) {
|
|
50
|
+
if (!Dom.isTag(sibling, 'table')) {
|
|
51
|
+
return sibling;
|
|
52
|
+
}
|
|
53
|
+
const cells = sibling.querySelectorAll('td,th');
|
|
54
|
+
return cells.length ? cells[backspace ? cells.length - 1 : 0] : null;
|
|
55
|
+
}
|
|
42
56
|
function checkMoveListContent(jodit, mainClosestBox, sibling, backspace) {
|
|
43
57
|
// Process UL/LI/OL cases
|
|
44
58
|
const siblingIsList = Dom.isTag(sibling, LIST_TAGS);
|
|
@@ -86,9 +86,15 @@ export class cleanHtml extends Plugin {
|
|
|
86
86
|
onBeforeSetNativeEditorValue(data) {
|
|
87
87
|
const [sandBox, iframe] = this.j.o.cleanHTML.useIframeSandbox
|
|
88
88
|
? this.j.createInside.sandbox()
|
|
89
|
-
: [
|
|
89
|
+
: [
|
|
90
|
+
// an inert document never loads sub-resources, so the
|
|
91
|
+
// images in the value are not re-requested from the
|
|
92
|
+
// server on every assignment (e.g. on each change in
|
|
93
|
+
// jodit-react). See #1237
|
|
94
|
+
this.j.od.implementation.createHTMLDocument('').body
|
|
95
|
+
];
|
|
90
96
|
sandBox.innerHTML = data.value;
|
|
91
|
-
this.
|
|
97
|
+
this.j.e.fire('safeHTML', sandBox);
|
|
92
98
|
data.value = sandBox.innerHTML;
|
|
93
99
|
safeHTML(sandBox, { safeJavaScriptLink: true, removeOnError: true });
|
|
94
100
|
Dom.safeRemove(iframe);
|
|
@@ -8,12 +8,19 @@ import { Dom } from "../../../../../core/dom/dom.js";
|
|
|
8
8
|
* @private
|
|
9
9
|
*/
|
|
10
10
|
export function allowAttributes(jodit, nodeElm, hadEffect, allow) {
|
|
11
|
-
|
|
11
|
+
const allowedForTag = allow && Dom.isElement(nodeElm) && allow[nodeElm.nodeName];
|
|
12
|
+
if (allow && Dom.isElement(nodeElm) && allowedForTag !== true) {
|
|
13
|
+
// the tag is not in the allow list at all — attributes do not matter,
|
|
14
|
+
// the element itself will be removed by the tags filter. Without this
|
|
15
|
+
// check `allow[nodeName][attr]` threw on e.g. `<meta charset>`. See #1224
|
|
16
|
+
if (!allowedForTag) {
|
|
17
|
+
return hadEffect;
|
|
18
|
+
}
|
|
12
19
|
const attrs = nodeElm.attributes;
|
|
13
20
|
if (attrs && attrs.length) {
|
|
14
21
|
const removeAttrs = [];
|
|
15
22
|
for (let i = 0; i < attrs.length; i += 1) {
|
|
16
|
-
const attr =
|
|
23
|
+
const attr = allowedForTag[attrs[i].name];
|
|
17
24
|
if (!attr || (attr !== true && attr !== attrs[i].value)) {
|
|
18
25
|
removeAttrs.push(attrs[i].name);
|
|
19
26
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { CLIPBOARD_ID, INSERT_AS_HTML, TEXT_HTML, TEXT_PLAIN } from "../../core/constants.js";
|
|
7
7
|
import { cached } from "../../core/decorators/cache/cache.js";
|
|
8
|
+
import { Dom } from "../../core/dom/dom.js";
|
|
8
9
|
import { pluginSystem } from "../../core/global.js";
|
|
9
10
|
import { getDataTransfer, stripTags } from "../../core/helpers/index.js";
|
|
10
11
|
import "./config.js";
|
|
@@ -40,7 +41,7 @@ export class clipboard {
|
|
|
40
41
|
.off(`copy.${CLIPBOARD_ID} cut.${CLIPBOARD_ID}`)
|
|
41
42
|
.on(`copy.${CLIPBOARD_ID} cut.${CLIPBOARD_ID}`, (event) => {
|
|
42
43
|
var _a;
|
|
43
|
-
const selectedText = editor.s.html;
|
|
44
|
+
const selectedText = wrapWithInlineAncestors(editor, editor.s.html);
|
|
44
45
|
const clipboardData = getDataTransfer(event) ||
|
|
45
46
|
getDataTransfer(editor.ew) ||
|
|
46
47
|
getDataTransfer(event.originalEvent);
|
|
@@ -69,4 +70,31 @@ export class clipboard {
|
|
|
69
70
|
(_b = editor === null || editor === void 0 ? void 0 : editor.events) === null || _b === void 0 ? void 0 : _b.off('.' + CLIPBOARD_ID);
|
|
70
71
|
}
|
|
71
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* `Selection.html` clones only the range contents — when the selection sits
|
|
75
|
+
* entirely inside the text of a formatted element (`<strong>te|st|</strong>`),
|
|
76
|
+
* the clone is bare text and the formatting would be lost on paste. Native
|
|
77
|
+
* browser copy keeps that context, so the interception must restore it: wrap
|
|
78
|
+
* the fragment in shallow clones of the inline ancestors of the range. See #1202
|
|
79
|
+
*/
|
|
80
|
+
function wrapWithInlineAncestors(editor, html) {
|
|
81
|
+
if (!html || editor.s.isCollapsed()) {
|
|
82
|
+
return html;
|
|
83
|
+
}
|
|
84
|
+
let node = editor.s.range.commonAncestorContainer;
|
|
85
|
+
if (!Dom.isElement(node)) {
|
|
86
|
+
node = node.parentElement;
|
|
87
|
+
}
|
|
88
|
+
let result = html;
|
|
89
|
+
while (node &&
|
|
90
|
+
node !== editor.editor &&
|
|
91
|
+
Dom.isElement(node) &&
|
|
92
|
+
!Dom.isBlock(node)) {
|
|
93
|
+
const shell = node.cloneNode(false);
|
|
94
|
+
shell.innerHTML = result;
|
|
95
|
+
result = shell.outerHTML;
|
|
96
|
+
node = node.parentElement;
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
72
100
|
pluginSystem.add('clipboard', clipboard);
|
|
@@ -15,28 +15,28 @@ export function color(editor) {
|
|
|
15
15
|
group: 'color'
|
|
16
16
|
});
|
|
17
17
|
const callback = (command, second, third) => {
|
|
18
|
+
var _a;
|
|
18
19
|
const colorHEX = normalizeColor(third);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
editor.s.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
break;
|
|
20
|
+
const value = !colorHEX ? '' : colorHEX;
|
|
21
|
+
const style = command === 'background'
|
|
22
|
+
? { backgroundColor: value }
|
|
23
|
+
: { color: value };
|
|
24
|
+
// Cells selected with the `select-cells` plugin drop or collapse the
|
|
25
|
+
// native range, so `commitStyle` would paint a pending caret format
|
|
26
|
+
// outside the table instead of the selection the user sees. Apply the
|
|
27
|
+
// style to the content of every selected cell instead. See #1250
|
|
28
|
+
const selectedCells = editor
|
|
29
|
+
.getInstance('Table', editor.o)
|
|
30
|
+
.getAllSelectedCells();
|
|
31
|
+
if (selectedCells.length && editor.s.isCollapsed()) {
|
|
32
|
+
selectedCells.forEach(cell => {
|
|
33
|
+
editor.s.select(cell, true);
|
|
34
|
+
editor.s.commitStyle({ attributes: { style } });
|
|
35
|
+
});
|
|
36
|
+
(_a = editor.s.sel) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
editor.s.commitStyle({ attributes: { style } });
|
|
40
40
|
}
|
|
41
41
|
editor.synchronizeValues();
|
|
42
42
|
return false;
|
|
@@ -42,6 +42,21 @@ export declare class inlinePopup extends Plugin {
|
|
|
42
42
|
private isExcludedTarget;
|
|
43
43
|
/** @override **/
|
|
44
44
|
protected afterInit(jodit: IJodit): void;
|
|
45
|
+
/**
|
|
46
|
+
* The user pressed a button inside the selection toolbar — after the
|
|
47
|
+
* command fires `closeAllPopups`, the toolbar should be shown again
|
|
48
|
+
* while the selection is still there. See #1238
|
|
49
|
+
*/
|
|
50
|
+
private __reopenSelectionPopup;
|
|
51
|
+
private __onDocumentMouseDown;
|
|
52
|
+
private __onCloseAllPopups;
|
|
53
|
+
/**
|
|
54
|
+
* The selection rect comes from the editor document — in iframe mode its
|
|
55
|
+
* coordinates are iframe-local, while the popup lives in the host
|
|
56
|
+
* document, so the iframe offset must be added. See
|
|
57
|
+
* https://github.com/xdan/jodit/issues/1058
|
|
58
|
+
*/
|
|
59
|
+
private __selectionBound;
|
|
45
60
|
private snapRange;
|
|
46
61
|
private onSelectionStart;
|
|
47
62
|
private onSelectionEnd;
|