suneditor 2.41.2 → 2.43.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 (50) hide show
  1. package/README.md +116 -28
  2. package/dist/css/suneditor.min.css +1 -1
  3. package/dist/suneditor.min.js +2 -2
  4. package/package.json +4 -4
  5. package/src/assets/css/suneditor-contents.css +1 -1
  6. package/src/assets/css/suneditor.css +27 -7
  7. package/src/assets/defaultIcons.js +2 -0
  8. package/src/lang/Lang.d.ts +3 -1
  9. package/src/lang/ckb.js +2 -0
  10. package/src/lang/da.js +2 -0
  11. package/src/lang/de.js +2 -0
  12. package/src/lang/en.js +2 -0
  13. package/src/lang/es.js +2 -0
  14. package/src/lang/fr.js +10 -8
  15. package/src/lang/he.js +2 -0
  16. package/src/lang/it.js +2 -0
  17. package/src/lang/ja.js +2 -0
  18. package/src/lang/ko.js +2 -0
  19. package/src/lang/lv.js +2 -0
  20. package/src/lang/nl.js +2 -0
  21. package/src/lang/pl.js +2 -0
  22. package/src/lang/pt_br.js +2 -0
  23. package/src/lang/ro.js +2 -0
  24. package/src/lang/ru.js +2 -0
  25. package/src/lang/se.js +2 -0
  26. package/src/lang/ua.js +2 -0
  27. package/src/lang/zh_cn.js +3 -1
  28. package/src/lib/constructor.js +68 -16
  29. package/src/lib/context.js +5 -1
  30. package/src/lib/core.d.ts +88 -12
  31. package/src/lib/core.js +789 -212
  32. package/src/lib/util.d.ts +24 -1
  33. package/src/lib/util.js +105 -18
  34. package/src/options.d.ts +79 -9
  35. package/src/plugins/dialog/audio.js +5 -5
  36. package/src/plugins/dialog/image.js +30 -20
  37. package/src/plugins/dialog/link.js +1 -0
  38. package/src/plugins/dialog/video.js +16 -15
  39. package/src/plugins/fileBrowser/imageGallery.js +2 -3
  40. package/src/plugins/modules/_anchor.js +24 -15
  41. package/src/plugins/modules/component.d.ts +1 -1
  42. package/src/plugins/modules/fileBrowser.js +6 -1
  43. package/src/plugins/modules/fileManager.js +1 -3
  44. package/src/plugins/modules/resizing.js +11 -6
  45. package/src/plugins/submenu/align.js +32 -27
  46. package/src/plugins/submenu/font.js +1 -1
  47. package/src/plugins/submenu/fontSize.js +1 -1
  48. package/src/plugins/submenu/horizontalRule.js +19 -25
  49. package/src/plugins/submenu/list.js +13 -5
  50. package/src/plugins/submenu/template.js +5 -2
package/src/lib/util.d.ts CHANGED
@@ -263,6 +263,14 @@ declare interface util {
263
263
  */
264
264
  getArrayItem(array: any[] | HTMLCollection | NodeList, validation: Function | null, multi: boolean): any[] | Node | null;
265
265
 
266
+ /**
267
+ * @description Check if an array contains an element
268
+ * @param {Array|HTMLCollection|NodeList} array element array
269
+ * @param {Node} element The element to find index
270
+ * @returns {Boolean}
271
+ */
272
+ arrayIncludes(array: any[] | HTMLCollection | NodeList, element: Node): boolean;
273
+
266
274
  /**
267
275
  * @description Get the index of the argument value in the element array
268
276
  * @param array element array
@@ -534,14 +542,21 @@ declare interface util {
534
542
  */
535
543
  toggleClass(element: Element, className: string): boolean | undefined;
536
544
 
545
+ /**
546
+ * @description Checks if element can't be easily enabled
547
+ * @param {Element} element Element to check for
548
+ */
549
+ isImportantDisabled(element: Element): boolean;
550
+
537
551
  /**
538
552
  * @description In the predefined code view mode, the buttons except the executable button are changed to the 'disabled' state.
539
553
  * core.codeViewDisabledButtons (An array of buttons whose class name is not "se-code-view-enabled")
540
554
  * core.resizingDisabledButtons (An array of buttons whose class name is not "se-resizing-enabled")
541
555
  * @param disabled Disabled value
542
556
  * @param buttonList Button array
557
+ * @param important If priveleged mode should be used (Necessary to switch importantDisabled buttons)
543
558
  */
544
- setDisabledButtons(disabled: boolean, buttonList: Element[] | HTMLCollection | NodeList): void;
559
+ setDisabledButtons(disabled: boolean, buttonList: Element[] | HTMLCollection | NodeList, important: Boolean): void;
545
560
 
546
561
  /**
547
562
  * @description Delete argumenu value element
@@ -624,6 +639,14 @@ declare interface util {
624
639
  * @returns
625
640
  */
626
641
  createTagsWhitelist(list: string): RegExp;
642
+
643
+ /**
644
+ * @description Create blacklist RegExp object.
645
+ * Return RegExp format: new RegExp("<\\/?\\b(?:" + list + ")\\b[^>^<]*+>", "gi")
646
+ * @param list Tags list ("br|p|div|pre...")
647
+ * @returns
648
+ */
649
+ createTagsBlacklist(list: string): RegExp;
627
650
  }
628
651
 
629
652
  export default util;
package/src/lib/util.js CHANGED
@@ -16,6 +16,7 @@ const util = {
16
16
  isIE: null,
17
17
  isIE_Edge: null,
18
18
  isOSX_IOS: null,
19
+ isChromium: null,
19
20
  _propertiesInit: function () {
20
21
  if (this._d) return;
21
22
  this._d = document;
@@ -23,6 +24,7 @@ const util = {
23
24
  this.isIE = navigator.userAgent.indexOf('Trident') > -1;
24
25
  this.isIE_Edge = (navigator.userAgent.indexOf('Trident') > -1) || (navigator.appVersion.indexOf('Edge') > -1);
25
26
  this.isOSX_IOS = /(Mac|iPhone|iPod|iPad)/.test(navigator.platform);
27
+ this.isChromium = !!window.chrome;
26
28
  },
27
29
 
28
30
  _allowedEmptyNodeList: '.se-component, pre, blockquote, hr, li, table, img, iframe, video, audio, canvas',
@@ -61,6 +63,7 @@ const util = {
61
63
  * @returns {Boolean}
62
64
  */
63
65
  onlyZeroWidthSpace: function (text) {
66
+ if (!text) return false;
64
67
  if (typeof text !== 'string') text = text.textContent;
65
68
  return text === '' || this.onlyZeroWidthRegExp.test(text);
66
69
  },
@@ -92,6 +95,29 @@ const util = {
92
95
  }
93
96
  },
94
97
 
98
+ /**
99
+ * @description Object.values
100
+ * @param {Object} obj Object parameter.
101
+ * @returns {Array}
102
+ */
103
+ getValues: function (obj) {
104
+ return !obj ? [] : this._w.Object.keys(obj).map(function (i) {
105
+ return obj[i];
106
+ });
107
+ },
108
+
109
+ /**
110
+ * @description Convert the CamelCase To the KebabCase.
111
+ * @param {String|Array} param [Camel string]
112
+ */
113
+ camelToKebabCase: function (param) {
114
+ if (typeof param === 'string') {
115
+ return param.replace(/[A-Z]/g, function (letter) { return "-" + letter.toLowerCase(); });
116
+ } else {
117
+ return param.map(function(str) { return util.camelToKebabCase(str); });
118
+ }
119
+ },
120
+
95
121
  /**
96
122
  * @description Create Element node
97
123
  * @param {String} elementName Element name
@@ -551,6 +577,21 @@ const util = {
551
577
  return !multi ? null : arr;
552
578
  },
553
579
 
580
+ /**
581
+ * @description Check if an array contains an element
582
+ * @param {Array|HTMLCollection|NodeList} array element array
583
+ * @param {Node} element The element to check for
584
+ * @returns {Boolean}
585
+ */
586
+ arrayIncludes: function(array, element) {
587
+ for (let i = 0; i < array.length; i++) {
588
+ if (array[i] === element) {
589
+ return true;
590
+ }
591
+ }
592
+ return false;
593
+ },
594
+
554
595
  /**
555
596
  * @description Get the index of the argument value in the element array
556
597
  * @param {Array|HTMLCollection|NodeList} array element array
@@ -1173,16 +1214,33 @@ const util = {
1173
1214
  return result;
1174
1215
  },
1175
1216
 
1217
+ /**
1218
+ * @description Checks if element can't be easily enabled
1219
+ * @param {Element} element Element to check for
1220
+ */
1221
+ isImportantDisabled: function (element) {
1222
+ return element.hasAttribute('data-important-disabled');
1223
+ },
1224
+
1176
1225
  /**
1177
1226
  * @description In the predefined code view mode, the buttons except the executable button are changed to the 'disabled' state.
1178
1227
  * core.codeViewDisabledButtons (An array of buttons whose class name is not "se-code-view-enabled")
1179
1228
  * core.resizingDisabledButtons (An array of buttons whose class name is not "se-resizing-enabled")
1180
1229
  * @param {Boolean} disabled Disabled value
1181
1230
  * @param {Array|HTMLCollection|NodeList} buttonList Button array
1231
+ * @param {Boolean} important If priveleged mode should be used (Necessary to switch importantDisabled buttons)
1182
1232
  */
1183
- setDisabledButtons: function (disabled, buttonList) {
1233
+ setDisabledButtons: function (disabled, buttonList, important) {
1184
1234
  for (let i = 0, len = buttonList.length; i < len; i++) {
1185
- buttonList[i].disabled = disabled;
1235
+ let button = buttonList[i];
1236
+ if (important || !this.isImportantDisabled(button)) button.disabled = disabled;
1237
+ if (important) {
1238
+ if (disabled) {
1239
+ button.setAttribute('data-important-disabled', '');
1240
+ } else {
1241
+ button.removeAttribute('data-important-disabled');
1242
+ }
1243
+ }
1186
1244
  }
1187
1245
  },
1188
1246
 
@@ -1594,7 +1652,7 @@ const util = {
1594
1652
  */
1595
1653
  htmlRemoveWhiteSpace: function (html) {
1596
1654
  if (!html) return '';
1597
- return html.trim().replace(/<\/?(?!strong|span|font|b|var|i|em|u|ins|s|strike|del|sub|sup|mark|a|label|code|summary)[^>^<]+>\s+(?=<)/ig, function (m) { return m.trim(); });
1655
+ return html.trim().replace(/<\/?(?!strong|span|font|b|var|i|em|u|ins|s|strike|del|sub|sup|mark|a|label|code|summary)[^>^<]+>\s+(?=<)/ig, function (m) { return m.replace(/\n/g, '').replace(/\s+/, ' '); });
1598
1656
  },
1599
1657
 
1600
1658
  /**
@@ -1671,17 +1729,28 @@ const util = {
1671
1729
  * @returns {RegExp}
1672
1730
  */
1673
1731
  createTagsWhitelist: function (list) {
1674
- return new RegExp('<\\/?\\b(?!\\b' + list.replace(/\|/g, '\\b|\\b') + '\\b)[^>]*>', 'gi');
1732
+ return new RegExp('<\\/?\\b(?!\\b' + (list || '').replace(/\|/g, '\\b|\\b') + '\\b)[^>]*>', 'gi');
1733
+ },
1734
+
1735
+ /**
1736
+ * @description Create blacklist RegExp object.
1737
+ * Return RegExp format: new RegExp("<\\/?\\b(?:" + list + ")\\b[^>^<]*+>", "gi")
1738
+ * @param {String} list Tags list ("br|p|div|pre...")
1739
+ * @returns {RegExp}
1740
+ */
1741
+ createTagsBlacklist: function (list) {
1742
+ return new RegExp('<\\/?\\b(?:\\b' + (list || '^').replace(/\|/g, '\\b|\\b') + '\\b)[^>]*>', 'gi');
1675
1743
  },
1676
1744
 
1677
1745
  /**
1678
1746
  * @description Fix tags that do not fit the editor format.
1679
1747
  * @param {Element} documentFragment Document fragment "DOCUMENT_FRAGMENT_NODE" (nodeType === 11)
1680
1748
  * @param {RegExp} htmlCheckWhitelistRegExp Editor tags whitelist (core._htmlCheckWhitelistRegExp)
1749
+ * @param {RegExp} htmlCheckBlacklistRegExp Editor tags blacklist (core._htmlCheckBlacklistRegExp)
1681
1750
  * @param {Boolean} lowLevelCheck Row level check
1682
1751
  * @private
1683
1752
  */
1684
- _consistencyCheckOfHTML: function (documentFragment, htmlCheckWhitelistRegExp, lowLevelCheck) {
1753
+ _consistencyCheckOfHTML: function (documentFragment, htmlCheckWhitelistRegExp, htmlCheckBlacklistRegExp, lowLevelCheck) {
1685
1754
  /**
1686
1755
  * It is can use ".children(util.getListChildren)" to exclude text nodes, but "documentFragment.children" is not supported in IE.
1687
1756
  * So check the node type and exclude the text no (current.nodeType !== 1)
@@ -1690,17 +1759,20 @@ const util = {
1690
1759
 
1691
1760
  // wrong position
1692
1761
  const wrongTags = this.getListChildNodes(documentFragment, function (current) {
1693
- if (current.nodeType !== 1) return false;
1762
+ if (current.nodeType !== 1) {
1763
+ if (this.isList(current.parentNode)) removeTags.push(current);
1764
+ return false;
1765
+ }
1694
1766
 
1695
1767
  // white list
1696
- if (!htmlCheckWhitelistRegExp.test(current.nodeName) && current.childNodes.length === 0 && this.isNotCheckingNode(current)) {
1768
+ if (htmlCheckBlacklistRegExp.test(current.nodeName) || (!htmlCheckWhitelistRegExp.test(current.nodeName) && current.childNodes.length === 0 && this.isNotCheckingNode(current))) {
1697
1769
  removeTags.push(current);
1698
1770
  return false;
1699
1771
  }
1700
1772
 
1701
1773
  const nrtag = !this.getParentElement(current, this.isNotCheckingNode);
1702
1774
  // empty tags
1703
- if ((!this.isTable(current) && !this.isListCell(current)) && (this.isFormatElement(current) || this.isRangeFormatElement(current) || this.isTextStyleElement(current)) && current.childNodes.length === 0 && nrtag) {
1775
+ if ((!this.isTable(current) && !this.isListCell(current) && !this.isAnchor(current)) && (this.isFormatElement(current) || this.isRangeFormatElement(current) || this.isTextStyleElement(current)) && current.childNodes.length === 0 && nrtag) {
1704
1776
  emptyTags.push(current);
1705
1777
  return false;
1706
1778
  }
@@ -1736,8 +1808,17 @@ const util = {
1736
1808
  t = wrongTags[i];
1737
1809
  p = t.parentNode;
1738
1810
  if (!p || !p.parentNode) continue;
1739
- p.parentNode.insertBefore(t, p);
1740
- checkTags.push(p);
1811
+
1812
+ if (this.getParentElement(t, this.isListCell)) {
1813
+ const cellChildren = t.childNodes;
1814
+ for (let j = cellChildren.length - 1; len >= 0; j--) {
1815
+ p.insertBefore(t, cellChildren[j]);
1816
+ }
1817
+ checkTags.push(t);
1818
+ } else {
1819
+ p.parentNode.insertBefore(t, p);
1820
+ checkTags.push(p);
1821
+ }
1741
1822
  }
1742
1823
 
1743
1824
  for (let i = 0, len = checkTags.length, t; i < len; i++) {
@@ -1753,17 +1834,23 @@ const util = {
1753
1834
 
1754
1835
  for (let i = 0, len = wrongList.length, t, tp, children, p; i < len; i++) {
1755
1836
  t = wrongList[i];
1837
+ p = t.parentNode;
1838
+ if (!p) continue;
1756
1839
 
1757
1840
  tp = this.createElement('LI');
1758
- children = t.childNodes;
1759
- while (children[0]) {
1760
- tp.appendChild(children[0]);
1841
+
1842
+ if (this.isFormatElement(t)) {
1843
+ children = t.childNodes;
1844
+ while (children[0]) {
1845
+ tp.appendChild(children[0]);
1846
+ }
1847
+ p.insertBefore(tp, t);
1848
+ this.removeItem(t);
1849
+ } else {
1850
+ t = t.nextSibling;
1851
+ tp.appendChild(wrongList[i]);
1852
+ p.insertBefore(tp, t);
1761
1853
  }
1762
-
1763
- p = t.parentNode;
1764
- if (!p) continue;
1765
- p.insertBefore(tp, t);
1766
- this.removeItem(t);
1767
1854
  }
1768
1855
 
1769
1856
  for (let i = 0, len = withoutFormatCells.length, t, f; i < len; i++) {
package/src/options.d.ts CHANGED
@@ -24,6 +24,10 @@ export interface SunEditorOptions {
24
24
  * If not, the value of the "target textarea".
25
25
  */
26
26
  value?: string;
27
+ /**
28
+ * When recording the history stack, this is the delay time (miliseconds) since the last input. default: 400
29
+ */
30
+ historyStackDelayTime?: number;
27
31
  /**
28
32
  * Whitelist
29
33
  * ======
@@ -31,15 +35,15 @@ export interface SunEditorOptions {
31
35
  /**
32
36
  * Add tags to the default tags whitelist of editor.
33
37
  */
34
- addTagsWhitelist?: string;
35
- /**
36
- * Whitelist of tags when pasting.
37
- */
38
- pasteTagsWhitelist?: string;
38
+ addTagsWhitelist?: string | '*';
39
39
  /**
40
40
  * Blacklist of the editor default tags.
41
41
  */
42
42
  tagsBlacklist?: string;
43
+ /**
44
+ * Whitelist of tags when pasting.
45
+ */
46
+ pasteTagsWhitelist?: string | '*';
43
47
  /**
44
48
  * Blacklist of tags when pasting.
45
49
  */
@@ -47,7 +51,11 @@ export interface SunEditorOptions {
47
51
  /**
48
52
  * Add attributes whitelist of tags that should be kept undeleted from the editor.
49
53
  */
50
- attributesWhitelist?: Record<string, string>;
54
+ attributesWhitelist?: Record<string, string | '*'>;
55
+ /**
56
+ * Add attribute blacklist of tags that should be deleted in editor.
57
+ */
58
+ attributesBlacklist?: Record<string, string | '*'>;
51
59
  /**
52
60
  * Layout
53
61
  * ======
@@ -60,6 +68,13 @@ export interface SunEditorOptions {
60
68
  * If true, the editor is set to RTL(Right To Left) mode.
61
69
  */
62
70
  rtl?: boolean;
71
+ /**
72
+ * Deletes other attributes except for the property set at the time of line break.
73
+ * If there is no value, no all attribute is deleted.
74
+ * @example 'class|style': Attributes other than "class" and "style" are deleted at line break.
75
+ * '*': All attributes are deleted at line break.
76
+ */
77
+ lineAttrReset?: string;
63
78
  /**
64
79
  * Button List
65
80
  */
@@ -91,6 +106,10 @@ export interface SunEditorOptions {
91
106
  * Allows the usage of HTML, HEAD, BODY tags and DOCTYPE declaration
92
107
  */
93
108
  fullPage?: boolean;
109
+ /**
110
+ * Attributes of the iframe.
111
+ */
112
+ iframeAttributes?: Record<string, string>;
94
113
  /**
95
114
  * Name of the CSS file(s) to apply inside the iframe.
96
115
  */
@@ -147,6 +166,16 @@ export interface SunEditorOptions {
147
166
  * Displays the current node structure to resizingBar
148
167
  */
149
168
  showPathLabel?: boolean;
169
+ /**
170
+ * Enable/disable resize function of bottom resizing bar.
171
+ */
172
+ resizeEnable?: boolean;
173
+ /**
174
+ * A custom HTML selector placing the resizing bar inside.
175
+ The class name of the element must be 'sun-editor'.
176
+ Element or querySelector argument.
177
+ */
178
+ resizingBarContainer?: HTMLElement | string;
150
179
  /**
151
180
  * Character count
152
181
  * ===============
@@ -196,8 +225,19 @@ export interface SunEditorOptions {
196
225
  */
197
226
  maxHeight?: string;
198
227
  /**
199
- * Editing area default style
228
+ * Editing area
229
+ * ===================
200
230
  */
231
+ /**
232
+ * Add a "class" to the editing area[.sun-editor-editable]
233
+ */
234
+ className?: string;
235
+ /**
236
+ * You can define the style of the editing area[.sun-editor-editable].
237
+ * It affects the entire editing area.
238
+ * ('z-index', 'position' and 'width' properties apply to the top div.)
239
+ * @example 'font-family: cursive; font-size: 10px;'
240
+ */
201
241
  defaultStyle?: string;
202
242
  /**
203
243
  * Defining menu items
@@ -212,9 +252,13 @@ export interface SunEditorOptions {
212
252
  */
213
253
  fontSize?: number[];
214
254
  /**
215
- *
255
+ * The font size unit
216
256
  */
217
257
  fontSizeUnit?: string;
258
+ /**
259
+ * A list of drop-down options for the 'align' plugin.
260
+ */
261
+ alignItems?: ('left' | 'center' | 'right' | 'justify')[];
218
262
  /**
219
263
  * Change default formatBlock array
220
264
  */
@@ -247,6 +291,10 @@ export interface SunEditorOptions {
247
291
  * Choose whether the image height input is visible.
248
292
  */
249
293
  imageHeightShow?: boolean;
294
+ /**
295
+ * Choose whether the image align radio buttons are visible.
296
+ */
297
+ imageAlignShow?: boolean;
250
298
  /**
251
299
  * The default width size of the image frame
252
300
  */
@@ -296,7 +344,7 @@ export interface SunEditorOptions {
296
344
  imageMultipleFile?: boolean;
297
345
  /**
298
346
  * Define the "accept" attribute of the input.
299
- * ex) "*" or ".jpg, .png .."
347
+ * @example "*" or ".jpg, .png .."
300
348
  */
301
349
  imageAccept?: string;
302
350
  /**
@@ -323,6 +371,10 @@ export interface SunEditorOptions {
323
371
  * Choose whether the video height input is visible.
324
372
  */
325
373
  videoHeightShow?: boolean;
374
+ /**
375
+ * Choose whether the video align radio buttons are visible.
376
+ */
377
+ videoAlignShow?: boolean;
326
378
  /**
327
379
  * Choose whether the video ratio options is visible.
328
380
  */
@@ -455,6 +507,10 @@ export interface SunEditorOptions {
455
507
  * Link
456
508
  * =====
457
509
  */
510
+ /**
511
+ * Default checked value of the "Open in new window" checkbox.
512
+ */
513
+ linkTargetNewWindow?: boolean;
458
514
  /**
459
515
  * Protocol for the links (if link has been added without any protocol this one will be used).
460
516
  */
@@ -467,6 +523,20 @@ export interface SunEditorOptions {
467
523
  * Defines default "rel" attribute list of anchor tag.
468
524
  */
469
525
  linkRelDefault?: {default?: string; check_new_window?: string; check_bookmark?: string;};
526
+ /**
527
+ * If true, disables the automatic prefixing of the host URL to the value of the link. default: false {Boolean}
528
+ */
529
+ linkNoPrefix?: boolean;
530
+ /*
531
+ * HR
532
+ * =====
533
+ */
534
+ /**
535
+ * Defines the hr items.
536
+ * "class" or "style" must be specified.
537
+ * @example [{name: "solid", class: "__se__xxx", style: "border-style: outset;"}]
538
+ */
539
+ hrItems?: { name: string; class?: string; style?: string }[];
470
540
  /**
471
541
  * Key actions
472
542
  * =====
@@ -454,10 +454,8 @@ export default {
454
454
  this.plugins.audio._setTagAttrs.call(this, element);
455
455
 
456
456
  // find component element
457
- const existElement = this.util.getParentElement(element, this.util.isMediaComponent) ||
458
- this.util.getParentElement(element, function (current) {
459
- return this.isWysiwygDiv(current.parentNode);
460
- }.bind(this.util));
457
+ const existElement = (this.util.isRangeFormatElement(element.parentNode) || this.util.isWysiwygDiv(element.parentNode)) ?
458
+ element : this.util.getFormatElement(element) || element;
461
459
 
462
460
  // clone element
463
461
  const prevElement = element;
@@ -466,7 +464,9 @@ export default {
466
464
  const container = this.plugins.component.set_container.call(this, cover, 'se-audio-container');
467
465
 
468
466
  try {
469
- if (this.util.isFormatElement(existElement) && existElement.childNodes.length > 0) {
467
+ if (this.util.isListCell(existElement) || this.util.isFormatElement(existElement)) {
468
+ prevElement.parentNode.replaceChild(container, prevElement);
469
+ } else if (this.util.isFormatElement(existElement) && existElement.childNodes.length > 0) {
470
470
  existElement.parentNode.insertBefore(container, existElement);
471
471
  this.util.removeItem(prevElement);
472
472
  // clean format tag
@@ -53,6 +53,7 @@ export default {
53
53
  _resizing: options.imageResizing,
54
54
  _resizeDotHide: !options.imageHeightShow,
55
55
  _rotation: options.imageRotation,
56
+ _alignHide: !options.imageAlignShow,
56
57
  _onlyPercentage: options.imageSizeOnlyPercentage,
57
58
  _ratio: false,
58
59
  _ratioX: 1,
@@ -203,7 +204,7 @@ export default {
203
204
  core.context.anchor.forms.innerHTML +
204
205
  '</div>' +
205
206
  '<div class="se-dialog-footer">' +
206
- '<div>' +
207
+ '<div' + (option.imageAlignShow ? '' : ' style="display: none"') + '>' +
207
208
  '<label><input type="radio" name="suneditor_image_radio" class="se-dialog-btn-radio" value="none" checked>' + lang.dialogBox.basic + '</label>' +
208
209
  '<label><input type="radio" name="suneditor_image_radio" class="se-dialog-btn-radio" value="left">' + lang.dialogBox.left + '</label>' +
209
210
  '<label><input type="radio" name="suneditor_image_radio" class="se-dialog-btn-radio" value="center">' + lang.dialogBox.center + '</label>' +
@@ -375,7 +376,7 @@ export default {
375
376
  imagePlugin.submitAction.call(this, this.context.image.imgInputFile.files);
376
377
  } else if (contextImage.imgUrlFile && contextImage._v_src._linkValue.length > 0) {
377
378
  this.showLoading();
378
- imagePlugin.onRender_imgUrl.call(this);
379
+ imagePlugin.onRender_imgUrl.call(this, contextImage._v_src._linkValue);
379
380
  }
380
381
  } catch (error) {
381
382
  this.closeLoading();
@@ -427,6 +428,7 @@ export default {
427
428
  inputHeight: contextImage.inputY.value,
428
429
  align: contextImage._align,
429
430
  isUpdate: this.context.dialog.updateModal,
431
+ alt: contextImage._altText,
430
432
  element: contextImage._element
431
433
  };
432
434
 
@@ -479,7 +481,7 @@ export default {
479
481
  }
480
482
  this.plugins.fileManager.upload.call(this, imageUploadUrl, this.options.imageUploadHeader, formData, this.plugins.image.callBack_imgUpload.bind(this, info), this.functions.onImageUploadError);
481
483
  } else { // base64
482
- this.plugins.image.setup_reader.call(this, files, info.anchor, info.inputWidth, info.inputHeight, info.align, filesLen, info.isUpdate);
484
+ this.plugins.image.setup_reader.call(this, files, info.anchor, info.inputWidth, info.inputHeight, info.align, info.alt, filesLen, info.isUpdate);
483
485
  }
484
486
  },
485
487
 
@@ -505,14 +507,14 @@ export default {
505
507
  this.plugins.image.update_src.call(this, fileList[i].url, info.element, file);
506
508
  break;
507
509
  } else {
508
- this.plugins.image.create_image.call(this, fileList[i].url, info.anchor, info.inputWidth, info.inputHeight, info.align, file);
510
+ this.plugins.image.create_image.call(this, fileList[i].url, info.anchor, info.inputWidth, info.inputHeight, info.align, file, info.alt);
509
511
  }
510
512
  }
511
513
 
512
514
  this.closeLoading();
513
515
  },
514
516
 
515
- setup_reader: function (files, anchor, width, height, align, filesLen, isUpdate) {
517
+ setup_reader: function (files, anchor, width, height, align, alt, filesLen, isUpdate) {
516
518
  try {
517
519
  this.context.image.base64RenderIndex = filesLen;
518
520
  const wFileReader = this._w.FileReader;
@@ -528,7 +530,7 @@ export default {
528
530
  filesStack[index] = { result: reader.result, file: file };
529
531
 
530
532
  if (--this.context.image.base64RenderIndex === 0) {
531
- this.plugins.image.onRender_imgBase64.call(this, update, filesStack, updateElement, anchor, width, height, align);
533
+ this.plugins.image.onRender_imgBase64.call(this, update, filesStack, updateElement, anchor, width, height, align, alt);
532
534
  this.closeLoading();
533
535
  }
534
536
  }.bind(this, reader, isUpdate, this.context.image._element, file, i);
@@ -541,7 +543,7 @@ export default {
541
543
  }
542
544
  },
543
545
 
544
- onRender_imgBase64: function (update, filesStack, updateElement, anchor, width, height, align) {
546
+ onRender_imgBase64: function (update, filesStack, updateElement, anchor, width, height, align, alt) {
545
547
  const updateMethod = this.plugins.image.update_src;
546
548
  const createMethod = this.plugins.image.create_image;
547
549
 
@@ -551,19 +553,20 @@ export default {
551
553
  this.context.image._element.setAttribute('data-file-size', filesStack[i].file.size);
552
554
  updateMethod.call(this, filesStack[i].result, updateElement, filesStack[i].file);
553
555
  } else {
554
- createMethod.call(this, filesStack[i].result, anchor, width, height, align, filesStack[i].file);
556
+ createMethod.call(this, filesStack[i].result, anchor, width, height, align, filesStack[i].file, alt);
555
557
  }
556
558
  }
557
559
  },
558
560
 
559
- onRender_imgUrl: function () {
561
+ onRender_imgUrl: function (url) {
562
+ if (!url) url = this.context.image._v_src._linkValue;
563
+ if (!url) return false;
560
564
  const contextImage = this.context.image;
561
- if (contextImage._v_src._linkValue.length === 0) return false;
562
565
 
563
566
  try {
564
- const file = {name: contextImage._v_src._linkValue.split('/').pop(), size: 0};
565
- if (this.context.dialog.updateModal) this.plugins.image.update_src.call(this, contextImage._v_src._linkValue, contextImage._element, file);
566
- else this.plugins.image.create_image.call(this, contextImage._v_src._linkValue, this.plugins.anchor.createAnchor.call(this, contextImage.anchorCtx, true), contextImage.inputX.value, contextImage.inputY.value, contextImage._align, file);
567
+ const file = {name: url.split('/').pop(), size: 0};
568
+ if (this.context.dialog.updateModal) this.plugins.image.update_src.call(this, url, contextImage._element, file);
569
+ else this.plugins.image.create_image.call(this, url, this.plugins.anchor.createAnchor.call(this, contextImage.anchorCtx, true), contextImage.inputX.value, contextImage.inputY.value, contextImage._align, file, contextImage._altText);
567
570
  } catch (e) {
568
571
  throw Error('[SUNEDITOR.image.URLRendering.fail] cause : "' + e.message + '"');
569
572
  } finally {
@@ -637,14 +640,14 @@ export default {
637
640
  this.plugins.fileManager.resetInfo.call(this, 'image', this.functions.onImageUpload);
638
641
  },
639
642
 
640
- create_image: function (src, anchor, width, height, align, file) {
643
+ create_image: function (src, anchor, width, height, align, file, alt) {
641
644
  const imagePlugin = this.plugins.image;
642
645
  const contextImage = this.context.image;
643
646
  this.context.resizing._resize_plugin = 'image';
644
647
 
645
648
  let oImg = this.util.createElement('IMG');
646
649
  oImg.src = src;
647
- oImg.alt = contextImage._altText;
650
+ oImg.alt = alt;
648
651
  oImg.setAttribute('data-rotate', '0');
649
652
  anchor = imagePlugin.onRender_link.call(this, oImg, anchor);
650
653
 
@@ -746,8 +749,13 @@ export default {
746
749
  // link
747
750
  const anchor = this.plugins.anchor.createAnchor.call(this, contextImage.anchorCtx, true);
748
751
  if (anchor) {
749
- contextImage._linkElement = contextImage._linkElement === anchor ? anchor.cloneNode(false) : anchor;
750
- cover.insertBefore(this.plugins.image.onRender_link.call(this, imageEl, contextImage._linkElement), contextImage._caption);
752
+ if (contextImage._linkElement !== anchor) {
753
+ contextImage._linkElement = anchor.cloneNode(false);
754
+ cover.insertBefore(this.plugins.image.onRender_link.call(this, imageEl, contextImage._linkElement), contextImage._caption);
755
+ this.util.removeItem(anchor);
756
+ } else {
757
+ contextImage._linkElement.setAttribute('data-image-link', 'image');
758
+ }
751
759
  } else if (contextImage._linkElement !== null) {
752
760
  const imageElement = imageEl;
753
761
  imageElement.setAttribute('data-image-link', '');
@@ -755,7 +763,7 @@ export default {
755
763
  const newEl = imageElement.cloneNode(true);
756
764
  cover.removeChild(contextImage._linkElement);
757
765
  cover.insertBefore(newEl, contextImage._caption);
758
- imageEl = newEl;
766
+ contextImage._element = imageEl = newEl;
759
767
  }
760
768
  }
761
769
 
@@ -764,7 +772,9 @@ export default {
764
772
  contextImage._element :
765
773
  /^A$/i.test(contextImage._element.parentNode.nodeName) ? contextImage._element.parentNode : this.util.getFormatElement(contextImage._element) || contextImage._element;
766
774
 
767
- if (this.util.isFormatElement(existElement) && existElement.childNodes.length > 0) {
775
+ if (this.util.isListCell(existElement) || this.util.isFormatElement(existElement)) {
776
+ contextImage._element.parentNode.replaceChild(container, contextImage._element);
777
+ } else if (this.util.isFormatElement(existElement) && existElement.childNodes.length > 0) {
768
778
  existElement.parentNode.insertBefore(container, existElement);
769
779
  this.util.removeItem(contextImage._element);
770
780
  // clean format tag
@@ -875,7 +885,7 @@ export default {
875
885
  contextImage._v_src._linkValue = contextImage.previewSrc.textContent = contextImage.imgUrlFile.value = contextImage._element.src;
876
886
  }
877
887
  contextImage._altText = contextImage.altText.value = contextImage._element.alt;
878
- contextImage.modal.querySelector('input[name="suneditor_image_radio"][value="' + contextImage._align + '"]').checked = true;
888
+ (contextImage.modal.querySelector('input[name="suneditor_image_radio"][value="' + contextImage._align + '"]') || contextImage.modal.querySelector('input[name="suneditor_image_radio"][value="none"]')).checked = true;
879
889
  contextImage._align = contextImage.modal.querySelector('input[name="suneditor_image_radio"]:checked').value;
880
890
  contextImage._captionChecked = contextImage.captionCheckEl.checked = !!contextImage._caption;
881
891
 
@@ -118,6 +118,7 @@ export default {
118
118
 
119
119
  try {
120
120
  const oA = this.plugins.anchor.createAnchor.call(this, this.context.anchor.caller.link, false);
121
+ if (oA === null) return;
121
122
 
122
123
  if (!this.context.dialog.updateModal) {
123
124
  const selectedFormats = this.getSelectedElements();