suneditor 2.41.3 → 2.42.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 (45) hide show
  1. package/README.md +107 -24
  2. package/dist/css/suneditor.min.css +1 -1
  3. package/dist/suneditor.min.js +2 -2
  4. package/package.json +1 -1
  5. package/src/assets/css/suneditor.css +25 -6
  6. package/src/assets/defaultIcons.js +2 -0
  7. package/src/lang/Lang.d.ts +3 -1
  8. package/src/lang/ckb.js +2 -0
  9. package/src/lang/da.js +2 -0
  10. package/src/lang/de.js +2 -0
  11. package/src/lang/en.js +2 -0
  12. package/src/lang/es.js +2 -0
  13. package/src/lang/fr.js +10 -8
  14. package/src/lang/he.js +2 -0
  15. package/src/lang/it.js +2 -0
  16. package/src/lang/ja.js +2 -0
  17. package/src/lang/ko.js +2 -0
  18. package/src/lang/lv.js +2 -0
  19. package/src/lang/nl.js +2 -0
  20. package/src/lang/pl.js +2 -0
  21. package/src/lang/pt_br.js +2 -0
  22. package/src/lang/ro.js +2 -0
  23. package/src/lang/ru.js +2 -0
  24. package/src/lang/se.js +2 -0
  25. package/src/lang/ua.js +2 -0
  26. package/src/lang/zh_cn.js +2 -0
  27. package/src/lib/constructor.js +50 -11
  28. package/src/lib/context.js +4 -1
  29. package/src/lib/core.d.ts +86 -11
  30. package/src/lib/core.js +555 -147
  31. package/src/lib/util.d.ts +24 -1
  32. package/src/lib/util.js +64 -15
  33. package/src/options.d.ts +63 -8
  34. package/src/plugins/dialog/audio.js +5 -5
  35. package/src/plugins/dialog/image.js +30 -20
  36. package/src/plugins/dialog/video.js +13 -13
  37. package/src/plugins/fileBrowser/imageGallery.js +2 -3
  38. package/src/plugins/modules/_anchor.js +6 -4
  39. package/src/plugins/modules/component.d.ts +1 -1
  40. package/src/plugins/modules/fileBrowser.js +6 -1
  41. package/src/plugins/modules/fileManager.js +1 -3
  42. package/src/plugins/modules/resizing.js +11 -6
  43. package/src/plugins/submenu/align.js +32 -27
  44. package/src/plugins/submenu/font.js +1 -1
  45. package/src/plugins/submenu/horizontalRule.js +19 -25
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
@@ -551,6 +551,21 @@ const util = {
551
551
  return !multi ? null : arr;
552
552
  },
553
553
 
554
+ /**
555
+ * @description Check if an array contains an element
556
+ * @param {Array|HTMLCollection|NodeList} array element array
557
+ * @param {Node} element The element to check for
558
+ * @returns {Boolean}
559
+ */
560
+ arrayIncludes: function(array, element) {
561
+ for (let i = 0; i < array.length; i++) {
562
+ if (array[i] === element) {
563
+ return true;
564
+ }
565
+ }
566
+ return false;
567
+ },
568
+
554
569
  /**
555
570
  * @description Get the index of the argument value in the element array
556
571
  * @param {Array|HTMLCollection|NodeList} array element array
@@ -1173,16 +1188,33 @@ const util = {
1173
1188
  return result;
1174
1189
  },
1175
1190
 
1191
+ /**
1192
+ * @description Checks if element can't be easily enabled
1193
+ * @param {Element} element Element to check for
1194
+ */
1195
+ isImportantDisabled: function (element) {
1196
+ return element.hasAttribute('data-important-disabled');
1197
+ },
1198
+
1176
1199
  /**
1177
1200
  * @description In the predefined code view mode, the buttons except the executable button are changed to the 'disabled' state.
1178
1201
  * core.codeViewDisabledButtons (An array of buttons whose class name is not "se-code-view-enabled")
1179
1202
  * core.resizingDisabledButtons (An array of buttons whose class name is not "se-resizing-enabled")
1180
1203
  * @param {Boolean} disabled Disabled value
1181
1204
  * @param {Array|HTMLCollection|NodeList} buttonList Button array
1205
+ * @param {Boolean} important If priveleged mode should be used (Necessary to switch importantDisabled buttons)
1182
1206
  */
1183
- setDisabledButtons: function (disabled, buttonList) {
1207
+ setDisabledButtons: function (disabled, buttonList, important) {
1184
1208
  for (let i = 0, len = buttonList.length; i < len; i++) {
1185
- buttonList[i].disabled = disabled;
1209
+ let button = buttonList[i];
1210
+ if (important || !this.isImportantDisabled(button)) button.disabled = disabled;
1211
+ if (important) {
1212
+ if (disabled) {
1213
+ button.setAttribute('data-important-disabled', '');
1214
+ } else {
1215
+ button.removeAttribute('data-important-disabled');
1216
+ }
1217
+ }
1186
1218
  }
1187
1219
  },
1188
1220
 
@@ -1594,7 +1626,7 @@ const util = {
1594
1626
  */
1595
1627
  htmlRemoveWhiteSpace: function (html) {
1596
1628
  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(); });
1629
+ 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
1630
  },
1599
1631
 
1600
1632
  /**
@@ -1671,17 +1703,28 @@ const util = {
1671
1703
  * @returns {RegExp}
1672
1704
  */
1673
1705
  createTagsWhitelist: function (list) {
1674
- return new RegExp('<\\/?\\b(?!\\b' + list.replace(/\|/g, '\\b|\\b') + '\\b)[^>]*>', 'gi');
1706
+ return new RegExp('<\\/?\\b(?!\\b' + (list || '').replace(/\|/g, '\\b|\\b') + '\\b)[^>]*>', 'gi');
1707
+ },
1708
+
1709
+ /**
1710
+ * @description Create blacklist RegExp object.
1711
+ * Return RegExp format: new RegExp("<\\/?\\b(?:" + list + ")\\b[^>^<]*+>", "gi")
1712
+ * @param {String} list Tags list ("br|p|div|pre...")
1713
+ * @returns {RegExp}
1714
+ */
1715
+ createTagsBlacklist: function (list) {
1716
+ return new RegExp('<\\/?\\b(?:\\b' + (list || '^').replace(/\|/g, '\\b|\\b') + '\\b)[^>]*>', 'gi');
1675
1717
  },
1676
1718
 
1677
1719
  /**
1678
1720
  * @description Fix tags that do not fit the editor format.
1679
1721
  * @param {Element} documentFragment Document fragment "DOCUMENT_FRAGMENT_NODE" (nodeType === 11)
1680
1722
  * @param {RegExp} htmlCheckWhitelistRegExp Editor tags whitelist (core._htmlCheckWhitelistRegExp)
1723
+ * @param {RegExp} htmlCheckBlacklistRegExp Editor tags blacklist (core._htmlCheckBlacklistRegExp)
1681
1724
  * @param {Boolean} lowLevelCheck Row level check
1682
1725
  * @private
1683
1726
  */
1684
- _consistencyCheckOfHTML: function (documentFragment, htmlCheckWhitelistRegExp, lowLevelCheck) {
1727
+ _consistencyCheckOfHTML: function (documentFragment, htmlCheckWhitelistRegExp, htmlCheckBlacklistRegExp, lowLevelCheck) {
1685
1728
  /**
1686
1729
  * It is can use ".children(util.getListChildren)" to exclude text nodes, but "documentFragment.children" is not supported in IE.
1687
1730
  * So check the node type and exclude the text no (current.nodeType !== 1)
@@ -1693,14 +1736,14 @@ const util = {
1693
1736
  if (current.nodeType !== 1) return false;
1694
1737
 
1695
1738
  // white list
1696
- if (!htmlCheckWhitelistRegExp.test(current.nodeName) && current.childNodes.length === 0 && this.isNotCheckingNode(current)) {
1739
+ if (htmlCheckBlacklistRegExp.test(current.nodeName) || (!htmlCheckWhitelistRegExp.test(current.nodeName) && current.childNodes.length === 0 && this.isNotCheckingNode(current))) {
1697
1740
  removeTags.push(current);
1698
1741
  return false;
1699
1742
  }
1700
1743
 
1701
1744
  const nrtag = !this.getParentElement(current, this.isNotCheckingNode);
1702
1745
  // empty tags
1703
- if ((!this.isTable(current) && !this.isListCell(current)) && (this.isFormatElement(current) || this.isRangeFormatElement(current) || this.isTextStyleElement(current)) && current.childNodes.length === 0 && nrtag) {
1746
+ 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
1747
  emptyTags.push(current);
1705
1748
  return false;
1706
1749
  }
@@ -1762,17 +1805,23 @@ const util = {
1762
1805
 
1763
1806
  for (let i = 0, len = wrongList.length, t, tp, children, p; i < len; i++) {
1764
1807
  t = wrongList[i];
1808
+ p = t.parentNode;
1809
+ if (!p) continue;
1765
1810
 
1766
1811
  tp = this.createElement('LI');
1767
- children = t.childNodes;
1768
- while (children[0]) {
1769
- tp.appendChild(children[0]);
1812
+
1813
+ if (this.isFormatElement(t)) {
1814
+ children = t.childNodes;
1815
+ while (children[0]) {
1816
+ tp.appendChild(children[0]);
1817
+ }
1818
+ p.insertBefore(tp, t);
1819
+ this.removeItem(t);
1820
+ } else {
1821
+ t = t.nextSibling;
1822
+ tp.appendChild(wrongList[i]);
1823
+ p.insertBefore(tp, t);
1770
1824
  }
1771
-
1772
- p = t.parentNode;
1773
- if (!p) continue;
1774
- p.insertBefore(tp, t);
1775
- this.removeItem(t);
1776
1825
  }
1777
1826
 
1778
1827
  for (let i = 0, len = withoutFormatCells.length, t, f; i < len; i++) {
package/src/options.d.ts CHANGED
@@ -31,15 +31,15 @@ export interface SunEditorOptions {
31
31
  /**
32
32
  * Add tags to the default tags whitelist of editor.
33
33
  */
34
- addTagsWhitelist?: string;
35
- /**
36
- * Whitelist of tags when pasting.
37
- */
38
- pasteTagsWhitelist?: string;
34
+ addTagsWhitelist?: string | '*';
39
35
  /**
40
36
  * Blacklist of the editor default tags.
41
37
  */
42
38
  tagsBlacklist?: string;
39
+ /**
40
+ * Whitelist of tags when pasting.
41
+ */
42
+ pasteTagsWhitelist?: string | '*';
43
43
  /**
44
44
  * Blacklist of tags when pasting.
45
45
  */
@@ -47,7 +47,11 @@ export interface SunEditorOptions {
47
47
  /**
48
48
  * Add attributes whitelist of tags that should be kept undeleted from the editor.
49
49
  */
50
- attributesWhitelist?: Record<string, string>;
50
+ attributesWhitelist?: Record<string, string | '*'>;
51
+ /**
52
+ * Add attribute blacklist of tags that should be deleted in editor.
53
+ */
54
+ attributesBlacklist?: Record<string, string | '*'>;
51
55
  /**
52
56
  * Layout
53
57
  * ======
@@ -60,6 +64,13 @@ export interface SunEditorOptions {
60
64
  * If true, the editor is set to RTL(Right To Left) mode.
61
65
  */
62
66
  rtl?: boolean;
67
+ /**
68
+ * Deletes other attributes except for the property set at the time of line break.
69
+ * If there is no value, no all attribute is deleted.
70
+ * @example 'class|style': Attributes other than "class" and "style" are deleted at line break.
71
+ * '*': All attributes are deleted at line break.
72
+ */
73
+ lineAttrReset?: string;
63
74
  /**
64
75
  * Button List
65
76
  */
@@ -91,6 +102,10 @@ export interface SunEditorOptions {
91
102
  * Allows the usage of HTML, HEAD, BODY tags and DOCTYPE declaration
92
103
  */
93
104
  fullPage?: boolean;
105
+ /**
106
+ * Attributes of the iframe.
107
+ */
108
+ iframeAttributes?: Record<string, string>;
94
109
  /**
95
110
  * Name of the CSS file(s) to apply inside the iframe.
96
111
  */
@@ -147,6 +162,16 @@ export interface SunEditorOptions {
147
162
  * Displays the current node structure to resizingBar
148
163
  */
149
164
  showPathLabel?: boolean;
165
+ /**
166
+ * Enable/disable resize function of bottom resizing bar.
167
+ */
168
+ resizeEnable?: boolean;
169
+ /**
170
+ * A custom HTML selector placing the resizing bar inside.
171
+ The class name of the element must be 'sun-editor'.
172
+ Element or querySelector argument.
173
+ */
174
+ resizingBarContainer?: HTMLElement | string;
150
175
  /**
151
176
  * Character count
152
177
  * ===============
@@ -212,9 +237,13 @@ export interface SunEditorOptions {
212
237
  */
213
238
  fontSize?: number[];
214
239
  /**
215
- *
240
+ * The font size unit
216
241
  */
217
242
  fontSizeUnit?: string;
243
+ /**
244
+ * A list of drop-down options for the 'align' plugin.
245
+ */
246
+ alignItems?: ('left' | 'center' | 'right' | 'justify')[];
218
247
  /**
219
248
  * Change default formatBlock array
220
249
  */
@@ -247,6 +276,10 @@ export interface SunEditorOptions {
247
276
  * Choose whether the image height input is visible.
248
277
  */
249
278
  imageHeightShow?: boolean;
279
+ /**
280
+ * Choose whether the image align radio buttons are visible.
281
+ */
282
+ imageAlignShow?: boolean;
250
283
  /**
251
284
  * The default width size of the image frame
252
285
  */
@@ -296,7 +329,7 @@ export interface SunEditorOptions {
296
329
  imageMultipleFile?: boolean;
297
330
  /**
298
331
  * Define the "accept" attribute of the input.
299
- * ex) "*" or ".jpg, .png .."
332
+ * @example "*" or ".jpg, .png .."
300
333
  */
301
334
  imageAccept?: string;
302
335
  /**
@@ -323,6 +356,10 @@ export interface SunEditorOptions {
323
356
  * Choose whether the video height input is visible.
324
357
  */
325
358
  videoHeightShow?: boolean;
359
+ /**
360
+ * Choose whether the video align radio buttons are visible.
361
+ */
362
+ videoAlignShow?: boolean;
326
363
  /**
327
364
  * Choose whether the video ratio options is visible.
328
365
  */
@@ -455,6 +492,10 @@ export interface SunEditorOptions {
455
492
  * Link
456
493
  * =====
457
494
  */
495
+ /**
496
+ * Default checked value of the "Open in new window" checkbox.
497
+ */
498
+ linkTargetNewWindow?: boolean;
458
499
  /**
459
500
  * Protocol for the links (if link has been added without any protocol this one will be used).
460
501
  */
@@ -467,6 +508,20 @@ export interface SunEditorOptions {
467
508
  * Defines default "rel" attribute list of anchor tag.
468
509
  */
469
510
  linkRelDefault?: {default?: string; check_new_window?: string; check_bookmark?: string;};
511
+ /**
512
+ * If true, disables the automatic prefixing of the host URL to the value of the link. default: false {Boolean}
513
+ */
514
+ linkNoPrefix?: boolean;
515
+ /*
516
+ * HR
517
+ * =====
518
+ */
519
+ /**
520
+ * Defines the hr items.
521
+ * "class" or "style" must be specified.
522
+ * @example [{name: "solid", class: "__se__xxx", style: "border-style: outset;"}]
523
+ */
524
+ hrItems?: { name: string; class?: string; style?: string }[];
470
525
  /**
471
526
  * Key actions
472
527
  * =====
@@ -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
 
@@ -51,6 +51,7 @@ export default {
51
51
  _resizing: options.videoResizing,
52
52
  _resizeDotHide: !options.videoHeightShow,
53
53
  _rotation: options.videoRotation,
54
+ _alignHide: !options.videoAlignShow,
54
55
  _onlyPercentage: options.videoSizeOnlyPercentage,
55
56
  _ratio: false,
56
57
  _ratioX: 1,
@@ -175,7 +176,7 @@ export default {
175
176
  html += '' +
176
177
  '</div>' +
177
178
  '<div class="se-dialog-footer">' +
178
- '<div>' +
179
+ '<div' + (option.videoAlignShow ? '' : ' style="display: none"') + '>' +
179
180
  '<label><input type="radio" name="suneditor_video_radio" class="se-dialog-btn-radio" value="none" checked>' + lang.dialogBox.basic + '</label>' +
180
181
  '<label><input type="radio" name="suneditor_video_radio" class="se-dialog-btn-radio" value="left">' + lang.dialogBox.left + '</label>' +
181
182
  '<label><input type="radio" name="suneditor_video_radio" class="se-dialog-btn-radio" value="center">' + lang.dialogBox.center + '</label>' +
@@ -372,7 +373,7 @@ export default {
372
373
  videoPlugin.submitAction.call(this, this.context.video.videoInputFile.files);
373
374
  } else if (contextVideo.videoUrlFile && contextVideo._linkValue.length > 0) {
374
375
  this.showLoading();
375
- videoPlugin.setup_url.call(this);
376
+ videoPlugin.setup_url.call(this, contextVideo._linkValue);
376
377
  }
377
378
  } catch (error) {
378
379
  this.closeLoading();
@@ -503,12 +504,11 @@ export default {
503
504
  this.closeLoading();
504
505
  },
505
506
 
506
- setup_url: function () {
507
+ setup_url: function (url) {
507
508
  try {
508
509
  const contextVideo = this.context.video;
509
- let url = contextVideo._linkValue;
510
-
511
- if (url.length === 0) return false;
510
+ if (!url) url = contextVideo._linkValue;
511
+ if (!url) return false;
512
512
 
513
513
  /** iframe source */
514
514
  if (/^<iframe.*\/iframe>$/.test(url)) {
@@ -540,7 +540,7 @@ export default {
540
540
  url = 'https://player.vimeo.com/video/' + url.slice(url.lastIndexOf('/') + 1);
541
541
  }
542
542
 
543
- this.plugins.video.create_video.call(this, this.plugins.video.createIframeTag.call(this), url, contextVideo.inputX.value, contextVideo.inputY.value, contextVideo._align, null, this.context.dialog.updateModal);
543
+ this.plugins.video.create_video.call(this, this.plugins.video[(!/youtu\.?be/.test(url) && !/vimeo\.com/.test(url) ? "createVideoTag" : "createIframeTag")].call(this), url, contextVideo.inputX.value, contextVideo.inputY.value, contextVideo._align, null, this.context.dialog.updateModal);
544
544
  } catch (error) {
545
545
  throw Error('[SUNEDITOR.video.upload.fail] cause : "' + error.message + '"');
546
546
  } finally {
@@ -645,10 +645,8 @@ export default {
645
645
  if (/^video$/i.test(oFrame.nodeName)) this.plugins.video._setTagAttrs.call(this, oFrame);
646
646
  else this.plugins.video._setIframeAttrs.call(this, oFrame);
647
647
 
648
- const existElement = this.util.getParentElement(oFrame, this.util.isMediaComponent) ||
649
- this.util.getParentElement(oFrame, function (current) {
650
- return this.isWysiwygDiv(current.parentNode);
651
- }.bind(this.util));
648
+ const existElement = (this.util.isRangeFormatElement(oFrame.parentNode) || this.util.isWysiwygDiv(oFrame.parentNode)) ?
649
+ oFrame : this.util.getFormatElement(oFrame) || oFrame;
652
650
 
653
651
  const prevFrame = oFrame;
654
652
  contextVideo._element = oFrame = oFrame.cloneNode(true);
@@ -673,7 +671,9 @@ export default {
673
671
  if (format) contextVideo._align = format.style.textAlign || format.style.float;
674
672
  this.plugins.video.setAlign.call(this, null, oFrame, cover, container);
675
673
 
676
- if (this.util.isFormatElement(existElement) && existElement.childNodes.length > 0) {
674
+ if (this.util.isListCell(existElement) || this.util.isFormatElement(existElement)) {
675
+ prevFrame.parentNode.replaceChild(container, prevFrame);
676
+ } else if (this.util.isFormatElement(existElement) && existElement.childNodes.length > 0) {
677
677
  existElement.parentNode.insertBefore(container, existElement);
678
678
  this.util.removeItem(prevFrame);
679
679
  // clean format tag
@@ -732,7 +732,7 @@ export default {
732
732
  const contextVideo = this.context.video;
733
733
 
734
734
  if (contextVideo.videoUrlFile) contextVideo._linkValue = contextVideo.preview.textContent = contextVideo.videoUrlFile.value = (contextVideo._element.src || (contextVideo._element.querySelector('source') || '').src || '');
735
- contextVideo.modal.querySelector('input[name="suneditor_video_radio"][value="' + contextVideo._align + '"]').checked = true;
735
+ (contextVideo.modal.querySelector('input[name="suneditor_video_radio"][value="' + contextVideo._align + '"]') || contextVideo.modal.querySelector('input[name="suneditor_video_radio"][value="none"]')).checked = true;
736
736
 
737
737
  if (contextVideo._resizing) {
738
738
  this.plugins.resizing._module_setModifyInputSize.call(this, contextVideo, this.plugins.video);
@@ -49,7 +49,7 @@ export default {
49
49
  */
50
50
  drawItems: function (item) {
51
51
  const srcName = item.src.split('/').pop();
52
- return '<div class="se-file-item-img"><img src="' + item.src + '" alt="' + (item.alt || srcName) + '" data-command="pick">' +
52
+ return '<div class="se-file-item-img"><img src="' + (item.thumbnail || item.src) + '" alt="' + (item.alt || srcName) + '" data-command="pick" data-value="' + (item.src || item.thumbnail) + '">' +
53
53
  '<div class="se-file-img-name se-file-name-back"></div>' +
54
54
  '<div class="se-file-img-name __se__img_name">' + (item.name || srcName) + '</div>' +
55
55
  '</div>';
@@ -58,8 +58,7 @@ export default {
58
58
  setImage: function (target) {
59
59
  this.callPlugin('image', function () {
60
60
  const file = {name: target.parentNode.querySelector('.__se__img_name').textContent, size: 0};
61
- this.context.image._altText = target.alt;
62
- this.plugins.image.create_image.call(this, target.src, null, this.context.image._origin_w, this.context.image._origin_h, 'none', file);
61
+ this.plugins.image.create_image.call(this, target.getAttribute('data-value'), null, this.context.image._origin_w, this.context.image._origin_h, 'none', file, target.alt);
63
62
  }.bind(this), null);
64
63
  }
65
64
  };