jodit 4.15.17 → 4.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/es2015/jodit.css +1 -1
  3. package/es2015/jodit.fat.min.js +12 -11
  4. package/es2015/jodit.js +198 -80
  5. package/es2015/jodit.min.js +12 -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 -10
  13. package/es2018/jodit.min.js +12 -11
  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 +15 -14
  18. package/es2021/jodit.js +196 -80
  19. package/es2021/jodit.min.js +17 -16
  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 +15 -14
  28. package/es2021.en/jodit.js +196 -80
  29. package/es2021.en/jodit.min.js +15 -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 +258 -116
  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 +23 -0
  51. package/esm/config.js +21 -0
  52. package/esm/core/constants.js +1 -1
  53. package/esm/core/helpers/utils/align.d.ts +8 -3
  54. package/esm/core/helpers/utils/align.js +40 -2
  55. package/esm/core/selection/selection.js +5 -0
  56. package/esm/core/ui/button/button/button.js +10 -4
  57. package/esm/core/ui/form/inputs/file/file.js +1 -0
  58. package/esm/core/ui/group/group.d.ts +4 -0
  59. package/esm/core/ui/group/group.js +5 -1
  60. package/esm/core/ui/group/list.js +2 -2
  61. package/esm/modules/dialog/dialog.js +7 -0
  62. package/esm/modules/toolbar/button/content.d.ts +7 -0
  63. package/esm/modules/toolbar/button/content.js +9 -1
  64. package/esm/plugins/image-properties/readers/align.d.ts +5 -1
  65. package/esm/plugins/image-properties/readers/align.js +4 -18
  66. package/esm/plugins/image-properties/readers/index.js +1 -1
  67. package/esm/plugins/image-properties/writers/index.js +1 -1
  68. package/esm/plugins/inline-popup/config/items/img.js +17 -2
  69. package/esm/plugins/source/editor/engines/ace.js +13 -6
  70. package/esm/plugins/source/source.js +20 -1
  71. package/esm/types/types.d.ts +4 -0
  72. package/package.json +1 -1
  73. package/types/config.d.ts +23 -0
  74. package/types/core/helpers/utils/align.d.ts +8 -3
  75. package/types/core/ui/group/group.d.ts +4 -0
  76. package/types/modules/toolbar/button/content.d.ts +7 -0
  77. package/types/plugins/image-properties/readers/align.d.ts +5 -1
  78. package/types/types/types.d.ts +4 -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.15.17
4
+ * Version: v4.16.0
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.15.17
4
+ * Version: v4.16.0
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.15.17
4
+ * Version: v4.16.0
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.15.17
4
+ * Version: v4.16.0
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.15.17
4
+ * Version: v4.16.0
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.15.17
4
+ * Version: v4.16.0
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.15.17
4
+ * Version: v4.16.0
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.15.17
4
+ * Version: v4.16.0
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.15.17
4
+ * Version: v4.16.0
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
package/esm/config.d.ts CHANGED
@@ -23,6 +23,8 @@ import type { Attributes } from "./types/index";
23
23
  import type { Controls } from "./types/index";
24
24
  import type { IAsyncStorageOptions } from "./types/index";
25
25
  import type { IExtraPlugin } from "./types/index";
26
+ import type { ImageAlignClasses } from "./types/index";
27
+ import type { ImageHAlign } from "./types/index";
26
28
  import type { IUIButtonState } from "./types/index";
27
29
  import type { IViewOptions } from "./types/index";
28
30
  import type { NodeFunction } from "./types/index";
@@ -631,6 +633,27 @@ declare class Config implements IViewOptions {
631
633
  * Default width (in pixels) applied to images inserted into the editor
632
634
  */
633
635
  imageDefaultWidth: number;
636
+ /**
637
+ * Alignment given to images inserted into the editor. `'normal'` leaves
638
+ * them unaligned
639
+ */
640
+ imageDefaultAlign: ImageHAlign;
641
+ /**
642
+ * Class names to align images with, instead of an inline `float` or
643
+ * `margin`. Aligning an image removes every class listed here and adds the
644
+ * chosen alignment's, which can be several separated by spaces.
645
+ *
646
+ * ```javascript
647
+ * Jodit.make('#editor', {
648
+ * imageAlignClasses: {
649
+ * left: 'align-left',
650
+ * right: 'align-right',
651
+ * center: 'align-center'
652
+ * }
653
+ * });
654
+ * ```
655
+ */
656
+ imageAlignClasses: Nullable<ImageAlignClasses>;
634
657
  /**
635
658
  * Do not display these buttons that are on the list
636
659
  *
package/esm/config.js CHANGED
@@ -740,6 +740,27 @@ class Config {
740
740
  * Default width (in pixels) applied to images inserted into the editor
741
741
  */
742
742
  this.imageDefaultWidth = 300;
743
+ /**
744
+ * Alignment given to images inserted into the editor. `'normal'` leaves
745
+ * them unaligned
746
+ */
747
+ this.imageDefaultAlign = 'normal';
748
+ /**
749
+ * Class names to align images with, instead of an inline `float` or
750
+ * `margin`. Aligning an image removes every class listed here and adds the
751
+ * chosen alignment's, which can be several separated by spaces.
752
+ *
753
+ * ```javascript
754
+ * Jodit.make('#editor', {
755
+ * imageAlignClasses: {
756
+ * left: 'align-left',
757
+ * right: 'align-right',
758
+ * center: 'align-center'
759
+ * }
760
+ * });
761
+ * ```
762
+ */
763
+ this.imageAlignClasses = null;
743
764
  /**
744
765
  * Do not display these buttons that are on the list
745
766
  *
@@ -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.15.17";
6
+ export const APP_VERSION = "4.16.0";
7
7
  // prettier-ignore
8
8
  export const ES = "es2020";
9
9
  export const IS_ES_MODERN = true;
@@ -6,11 +6,16 @@
6
6
  /**
7
7
  * @module helpers/utils
8
8
  */
9
- import type { ImageHAlign } from "../../../types/index";
9
+ import type { ImageAlignClasses, ImageHAlign, Nullable } from "../../../types/index";
10
10
  /**
11
- * Align image
11
+ * Align image, with `classes` (see `imageAlignClasses`) instead of inline
12
+ * styles when they are given
12
13
  */
13
- export declare function hAlignElement(image: HTMLElement, align: ImageHAlign): void;
14
+ export declare function hAlignElement(image: HTMLElement, align: ImageHAlign, classes?: Nullable<ImageAlignClasses>): void;
15
+ /**
16
+ * The alignment `hAlignElement` has set on an element
17
+ */
18
+ export declare function getHAlign(elm: HTMLElement, classes?: Nullable<ImageAlignClasses>): ImageHAlign;
14
19
  /**
15
20
  * Remove text-align style for all selected children
16
21
  */
@@ -6,10 +6,24 @@
6
6
  import { Dom } from "../../dom/index.js";
7
7
  import { attr } from "./attr.js";
8
8
  import { clearCenterAlign, css, cssInline } from "./css.js";
9
+ const classNames = (value) => (value !== null && value !== void 0 ? value : '').split(/\s+/).filter(Boolean);
9
10
  /**
10
- * Align image
11
+ * Align image, with `classes` (see `imageAlignClasses`) instead of inline
12
+ * styles when they are given
11
13
  */
12
- export function hAlignElement(image, align) {
14
+ export function hAlignElement(image, align, classes) {
15
+ if (classes) {
16
+ hAlignElement(image, 'normal');
17
+ Object.values(classes).forEach(value => image.classList.remove(...classNames(value)));
18
+ image.classList.add(...classNames(classes[align || 'normal']));
19
+ ['class', 'style'].forEach(name => {
20
+ var _a;
21
+ if (!((_a = attr(image, name)) === null || _a === void 0 ? void 0 : _a.trim())) {
22
+ attr(image, name, null);
23
+ }
24
+ });
25
+ return;
26
+ }
13
27
  if (align && align !== 'normal') {
14
28
  if (align !== 'center') {
15
29
  css(image, 'float', align);
@@ -32,6 +46,30 @@ export function hAlignElement(image, align) {
32
46
  clearCenterAlign(image);
33
47
  }
34
48
  }
49
+ /**
50
+ * The alignment `hAlignElement` has set on an element
51
+ */
52
+ export function getHAlign(elm, classes) {
53
+ if (classes) {
54
+ for (const align of ['left', 'right', 'center', 'normal']) {
55
+ const names = classNames(classes[align]);
56
+ if (names.length &&
57
+ names.every(name => elm.classList.contains(name))) {
58
+ return align;
59
+ }
60
+ }
61
+ }
62
+ const float = cssInline(elm, 'float').toLowerCase();
63
+ if (float === 'left' || float === 'right') {
64
+ return float;
65
+ }
66
+ if (css(elm, 'display') === 'block' &&
67
+ cssInline(elm, 'margin-left') === 'auto' &&
68
+ cssInline(elm, 'margin-right') === 'auto') {
69
+ return 'center';
70
+ }
71
+ return 'normal';
72
+ }
35
73
  /**
36
74
  * Remove text-align style for all selected children
37
75
  */
@@ -19,6 +19,7 @@ import { autobind } from "../decorators/index.js";
19
19
  import { Dom } from "../dom/dom.js";
20
20
  import { $$, attr, css, error, getScrollParent, scrollIntoViewIfNeeded, size, toArray } from "../helpers/index.js";
21
21
  import { isFunction, isMarker, isString } from "../helpers/checker/index.js";
22
+ import { hAlignElement } from "../helpers/utils/align.js";
22
23
  import { assert } from "../helpers/utils/assert.js";
23
24
  import { moveTheNodeAlongTheEdgeOutward } from "./helpers/move-the-node-along-the-edge-outward.js";
24
25
  import "./interface.js";
@@ -661,6 +662,10 @@ export class Selection {
661
662
  if (styles && typeof styles === 'object') {
662
663
  css(image, styles);
663
664
  }
665
+ const { imageDefaultAlign, imageAlignClasses } = this.j.o;
666
+ if (imageDefaultAlign && imageDefaultAlign !== 'normal') {
667
+ hAlignElement(image, imageDefaultAlign, imageAlignClasses);
668
+ }
664
669
  const onload = () => {
665
670
  if (image.naturalHeight < image.offsetHeight ||
666
671
  image.naturalWidth < image.offsetWidth) {
@@ -112,6 +112,7 @@ let UIButton = class UIButton extends UIElement {
112
112
  this.name = this.state.name;
113
113
  attr(this.container, 'data-ref', this.state.name);
114
114
  attr(this.container, 'ref', this.state.name);
115
+ this.updateAriaLabel();
115
116
  }
116
117
  onChangeTooltip() {
117
118
  const i8nTooltip = this.state.tooltip
@@ -125,11 +126,16 @@ let UIButton = class UIButton extends UIElement {
125
126
  }
126
127
  updateAriaLabel() {
127
128
  const hasText = this.state.text.trim().length > 0;
128
- const i8nTooltip = this.state.tooltip
129
+ // An icon-only button without a tooltip would have no accessible
130
+ // name at all — fall back to its raw name (not translated: control
131
+ // names are identifiers, not i18n keys).
132
+ const label = this.state.tooltip
129
133
  ? this.jodit.i18n(this.state.tooltip)
130
- : null;
131
- attr(this.container, 'aria-label', i8nTooltip);
132
- attr(this.button, 'aria-label', !hasText ? i8nTooltip : null);
134
+ : !hasText && this.state.name
135
+ ? this.state.name
136
+ : null;
137
+ attr(this.container, 'aria-label', label);
138
+ attr(this.button, 'aria-label', !hasText ? label : null);
133
139
  }
134
140
  onChangeTabIndex() {
135
141
  attr(this.container, 'tabindex', this.state.tabIndex);
@@ -43,6 +43,7 @@ let UIFileInput = class UIFileInput extends UIInput {
43
43
  return this.j.create.fromHTML(`<input
44
44
  type="file"
45
45
  accept="${options.onlyImages ? 'image/*' : '*'}"
46
+ aria-label="${this.j.i18n(options.tooltip || 'Upload')}"
46
47
  tabindex="-1"
47
48
  dir="auto"
48
49
  multiple=""
@@ -13,6 +13,10 @@ import { UIElement } from "../element";
13
13
  export declare class UIGroup<T extends IViewBased = IViewBased> extends UIElement<T> implements IUIGroup {
14
14
  readonly options?: IDictionary | undefined;
15
15
  className(): string;
16
+ /**
17
+ * A generic container: `group` allows any children. Toolbar button groups
18
+ * are lists of `listitem`s and get `role="list"` explicitly from `UIList`.
19
+ */
16
20
  getRole(): string;
17
21
  /**
18
22
  * Synchronize mods to all children
@@ -23,9 +23,13 @@ let UIGroup = UIGroup_1 = class UIGroup extends UIElement {
23
23
  className() {
24
24
  return 'UIGroup';
25
25
  }
26
+ /**
27
+ * A generic container: `group` allows any children. Toolbar button groups
28
+ * are lists of `listitem`s and get `role="list"` explicitly from `UIList`.
29
+ */
26
30
  getRole() {
27
31
  var _a;
28
- return ((_a = this.options) === null || _a === void 0 ? void 0 : _a.role) || 'list';
32
+ return ((_a = this.options) === null || _a === void 0 ? void 0 : _a.role) || 'group';
29
33
  }
30
34
  /**
31
35
  * All group children
@@ -43,8 +43,8 @@ let UIList = class UIList extends UIGroup {
43
43
  /**
44
44
  * Make new group and append it in list of elements
45
45
  */
46
- makeGroup(role) {
47
- return new UIGroup(this.jodit, [], role ? { role } : undefined);
46
+ makeGroup(role = 'list') {
47
+ return new UIGroup(this.jodit, [], { role });
48
48
  }
49
49
  /**
50
50
  * All buttons from list
@@ -21,6 +21,7 @@ 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
23
  import { hasContainer, isArray, isBoolean, isFunction, isNumber, isString, isVoid } from "../../core/helpers/checker/index.js";
24
+ import { stripTags } from "../../core/helpers/html/strip-tags.js";
24
25
  import { attr, ConfigProto, css } from "../../core/helpers/utils/index.js";
25
26
  import { assert } from "../../core/helpers/utils/assert.js";
26
27
  import { Icon } from "../../core/ui/index.js";
@@ -292,6 +293,12 @@ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar {
292
293
  */
293
294
  setHeader(content) {
294
295
  this.setElements(this.dialogbox_header, content);
296
+ // `role="dialog"` needs an accessible name; a text title is the
297
+ // natural one (element titles are left to the caller).
298
+ if (isString(content)) {
299
+ const name = stripTags(content, this.od).trim();
300
+ attr(this.container, 'aria-label', name || null);
301
+ }
295
302
  return this;
296
303
  }
297
304
  /**
@@ -13,6 +13,13 @@ export declare class ToolbarContent<T extends IViewBased = IViewBased> extends U
13
13
  readonly target: Nullable<HTMLElement>;
14
14
  /** @override */
15
15
  className(): string;
16
+ /** A toolbar item like `ToolbarButton`: the parent group is a `list`. */
17
+ getRole(): string;
18
+ /**
19
+ * Arbitrary content is not a toggle button — `aria-pressed` is not
20
+ * allowed on a `listitem`.
21
+ */
22
+ protected onChangeActivated(): void;
16
23
  /** @override */
17
24
  update(): void;
18
25
  /**
@@ -28,6 +28,15 @@ let ToolbarContent = class ToolbarContent extends UIButton {
28
28
  className() {
29
29
  return 'ToolbarContent';
30
30
  }
31
+ /** A toolbar item like `ToolbarButton`: the parent group is a `list`. */
32
+ getRole() {
33
+ return 'listitem';
34
+ }
35
+ /**
36
+ * Arbitrary content is not a toggle button — `aria-pressed` is not
37
+ * allowed on a `listitem`.
38
+ */
39
+ onChangeActivated() { }
31
40
  /** @override */
32
41
  update() {
33
42
  var _a, _b, _c;
@@ -73,7 +82,6 @@ let ToolbarContent = class ToolbarContent extends UIButton {
73
82
  this.control = control;
74
83
  this.target = target;
75
84
  this.container.classList.add(`${this.componentName}_${this.clearName(control.name)}`);
76
- attr(this.container, 'role', 'content');
77
85
  }
78
86
  };
79
87
  ToolbarContent = __decorate([
@@ -3,8 +3,12 @@
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/image-properties
8
+ */
9
+ import type { ImageAlignClasses, Nullable } from "../../../types/index";
6
10
  import type { EditValues } from "../interface";
7
11
  /**
8
12
  * @private
9
13
  */
10
- export declare function readAlign(image: HTMLImageElement, values: EditValues): void;
14
+ export declare function readAlign(image: HTMLImageElement, values: EditValues, classes?: Nullable<ImageAlignClasses>): void;
@@ -3,25 +3,11 @@
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 { css, cssInline } from "../../../core/helpers/utils/css.js";
6
+ import { getHAlign } from "../../../core/helpers/utils/align.js";
7
7
  /**
8
8
  * @private
9
9
  */
10
- export function readAlign(image, values) {
11
- // Align
12
- if (cssInline(image, 'float') &&
13
- ['left', 'right'].indexOf(cssInline(image, 'float').toLowerCase()) !==
14
- -1) {
15
- values.align = css(image, 'float');
16
- }
17
- else {
18
- if (css(image, 'display') === 'block' &&
19
- cssInline(image, 'margin-left') === 'auto' &&
20
- cssInline(image, 'margin-right') === 'auto') {
21
- values.align = 'center';
22
- }
23
- else {
24
- values.align = '';
25
- }
26
- }
10
+ export function readAlign(image, values, classes) {
11
+ const align = getHAlign(image, classes);
12
+ values.align = align === 'normal' ? '' : align;
27
13
  }
@@ -15,7 +15,7 @@ import { readSizes } from "./size.js";
15
15
  */
16
16
  export async function readValuesFromImage(j, state) {
17
17
  const { sourceImage: image, values } = state;
18
- readAlign(image, values);
18
+ readAlign(image, values, j.o.imageAlignClasses);
19
19
  // Border radius
20
20
  values.borderRadius =
21
21
  parseInt(cssInline(image, 'border-radius') || '0', 10) || 0;
@@ -49,6 +49,6 @@ export function applyValuesToImage(j, state, image) {
49
49
  attr(image, 'id', id || null);
50
50
  }
51
51
  if (opt.image.editAlign) {
52
- hAlignElement(image, align);
52
+ hAlignElement(image, align, j.o.imageAlignClasses);
53
53
  }
54
54
  }
@@ -5,12 +5,27 @@
5
5
  */
6
6
  import { Dom } from "../../../../core/dom/index.js";
7
7
  import { isString } from "../../../../core/helpers/checker/is-string.js";
8
- import { hAlignElement } from "../../../../core/helpers/utils/align.js";
8
+ import { getHAlign, hAlignElement } from "../../../../core/helpers/utils/align.js";
9
9
  import { css } from "../../../../core/helpers/utils/css.js";
10
+ // Images align with `imageAlignClasses`; the wrappers of iframes and media
11
+ // keep inline styles
12
+ const alignClasses = (editor, elm) => Dom.isTag(elm, 'img') ? editor.o.imageAlignClasses : null;
13
+ const currentAlign = (editor, elm) => (elm ? getHAlign(elm, alignClasses(editor, elm)) : 'normal');
10
14
  export const align = {
11
15
  name: 'left',
12
16
  childTemplate: (_, __, value) => value,
13
17
  list: ['Left', 'Right', 'Center', 'Normal'],
18
+ update: (editor, button) => {
19
+ const current = currentAlign(editor, button.target);
20
+ button.state.icon.name = current === 'normal' ? 'left' : current;
21
+ },
22
+ isActive: (editor, button) => currentAlign(editor, button.target) !== 'normal',
23
+ isChildActive: (editor, button) => {
24
+ var _a;
25
+ const [value] = (_a = button.control.args) !== null && _a !== void 0 ? _a : [];
26
+ return (isString(value) &&
27
+ currentAlign(editor, button.target) === value.toLowerCase());
28
+ },
14
29
  exec: (editor, elm, { control }) => {
15
30
  if (!Dom.isTag(elm, new Set(['img', 'jodit', 'jodit-media']))) {
16
31
  return;
@@ -21,7 +36,7 @@ export const align = {
21
36
  if (!command) {
22
37
  return false;
23
38
  }
24
- hAlignElement(elm, command);
39
+ hAlignElement(elm, command, alignClasses(editor, elm));
25
40
  if (Dom.isTag(elm, new Set(['jodit', 'jodit-media'])) &&
26
41
  elm.firstElementChild) {
27
42
  hAlignElement(elm.firstElementChild, command);
@@ -127,23 +127,30 @@ export class AceEditor extends SourceEditor {
127
127
  onResize();
128
128
  this.onReady();
129
129
  };
130
+ let loading = false;
130
131
  const initAceEditor = () => {
131
132
  if (editor.isInDestruct ||
132
133
  (editor.getRealMode() !== constants.MODE_SOURCE &&
133
134
  editor.getMode() !== constants.MODE_SPLIT)) {
134
135
  return;
135
136
  }
137
+ // ACE (~400 KiB from the CDN) is only needed once the source view
138
+ // is actually shown — a page that stays in WYSIWYG must not
139
+ // download it.
140
+ if (!this.aceExists()) {
141
+ if (!loading) {
142
+ loading = true;
143
+ loadNext(editor, editor.o.sourceEditorCDNUrlsJS)
144
+ .then(initAceEditor)
145
+ .catch(() => null);
146
+ }
147
+ return;
148
+ }
136
149
  this.fromWYSIWYG();
137
150
  tryInitAceEditor();
138
151
  };
139
152
  editor.e.on('afterSetMode', initAceEditor);
140
153
  initAceEditor();
141
- // global add ace editor in browser
142
- if (!this.aceExists()) {
143
- loadNext(editor, editor.o.sourceEditorCDNUrlsJS)
144
- .then(initAceEditor)
145
- .catch(() => null);
146
- }
147
154
  }
148
155
  destruct() {
149
156
  var _a, _b;
@@ -261,7 +261,26 @@ export class source extends Plugin {
261
261
  return false;
262
262
  };
263
263
  if (!addEventListener()) {
264
- loadNext(editor, editor.o.beautifyHTMLCDNUrlsJS).then(addEventListener, () => null);
264
+ // js-beautify is only needed once the source view is shown —
265
+ // do not download it for a page that stays in WYSIWYG.
266
+ let loading = false;
267
+ const loadBeautify = () => {
268
+ if (loading ||
269
+ (editor.getMode() !== MODE_SOURCE &&
270
+ editor.getMode() !== MODE_SPLIT)) {
271
+ return;
272
+ }
273
+ loading = true;
274
+ loadNext(editor, editor.o.beautifyHTMLCDNUrlsJS).then(() => {
275
+ // The source view was filled before the
276
+ // beautifier arrived — format it now.
277
+ if (addEventListener()) {
278
+ this.syncValueFromWYSIWYG(true);
279
+ }
280
+ }, () => null);
281
+ };
282
+ editor.e.on('afterSetMode.source', loadBeautify);
283
+ loadBeautify();
265
284
  }
266
285
  }
267
286
  this.syncValueFromWYSIWYG(true);
@@ -187,6 +187,10 @@ export interface ImageEditorOptions {
187
187
  export type InsertMode = 'insert_as_html' | 'insert_as_text' | 'insert_clear_html' | 'insert_only_text';
188
188
  export type ImageAction = 'resize' | 'crop';
189
189
  export type ImageHAlign = 'normal' | 'left' | 'right' | 'center' | '';
190
+ /**
191
+ * Class names for each image alignment, see `imageAlignClasses`
192
+ */
193
+ export type ImageAlignClasses = Partial<Record<Exclude<ImageHAlign, ''>, string>>;
190
194
  export interface ImageBox {
191
195
  w: number;
192
196
  h: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jodit",
3
- "version": "4.15.17",
3
+ "version": "4.16.0",
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
@@ -23,6 +23,8 @@ import type { Attributes } from "./types/index";
23
23
  import type { Controls } from "./types/index";
24
24
  import type { IAsyncStorageOptions } from "./types/index";
25
25
  import type { IExtraPlugin } from "./types/index";
26
+ import type { ImageAlignClasses } from "./types/index";
27
+ import type { ImageHAlign } from "./types/index";
26
28
  import type { IUIButtonState } from "./types/index";
27
29
  import type { IViewOptions } from "./types/index";
28
30
  import type { NodeFunction } from "./types/index";
@@ -631,6 +633,27 @@ declare class Config implements IViewOptions {
631
633
  * Default width (in pixels) applied to images inserted into the editor
632
634
  */
633
635
  imageDefaultWidth: number;
636
+ /**
637
+ * Alignment given to images inserted into the editor. `'normal'` leaves
638
+ * them unaligned
639
+ */
640
+ imageDefaultAlign: ImageHAlign;
641
+ /**
642
+ * Class names to align images with, instead of an inline `float` or
643
+ * `margin`. Aligning an image removes every class listed here and adds the
644
+ * chosen alignment's, which can be several separated by spaces.
645
+ *
646
+ * ```javascript
647
+ * Jodit.make('#editor', {
648
+ * imageAlignClasses: {
649
+ * left: 'align-left',
650
+ * right: 'align-right',
651
+ * center: 'align-center'
652
+ * }
653
+ * });
654
+ * ```
655
+ */
656
+ imageAlignClasses: Nullable<ImageAlignClasses>;
634
657
  /**
635
658
  * Do not display these buttons that are on the list
636
659
  *