jodit 4.12.23 → 4.12.25

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 (79) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/es2015/jodit.css +1 -1
  3. package/es2015/jodit.fat.min.js +8 -8
  4. package/es2015/jodit.js +145 -20
  5. package/es2015/jodit.min.js +7 -7
  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 +7 -7
  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 +8 -8
  18. package/es2021/jodit.js +144 -20
  19. package/es2021/jodit.min.js +7 -7
  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 +7 -7
  28. package/es2021.en/jodit.js +144 -20
  29. package/es2021.en/jodit.min.js +8 -8
  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 +191 -52
  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/config.d.ts +30 -0
  51. package/esm/core/constants.js +1 -1
  52. package/esm/modules/image-editor/image-editor.js +5 -0
  53. package/esm/modules/uploader/helpers/send-files.js +9 -0
  54. package/esm/plugins/backspace/backspace.js +7 -3
  55. package/esm/plugins/backspace/cases/index.d.ts +19 -3
  56. package/esm/plugins/backspace/cases/index.js +18 -13
  57. package/esm/plugins/backspace/config.d.ts +15 -0
  58. package/esm/plugins/clean-html/clean-html.d.ts +8 -0
  59. package/esm/plugins/clean-html/clean-html.js +16 -0
  60. package/esm/plugins/clean-html/config.d.ts +9 -0
  61. package/esm/plugins/clean-html/config.js +1 -0
  62. package/esm/plugins/font/config.js +19 -0
  63. package/esm/plugins/link/config.d.ts +6 -0
  64. package/esm/plugins/link/config.js +1 -0
  65. package/esm/plugins/link/link.js +8 -1
  66. package/esm/plugins/link/template.js +10 -1
  67. package/esm/plugins/select-cells/select-cells.d.ts +1 -1
  68. package/esm/plugins/select-cells/select-cells.js +29 -2
  69. package/esm/plugins/source/config.js +1 -1
  70. package/esm/types/uploader.d.ts +23 -0
  71. package/package.json +1 -1
  72. package/types/config.d.ts +30 -0
  73. package/types/plugins/backspace/cases/index.d.ts +19 -3
  74. package/types/plugins/backspace/config.d.ts +15 -0
  75. package/types/plugins/clean-html/clean-html.d.ts +8 -0
  76. package/types/plugins/clean-html/config.d.ts +9 -0
  77. package/types/plugins/link/config.d.ts +6 -0
  78. package/types/plugins/select-cells/select-cells.d.ts +1 -1
  79. package/types/types/uploader.d.ts +23 -0
@@ -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.23
4
+ * Version: v4.12.25
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.23
4
+ * Version: v4.12.25
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.23
4
+ * Version: v4.12.25
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.23
4
+ * Version: v4.12.25
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.23
4
+ * Version: v4.12.25
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.23
4
+ * Version: v4.12.25
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.23
4
+ * Version: v4.12.25
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.23
4
+ * Version: v4.12.25
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.23
4
+ * Version: v4.12.25
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
package/esm/config.d.ts CHANGED
@@ -956,6 +956,21 @@ interface Config {
956
956
  backspaceWord: string[];
957
957
  backspaceSentence: string[];
958
958
  };
959
+ /**
960
+ * Disable specific Backspace/Delete cleanup cases by their stable
961
+ * key, so the plugin no longer applies that particular behavior.
962
+ * Available keys: `remove-unbreakable`, `remove-not-editable`,
963
+ * `remove-char`, `table-cell`, `remove-empty-parent`,
964
+ * `remove-empty-neighbor`, `join-two-lists`, `join-neighbors`,
965
+ * `unwrap-first-list-item`.
966
+ *
967
+ * ```javascript
968
+ * Jodit.make('#editor', {
969
+ * delete: { disableCases: new Set(['remove-empty-parent']) }
970
+ * });
971
+ * ```
972
+ */
973
+ disableCases?: Set<string>;
959
974
  };
960
975
  }
961
976
  interface Config {
@@ -973,6 +988,15 @@ interface Config {
973
988
  * Remove empty elements
974
989
  */
975
990
  removeEmptyElements: boolean;
991
+ /**
992
+ * Return an empty string from `editor.value` (and the synced source
993
+ * element) when the editor holds only a single empty block — e.g.
994
+ * `<p><br></p>` left after the user deletes all the content.
995
+ * `contenteditable` keeps that caret container in the DOM, so by
996
+ * default the value getter returns it as-is; enable this to collapse
997
+ * it to `''` for form submission.
998
+ */
999
+ collapseEmptyValueToEmptyString: boolean;
976
1000
  /**
977
1001
  * Replace old tags to new eg. <i> to <em>, <b> to <strong>
978
1002
  */
@@ -1371,6 +1395,12 @@ interface Config {
1371
1395
  * Default value for the `Open in new tab` checkbox when inserting a new link.
1372
1396
  */
1373
1397
  openInNewTabCheckboxDefaultChecked: boolean;
1398
+ /**
1399
+ * Show an `aria-label` text input in the link dialog so an
1400
+ * accessible name can be set on the `<a>` (useful when several
1401
+ * links share the same visible text, e.g. "here"). Default: false.
1402
+ */
1403
+ ariaLabelInput: boolean;
1374
1404
  /**
1375
1405
  * Use an input text to ask the classname or a select or not ask
1376
1406
  */
@@ -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.23";
6
+ export const APP_VERSION = "4.12.25";
7
7
  // prettier-ignore
8
8
  export const ES = "es2020";
9
9
  export const IS_ES_MODERN = true;
@@ -439,12 +439,17 @@ let ImageEditor = ImageEditor_1 = class ImageEditor extends ViewComponent {
439
439
  self.j.alert('The name should not be empty');
440
440
  return false;
441
441
  }
442
+ self.j.e.fire('afterImageEditorSave', data, name);
442
443
  self.onSave(name, data, self.hide, (e) => {
443
444
  self.j.alert(e.message);
444
445
  });
445
446
  });
446
447
  break;
447
448
  case self.buttons.save:
449
+ // Notify listeners that a crop/resize was applied,
450
+ // passing the action box (action + box dimensions).
451
+ // See https://github.com/xdan/jodit/issues/820
452
+ self.j.e.fire('afterImageEditorSave', data);
448
453
  self.onSave(undefined, data, self.hide, (e) => {
449
454
  self.j.alert(e.message);
450
455
  });
@@ -17,6 +17,15 @@ export function sendFiles(uploader, files, handlerSuccess, handlerError, process
17
17
  if (!fileList.length) {
18
18
  return Promise.reject(error('Need files'));
19
19
  }
20
+ // Client-side validation hook — returning `false` aborts the upload.
21
+ // See https://github.com/xdan/jodit/issues/1329
22
+ if (isFunction(o.beforeUpload)) {
23
+ if (o.beforeUpload.call(uploader, fileList) === false) {
24
+ const err = error('Upload canceled');
25
+ (handlerError || o.defaultHandlerError).call(uploader, err);
26
+ return Promise.reject(err);
27
+ }
28
+ }
20
29
  const promises = [];
21
30
  if (o.insertImageAsBase64URI) {
22
31
  readImagesWithReader(fileList, o.imagesExtensions, promises, uploader, handlerSuccess, o.defaultHandlerSuccess);
@@ -11,7 +11,7 @@ import { Plugin } from "../../core/plugin/index.js";
11
11
  import { moveNodeInsideStart } from "../../core/selection/helpers/index.js";
12
12
  import "./config.js";
13
13
  import { checkNotCollapsed } from "./cases/check-not-collapsed.js";
14
- import { cases } from "./cases/index.js";
14
+ import { casesMap } from "./cases/index.js";
15
15
  export class backspace extends Plugin {
16
16
  afterInit(jodit) {
17
17
  jodit
@@ -71,11 +71,15 @@ export class backspace extends Plugin {
71
71
  return false;
72
72
  }
73
73
  moveNodeInsideStart(jodit, fakeNode, backspace);
74
- if (cases.some((func) => {
74
+ const disabled = jodit.o.delete.disableCases;
75
+ if (casesMap.some(([key, func]) => {
76
+ if (disabled && disabled.has(key)) {
77
+ return;
78
+ }
75
79
  if (isFunction(func) &&
76
80
  func(jodit, fakeNode, backspace, mode)) {
77
81
  if (!IS_PROD) {
78
- console.info('Remove case:', func.name);
82
+ console.info('Remove case:', key);
79
83
  }
80
84
  return true;
81
85
  }
@@ -3,9 +3,25 @@
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
- import { checkRemoveChar } from "./check-remove-char";
7
- import { checkRemoveContentNotEditable } from "./check-remove-content-not-editable";
6
+ /**
7
+ * @module plugins/backspace
8
+ */
9
+ import type { IJodit } from "../../../types/index";
10
+ import type { DeleteMode } from "../interface";
11
+ type CaseFn = (jodit: IJodit, fakeNode: any, backspace: boolean, mode: DeleteMode) => void | boolean;
12
+ /**
13
+ * Ordered delete/backspace cases with stable keys. The first one returning
14
+ * `true` wins. The keys are stable across minified builds (function names are
15
+ * mangled by terser) so they can be referenced by `delete.disableCases`.
16
+ * @private
17
+ */
18
+ export declare const casesMap: ReadonlyArray<readonly [
19
+ string,
20
+ CaseFn
21
+ ]>;
8
22
  /**
9
23
  * @private
24
+ * @deprecated Use `casesMap` to also get the stable case keys.
10
25
  */
11
- export declare const cases: (typeof checkRemoveContentNotEditable | typeof checkRemoveChar)[];
26
+ export declare const cases: CaseFn[];
27
+ export {};
@@ -3,9 +3,6 @@
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
- /**
7
- * @module plugins/backspace
8
- */
9
6
  import { checkJoinNeighbors } from "./check-join-neighbors.js";
10
7
  import { checkJoinTwoLists } from "./check-join-two-lists.js";
11
8
  import { checkRemoveChar } from "./check-remove-char.js";
@@ -16,16 +13,24 @@ import { checkRemoveUnbreakableElement } from "./check-remove-unbreakable-elemen
16
13
  import { checkTableCell } from "./check-table-cell.js";
17
14
  import { checkUnwrapFirstListItem } from "./check-unwrap-first-list-item.js";
18
15
  /**
16
+ * Ordered delete/backspace cases with stable keys. The first one returning
17
+ * `true` wins. The keys are stable across minified builds (function names are
18
+ * mangled by terser) so they can be referenced by `delete.disableCases`.
19
19
  * @private
20
20
  */
21
- export const cases = [
22
- checkRemoveUnbreakableElement,
23
- checkRemoveContentNotEditable,
24
- checkRemoveChar,
25
- checkTableCell,
26
- checkRemoveEmptyParent,
27
- checkRemoveEmptyNeighbor,
28
- checkJoinTwoLists,
29
- checkJoinNeighbors,
30
- checkUnwrapFirstListItem
21
+ export const casesMap = [
22
+ ['remove-unbreakable', checkRemoveUnbreakableElement],
23
+ ['remove-not-editable', checkRemoveContentNotEditable],
24
+ ['remove-char', checkRemoveChar],
25
+ ['table-cell', checkTableCell],
26
+ ['remove-empty-parent', checkRemoveEmptyParent],
27
+ ['remove-empty-neighbor', checkRemoveEmptyNeighbor],
28
+ ['join-two-lists', checkJoinTwoLists],
29
+ ['join-neighbors', checkJoinNeighbors],
30
+ ['unwrap-first-list-item', checkUnwrapFirstListItem]
31
31
  ];
32
+ /**
33
+ * @private
34
+ * @deprecated Use `casesMap` to also get the stable case keys.
35
+ */
36
+ export const cases = casesMap.map(([, fn]) => fn);
@@ -17,6 +17,21 @@ declare module 'jodit/config' {
17
17
  backspaceWord: string[];
18
18
  backspaceSentence: string[];
19
19
  };
20
+ /**
21
+ * Disable specific Backspace/Delete cleanup cases by their stable
22
+ * key, so the plugin no longer applies that particular behavior.
23
+ * Available keys: `remove-unbreakable`, `remove-not-editable`,
24
+ * `remove-char`, `table-cell`, `remove-empty-parent`,
25
+ * `remove-empty-neighbor`, `join-two-lists`, `join-neighbors`,
26
+ * `unwrap-first-list-item`.
27
+ *
28
+ * ```javascript
29
+ * Jodit.make('#editor', {
30
+ * delete: { disableCases: new Set(['remove-empty-parent']) }
31
+ * });
32
+ * ```
33
+ */
34
+ disableCases?: Set<string>;
20
35
  };
21
36
  }
22
37
  }
@@ -34,6 +34,14 @@ export declare class cleanHtml extends Plugin {
34
34
  protected onBeforeSetNativeEditorValue(data: {
35
35
  value: string;
36
36
  }): boolean;
37
+ /**
38
+ * Collapse a value that holds only a single empty block (e.g.
39
+ * `<p><br></p>` left after deleting all content) to an empty string —
40
+ * opt-in via `cleanHTML.collapseEmptyValueToEmptyString`. See #1149
41
+ */
42
+ protected onAfterGetValueFromEditor(data: {
43
+ value: string;
44
+ }): void;
37
45
  protected onSafeHTML(sandBox: HTMLElement): void;
38
46
  /** @override */
39
47
  protected beforeDestruct(): void;
@@ -100,6 +100,19 @@ export class cleanHtml extends Plugin {
100
100
  Dom.safeRemove(iframe);
101
101
  return false;
102
102
  }
103
+ /**
104
+ * Collapse a value that holds only a single empty block (e.g.
105
+ * `<p><br></p>` left after deleting all content) to an empty string —
106
+ * opt-in via `cleanHTML.collapseEmptyValueToEmptyString`. See #1149
107
+ */
108
+ onAfterGetValueFromEditor(data) {
109
+ if (!this.j.o.cleanHTML.collapseEmptyValueToEmptyString) {
110
+ return;
111
+ }
112
+ if (/^<([a-z][a-z0-9]*)\b[^>]*>(?:<br\/?>)?<\/\1>$/i.test(data.value.trim())) {
113
+ data.value = '';
114
+ }
115
+ }
103
116
  onSafeHTML(sandBox) {
104
117
  const sanitizer = this.j.o.cleanHTML.sanitizer;
105
118
  if (sanitizer) {
@@ -124,6 +137,9 @@ __decorate([
124
137
  __decorate([
125
138
  watch(':beforeSetNativeEditorValue')
126
139
  ], cleanHtml.prototype, "onBeforeSetNativeEditorValue", null);
140
+ __decorate([
141
+ watch(':afterGetValueFromEditor')
142
+ ], cleanHtml.prototype, "onAfterGetValueFromEditor", null);
127
143
  __decorate([
128
144
  watch(':safeHTML')
129
145
  ], cleanHtml.prototype, "onSafeHTML", null);
@@ -23,6 +23,15 @@ declare module 'jodit/config' {
23
23
  * Remove empty elements
24
24
  */
25
25
  removeEmptyElements: boolean;
26
+ /**
27
+ * Return an empty string from `editor.value` (and the synced source
28
+ * element) when the editor holds only a single empty block — e.g.
29
+ * `<p><br></p>` left after the user deletes all the content.
30
+ * `contenteditable` keeps that caret container in the DOM, so by
31
+ * default the value getter returns it as-is; enable this to collapse
32
+ * it to `''` for form submission.
33
+ */
34
+ collapseEmptyValueToEmptyString: boolean;
26
35
  /**
27
36
  * Replace old tags to new eg. <i> to <em>, <b> to <strong>
28
37
  */
@@ -10,6 +10,7 @@ Config.prototype.cleanHTML = {
10
10
  timeout: 300,
11
11
  removeEmptyElements: true,
12
12
  fillEmptyParagraph: true,
13
+ collapseEmptyValueToEmptyString: false,
13
14
  replaceNBSP: true,
14
15
  replaceOldTags: {
15
16
  i: 'em',
@@ -117,5 +117,24 @@ Config.prototype.controls.font = {
117
117
  .replace(/[^a-z0-9-]+/g, ',');
118
118
  }
119
119
  },
120
+ // When no font is explicitly set, the computed font-family equals the
121
+ // editor's own default (e.g. `-apple-system, …`). Return '' so the
122
+ // button shows the `Default` list entry instead of that raw stack.
123
+ // See #1370
124
+ value: (editor) => {
125
+ const current = editor.s.current();
126
+ if (!current) {
127
+ return;
128
+ }
129
+ const box = Dom.closest(current, Dom.isElement, editor.editor);
130
+ if (!box) {
131
+ return;
132
+ }
133
+ const value = css(box, 'font-family').toString();
134
+ if (value === css(editor.editor, 'font-family').toString()) {
135
+ return '';
136
+ }
137
+ return value;
138
+ },
120
139
  tooltip: 'Font family'
121
140
  };
@@ -39,6 +39,12 @@ declare module 'jodit/config' {
39
39
  * Default value for the `Open in new tab` checkbox when inserting a new link.
40
40
  */
41
41
  openInNewTabCheckboxDefaultChecked: boolean;
42
+ /**
43
+ * Show an `aria-label` text input in the link dialog so an
44
+ * accessible name can be set on the `<a>` (useful when several
45
+ * links share the same visible text, e.g. "here"). Default: false.
46
+ */
47
+ ariaLabelInput: boolean;
42
48
  /**
43
49
  * Use an input text to ask the classname or a select or not ask
44
50
  */
@@ -17,6 +17,7 @@ Config.prototype.link = {
17
17
  noFollowCheckbox: true,
18
18
  openInNewTabCheckbox: true,
19
19
  openInNewTabCheckboxDefaultChecked: false,
20
+ ariaLabelInput: false,
20
21
  modeClassName: 'input',
21
22
  selectMultipleClassName: true,
22
23
  preventReadOnlyNavigation: true,
@@ -121,7 +121,7 @@ export class link extends Plugin {
121
121
  const currentElement = current;
122
122
  const isImageContent = Dom.isImage(currentElement);
123
123
  let { content_input } = elements;
124
- const { className_input } = elements, { className_select } = elements;
124
+ const { className_input } = elements, { className_select } = elements, { aria_label_input } = elements;
125
125
  if (!content_input) {
126
126
  content_input = jodit.c.element('input', {
127
127
  type: 'hidden',
@@ -147,6 +147,9 @@ export class link extends Plugin {
147
147
  if (!isImageContent && current) {
148
148
  content_input.value = getSelectionText();
149
149
  }
150
+ if (aria_label_input) {
151
+ aria_label_input.value = link ? attr(link, 'aria-label') || '' : '';
152
+ }
150
153
  if (link) {
151
154
  url_input.value = attr(link, 'href') || '';
152
155
  if (modeClassName) {
@@ -237,6 +240,10 @@ export class link extends Plugin {
237
240
  }
238
241
  attr(a, 'rel', relParts.length ? relParts.join(' ') : null);
239
242
  }
243
+ if (aria_label_input) {
244
+ const ariaLabel = aria_label_input.value.trim();
245
+ attr(a, 'aria-label', ariaLabel || null);
246
+ }
240
247
  jodit.e.fire('applyLink', jodit, a, form);
241
248
  });
242
249
  jodit.synchronizeValues();
@@ -6,7 +6,7 @@
6
6
  import { UIButton } from "../../core/ui/button/index.js";
7
7
  import { UIBlock, UICheckbox, UIForm, UIInput, UISelect } from "../../core/ui/form/index.js";
8
8
  export const formTemplate = (editor) => {
9
- const { openInNewTabCheckbox, noFollowCheckbox, modeClassName, selectSizeClassName, selectMultipleClassName, selectOptionsClassName } = editor.o.link;
9
+ const { openInNewTabCheckbox, noFollowCheckbox, ariaLabelInput, modeClassName, selectSizeClassName, selectMultipleClassName, selectOptionsClassName } = editor.o.link;
10
10
  return new UIForm(editor, [
11
11
  new UIBlock(editor, [
12
12
  new UIInput(editor, {
@@ -18,6 +18,15 @@ export const formTemplate = (editor) => {
18
18
  required: true
19
19
  })
20
20
  ]),
21
+ ariaLabelInput
22
+ ? new UIBlock(editor, [
23
+ new UIInput(editor, {
24
+ name: 'ariaLabel',
25
+ ref: 'aria_label_input',
26
+ label: 'Aria label'
27
+ })
28
+ ])
29
+ : null,
21
30
  new UIBlock(editor, [
22
31
  new UIInput(editor, {
23
32
  name: 'content',
@@ -29,7 +29,7 @@ export declare class selectCells extends Plugin {
29
29
  /**
30
30
  * Mouse click inside the table
31
31
  */
32
- protected onStartSelection(cell: HTMLTableCellElement): void | false;
32
+ protected onStartSelection(cell: HTMLTableCellElement, event?: MouseEvent): void | false;
33
33
  protected onOutsideClick(): void;
34
34
  protected onChange(): void;
35
35
  /**
@@ -79,15 +79,42 @@ export class selectCells extends Plugin {
79
79
  /**
80
80
  * Mouse click inside the table
81
81
  */
82
- onStartSelection(cell) {
82
+ onStartSelection(cell, event) {
83
+ var _a;
83
84
  if (this.j.o.readonly) {
84
85
  return;
85
86
  }
87
+ const table = Dom.closest(cell, 'table', this.j.editor);
88
+ // Ctrl/Cmd + click toggles a single cell into the existing selection
89
+ // instead of resetting it — non-contiguous multi-cell selection.
90
+ // See https://github.com/xdan/jodit/issues/1163
91
+ if (((event === null || event === void 0 ? void 0 : event.ctrlKey) || (event === null || event === void 0 ? void 0 : event.metaKey)) &&
92
+ cell !== this.j.editor &&
93
+ table &&
94
+ Dom.isCell(cell)) {
95
+ if (this.__tableModule.getAllSelectedCells().includes(cell)) {
96
+ this.__tableModule.removeSelection(cell);
97
+ }
98
+ else {
99
+ if (!cell.firstChild) {
100
+ cell.appendChild(this.j.createInside.element('br'));
101
+ }
102
+ this.__tableModule.addSelection(cell);
103
+ }
104
+ this.__selectedCell = cell;
105
+ (_a = this.j.s.sel) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
106
+ if (this.__tableModule.getAllSelectedCells().length) {
107
+ this.j.e.fire('showPopup', table, () => position(cell, this.j), 'cells');
108
+ }
109
+ else {
110
+ this.j.e.fire('hidePopup', 'cells');
111
+ }
112
+ return false;
113
+ }
86
114
  this.unselectCells();
87
115
  if (cell === this.j.editor) {
88
116
  return;
89
117
  }
90
- const table = Dom.closest(cell, 'table', this.j.editor);
91
118
  if (!cell || !table) {
92
119
  return;
93
120
  }
@@ -32,7 +32,7 @@ Config.prototype.sourceEditorNativeOptions = {
32
32
  highlightActiveLine: true
33
33
  };
34
34
  Config.prototype.sourceEditorCDNUrlsJS = [
35
- 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.2/ace.js'
35
+ 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.43.3/ace.js'
36
36
  ];
37
37
  Config.prototype.beautifyHTMLCDNUrlsJS = [
38
38
  'https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.14.4/beautify.min.js',
@@ -80,6 +80,29 @@ export interface IUploaderOptions<T> {
80
80
  getDisplayName(this: T, baseurl: string, filename: string): string;
81
81
  pathVariableName: string;
82
82
  withCredentials: boolean;
83
+ /**
84
+ * Called with the list of files right before they are uploaded (or read as
85
+ * base64). Return `false` to abort the whole upload — useful for client
86
+ * side validation (size, type, count). Throwing an Error also aborts and
87
+ * routes the message through the uploader error handler.
88
+ *
89
+ * ```javascript
90
+ * Jodit.make('#editor', {
91
+ * uploader: {
92
+ * url: '...',
93
+ * beforeUpload(files) {
94
+ * for (const file of files) {
95
+ * if (file.size > 2 * 1024 * 1024) {
96
+ * this.jodit.message.error('Max 2MB');
97
+ * return false;
98
+ * }
99
+ * }
100
+ * }
101
+ * }
102
+ * });
103
+ * ```
104
+ */
105
+ beforeUpload?: (this: T, files: File[]) => boolean | void;
83
106
  prepareData: (this: T, formData: FormData) => any;
84
107
  buildData?: (this: T, formData: any) => BuildDataResult;
85
108
  queryBuild?: (obj: string | IDictionary<string | object> | FormData, prefix?: string) => string | FormData;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jodit",
3
- "version": "4.12.23",
3
+ "version": "4.12.25",
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",
package/types/config.d.ts CHANGED
@@ -956,6 +956,21 @@ interface Config {
956
956
  backspaceWord: string[];
957
957
  backspaceSentence: string[];
958
958
  };
959
+ /**
960
+ * Disable specific Backspace/Delete cleanup cases by their stable
961
+ * key, so the plugin no longer applies that particular behavior.
962
+ * Available keys: `remove-unbreakable`, `remove-not-editable`,
963
+ * `remove-char`, `table-cell`, `remove-empty-parent`,
964
+ * `remove-empty-neighbor`, `join-two-lists`, `join-neighbors`,
965
+ * `unwrap-first-list-item`.
966
+ *
967
+ * ```javascript
968
+ * Jodit.make('#editor', {
969
+ * delete: { disableCases: new Set(['remove-empty-parent']) }
970
+ * });
971
+ * ```
972
+ */
973
+ disableCases?: Set<string>;
959
974
  };
960
975
  }
961
976
  interface Config {
@@ -973,6 +988,15 @@ interface Config {
973
988
  * Remove empty elements
974
989
  */
975
990
  removeEmptyElements: boolean;
991
+ /**
992
+ * Return an empty string from `editor.value` (and the synced source
993
+ * element) when the editor holds only a single empty block — e.g.
994
+ * `<p><br></p>` left after the user deletes all the content.
995
+ * `contenteditable` keeps that caret container in the DOM, so by
996
+ * default the value getter returns it as-is; enable this to collapse
997
+ * it to `''` for form submission.
998
+ */
999
+ collapseEmptyValueToEmptyString: boolean;
976
1000
  /**
977
1001
  * Replace old tags to new eg. <i> to <em>, <b> to <strong>
978
1002
  */
@@ -1371,6 +1395,12 @@ interface Config {
1371
1395
  * Default value for the `Open in new tab` checkbox when inserting a new link.
1372
1396
  */
1373
1397
  openInNewTabCheckboxDefaultChecked: boolean;
1398
+ /**
1399
+ * Show an `aria-label` text input in the link dialog so an
1400
+ * accessible name can be set on the `<a>` (useful when several
1401
+ * links share the same visible text, e.g. "here"). Default: false.
1402
+ */
1403
+ ariaLabelInput: boolean;
1374
1404
  /**
1375
1405
  * Use an input text to ask the classname or a select or not ask
1376
1406
  */