jodit 4.12.21 → 4.12.22

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 (69) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/es2015/jodit.css +1 -1
  3. package/es2015/jodit.fat.min.js +8 -8
  4. package/es2015/jodit.js +155 -36
  5. package/es2015/jodit.min.js +8 -8
  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 +8 -8
  13. package/es2018/jodit.min.js +24 -24
  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 +9 -9
  18. package/es2021/jodit.js +154 -35
  19. package/es2021/jodit.min.js +22 -22
  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 +9 -9
  28. package/es2021.en/jodit.js +153 -34
  29. package/es2021.en/jodit.min.js +11 -11
  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 +164 -36
  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/constants.js +1 -1
  51. package/esm/core/dom/dom.js +6 -4
  52. package/esm/langs/tr.js +1 -1
  53. package/esm/modules/dialog/dialog.d.ts +5 -0
  54. package/esm/modules/dialog/dialog.js +25 -2
  55. package/esm/plugins/clean-html/clean-html.js +8 -2
  56. package/esm/plugins/clean-html/helpers/visitor/filters/allow-attributes.js +9 -2
  57. package/esm/plugins/clipboard/clipboard.js +29 -1
  58. package/esm/plugins/inline-popup/inline-popup.d.ts +8 -0
  59. package/esm/plugins/inline-popup/inline-popup.js +43 -2
  60. package/esm/plugins/limit/limit.js +7 -1
  61. package/esm/plugins/paste/paste.d.ts +1 -1
  62. package/esm/plugins/paste/paste.js +7 -5
  63. package/esm/plugins/resize-handler/resize-handler.js +13 -2
  64. package/esm/plugins/resizer/resizer.js +5 -1
  65. package/esm/plugins/source/source.js +9 -0
  66. package/package.json +1 -1
  67. package/types/modules/dialog/dialog.d.ts +5 -0
  68. package/types/plugins/inline-popup/inline-popup.d.ts +8 -0
  69. 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.22
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.22
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.22
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.22
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.22
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.22
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.22
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.22
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.22
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -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.22";
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
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
@@ -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);
@@ -42,6 +42,14 @@ 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;
45
53
  private snapRange;
46
54
  private onSelectionStart;
47
55
  private onSelectionEnd;
@@ -29,6 +29,12 @@ export class inlinePopup extends Plugin {
29
29
  constructor() {
30
30
  super(...arguments);
31
31
  this.type = null;
32
+ /**
33
+ * The user pressed a button inside the selection toolbar — after the
34
+ * command fires `closeAllPopups`, the toolbar should be shown again
35
+ * while the selection is still there. See #1238
36
+ */
37
+ this.__reopenSelectionPopup = false;
32
38
  this.snapRange = null;
33
39
  this.elmsList = keys(this.j.o.popup, false).filter(s => !this.isExcludedTarget(s));
34
40
  }
@@ -144,9 +150,36 @@ export class inlinePopup extends Plugin {
144
150
  this.previousTarget = undefined;
145
151
  }
146
152
  })
147
- .on([this.j.ew, this.j.ow], 'mouseup keyup', this.onSelectionEnd);
153
+ .on([this.j.ew, this.j.ow], 'mouseup keyup', this.onSelectionEnd)
154
+ .on([this.j.ew, this.j.ow], 'mousedown touchstart', this.__onDocumentMouseDown)
155
+ .on('closeAllPopups', this.__onCloseAllPopups);
148
156
  this.addListenersForElements();
149
157
  }
158
+ __onDocumentMouseDown(e) {
159
+ if (this.popup.isOpened &&
160
+ this.type === 'selection' &&
161
+ e.target &&
162
+ UIElement.closestElement(e.target, Popup)) {
163
+ this.__reopenSelectionPopup = true;
164
+ }
165
+ }
166
+ __onCloseAllPopups() {
167
+ if (!this.__reopenSelectionPopup) {
168
+ return;
169
+ }
170
+ this.__reopenSelectionPopup = false;
171
+ if (!this.j.o.toolbarInlineForSelection) {
172
+ return;
173
+ }
174
+ // a zero timeout would run synchronously — before the popup's own
175
+ // `closeAllPopups` handler closes it; defer to the next macrotask
176
+ this.j.async.setTimeout(() => {
177
+ const sel = this.j.s.sel;
178
+ if (sel && !sel.isCollapsed) {
179
+ this.showPopup(() => this.j.s.range.getBoundingClientRect(), 'selection');
180
+ }
181
+ }, 1);
182
+ }
150
183
  onSelectionStart() {
151
184
  this.snapRange = this.j.s.range.cloneRange();
152
185
  }
@@ -208,7 +241,9 @@ export class inlinePopup extends Plugin {
208
241
  beforeDestruct(jodit) {
209
242
  jodit.e
210
243
  .off('showPopup')
211
- .off([this.j.ew, this.j.ow], 'mouseup keyup', this.onSelectionEnd);
244
+ .off([this.j.ew, this.j.ow], 'mouseup keyup', this.onSelectionEnd)
245
+ .off([this.j.ew, this.j.ow], 'mousedown touchstart', this.__onDocumentMouseDown)
246
+ .off('closeAllPopups', this.__onCloseAllPopups);
212
247
  this.removeListenersForElements();
213
248
  }
214
249
  _eventsList() {
@@ -257,6 +292,12 @@ __decorate([
257
292
  __decorate([
258
293
  watch(':outsideClick')
259
294
  ], inlinePopup.prototype, "onOutsideClick", null);
295
+ __decorate([
296
+ autobind
297
+ ], inlinePopup.prototype, "__onDocumentMouseDown", null);
298
+ __decorate([
299
+ autobind
300
+ ], inlinePopup.prototype, "__onCloseAllPopups", null);
260
301
  __decorate([
261
302
  autobind
262
303
  ], inlinePopup.prototype, "onSelectionStart", null);
@@ -62,7 +62,13 @@ export class limit extends Plugin {
62
62
  jodit.e.fire('denyWords.limit limit.limit');
63
63
  return true;
64
64
  }
65
- const should = Boolean(limitChars && isGt(words.join('').length, limitChars, strict));
65
+ // with `countTextSpaces` enabled the limiter counts characters the
66
+ // same way as the `stat` plugin's counter — including spaces. See #1144
67
+ const charsCount = jodit.o.countTextSpaces
68
+ ? text.replace(INVISIBLE_SPACE_REG_EXP(), '').replace(/[\r\n]/g, '')
69
+ .length
70
+ : words.join('').length;
71
+ const should = Boolean(limitChars && isGt(charsCount, limitChars, strict));
66
72
  if (should) {
67
73
  jodit.e.fire('denyChars.limit limit.limit');
68
74
  }
@@ -45,7 +45,7 @@ export declare class paste extends Plugin {
45
45
  */
46
46
  private __insertByType;
47
47
  /**
48
- * Replace all \\n chars in plain text to br
48
+ * Escape plain text and replace all \\n chars with br
49
49
  */
50
50
  private onProcessPasteReplaceNl2Br;
51
51
  }
@@ -38,9 +38,7 @@ export class paste extends Plugin {
38
38
  jodit.e
39
39
  .on('paste.paste', this.onPaste)
40
40
  .on('pasteStack.paste', (item) => this.pasteStack.push(item));
41
- if (jodit.o.nl2brInPlainText) {
42
- this.j.e.on('processPaste.paste', this.onProcessPasteReplaceNl2Br);
43
- }
41
+ this.j.e.on('processPaste.paste', this.onProcessPasteReplaceNl2Br);
44
42
  }
45
43
  /** @override **/
46
44
  beforeDestruct(jodit) {
@@ -169,11 +167,15 @@ export class paste extends Plugin {
169
167
  pasteInsertHtml(e, this.j, html);
170
168
  }
171
169
  /**
172
- * Replace all \\n chars in plain text to br
170
+ * Escape plain text and replace all \\n chars with br
173
171
  */
174
172
  onProcessPasteReplaceNl2Br(ignore, text, type) {
175
173
  if (type === TEXT_PLAIN + ';' && !isHTML(text)) {
176
- return nl2br(text);
174
+ // the clipboard contains only plain text — escape special chars
175
+ // so a stray `<` is not parsed as an unclosed tag and does not
176
+ // swallow the rest of the string. See #1227
177
+ const escaped = htmlspecialchars(text);
178
+ return this.j.o.nl2brInPlainText ? nl2br(escaped) : escaped;
177
179
  }
178
180
  }
179
181
  }
@@ -16,6 +16,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
16
16
  import { autobind } from "../../core/decorators/index.js";
17
17
  import { Dom } from "../../core/dom/index.js";
18
18
  import { pluginSystem } from "../../core/global.js";
19
+ import { offset } from "../../core/helpers/index.js";
19
20
  import { Plugin } from "../../core/plugin/index.js";
20
21
  import { Icon } from "../../core/ui/index.js";
21
22
  import "./config.js";
@@ -82,11 +83,21 @@ export class resizeHandler extends Plugin {
82
83
  if (!this.isResized) {
83
84
  return;
84
85
  }
86
+ let { clientX, clientY } = e;
87
+ if (e.view === this.j.ew && this.j.ew !== this.j.ow) {
88
+ // the event was proxied from the editor's iframe — its client
89
+ // coordinates are relative to the iframe viewport, while the
90
+ // start point was captured on the host-document handle;
91
+ // shift them into the host coordinate space
92
+ const workplacePosition = offset(this.j.workplace, this.j, this.j.od, true);
93
+ clientX += workplacePosition.left;
94
+ clientY += workplacePosition.top;
95
+ }
85
96
  if (this.j.o.allowResizeY) {
86
- this.j.e.fire('setHeight', this.start.h + e.clientY - this.start.y);
97
+ this.j.e.fire('setHeight', this.start.h + clientY - this.start.y);
87
98
  }
88
99
  if (this.j.o.allowResizeX) {
89
- this.j.e.fire('setWidth', this.start.w + e.clientX - this.start.x);
100
+ this.j.e.fire('setWidth', this.start.w + clientX - this.start.x);
90
101
  }
91
102
  this.j.e.fire('resize');
92
103
  }
@@ -202,7 +202,11 @@ export class resizer extends Plugin {
202
202
  this.pointerX = e.clientX;
203
203
  this.pointerY = e.clientY;
204
204
  let diff_x, diff_y;
205
- if (this.j.options.iframe) {
205
+ if (this.j.options.iframe && e.view === this.j.ew) {
206
+ // the event was proxied from the editor's iframe — its client
207
+ // coordinates are relative to the iframe viewport, while
208
+ // `startX/startY` were captured on a host-document handle;
209
+ // shift them into the host coordinate space
206
210
  const workplacePosition = this.getWorkplacePosition();
207
211
  diff_x = e.clientX + workplacePosition.left - this.startX;
208
212
  diff_y = e.clientY + workplacePosition.top - this.startY;
@@ -134,6 +134,15 @@ export class source extends Plugin {
134
134
  }
135
135
  setFocusToMirror() {
136
136
  var _a;
137
+ const active = this.j.od.activeElement;
138
+ // do not steal focus from another editor or control — e.g. when the
139
+ // mode is switched programmatically (a Vue/React wrapper re-render)
140
+ // while the user is already typing elsewhere. See #1356
141
+ if (active &&
142
+ active !== this.j.od.body &&
143
+ !this.j.container.contains(active)) {
144
+ return;
145
+ }
137
146
  (_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.focus();
138
147
  }
139
148
  saveSelection() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jodit",
3
- "version": "4.12.21",
3
+ "version": "4.12.22",
4
4
  "description": "Jodit is an awesome and useful wysiwyg editor with filebrowser",
5
5
  "main": "esm/index.js",
6
6
  "types": "types/index.d.ts",
@@ -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;
@@ -42,6 +42,14 @@ 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;
45
53
  private snapRange;
46
54
  private onSelectionStart;
47
55
  private onSelectionEnd;
@@ -45,7 +45,7 @@ export declare class paste extends Plugin {
45
45
  */
46
46
  private __insertByType;
47
47
  /**
48
- * Replace all \\n chars in plain text to br
48
+ * Escape plain text and replace all \\n chars with br
49
49
  */
50
50
  private onProcessPasteReplaceNl2Br;
51
51
  }