suneditor 2.47.0 → 2.47.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suneditor",
3
- "version": "2.47.0",
3
+ "version": "2.47.1",
4
4
  "description": "Vanilla javascript based WYSIWYG web editor, with no dependencies",
5
5
  "author": "JiHong.Lee",
6
6
  "license": "MIT",
@@ -416,6 +416,7 @@ export default {
416
416
  options.plugins = plugins;
417
417
  /** Values */
418
418
  options.strictMode = options.strictMode !== false;
419
+ options.strictHTMLValidation = options.strictHTMLValidation === true;
419
420
  options.lang = options.lang || _defaultLang;
420
421
  options.value = typeof options.value === 'string' ? options.value : null;
421
422
  options.allowedClassNames = new util._w.RegExp((options.allowedClassNames && typeof options.allowedClassNames === 'string' ? options.allowedClassNames + '|' : '') + '^__se__|se-|katex');
@@ -542,6 +543,7 @@ export default {
542
543
  options.imageMultipleFile = !!options.imageMultipleFile;
543
544
  options.imageAccept = (typeof options.imageAccept !== 'string' || options.imageAccept.trim() === "*") ? 'image/*' : options.imageAccept.trim() || 'image/*';
544
545
  /** Image - image gallery */
546
+ options.imageGalleryData = options.imageGalleryData || null;
545
547
  options.imageGalleryUrl = typeof options.imageGalleryUrl === 'string' ? options.imageGalleryUrl : null;
546
548
  options.imageGalleryHeader = options.imageGalleryHeader || null;
547
549
  /** Video */
package/src/lib/core.js CHANGED
@@ -1270,6 +1270,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1270
1270
  const range = this.getRange();
1271
1271
  if (this._selectionVoid(range)) return false;
1272
1272
 
1273
+ const collapsed = range.collapsed;
1273
1274
  let startCon = range.startContainer;
1274
1275
  let startOff = range.startOffset;
1275
1276
  let endCon = range.endContainer;
@@ -1294,7 +1295,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1294
1295
  while (endCon && endCon.nodeType === 1 && endCon.lastChild) {
1295
1296
  endCon = endCon.lastChild;
1296
1297
  }
1297
- endOff = endCon.textContent.length;
1298
+ endOff = collapsed ? 0 : endCon.textContent.length;
1298
1299
  }
1299
1300
 
1300
1301
  // startContainer
@@ -1474,18 +1475,19 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1474
1475
  * @description Determine if this offset is the edge offset of container
1475
1476
  * @param {Node} container The node of the selection object. (range.startContainer..)
1476
1477
  * @param {Number} offset The offset of the selection object. (core.getRange().startOffset...)
1477
- * @param {String|undefined} dir Select check point - Both edge, Front edge or End edge. ("front": Front edge, "end": End edge, undefined: Both edge)
1478
+ * @param {String|undefined} dir Select check point - Both edge, Front edge or End edge. ("start": Front edge, "end": End edge, undefined: Both edge)
1478
1479
  * @returns {Boolean}
1479
1480
  */
1480
1481
  isEdgePoint: function (container, offset, dir) {
1481
- return (dir !== 'end' && offset === 0) || ((!dir || dir !== 'front') && !container.nodeValue && offset === 1) || ((!dir || dir === 'end') && !!container.nodeValue && offset === container.nodeValue.length);
1482
+ if (container.nodeType === 1 && !container.textContent.length) return true;
1483
+ return (dir !== 'end' && offset === 0) || ((!dir || dir !== 'start') && !container.nodeValue && offset === 1) || ((!dir || dir === 'end') && !!container.nodeValue && offset === container.nodeValue.length);
1482
1484
  },
1483
1485
 
1484
1486
  /**
1485
1487
  * @description Check if the container and offset values are the edges of the format tag
1486
1488
  * @param {Node} container The node of the selection object. (range.startContainer..)
1487
1489
  * @param {Number} offset The offset of the selection object. (core.getRange().startOffset...)
1488
- * @param {String} dir Select check point - "front": Front edge, "end": End edge, undefined: Both edge.
1490
+ * @param {String} dir Select check point - "start": Front edge, "end": End edge, undefined: Both edge.
1489
1491
  * @returns {Array|null}
1490
1492
  * @private
1491
1493
  */
@@ -1493,7 +1495,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1493
1495
  if (!this.isEdgePoint(node, offset, dir)) return false;
1494
1496
 
1495
1497
  const result = [];
1496
- dir = dir === 'front' ? 'previousSibling' : 'nextSibling';
1498
+ dir = dir === 'start' ? 'previousSibling' : 'nextSibling';
1497
1499
  while (node && !util.isFormatElement(node) && !util.isWysiwygDiv(node)) {
1498
1500
  if (!node[dir] || (util.isBreak(node[dir]) && !node[dir][dir])) {
1499
1501
  if (node.nodeType === 1) result.push(node.cloneNode(false));
@@ -5541,7 +5543,11 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5541
5543
  const dom = _d.createRange().createContextualFragment(contents);
5542
5544
 
5543
5545
  try {
5544
- util._consistencyCheckOfHTML(dom, this._htmlCheckWhitelistRegExp, this._htmlCheckBlacklistRegExp, this._classNameFilter);
5546
+ if (this.options.strictHTMLValidation) {
5547
+ util._consistencyCheckOfHTML(dom, this._htmlCheckWhitelistRegExp, this._htmlCheckBlacklistRegExp, this._classNameFilter);
5548
+ } else {
5549
+ util._consistencyCheckOfHTML(dom, this._htmlCheckWhitelistRegExp, false);
5550
+ }
5545
5551
  } catch (error) {
5546
5552
  console.warn('[SUNEDITOR.convertContentsForEditor.consistencyCheck.fail] ' + error);
5547
5553
  }
@@ -6235,6 +6241,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6235
6241
  this.execCommand('formatBlock', false, (formatName || options.defaultTag));
6236
6242
  this.removeRange();
6237
6243
  this._editorRange();
6244
+ this.effectNode = null;
6245
+ return;
6238
6246
  }
6239
6247
 
6240
6248
  if (format) {
@@ -6248,7 +6256,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6248
6256
  }
6249
6257
 
6250
6258
  this.effectNode = null;
6251
- this.nativeFocus();
6259
+
6260
+ if (startCon) {
6261
+ this.setRange(startCon, 1, startCon, 1);
6262
+ } else {
6263
+ this.nativeFocus();
6264
+ }
6252
6265
  },
6253
6266
 
6254
6267
  /**
@@ -6532,7 +6545,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6532
6545
  }
6533
6546
  },
6534
6547
 
6535
- addGlobalEvent(type, listener, useCapture) {
6548
+ addGlobalEvent: function (type, listener, useCapture) {
6536
6549
  if (options.iframe) {
6537
6550
  core._ww.addEventListener(type, listener, useCapture);
6538
6551
  }
@@ -6544,7 +6557,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6544
6557
  };
6545
6558
  },
6546
6559
 
6547
- removeGlobalEvent(type, listener, useCapture) {
6560
+ removeGlobalEvent: function (type, listener, useCapture) {
6548
6561
  if (!type) return;
6549
6562
 
6550
6563
  if (typeof type === 'object') {
@@ -6588,7 +6601,9 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6588
6601
 
6589
6602
  event.removeGlobalEvent(event.__selectionSyncEvent);
6590
6603
  event.__selectionSyncEvent = event.addGlobalEvent('mouseup', function() {
6591
- core._editorRange();
6604
+ if (core) {
6605
+ core._editorRange();
6606
+ }
6592
6607
  event.removeGlobalEvent(event.__selectionSyncEvent);
6593
6608
  });
6594
6609
 
@@ -6670,7 +6685,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6670
6685
  const rangeEl = util.getRangeFormatElement(selectionNode, null);
6671
6686
 
6672
6687
  let selectionNodeDeepestFirstChild = selectionNode;
6673
- while (selectionNodeDeepestFirstChild.firstChild) selectionNodeDeepestFirstChild = selectionNodeDeepestFirstChild.firstChild;
6688
+ while (selectionNodeDeepestFirstChild && selectionNodeDeepestFirstChild.firstChild) selectionNodeDeepestFirstChild = selectionNodeDeepestFirstChild.firstChild;
6674
6689
 
6675
6690
  const selectedComponentInfo = core.getFileComponent(selectionNodeDeepestFirstChild);
6676
6691
  if (selectedComponentInfo) {
@@ -6716,10 +6731,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6716
6731
  },
6717
6732
 
6718
6733
  _toggleToolbarBalloon: function () {
6719
- core._editorRange();
6720
- const range = core.getRange();
6721
- if (core._bindControllersOff || (!core._isBalloonAlways && range.collapsed)) event._hideToolbar();
6722
- else event._showToolbarBalloon(range);
6734
+ if (core) {
6735
+ core._editorRange();
6736
+ const range = core.getRange();
6737
+ if (core._bindControllersOff || (!core._isBalloonAlways && range.collapsed)) event._hideToolbar();
6738
+ else event._showToolbarBalloon(range);
6739
+ }
6723
6740
  },
6724
6741
 
6725
6742
  _showToolbarBalloon: function (rangeObj) {
@@ -6896,6 +6913,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6896
6913
  return false;
6897
6914
  }
6898
6915
 
6916
+ const range = core.getRange();
6917
+ const selectionNode = core.getSelectionNode();
6918
+ const formatEl = util.getFormatElement(selectionNode, null);
6919
+ if (!formatEl && range.collapsed && !util.isComponent(selectionNode) && !util.isList(selectionNode)) {
6920
+ const rangeEl = util.getRangeFormatElement(formatEl, null);
6921
+ core._setDefaultFormat(util.isRangeFormatElement(rangeEl) ? 'DIV' : options.defaultTag);
6922
+ }
6923
+
6899
6924
  core._editorRange();
6900
6925
 
6901
6926
  const data = (e.data === null ? '' : e.data === undefined ? ' ' : e.data) || '';
@@ -6924,7 +6949,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6924
6949
  return siblingNode && siblingNode.nodeType === 1 && siblingNode.getAttribute('contenteditable') === 'false';
6925
6950
  } else {
6926
6951
  siblingNode = event._isUneditableNode_getSibling(container, siblingKey, container);
6927
- return core.isEdgePoint(container, offset, isFront ? 'front' : 'end') && (siblingNode && siblingNode.nodeType === 1 && siblingNode.getAttribute('contenteditable') === 'false');
6952
+ return core.isEdgePoint(container, offset, isFront ? 'start' : 'end') && (siblingNode && siblingNode.nodeType === 1 && siblingNode.getAttribute('contenteditable') === 'false');
6928
6953
  }
6929
6954
  },
6930
6955
 
@@ -6978,6 +7003,11 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6978
7003
  }
6979
7004
 
6980
7005
  /** default key action */
7006
+ if (keyCode === 13 && util.isFormatElement(core.getRange().startContainer)) {
7007
+ core._resetRangeToTextNode();
7008
+ selectionNode = core.getSelectionNode();
7009
+ }
7010
+
6981
7011
  const range = core.getRange();
6982
7012
  const selectRange = !range.collapsed || range.startContainer !== range.endContainer;
6983
7013
  const fileComponentName = core._fileManager.pluginRegExp.test(core.currentControllerName) ? core.currentControllerName : '';
@@ -7204,6 +7234,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7204
7234
  break;
7205
7235
  }
7206
7236
 
7237
+ if (!selectRange && core._isEdgeFormat(range.endContainer, range.endOffset, 'end') && !formatEl.nextSibling) {
7238
+ e.preventDefault();
7239
+ e.stopPropagation();
7240
+ return;
7241
+ }
7242
+
7207
7243
  // tag[contenteditable="false"]
7208
7244
  if (event._isUneditableNode(range, false)) {
7209
7245
  e.preventDefault();
@@ -7358,15 +7394,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7358
7394
  if (!shift) {
7359
7395
  const tabText = util.createTextNode(new _w.Array(core._variable.tabSize + 1).join('\u00A0'));
7360
7396
  if (lines.length === 1) {
7361
- const textRange = core.insertNode(tabText, null, true);
7362
- if (!textRange) return false;
7397
+ if (!core.insertNode(tabText, null, true)) return false;
7363
7398
  if (!fc) {
7364
7399
  r.sc = tabText;
7365
- r.so = textRange.endOffset;
7400
+ r.so = tabText.length;
7366
7401
  }
7367
7402
  if (!lc) {
7368
7403
  r.ec = tabText;
7369
- r.eo = textRange.endOffset;
7404
+ r.eo = tabText.length;
7370
7405
  }
7371
7406
  } else {
7372
7407
  const len = lines.length - 1;
@@ -7596,7 +7631,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7596
7631
  const isMultiLine = util.getFormatElement(range.startContainer, null) !== util.getFormatElement(range.endContainer, null);
7597
7632
  const newFormat = formatEl.cloneNode(false);
7598
7633
  newFormat.innerHTML = '<br>';
7599
- const r = core.removeNode();
7634
+ const commonCon = range.commonAncestorContainer;
7635
+ const r = commonCon === range.startContainer && commonCon === range.endContainer && util.onlyZeroWidthSpace(commonCon) ? range : core.removeNode();
7600
7636
  newEl = util.getFormatElement(r.container, null);
7601
7637
  if (!newEl) {
7602
7638
  if (util.isWysiwygDiv(r.container)) {
@@ -7711,8 +7747,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7711
7747
  e.preventDefault();
7712
7748
  e.stopPropagation();
7713
7749
  const nbsp = core.insertNode(util.createTextNode('\u00a0'));
7714
- if (nbsp && nbsp.container) {
7715
- core.setRange(nbsp.container, nbsp.endOffset, nbsp.container, nbsp.endOffset);
7750
+ if (nbsp) {
7751
+ core.setRange(nbsp, nbsp.length, nbsp, nbsp.length);
7716
7752
  return;
7717
7753
  }
7718
7754
  }
@@ -7724,7 +7760,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7724
7760
  }
7725
7761
 
7726
7762
  if (event._directionKeyCode.test(keyCode)) {
7727
- core._editorRange();
7763
+ _w.setTimeout(core._editorRange.bind(core), 0);
7728
7764
  event._applyTagEffects();
7729
7765
  }
7730
7766
  },
@@ -7734,7 +7770,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7734
7770
 
7735
7771
  let selectionNode = core.getSelectionNode();
7736
7772
 
7737
- const selectNode = function (node, offset = 0) {
7773
+ const selectNode = function (node, offset) {
7774
+ if (!offset) offset = 0;
7738
7775
  e.preventDefault();
7739
7776
  e.stopPropagation();
7740
7777
 
@@ -7757,12 +7794,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7757
7794
  let currentCellFirstNode = currentCell;
7758
7795
  let currentCellLastNode = currentCell;
7759
7796
  if (currentCell) {
7760
- while (currentCellFirstNode.firstChild) currentCellFirstNode = currentCellFirstNode.firstChild;
7761
- while (currentCellLastNode.lastChild) currentCellLastNode = currentCellLastNode.lastChild;
7797
+ while (currentCellFirstNode && currentCellFirstNode.firstChild) currentCellFirstNode = currentCellFirstNode.firstChild;
7798
+ while (currentCellLastNode && currentCellLastNode.lastChild) currentCellLastNode = currentCellLastNode.lastChild;
7762
7799
  }
7763
7800
 
7764
7801
  let selectionNodeDeepestFirstChild = selectionNode;
7765
- while (selectionNodeDeepestFirstChild.firstChild) selectionNodeDeepestFirstChild = selectionNodeDeepestFirstChild.firstChild;
7802
+ while (selectionNodeDeepestFirstChild && selectionNodeDeepestFirstChild.firstChild) selectionNodeDeepestFirstChild = selectionNodeDeepestFirstChild.firstChild;
7766
7803
  const isCellFirstNode = (selectionNodeDeepestFirstChild === currentCellFirstNode);
7767
7804
  const isCellLastNode = (selectionNodeDeepestFirstChild === currentCellLastNode);
7768
7805
 
@@ -7773,14 +7810,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7773
7810
  if (previousRow) siblingToSet = previousRow.children[currentCell.cellIndex];
7774
7811
  else siblingToSet = util.getPreviousDeepestNode(table, core.context.element.wysiwyg);
7775
7812
 
7776
- while (siblingToSet.lastChild) siblingToSet = siblingToSet.lastChild;
7813
+ while (siblingToSet && siblingToSet.lastChild) siblingToSet = siblingToSet.lastChild;
7777
7814
  if (siblingToSet) offset = siblingToSet.textContent.length;
7778
7815
  } else if (e.keyCode === 40 && isCellLastNode) { // DOWN
7779
7816
  const nextRow = currentRow && currentRow.nextElementSibling;
7780
7817
  if (nextRow) siblingToSet = nextRow.children[currentCell.cellIndex];
7781
7818
  else siblingToSet = util.getNextDeepestNode(table, core.context.element.wysiwyg);
7782
7819
 
7783
- while (siblingToSet.firstChild) siblingToSet = siblingToSet.firstChild;
7820
+ while (siblingToSet && siblingToSet.firstChild) siblingToSet = siblingToSet.firstChild;
7784
7821
  }
7785
7822
 
7786
7823
  if (siblingToSet) {
@@ -7831,7 +7868,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7831
7868
  }
7832
7869
 
7833
7870
  let selectionNodeDeepestFirstChild = selectionNode;
7834
- while (selectionNodeDeepestFirstChild.firstChild) selectionNodeDeepestFirstChild = selectionNodeDeepestFirstChild.firstChild;
7871
+ while (selectionNodeDeepestFirstChild && selectionNodeDeepestFirstChild.firstChild) selectionNodeDeepestFirstChild = selectionNodeDeepestFirstChild.firstChild;
7835
7872
 
7836
7873
  const selectedComponentInfo = core.getFileComponent(selectionNodeDeepestFirstChild);
7837
7874
  if (!(e.keyCode === 16 || e.shiftKey) && selectedComponentInfo) core.selectComponent(selectedComponentInfo.target, selectedComponentInfo.pluginName);
@@ -8353,7 +8390,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
8353
8390
  }
8354
8391
  },
8355
8392
 
8356
- _enterPrevent(e) {
8393
+ _enterPrevent: function (e) {
8357
8394
  e.preventDefault();
8358
8395
  if (!util.isMobile) return;
8359
8396
 
package/src/lib/util.js CHANGED
@@ -28,7 +28,7 @@ const util = {
28
28
  this.isOSX_IOS = /(Mac|iPhone|iPod|iPad)/.test(navigator.platform);
29
29
  this.isChromium = !!window.chrome;
30
30
  this.isResizeObserverSupported = (typeof ResizeObserver === 'function');
31
- this.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
31
+ this.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || ((navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0) && 'ontouchstart' in window);
32
32
  },
33
33
 
34
34
  _allowedEmptyNodeList: '.se-component, pre, blockquote, hr, li, table, img, iframe, video, audio, canvas',
package/src/options.d.ts CHANGED
@@ -3,6 +3,14 @@ import { Plugin } from './plugins/Plugin';
3
3
 
4
4
  export interface SunEditorOptions {
5
5
  plugins?: Plugin[] | Record<string, Plugin>;
6
+ /**
7
+ * Option to disable clean mode, which checks the styles, classes, etc. of the editor content
8
+ */
9
+ strictMode?: boolean;
10
+ /**
11
+ * Enforces strict HTML validation based on the editor`s policy. Applies to methods like setContents to ensure content compliance when enabled.
12
+ */
13
+ strictHTMLValidation?: boolean;
6
14
  /**
7
15
  * Values
8
16
  * ======
@@ -355,6 +363,10 @@ export interface SunEditorOptions {
355
363
  * Image - image gallery
356
364
  * =====
357
365
  */
366
+ /**
367
+ * Direct JSON data without making server requests.
368
+ */
369
+ imageGalleryData?: Array;
358
370
  /**
359
371
  * The url of the image gallery, if you use the image gallery
360
372
  */
@@ -684,7 +684,7 @@ export default {
684
684
  imagePlugin.setAlign.call(this, align, oImg, cover, container);
685
685
 
686
686
  oImg.onload = imagePlugin._image_create_onload.bind(this, oImg, contextImage.svgDefaultSize, container);
687
- if (this.insertComponent(container, true, true, true)) this.plugins.fileManager.setInfo.call(this, 'image', oImg, this.functions.onImageUpload, file, true);
687
+ if (this.insertComponent(container, true, true, !this.options.mediaAutoSelect)) this.plugins.fileManager.setInfo.call(this, 'image', oImg, this.functions.onImageUpload, file, true);
688
688
  this.context.resizing._resize_plugin = '';
689
689
  },
690
690
 
@@ -697,6 +697,7 @@ export default {
697
697
  const line = this.appendFormatTag(container, null);
698
698
  if (line) this.setRange(line, 0, line, 0);
699
699
  }
700
+ this.history.push(false);
700
701
  },
701
702
 
702
703
  update_image: function (init, openController, notHistoryPush) {
@@ -21,7 +21,8 @@ export default {
21
21
  const context = core.context;
22
22
  context.imageGallery = {
23
23
  title: core.lang.toolbar.imageGallery, // @Required @Override fileBrowser - File browser window title.
24
- url: core.options.imageGalleryUrl, // @Required @Override fileBrowser - File server url.
24
+ directData: core.options.imageGalleryData, // @option @Override fileBrowser - Direct JSON data without making server requests.
25
+ url: core.options.imageGalleryUrl, // @option @Override fileBrowser - File server url.
25
26
  header: core.options.imageGalleryHeader, // @Required @Override fileBrowser - File server http header.
26
27
  listClass: 'se-image-list', // @Required @Override fileBrowser - Class name of list div.
27
28
  itemTemplateHandler: this.drawItems, // @Required @Override fileBrowser - Function that defines the HTML of an file item.
@@ -162,7 +162,11 @@
162
162
  fileBrowserContext.titleArea.textContent = pluginContext.title;
163
163
  fileBrowserContext.area.style.display = 'block';
164
164
 
165
- this.plugins.fileBrowser._drawFileList.call(this, this.context[pluginName].url, this.context[pluginName].header);
165
+ if (this.context[pluginName].directData) {
166
+ this.plugins.fileBrowser._drawListItem.call(this, this.context[pluginName].directData, true);
167
+ } else {
168
+ this.plugins.fileBrowser._drawFileList.call(this, this.context[pluginName].url, this.context[pluginName].header);
169
+ }
166
170
  },
167
171
 
168
172
  _bindClose: null,
@@ -286,7 +286,7 @@ export default {
286
286
  }
287
287
 
288
288
  this.effectNode = null;
289
- return !isCollapsed ? originRange : afterRange;
289
+ return !isCollapsed ? originRange : afterRange || originRange;
290
290
  },
291
291
 
292
292
  _detachNested: function (cells) {
@@ -1,5 +0,0 @@
1
- import { DialogPlugin } from '../DialogPlugin';
2
-
3
- declare const mention: DialogPlugin;
4
-
5
- export default mention;
@@ -1,242 +0,0 @@
1
- /*
2
- * wysiwyg web editor
3
- *
4
- * suneditor.js
5
- * Copyright 2017 JiHong Lee.
6
- * MIT license.
7
- */
8
- "use strict";
9
-
10
- import dialog from "../modules/dialog";
11
-
12
-
13
- function insertAt(parent, child, index) {
14
- if (!index) index = 0;
15
- if (index >= parent.children.length) {
16
- parent.appendChild(child);
17
- } else {
18
- parent.insertBefore(child, parent.children[index]);
19
- }
20
- }
21
-
22
- export default {
23
- name: "mention",
24
- display: "dialog",
25
-
26
- renderItem: function(item) {
27
- return `<span>${item}</span>`;
28
- },
29
-
30
- getItems: function(term) {
31
- return Promise.resolve(
32
- ["overwite", "the", "mention", "plugin", "getItems", "method"].filter(
33
- (w) => w.includes(term.toLowerCase())
34
- )
35
- );
36
- },
37
-
38
- renderList: function(term) {
39
- const { mention } = this.context;
40
- let promise = Promise.resolve();
41
- if (mention.term !== term) {
42
- mention.focussed = 0;
43
- mention.term = term;
44
- promise = mention.getItems(term).then((items) => {
45
- mention._items = items;
46
-
47
- Object.keys(mention._itemElements).forEach((id) => {
48
- if (!items.find((i) => mention.getId(i) === id)) {
49
- const child = mention._itemElements[id];
50
- child.parentNode.removeChild(child);
51
- delete mention._itemElements[id];
52
- }
53
- });
54
-
55
- items.forEach((item, idx) => {
56
- const id = mention.getId(item);
57
- if (!mention._itemElements[id]) {
58
- const el = this.util.createElement("LI");
59
- el.setAttribute("data-mention", id);
60
- this.util.addClass(el, 'se-mention-item');
61
- el.innerHTML = mention.renderItem(item);
62
- el.addEventListener("click", () => {
63
- mention._addMention(item);
64
- });
65
- insertAt(mention._list, el, idx);
66
- mention._itemElements[id] = el;
67
- }
68
- });
69
- });
70
- }
71
-
72
- promise.then(() => {
73
- const current = mention._list.querySelectorAll(".se-mention-item")[
74
- mention.focussed
75
- ];
76
- if (current && !this.util.hasClass(current, "se-mention-active")) {
77
- const prev = mention._list.querySelector(".se-mention-active");
78
- if (prev) this.util.removeClass(prev, "se-mention-active");
79
- this.util.addClass(current, "se-mention-active");
80
- }
81
- });
82
- },
83
-
84
- setDialog: function(core) {
85
- const mention_dialog = core.util.createElement("DIV");
86
- const lang = core.lang;
87
- mention_dialog.className = "se-dialog-content";
88
- mention_dialog.style.display = "none";
89
- const html = `
90
- <form class="se-dialog-form">
91
- <div class="se-dialog-header">
92
- <button type="button" data-command="close" class="se-btn se-dialog-close" title="${lang.dialogBox.close}" aria-label="${lang.dialogBox.close}">
93
- ${core.icons.cancel}
94
- </button>
95
- <span class="se-modal-title">${lang.dialogBox.mentionBox.title}</span>
96
- </div>
97
- <div class="se-dialog-body">
98
- <input class="se-input-form se-mention-search" type="text" placeholder="${lang.dialogBox.browser.search}" />
99
- <ul class="se-mention-list">
100
- </ul>
101
- </div>
102
- </form>
103
- `;
104
- mention_dialog.innerHTML = html;
105
- return mention_dialog;
106
- },
107
-
108
- getId(mention) {
109
- return mention;
110
- },
111
-
112
- getValue(mention) {
113
- return `@${mention}`;
114
- },
115
-
116
- getLinkHref(/*mention*/) {
117
- return "";
118
- },
119
-
120
- open: function() {
121
- const { mention } = this.context;
122
- this.plugins.dialog.open.call(
123
- this,
124
- "mention",
125
- "mention" === this.currentControllerName
126
- );
127
- mention._search.focus();
128
- mention.renderList("");
129
- },
130
-
131
- on: function(update) {
132
- if (update) return;
133
- this.plugins.mention.init.call(this);
134
- },
135
-
136
- init: function() {
137
- const { mention } = this.context;
138
- mention._search.value = "";
139
- mention.focussed = 0;
140
- mention._items = [];
141
- mention._itemElements = {};
142
- mention._list.innerHTML = "";
143
- delete mention.term;
144
- },
145
-
146
- onKeyPress: function(e) {
147
- const { mention } = this.context;
148
- switch (e.key) {
149
- case "ArrowDown":
150
- mention.focussed += 1;
151
- e.preventDefault();
152
- e.stopPropagation();
153
- break;
154
-
155
- case "ArrowUp":
156
- if (mention.focussed > 0) {
157
- mention.focussed -= 1;
158
- }
159
- e.preventDefault();
160
- e.stopPropagation();
161
- break;
162
-
163
- case "Enter":
164
- mention._addMention();
165
- e.preventDefault();
166
- e.stopPropagation();
167
- break;
168
-
169
- default:
170
- }
171
- },
172
-
173
- onKeyUp: function(e) {
174
- const { mention } = this.context;
175
- mention.renderList(e.target.value);
176
- },
177
-
178
- getMentions: function(core) {
179
- const { mentions, getId } = core.context.mention;
180
- return mentions.filter((mention) => {
181
- const id = getId(mention);
182
- return core.context.element.wysiwyg.querySelector(
183
- `[data-mention="${id}"]`
184
- );
185
- });
186
- },
187
-
188
- _addMention: function(item) {
189
- const { mention } = this.context;
190
- const new_mention = item || mention._items[mention.focussed];
191
- if (new_mention) {
192
- if (
193
- !mention.mentions.find(
194
- (m) => mention.getId(m) === mention.getId(new_mention)
195
- )
196
- ) {
197
- mention.mentions.push(new_mention);
198
- }
199
- const el = this.util.createElement("A");
200
- el.href = mention.getLinkHref(new_mention);
201
- el.target = "_blank";
202
- el.innerHTML = mention.getValue(new_mention);
203
- el.setAttribute("data-mention", mention.getId(new_mention));
204
- this.insertNode(el, null, false);
205
- const spacer = this.util.createElement("SPAN");
206
- spacer.innerHTML = " ";
207
- this.insertNode(spacer, el, false);
208
- }
209
- this.plugins.dialog.close.call(this);
210
- },
211
- add: function(core) {
212
- core.addModule([dialog]);
213
- this.title = core.lang.toolbar.mention;
214
- const _dialog = this.setDialog(core);
215
- core.getMentions = this.getMentions(core);
216
-
217
- const _search = _dialog.querySelector(".se-mention-search");
218
- _search.addEventListener("keyup", this.onKeyUp.bind(core));
219
- _search.addEventListener("keydown", this.onKeyPress.bind(core));
220
- const _list = _dialog.querySelector(".se-mention-list");
221
-
222
- core.context.mention = {
223
- _addMention: this._addMention.bind(core),
224
- _itemElements: {},
225
- _items: [],
226
- _list,
227
- _search,
228
- focussed: 0,
229
- getId: this.getId.bind(core),
230
- getItems: this.getItems,
231
- getLinkHref: this.getLinkHref.bind(core),
232
- getValue: this.getValue.bind(core),
233
- mentions: [],
234
- modal: _dialog,
235
- open: this.open.bind(core),
236
- renderItem: this.renderItem,
237
- renderList: this.renderList.bind(core),
238
- };
239
- core.context.dialog.modal.appendChild(_dialog);
240
- },
241
- action: function() {},
242
- };