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.
Files changed (86) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/es2015/jodit.css +1 -1
  3. package/es2015/jodit.fat.min.js +11 -11
  4. package/es2015/jodit.js +362 -101
  5. package/es2015/jodit.min.js +11 -11
  6. package/es2015/plugins/debug/debug.css +1 -1
  7. package/es2015/plugins/debug/debug.js +1 -1
  8. package/es2015/plugins/debug/debug.min.js +1 -1
  9. package/es2015/plugins/speech-recognize/speech-recognize.css +1 -1
  10. package/es2015/plugins/speech-recognize/speech-recognize.js +1 -1
  11. package/es2015/plugins/speech-recognize/speech-recognize.min.js +1 -1
  12. package/es2018/jodit.fat.min.js +11 -11
  13. package/es2018/jodit.min.js +25 -25
  14. package/es2018/plugins/debug/debug.min.js +1 -1
  15. package/es2018/plugins/speech-recognize/speech-recognize.min.js +1 -1
  16. package/es2021/jodit.css +1 -1
  17. package/es2021/jodit.fat.min.js +13 -13
  18. package/es2021/jodit.js +352 -99
  19. package/es2021/jodit.min.js +23 -23
  20. package/es2021/plugins/debug/debug.css +1 -1
  21. package/es2021/plugins/debug/debug.js +1 -1
  22. package/es2021/plugins/debug/debug.min.js +1 -1
  23. package/es2021/plugins/speech-recognize/speech-recognize.css +1 -1
  24. package/es2021/plugins/speech-recognize/speech-recognize.js +1 -1
  25. package/es2021/plugins/speech-recognize/speech-recognize.min.js +1 -1
  26. package/es2021.en/jodit.css +1 -1
  27. package/es2021.en/jodit.fat.min.js +14 -14
  28. package/es2021.en/jodit.js +330 -77
  29. package/es2021.en/jodit.min.js +14 -14
  30. package/es2021.en/plugins/debug/debug.css +1 -1
  31. package/es2021.en/plugins/debug/debug.js +1 -1
  32. package/es2021.en/plugins/debug/debug.min.js +1 -1
  33. package/es2021.en/plugins/speech-recognize/speech-recognize.css +1 -1
  34. package/es2021.en/plugins/speech-recognize/speech-recognize.js +1 -1
  35. package/es2021.en/plugins/speech-recognize/speech-recognize.min.js +1 -1
  36. package/es5/jodit.css +2 -2
  37. package/es5/jodit.fat.min.js +2 -2
  38. package/es5/jodit.js +415 -136
  39. package/es5/jodit.min.css +2 -2
  40. package/es5/jodit.min.js +2 -2
  41. package/es5/plugins/debug/debug.css +1 -1
  42. package/es5/plugins/debug/debug.js +1 -1
  43. package/es5/plugins/debug/debug.min.js +1 -1
  44. package/es5/plugins/speech-recognize/speech-recognize.css +1 -1
  45. package/es5/plugins/speech-recognize/speech-recognize.js +1 -1
  46. package/es5/plugins/speech-recognize/speech-recognize.min.js +1 -1
  47. package/es5/polyfills.fat.min.js +1 -1
  48. package/es5/polyfills.js +1 -1
  49. package/es5/polyfills.min.js +1 -1
  50. package/esm/core/component/view-component.js +8 -0
  51. package/esm/core/constants.js +1 -1
  52. package/esm/core/dom/dom.js +6 -4
  53. package/esm/core/helpers/checker/is-html-from-word.d.ts +1 -1
  54. package/esm/core/helpers/checker/is-html-from-word.js +12 -1
  55. package/esm/core/selection/style/api/wrap.js +18 -3
  56. package/esm/core/ui/popup/popup.js +4 -1
  57. package/esm/jodit.js +12 -0
  58. package/esm/langs/tr.js +1 -1
  59. package/esm/modules/dialog/dialog.d.ts +5 -0
  60. package/esm/modules/dialog/dialog.js +25 -2
  61. package/esm/modules/toolbar/button/content.d.ts +6 -0
  62. package/esm/modules/toolbar/button/content.js +26 -1
  63. package/esm/plugins/add-new-line/add-new-line.js +4 -1
  64. package/esm/plugins/ai-assistant/ai-assistant.js +7 -1
  65. package/esm/plugins/backspace/cases/check-join-neighbors.js +15 -1
  66. package/esm/plugins/clean-html/clean-html.js +8 -2
  67. package/esm/plugins/clean-html/helpers/visitor/filters/allow-attributes.js +9 -2
  68. package/esm/plugins/clipboard/clipboard.js +29 -1
  69. package/esm/plugins/color/color.js +21 -21
  70. package/esm/plugins/inline-popup/inline-popup.d.ts +15 -0
  71. package/esm/plugins/inline-popup/inline-popup.js +60 -3
  72. package/esm/plugins/limit/limit.js +7 -1
  73. package/esm/plugins/paste/config.js +11 -2
  74. package/esm/plugins/paste/paste.d.ts +1 -1
  75. package/esm/plugins/paste/paste.js +7 -5
  76. package/esm/plugins/resize-handler/resize-handler.js +13 -2
  77. package/esm/plugins/resizer/resizer.js +5 -1
  78. package/esm/plugins/search/ui/search.js +22 -12
  79. package/esm/plugins/select-cells/select-cells.js +10 -6
  80. package/esm/plugins/source/source.js +9 -0
  81. package/package.json +1 -1
  82. package/types/core/helpers/checker/is-html-from-word.d.ts +1 -1
  83. package/types/modules/dialog/dialog.d.ts +5 -0
  84. package/types/modules/toolbar/button/content.d.ts +6 -0
  85. package/types/plugins/inline-popup/inline-popup.d.ts +15 -0
  86. package/types/plugins/paste/paste.d.ts +1 -1
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.21
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.21
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.21
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.21
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.21
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.21
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.21
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
package/es5/polyfills.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.21
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.21
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -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
  }
@@ -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.21";
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;
@@ -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
- const win = (_a = node.ownerDocument) === null || _a === void 0 ? void 0 : _a.defaultView;
291
- return Boolean(win && node.nodeType === Node.ELEMENT_NODE);
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 Boolean(win && node instanceof win.HTMLElement);
315
+ return node instanceof (win ? win.HTMLElement : HTMLElement);
314
316
  }
315
317
  /**
316
318
  * Check element is inline block
@@ -7,6 +7,6 @@
7
7
  * @module helpers/checker
8
8
  */
9
9
  /**
10
- * Detect if string is HTML from MS Word or Excel
10
+ * Detect if string is HTML from MS Word, Excel or LibreOffice/OpenOffice
11
11
  */
12
12
  export declare function isHtmlFromWord(data: string): boolean;
@@ -7,10 +7,21 @@
7
7
  * @module helpers/checker
8
8
  */
9
9
  /**
10
- * Detect if string is HTML from MS Word or Excel
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
- return commitStyle.elementIsList
17
- ? wrapList(commitStyle, wrapper, jodit)
18
- : Dom.replace(wrapper, commitStyle.element, jodit.createInside, true);
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
- const elmUnderCursor = this.od.elementFromPoint(oldBound.left, oldBound.top);
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
- 'Der Inhalt, den Sie einfügen, stammt aus einem Microsoft Word / Excel-Dokument. Möchten Sie das Format erhalten oder löschen?',
88
+ 'Yapıştırılan içerik bir Microsoft Word/Excel belgesinden geliyor. Formatı korumak 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
- const content = this.control.getContent(this.j, this);
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
- let currentElement = editor.ed.elementFromPoint(e.clientX, e.clientY);
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
- jodit.s.setCursorAfter(jodit.s.current());
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
- : [this.j.createInside.div()];
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.onSafeHTML(sandBox);
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
- if (allow && Dom.isElement(nodeElm) && allow[nodeElm.nodeName] !== true) {
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 = allow[nodeElm.nodeName][attrs[i].name];
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
- switch (command) {
20
- case 'background':
21
- editor.s.commitStyle({
22
- attributes: {
23
- style: {
24
- backgroundColor: !colorHEX
25
- ? ''
26
- : colorHEX
27
- }
28
- }
29
- });
30
- break;
31
- case 'forecolor':
32
- editor.s.commitStyle({
33
- attributes: {
34
- style: {
35
- color: !colorHEX ? '' : colorHEX
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;