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
@@ -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);
@@ -817,12 +817,13 @@ export default {
817
817
 
818
818
  if (!onlyH) w = this.util.getNumber(w, 0);
819
819
  if (!onlyW) h = this.util.isNumber(h) ? h + contextVideo.sizeUnit : !h ? '' : h;
820
+ w ? w + contextVideo.sizeUnit : '';
820
821
 
821
- if (!onlyH) contextVideo._element.style.width = w ? w + contextVideo.sizeUnit : '';
822
+ if (!onlyH) contextVideo._element.style.width = w;
822
823
  if (!onlyW) contextVideo._cover.style.paddingBottom = contextVideo._cover.style.height = h;
823
824
 
824
825
  if (!onlyH && !/%$/.test(w)) {
825
- contextVideo._cover.style.width = '';
826
+ contextVideo._cover.style.width = w;
826
827
  contextVideo._container.style.width = '';
827
828
  }
828
829
 
@@ -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
  };
@@ -119,24 +119,32 @@ export default {
119
119
  },
120
120
 
121
121
  on: function (contextAnchor, update) {
122
+ const anchorPlugin = this.plugins.anchor;
123
+
122
124
  if (!update) {
123
- this.plugins.anchor.init.call(this, contextAnchor);
124
- contextAnchor.anchorText.value = this.getSelection().toString();
125
+ anchorPlugin.init.call(this, contextAnchor);
126
+ contextAnchor.anchorText.value = this.getSelection().toString().trim();
127
+ contextAnchor.newWindowCheck.checked = this.options.linkTargetNewWindow;
125
128
  } else if (contextAnchor.linkAnchor) {
126
129
  this.context.dialog.updateModal = true;
127
- const href = contextAnchor.linkAnchor.href;
128
- contextAnchor.linkValue = contextAnchor.preview.textContent = contextAnchor.urlInput.value = /\#.+$/.test(href) ? href.substr(href.lastIndexOf('#')) : href;
129
- contextAnchor.anchorText.value = contextAnchor.linkAnchor.textContent.trim() || contextAnchor.linkAnchor.getAttribute('alt');
130
+ const href = this.options.linkNoPrefix ? contextAnchor.linkAnchor.href.replace(contextAnchor.linkAnchor.origin + '/', '') : contextAnchor.linkAnchor.href;
131
+ contextAnchor.linkValue = contextAnchor.preview.textContent = contextAnchor.urlInput.value = anchorPlugin.selfPathBookmark.call(this, href) ? href.substr(href.lastIndexOf('#')) : href;
132
+ contextAnchor.anchorText.value = contextAnchor.linkAnchor.textContent || contextAnchor.linkAnchor.getAttribute('alt');
130
133
  contextAnchor.newWindowCheck.checked = (/_blank/i.test(contextAnchor.linkAnchor.target) ? true : false);
131
134
  contextAnchor.downloadCheck.checked = contextAnchor.linkAnchor.download;
132
135
  }
133
136
 
134
137
  this.context.anchor.callerContext = contextAnchor;
135
- this.plugins.anchor.setRel.call(this, contextAnchor, (update && contextAnchor.linkAnchor) ? contextAnchor.linkAnchor.rel : contextAnchor.defaultRel);
136
- this.plugins.anchor.setLinkPreview.call(this, contextAnchor, contextAnchor.linkValue);
138
+ anchorPlugin.setRel.call(this, contextAnchor, (update && contextAnchor.linkAnchor) ? contextAnchor.linkAnchor.rel : contextAnchor.defaultRel);
139
+ anchorPlugin.setLinkPreview.call(this, contextAnchor, contextAnchor.linkValue);
137
140
  this.plugins.selectMenu.on.call(this, contextAnchor.callerName, this.plugins.anchor.setHeaderBookmark);
138
141
  },
139
142
 
143
+ selfPathBookmark: function(path) {
144
+ const href = this._w.location.href.replace(/\/$/, '');
145
+ return path.indexOf('#') === 0 || (path.indexOf(href) === 0 && path.indexOf('#') === (href.indexOf("#") === -1 ? href.length : href.substr(0, href.indexOf("#")).length));
146
+ },
147
+
140
148
  _closeRelMenu: null,
141
149
  toggleRelList: function (contextAnchor, show) {
142
150
  if (!show) {
@@ -277,13 +285,13 @@ export default {
277
285
  const value = e.target.value.trim();
278
286
  this.plugins.anchor.setLinkPreview.call(this, contextAnchor, value);
279
287
 
280
- if (/^#/.test(value)) this.plugins.anchor.createHeaderList.call(this, contextAnchor, this.context.selectMenu.callerContext, value);
288
+ if (this.plugins.anchor.selfPathBookmark.call(this, value)) this.plugins.anchor.createHeaderList.call(this, contextAnchor, this.context.selectMenu.callerContext, value);
281
289
  else this.plugins.selectMenu.close.call(this, this.context.selectMenu.callerContext);
282
290
  },
283
291
 
284
292
  onFocusUrlInput: function (contextAnchor, contextLink) {
285
293
  const value = contextAnchor.urlInput.value;
286
- if (/^#/.test(value)) this.plugins.anchor.createHeaderList.call(this, contextAnchor, contextLink, value);
294
+ if (this.plugins.anchor.selfPathBookmark.call(this, value)) this.plugins.anchor.createHeaderList.call(this, contextAnchor, contextLink, value);
287
295
  },
288
296
 
289
297
  onBlurUrlInput: function (contextList) {
@@ -293,11 +301,12 @@ export default {
293
301
  setLinkPreview: function (context, value) {
294
302
  const preview = context.preview;
295
303
  const protocol = this.options.linkProtocol;
304
+ const noPrefix = this.options.linkNoPrefix;
296
305
  const reservedProtocol = /^(mailto\:|tel\:|sms\:|https*\:\/\/|#)/.test(value);
297
306
  const sameProtocol = !protocol ? false : this._w.RegExp('^' + value.substr(0, protocol.length)).test(protocol);
298
- context.linkValue = preview.textContent = !value ? '' : (protocol && !reservedProtocol && !sameProtocol) ? protocol + value : reservedProtocol ? value : /^www\./.test(value) ? 'http://' + value : this.context.anchor.host + (/^\//.test(value) ? '' : '/') + value;
307
+ value = context.linkValue = preview.textContent = !value ? '' : noPrefix ? value : (protocol && !reservedProtocol && !sameProtocol) ? protocol + value : reservedProtocol ? value : /^www\./.test(value) ? 'http://' + value : this.context.anchor.host + (/^\//.test(value) ? '' : '/') + value;
299
308
 
300
- if (value.indexOf('#') === 0) {
309
+ if (this.plugins.anchor.selfPathBookmark.call(this, value)) {
301
310
  context.bookmark.style.display = 'block';
302
311
  this.util.addClass(context.bookmarkButton, 'active');
303
312
  } else {
@@ -305,7 +314,7 @@ export default {
305
314
  this.util.removeClass(context.bookmarkButton, 'active');
306
315
  }
307
316
 
308
- if (value.indexOf('#') === -1 && context.downloadCheck.checked) {
317
+ if (!this.plugins.anchor.selfPathBookmark.call(this, value) && context.downloadCheck.checked) {
309
318
  context.download.style.display = 'block';
310
319
  } else {
311
320
  context.download.style.display = 'none';
@@ -321,7 +330,7 @@ export default {
321
330
 
322
331
  updateAnchor: function (anchor, url, alt, contextAnchor, notText) {
323
332
  // download
324
- if (!/^\#/.test(url) && contextAnchor.downloadCheck.checked) {
333
+ if (!this.plugins.anchor.selfPathBookmark.call(this, url) && contextAnchor.downloadCheck.checked) {
325
334
  anchor.setAttribute('download', alt || url);
326
335
  } else {
327
336
  anchor.removeAttribute('download');
@@ -354,7 +363,7 @@ export default {
354
363
  const anchorText = anchor.value.length === 0 ? url : anchor.value;
355
364
 
356
365
  const oA = contextAnchor.linkAnchor || this.util.createElement('A');
357
- this.plugins.anchor.updateAnchor(oA, url, anchorText, contextAnchor, notText);
366
+ this.plugins.anchor.updateAnchor.call(this, oA, url, anchorText, contextAnchor, notText);
358
367
 
359
368
  contextAnchor.linkValue = contextAnchor.preview.textContent = contextAnchor.urlInput.value = contextAnchor.anchorText.value = '';
360
369
 
@@ -363,7 +372,7 @@ export default {
363
372
 
364
373
  onClick_bookmarkButton: function (contextAnchor) {
365
374
  let url = contextAnchor.urlInput.value;
366
- if (/^\#/.test(url)) {
375
+ if (this.plugins.anchor.selfPathBookmark.call(this, url)) {
367
376
  url = url.substr(1);
368
377
  contextAnchor.bookmark.style.display = 'none';
369
378
  this.util.removeClass(contextAnchor.bookmarkButton, 'active');
@@ -22,4 +22,4 @@ declare interface component extends Module {
22
22
  create_caption(): string;
23
23
  }
24
24
 
25
- export default resizing;
25
+ export default component;
@@ -229,7 +229,12 @@
229
229
  this.plugins.fileBrowser._xmlHttp = null;
230
230
  if (xmlHttp.status === 200) {
231
231
  try {
232
- this.plugins.fileBrowser._drawListItem.call(this, JSON.parse(xmlHttp.responseText).result, true);
232
+ const res = JSON.parse(xmlHttp.responseText);
233
+ if (res.result.length > 0) {
234
+ this.plugins.fileBrowser._drawListItem.call(this, res.result, true);
235
+ } else if (res.nullMessage) {
236
+ this.context.fileBrowser.list.innerHTML = res.nullMessage;
237
+ }
233
238
  } catch (e) {
234
239
  throw Error('[SUNEDITOR.fileBrowser.drawList.fail] cause : "' + e.message + '"');
235
240
  } finally {
@@ -28,8 +28,6 @@
28
28
  _checkMediaComponent: function (tag) {
29
29
  if (/IMG/i.test(tag)) {
30
30
  return !/FIGURE/i.test(tag.parentElement.nodeName) || !/FIGURE/i.test(tag.parentElement.parentElement.nodeName);
31
- } else if (/VIDEO/i.test(tag)) {
32
- return !/FIGURE/i.test(tag.parentElement.nodeName);
33
31
  }
34
32
  return true;
35
33
  },
@@ -98,7 +96,7 @@
98
96
  checkInfo: function (pluginName, tagNames, uploadEventHandler, modifyHandler, resizing) {
99
97
  let tags = [];
100
98
  for (let i = 0, len = tagNames.length; i < len; i++) {
101
- tags = tags.concat([].slice.call(this.context.element.wysiwyg.getElementsByTagName(tagNames[i])));
99
+ tags = tags.concat([].slice.call(this.context.element.wysiwyg.querySelectorAll(tagNames[i] + ':not([data-se-embed="true"])')));
102
100
  }
103
101
 
104
102
  const fileManagerPlugin = this.plugins.fileManager;
@@ -437,11 +437,16 @@
437
437
  }
438
438
 
439
439
  // align icon
440
- const alignList = contextResizing.alignMenuList;
441
- this.util.changeElement(contextResizing.alignButton.firstElementChild, contextResizing.alignIcons[align]);
442
- for (let i = 0, len = alignList.length; i < len; i++) {
443
- if (alignList[i].getAttribute('data-value') === align) this.util.addClass(alignList[i], 'on');
444
- else this.util.removeClass(alignList[i], 'on');
440
+ if (contextPlugin._alignHide) {
441
+ contextResizing.alignButton.style.display = 'none';
442
+ } else {
443
+ contextResizing.alignButton.style.display = '';
444
+ const alignList = contextResizing.alignMenuList;
445
+ this.util.changeElement(contextResizing.alignButton.firstElementChild, contextResizing.alignIcons[align]);
446
+ for (let i = 0, len = alignList.length; i < len; i++) {
447
+ if (alignList[i].getAttribute('data-value') === align) this.util.addClass(alignList[i], 'on');
448
+ else this.util.removeClass(alignList[i], 'on');
449
+ }
445
450
  }
446
451
 
447
452
  // percentage active
@@ -478,7 +483,7 @@
478
483
  }
479
484
 
480
485
  this.setControllerPosition(contextResizing.resizeButton, resizeContainer, 'bottom', addOffset);
481
- this.controllersOn(resizeContainer, contextResizing.resizeButton, this.util.setDisabledButtons.bind(this, false, this.resizingDisabledButtons), targetElement, plugin);
486
+ this.controllersOn(resizeContainer, contextResizing.resizeButton, this.util.setDisabledButtons.bind(this.util, false, this.resizingDisabledButtons), targetElement, plugin);
482
487
  this.util.setDisabledButtons(true, this.resizingDisabledButtons);
483
488
 
484
489
  contextResizing._resize_w = w;
@@ -15,9 +15,10 @@ export default {
15
15
  const context = core.context;
16
16
  context.align = {
17
17
  targetButton: targetElement,
18
+ _itemMenu: null,
18
19
  _alignList: null,
19
20
  currentAlign: '',
20
- defaultDir: core.options.rtl ? 'right' : 'left',
21
+ defaultDir: core.options.rtl ? 'right' : 'left',
21
22
  icons: {
22
23
  justify: icons.align_justify,
23
24
  left: icons.align_left,
@@ -28,7 +29,7 @@ export default {
28
29
 
29
30
  /** set submenu */
30
31
  let listDiv = this.setSubmenu(core);
31
- let listUl = listDiv.querySelector('ul');
32
+ let listUl = context.align._itemMenu = listDiv.querySelector('ul');
32
33
 
33
34
  /** add event listeners */
34
35
  listUl.addEventListener('click', this.pickup.bind(core));
@@ -45,36 +46,24 @@ export default {
45
46
  const lang = core.lang;
46
47
  const icons = core.icons;
47
48
  const listDiv = core.util.createElement('DIV');
48
- const leftDir = core.context.align.defaultDir === 'left';
49
-
50
- const leftMenu = '<li>' +
51
- '<button type="button" class="se-btn-list se-btn-align" data-command="justifyleft" data-value="left" title="' + lang.toolbar.alignLeft + '">' +
52
- '<span class="se-list-icon">' + icons.align_left + '</span>' + lang.toolbar.alignLeft +
53
- '</button>' +
54
- '</li>';
55
-
56
- const rightMenu = '<li>' +
57
- '<button type="button" class="se-btn-list se-btn-align" data-command="justifyright" data-value="right" title="' + lang.toolbar.alignRight + '">' +
58
- '<span class="se-list-icon">' + icons.align_right +'</span>' + lang.toolbar.alignRight +
59
- '</button>' +
60
- '</li>';
49
+ const alignItems = core.options.alignItems;
50
+
51
+ let html = '';
52
+ for (let i = 0, item, text; i < alignItems.length; i++) {
53
+ item = alignItems[i];
54
+ text = lang.toolbar['align' + item.charAt(0).toUpperCase() + item.slice(1)];
55
+ html += '<li>' +
56
+ '<button type="button" class="se-btn-list se-btn-align" data-value="' + item + '" title="' + text + '">' +
57
+ '<span class="se-list-icon">' + icons['align_' + item] + '</span>' + text +
58
+ '</button>' +
59
+ '</li>';
60
+ }
61
61
 
62
62
  listDiv.className = 'se-submenu se-list-layer se-list-align';
63
63
  listDiv.innerHTML = '' +
64
64
  '<div class="se-list-inner">' +
65
65
  '<ul class="se-list-basic">' +
66
- (leftDir ? leftMenu : rightMenu) +
67
- '<li>' +
68
- '<button type="button" class="se-btn-list se-btn-align" data-command="justifycenter" data-value="center" title="' + lang.toolbar.alignCenter + '">' +
69
- '<span class="se-list-icon">' + icons.align_center + '</span>' + lang.toolbar.alignCenter +
70
- '</button>' +
71
- '</li>' +
72
- (leftDir? rightMenu : leftMenu) +
73
- '<li>' +
74
- '<button type="button" class="se-btn-list se-btn-align" data-command="justifyfull" data-value="justify" title="' + lang.toolbar.alignJustify + '">' +
75
- '<span class="se-list-icon">' + icons.align_justify + '</span>' + lang.toolbar.alignJustify +
76
- '</button>' +
77
- '</li>' +
66
+ html +
78
67
  '</ul>' +
79
68
  '</div>';
80
69
 
@@ -125,6 +114,22 @@ export default {
125
114
  }
126
115
  },
127
116
 
117
+ exchangeDir: function () {
118
+ const dir = this.options.rtl ? 'right' : 'left';
119
+ if (!this.context.align || this.context.align.defaultDir === dir) return;
120
+
121
+ this.context.align.defaultDir = dir;
122
+ let menu = this.context.align._itemMenu;
123
+ let leftBtn = menu.querySelector('[data-value="left"]');
124
+ let rightBtn = menu.querySelector('[data-value="right"]');
125
+ if (leftBtn && rightBtn) {
126
+ const lp = leftBtn.parentElement;
127
+ const rp = rightBtn.parentElement;
128
+ lp.appendChild(rightBtn);
129
+ rp.appendChild(leftBtn);
130
+ }
131
+ },
132
+
128
133
  pickup: function (e) {
129
134
  e.preventDefault();
130
135
  e.stopPropagation();
@@ -78,7 +78,7 @@ export default {
78
78
  if (!element) {
79
79
  const font = this.hasFocus ? this.wwComputedStyle.fontFamily : this.lang.toolbar.font;
80
80
  this.util.changeTxt(target, font);
81
- this.util.changeTxt(tooltip, this.hasFocus ? this.lang.toolbar.font + ' (' + font + ')' : font);
81
+ this.util.changeTxt(tooltip, this.hasFocus ? this.lang.toolbar.font + (font ? ' (' + font + ')' : '') : font);
82
82
  } else if (element.style && element.style.fontFamily.length > 0) {
83
83
  const selectFont = element.style.fontFamily.replace(/["']/g,'');
84
84
  this.util.changeTxt(target, selectFont);
@@ -61,7 +61,7 @@ export default {
61
61
  */
62
62
  active: function (element) {
63
63
  if (!element) {
64
- this.util.changeTxt(this.context.fontSize.targetText, this.hasFocus ? this.wwComputedStyle.fontSize : this.lang.toolbar.fontSize);
64
+ this.util.changeTxt(this.context.fontSize.targetText, this.hasFocus ? (this.options.__defaultFontSize || this.wwComputedStyle.fontSize) : this.lang.toolbar.fontSize);
65
65
  } else if (element.style && element.style.fontSize.length > 0) {
66
66
  this.util.changeTxt(this.context.fontSize.targetText, element.style.fontSize);
67
67
  return true;
@@ -31,26 +31,22 @@ export default {
31
31
  setSubmenu: function (core) {
32
32
  const lang = core.lang;
33
33
  const listDiv = core.util.createElement('DIV');
34
-
34
+ const items = core.options.hrItems || [{name: lang.toolbar.hr_solid, class: '__se__solid'}, {name: lang.toolbar.hr_dashed, class: '__se__dashed'}, {name: lang.toolbar.hr_dotted, class: '__se__dotted'}];
35
+
36
+ let list = '';
37
+ for (let i = 0, len = items.length; i < len; i++) {
38
+ list += '<li>' +
39
+ '<button type="button" class="se-btn-list btn_line" data-command="horizontalRule" data-value="' + items[i].class + '" title="' + items[i].name + '">' +
40
+ '<hr' + (items[i].class ? ' class="' + items[i].class + '"' : '') + (items[i].style ? ' style="' + items[i].style + '"' : '') + '/>' +
41
+ '</button>' +
42
+ '</li>';
43
+ }
44
+
35
45
  listDiv.className = 'se-submenu se-list-layer se-list-line';
36
46
  listDiv.innerHTML = '' +
37
47
  '<div class="se-list-inner">' +
38
48
  '<ul class="se-list-basic">' +
39
- '<li>' +
40
- '<button type="button" class="se-btn-list btn_line" data-command="horizontalRule" data-value="solid" title="' + lang.toolbar.hr_solid + '">' +
41
- '<hr style="border-width: 1px 0 0; border-style: solid none none; border-color: black; border-image: initial; height: 1px;" />' +
42
- '</button>' +
43
- '</li>' +
44
- '<li>' +
45
- '<button type="button" class="se-btn-list btn_line" data-command="horizontalRule" data-value="dotted" title="' + lang.toolbar.hr_dotted + '">' +
46
- '<hr style="border-width: 1px 0 0; border-style: dotted none none; border-color: black; border-image: initial; height: 1px;" />' +
47
- '</button>' +
48
- '</li>' +
49
- '<li>' +
50
- '<button type="button" class="se-btn-list btn_line" data-command="horizontalRule" data-value="dashed" title="' + lang.toolbar.hr_dashed + '">' +
51
- '<hr style="border-width: 1px 0 0; border-style: dashed none none; border-color: black; border-image: initial; height: 1px;" />' +
52
- '</button>' +
53
- '</li>' +
49
+ list +
54
50
  '</ul>' +
55
51
  '</div>';
56
52
 
@@ -74,11 +70,9 @@ export default {
74
70
  return false;
75
71
  },
76
72
 
77
- appendHr: function (className) {
78
- const oHr = this.util.createElement('HR');
79
- oHr.className = className;
73
+ appendHr: function (hrTemp) {
80
74
  this.focus();
81
- return this.insertComponent(oHr, false, true, false);
75
+ return this.insertComponent(hrTemp.cloneNode(false), false, true, false);
82
76
  },
83
77
 
84
78
  horizontalRulePick: function (e) {
@@ -86,16 +80,16 @@ export default {
86
80
  e.stopPropagation();
87
81
 
88
82
  let target = e.target;
89
- let value = null;
83
+ let command = target.getAttribute('data-command');
90
84
 
91
- while (!value && !/UL/i.test(target.tagName)) {
92
- value = target.getAttribute('data-value');
85
+ while (!command && !/UL/i.test(target.tagName)) {
93
86
  target = target.parentNode;
87
+ command = target.getAttribute('data-command');
94
88
  }
95
89
 
96
- if (!value) return;
90
+ if (!command) return;
97
91
 
98
- const oNode = this.plugins.horizontalRule.appendHr.call(this, '__se__' + value);
92
+ const oNode = this.plugins.horizontalRule.appendHr.call(this, target.firstElementChild);
99
93
  if (oNode) {
100
94
  this.setRange(oNode, 0, oNode, 0);
101
95
  this.submenuOff();
@@ -65,11 +65,7 @@ export default {
65
65
  const icon = button.firstElementChild;
66
66
  const util = this.util;
67
67
 
68
- if (!element) {
69
- button.removeAttribute('data-focus');
70
- util.changeElement(icon, this.context.list.icons.number);
71
- util.removeClass(button, 'active');
72
- } else if (util.isList(element)) {
68
+ if (util.isList(element)) {
73
69
  const nodeName = element.nodeName;
74
70
  button.setAttribute('data-focus', nodeName);
75
71
  util.addClass(button, 'active');
@@ -80,6 +76,10 @@ export default {
80
76
  }
81
77
 
82
78
  return true;
79
+ } else {
80
+ button.removeAttribute('data-focus');
81
+ util.changeElement(icon, this.context.list.icons.number);
82
+ util.removeClass(button, 'active');
83
83
  }
84
84
 
85
85
  return false;
@@ -234,6 +234,14 @@ export default {
234
234
 
235
235
  newCell = util.createElement('LI');
236
236
  util.copyFormatAttributes(newCell, fTag);
237
+
238
+ if (i === 0 && originRange.sc === fTag) {
239
+ originRange.sc = newCell;
240
+ }
241
+ if (i === len - 1 && originRange.ec === fTag) {
242
+ originRange.ec = newCell;
243
+ }
244
+
237
245
  if (util.isComponent(fTag)) {
238
246
  const isHR = /^HR$/i.test(fTag.nodeName);
239
247
  if (!isHR) newCell.innerHTML = '<br>';
@@ -12,7 +12,9 @@ export default {
12
12
  display: 'submenu',
13
13
  add: function (core, targetElement) {
14
14
  const context = core.context;
15
- context.template = {};
15
+ context.template = {
16
+ selectedIndex: -1
17
+ };
16
18
 
17
19
  /** set submenu */
18
20
  let templateDiv = this.setSubmenu(core);
@@ -55,7 +57,8 @@ export default {
55
57
  e.preventDefault();
56
58
  e.stopPropagation();
57
59
 
58
- const temp = this.options.templates[e.target.getAttribute('data-value')];
60
+ this.context.template.selectedIndex = e.target.getAttribute('data-value') * 1;
61
+ const temp = this.options.templates[this.context.template.selectedIndex];
59
62
 
60
63
  if (temp.html) {
61
64
  this.setContents(temp.html);