suneditor 2.46.3 → 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.46.3",
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",
@@ -97,7 +97,7 @@
97
97
  .sun-editor .se-toolbar .se-arrow.se-arrow-down::after {border-top-color:#fafafa;}
98
98
 
99
99
  /** --- container */
100
- .sun-editor .se-container {position:relative; width:100%; height:100%;}
100
+ .sun-editor .se-container {position:relative; width:auto; height:auto;}
101
101
 
102
102
  /** button */
103
103
  .sun-editor button {color:#000;}
@@ -563,4 +563,4 @@
563
563
 
564
564
  /** animation */
565
565
  @keyframes blinker { 50% {opacity:0;} }
566
- @keyframes spinner { to {transform:rotate(361deg);} }
566
+ @keyframes spinner { to {transform:rotate(361deg);} }
@@ -87,7 +87,7 @@ export default {
87
87
  /// focus temp
88
88
  const focusTemp = doc.createElement('INPUT');
89
89
  focusTemp.tabIndex = -1;
90
- focusTemp.style.cssText = 'position: absolute !important; top: -10000px !important; display: block !important; width: 0 !important; height: 0 !important; margin: 0 !important; padding: 0 !important;';
90
+ focusTemp.style.cssText = 'position: fixed !important; top: -10000px !important; display: block !important; width: 0 !important; height: 0 !important; margin: 0 !important; padding: 0 !important;';
91
91
 
92
92
  // toolbar container
93
93
  const toolbarContainer = options.toolbarContainer;
@@ -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 */
@@ -557,6 +559,7 @@ export default {
557
559
  options.videoRatio = (util.getNumber(options.videoRatio, 4) || 0.5625);
558
560
  options.videoRatioList = !options.videoRatioList ? null : options.videoRatioList;
559
561
  options.youtubeQuery = (options.youtubeQuery || '').replace('?', '');
562
+ options.vimeoQuery = (options.vimeoQuery || '').replace('?', '');
560
563
  options.videoFileInput = !!options.videoFileInput;
561
564
  options.videoUrlInput = (options.videoUrlInput === undefined || !options.videoFileInput) ? true : options.videoUrlInput;
562
565
  options.videoUploadHeader = options.videoUploadHeader || null;
package/src/lib/core.d.ts CHANGED
@@ -693,6 +693,7 @@ export default class SunEditor {
693
693
  onPaste: (e: Event, cleanData: string, maxCharCount: number, core: Core) => boolean | string;
694
694
  onCopy: (e: Event, clipboardData: any, core: Core) => boolean;
695
695
  onCut: (e: Event, clipboardData: any, core: Core) => boolean;
696
+ onPasteMath: (e: Event, core: Core) => void;
696
697
 
697
698
  /**
698
699
  * @description Called just after the save was executed.
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));
@@ -1756,6 +1758,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1756
1758
  return null;
1757
1759
  }
1758
1760
 
1761
+ let fNode = null;
1759
1762
  let range = this.getRange();
1760
1763
  let line = util.isListCell(range.commonAncestorContainer) ? range.commonAncestorContainer : util.getFormatElement(this.getSelectionNode(), null);
1761
1764
  let insertListCell = util.isListCell(line) && (util.isListCell(oNode) || util.isList(oNode));
@@ -1958,9 +1961,10 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1958
1961
  }
1959
1962
 
1960
1963
  if (util.isWysiwygDiv(parentNode) && (oNode.nodeType === 3 || util.isBreak(oNode))) {
1961
- const fNode = util.createElement(options.defaultTag);
1962
- fNode.appendChild(oNode);
1963
- oNode = fNode;
1964
+ const fomatNode = util.createElement(options.defaultTag);
1965
+ fomatNode.appendChild(oNode);
1966
+ fNode = oNode;
1967
+ oNode = fomatNode;
1964
1968
  }
1965
1969
  }
1966
1970
 
@@ -2018,6 +2022,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
2018
2022
  parentNode.appendChild(oNode);
2019
2023
  console.warn('[SUNEDITOR.insertNode.warn] ' + error);
2020
2024
  } finally {
2025
+ if (fNode) oNode = fNode;
2026
+
2021
2027
  const dupleNodes = parentNode.querySelectorAll('[data-se-duple]');
2022
2028
  if (dupleNodes.length > 0) {
2023
2029
  for (let i = 0, len = dupleNodes.length, d, c, ch, parent; i < len; i++) {
@@ -2049,30 +2055,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
2049
2055
  if (!util.isComponent(oNode)) {
2050
2056
  let offset = 1;
2051
2057
  if (oNode.nodeType === 3) {
2052
- const previous = oNode.previousSibling;
2053
- const next = oNode.nextSibling;
2054
- const previousText = (!previous || previous.nodeType === 1 || util.onlyZeroWidthSpace(previous)) ? '' : previous.textContent;
2055
- const nextText = (!next || next.nodeType === 1 || util.onlyZeroWidthSpace(next)) ? '' : next.textContent;
2056
-
2057
- if (previous && previousText.length > 0) {
2058
- oNode.textContent = previousText + oNode.textContent;
2059
- util.removeItem(previous);
2060
- }
2061
-
2062
- if (next && next.length > 0) {
2063
- oNode.textContent += nextText;
2064
- util.removeItem(next);
2065
- }
2066
-
2067
- const newRange = {
2068
- container: oNode,
2069
- startOffset: previousText.length,
2070
- endOffset: oNode.textContent.length - nextText.length
2071
- };
2072
-
2073
- this.setRange(oNode, newRange.startOffset, oNode, newRange.endOffset);
2074
-
2075
- return newRange;
2058
+ offset = oNode.textContent.length;
2059
+ this.setRange(oNode, offset, oNode, offset);
2076
2060
  } else if (!util.isBreak(oNode) && !util.isListCell(oNode) && util.isFormatElement(parentNode)) {
2077
2061
  let zeroWidth = null;
2078
2062
  if (!oNode.previousSibling || util.isBreak(oNode.previousSibling)) {
@@ -2094,9 +2078,6 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
2094
2078
  this.setRange(oNode, offset, oNode, offset);
2095
2079
  }
2096
2080
 
2097
- // history stack
2098
- this.history.push(true);
2099
-
2100
2081
  return oNode;
2101
2082
  }
2102
2083
  },
@@ -5320,7 +5301,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5320
5301
  r = style[i].match(/([a-zA-Z0-9-]+)(:)([^"']+)/);
5321
5302
  if (r && !/inherit|initial|revert|unset/i.test(r[3])) {
5322
5303
  const k = util.kebabToCamelCase(r[1].trim());
5323
- const v = this.wwComputedStyle[k].replace(/"/g, '');
5304
+ const v = this.wwComputedStyle[k] ? this.wwComputedStyle[k].replace(/"/g, '') : '';
5324
5305
  const c = r[3].trim();
5325
5306
  switch (k) {
5326
5307
  case 'fontFamily':
@@ -5562,7 +5543,11 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5562
5543
  const dom = _d.createRange().createContextualFragment(contents);
5563
5544
 
5564
5545
  try {
5565
- 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
+ }
5566
5551
  } catch (error) {
5567
5552
  console.warn('[SUNEDITOR.convertContentsForEditor.consistencyCheck.fail] ' + error);
5568
5553
  }
@@ -5943,7 +5928,9 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5943
5928
  // set whitelist
5944
5929
  const getRegList = function (str, str2) { return !str ? '^' : (str === '*' ? '[a-z-]+' : (!str2 ? str : (str + '|' + str2))); };
5945
5930
  // tags
5946
- const defaultAttr = 'contenteditable|colspan|rowspan|target|href|download|rel|src|alt|class|type|controls|origin-size';
5931
+ const videoAttr = '|controls|autoplay|loop|muted|poster|preload|playsinline';
5932
+ const iframeAttr = '|allowfullscreen|sandbox|loading|allow|referrerpolicy|frameborder|scrolling';
5933
+ const defaultAttr = 'contenteditable|colspan|rowspan|target|href|download|rel|src|alt|class|type|origin-size' + videoAttr + iframeAttr;
5947
5934
  const dataAttr = 'data-format|data-size|data-file-size|data-file-name|data-origin|data-align|data-image-link|data-rotate|data-proportion|data-percentage|data-exp|data-font-size';
5948
5935
  this._allowHTMLComments = options._editorTagsWhitelist.indexOf('//') > -1 || options._editorTagsWhitelist === '*';
5949
5936
  // html check
@@ -6254,6 +6241,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6254
6241
  this.execCommand('formatBlock', false, (formatName || options.defaultTag));
6255
6242
  this.removeRange();
6256
6243
  this._editorRange();
6244
+ this.effectNode = null;
6245
+ return;
6257
6246
  }
6258
6247
 
6259
6248
  if (format) {
@@ -6267,7 +6256,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6267
6256
  }
6268
6257
 
6269
6258
  this.effectNode = null;
6270
- this.nativeFocus();
6259
+
6260
+ if (startCon) {
6261
+ this.setRange(startCon, 1, startCon, 1);
6262
+ } else {
6263
+ this.nativeFocus();
6264
+ }
6271
6265
  },
6272
6266
 
6273
6267
  /**
@@ -6445,6 +6439,10 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6445
6439
  },
6446
6440
 
6447
6441
  _applyTagEffects: function () {
6442
+ if (util.hasClass(context.element.wysiwyg, 'se-read-only')) {
6443
+ return false;
6444
+ }
6445
+
6448
6446
  let selectionNode = core.getSelectionNode();
6449
6447
  if (selectionNode === core.effectNode) return;
6450
6448
  core.effectNode = selectionNode;
@@ -6547,7 +6545,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6547
6545
  }
6548
6546
  },
6549
6547
 
6550
- addGlobalEvent(type, listener, useCapture) {
6548
+ addGlobalEvent: function (type, listener, useCapture) {
6551
6549
  if (options.iframe) {
6552
6550
  core._ww.addEventListener(type, listener, useCapture);
6553
6551
  }
@@ -6559,7 +6557,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6559
6557
  };
6560
6558
  },
6561
6559
 
6562
- removeGlobalEvent(type, listener, useCapture) {
6560
+ removeGlobalEvent: function (type, listener, useCapture) {
6563
6561
  if (!type) return;
6564
6562
 
6565
6563
  if (typeof type === 'object') {
@@ -6603,7 +6601,9 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6603
6601
 
6604
6602
  event.removeGlobalEvent(event.__selectionSyncEvent);
6605
6603
  event.__selectionSyncEvent = event.addGlobalEvent('mouseup', function() {
6606
- core._editorRange();
6604
+ if (core) {
6605
+ core._editorRange();
6606
+ }
6607
6607
  event.removeGlobalEvent(event.__selectionSyncEvent);
6608
6608
  });
6609
6609
 
@@ -6626,6 +6626,11 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6626
6626
  },
6627
6627
 
6628
6628
  onClick_wysiwyg: function (e) {
6629
+ // if (util.hasClass(context.element.wysiwyg, 'se-read-only')) {
6630
+ // e.preventDefault();
6631
+ // return false;
6632
+ // }
6633
+
6629
6634
  const targetElement = e.target;
6630
6635
 
6631
6636
  if (core.isReadOnly) {
@@ -6680,7 +6685,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6680
6685
  const rangeEl = util.getRangeFormatElement(selectionNode, null);
6681
6686
 
6682
6687
  let selectionNodeDeepestFirstChild = selectionNode;
6683
- while (selectionNodeDeepestFirstChild.firstChild) selectionNodeDeepestFirstChild = selectionNodeDeepestFirstChild.firstChild;
6688
+ while (selectionNodeDeepestFirstChild && selectionNodeDeepestFirstChild.firstChild) selectionNodeDeepestFirstChild = selectionNodeDeepestFirstChild.firstChild;
6684
6689
 
6685
6690
  const selectedComponentInfo = core.getFileComponent(selectionNodeDeepestFirstChild);
6686
6691
  if (selectedComponentInfo) {
@@ -6726,10 +6731,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6726
6731
  },
6727
6732
 
6728
6733
  _toggleToolbarBalloon: function () {
6729
- core._editorRange();
6730
- const range = core.getRange();
6731
- if (core._bindControllersOff || (!core._isBalloonAlways && range.collapsed)) event._hideToolbar();
6732
- 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
+ }
6733
6740
  },
6734
6741
 
6735
6742
  _showToolbarBalloon: function (rangeObj) {
@@ -6906,6 +6913,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6906
6913
  return false;
6907
6914
  }
6908
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
+
6909
6924
  core._editorRange();
6910
6925
 
6911
6926
  const data = (e.data === null ? '' : e.data === undefined ? ' ' : e.data) || '';
@@ -6934,7 +6949,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6934
6949
  return siblingNode && siblingNode.nodeType === 1 && siblingNode.getAttribute('contenteditable') === 'false';
6935
6950
  } else {
6936
6951
  siblingNode = event._isUneditableNode_getSibling(container, siblingKey, container);
6937
- 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');
6938
6953
  }
6939
6954
  },
6940
6955
 
@@ -6988,6 +7003,11 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6988
7003
  }
6989
7004
 
6990
7005
  /** default key action */
7006
+ if (keyCode === 13 && util.isFormatElement(core.getRange().startContainer)) {
7007
+ core._resetRangeToTextNode();
7008
+ selectionNode = core.getSelectionNode();
7009
+ }
7010
+
6991
7011
  const range = core.getRange();
6992
7012
  const selectRange = !range.collapsed || range.startContainer !== range.endContainer;
6993
7013
  const fileComponentName = core._fileManager.pluginRegExp.test(core.currentControllerName) ? core.currentControllerName : '';
@@ -7214,6 +7234,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7214
7234
  break;
7215
7235
  }
7216
7236
 
7237
+ if (!selectRange && core._isEdgeFormat(range.endContainer, range.endOffset, 'end') && !formatEl.nextSibling) {
7238
+ e.preventDefault();
7239
+ e.stopPropagation();
7240
+ return;
7241
+ }
7242
+
7217
7243
  // tag[contenteditable="false"]
7218
7244
  if (event._isUneditableNode(range, false)) {
7219
7245
  e.preventDefault();
@@ -7368,15 +7394,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7368
7394
  if (!shift) {
7369
7395
  const tabText = util.createTextNode(new _w.Array(core._variable.tabSize + 1).join('\u00A0'));
7370
7396
  if (lines.length === 1) {
7371
- const textRange = core.insertNode(tabText, null, true);
7372
- if (!textRange) return false;
7397
+ if (!core.insertNode(tabText, null, true)) return false;
7373
7398
  if (!fc) {
7374
7399
  r.sc = tabText;
7375
- r.so = textRange.endOffset;
7400
+ r.so = tabText.length;
7376
7401
  }
7377
7402
  if (!lc) {
7378
7403
  r.ec = tabText;
7379
- r.eo = textRange.endOffset;
7404
+ r.eo = tabText.length;
7380
7405
  }
7381
7406
  } else {
7382
7407
  const len = lines.length - 1;
@@ -7606,7 +7631,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7606
7631
  const isMultiLine = util.getFormatElement(range.startContainer, null) !== util.getFormatElement(range.endContainer, null);
7607
7632
  const newFormat = formatEl.cloneNode(false);
7608
7633
  newFormat.innerHTML = '<br>';
7609
- const r = core.removeNode();
7634
+ const commonCon = range.commonAncestorContainer;
7635
+ const r = commonCon === range.startContainer && commonCon === range.endContainer && util.onlyZeroWidthSpace(commonCon) ? range : core.removeNode();
7610
7636
  newEl = util.getFormatElement(r.container, null);
7611
7637
  if (!newEl) {
7612
7638
  if (util.isWysiwygDiv(r.container)) {
@@ -7721,8 +7747,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7721
7747
  e.preventDefault();
7722
7748
  e.stopPropagation();
7723
7749
  const nbsp = core.insertNode(util.createTextNode('\u00a0'));
7724
- if (nbsp && nbsp.container) {
7725
- core.setRange(nbsp.container, nbsp.endOffset, nbsp.container, nbsp.endOffset);
7750
+ if (nbsp) {
7751
+ core.setRange(nbsp, nbsp.length, nbsp, nbsp.length);
7726
7752
  return;
7727
7753
  }
7728
7754
  }
@@ -7734,7 +7760,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7734
7760
  }
7735
7761
 
7736
7762
  if (event._directionKeyCode.test(keyCode)) {
7737
- core._editorRange();
7763
+ _w.setTimeout(core._editorRange.bind(core), 0);
7738
7764
  event._applyTagEffects();
7739
7765
  }
7740
7766
  },
@@ -7744,7 +7770,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7744
7770
 
7745
7771
  let selectionNode = core.getSelectionNode();
7746
7772
 
7747
- const selectNode = function (node, offset = 0) {
7773
+ const selectNode = function (node, offset) {
7774
+ if (!offset) offset = 0;
7748
7775
  e.preventDefault();
7749
7776
  e.stopPropagation();
7750
7777
 
@@ -7767,12 +7794,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7767
7794
  let currentCellFirstNode = currentCell;
7768
7795
  let currentCellLastNode = currentCell;
7769
7796
  if (currentCell) {
7770
- while (currentCellFirstNode.firstChild) currentCellFirstNode = currentCellFirstNode.firstChild;
7771
- while (currentCellLastNode.lastChild) currentCellLastNode = currentCellLastNode.lastChild;
7797
+ while (currentCellFirstNode && currentCellFirstNode.firstChild) currentCellFirstNode = currentCellFirstNode.firstChild;
7798
+ while (currentCellLastNode && currentCellLastNode.lastChild) currentCellLastNode = currentCellLastNode.lastChild;
7772
7799
  }
7773
7800
 
7774
7801
  let selectionNodeDeepestFirstChild = selectionNode;
7775
- while (selectionNodeDeepestFirstChild.firstChild) selectionNodeDeepestFirstChild = selectionNodeDeepestFirstChild.firstChild;
7802
+ while (selectionNodeDeepestFirstChild && selectionNodeDeepestFirstChild.firstChild) selectionNodeDeepestFirstChild = selectionNodeDeepestFirstChild.firstChild;
7776
7803
  const isCellFirstNode = (selectionNodeDeepestFirstChild === currentCellFirstNode);
7777
7804
  const isCellLastNode = (selectionNodeDeepestFirstChild === currentCellLastNode);
7778
7805
 
@@ -7783,14 +7810,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7783
7810
  if (previousRow) siblingToSet = previousRow.children[currentCell.cellIndex];
7784
7811
  else siblingToSet = util.getPreviousDeepestNode(table, core.context.element.wysiwyg);
7785
7812
 
7786
- while (siblingToSet.lastChild) siblingToSet = siblingToSet.lastChild;
7813
+ while (siblingToSet && siblingToSet.lastChild) siblingToSet = siblingToSet.lastChild;
7787
7814
  if (siblingToSet) offset = siblingToSet.textContent.length;
7788
7815
  } else if (e.keyCode === 40 && isCellLastNode) { // DOWN
7789
7816
  const nextRow = currentRow && currentRow.nextElementSibling;
7790
7817
  if (nextRow) siblingToSet = nextRow.children[currentCell.cellIndex];
7791
7818
  else siblingToSet = util.getNextDeepestNode(table, core.context.element.wysiwyg);
7792
7819
 
7793
- while (siblingToSet.firstChild) siblingToSet = siblingToSet.firstChild;
7820
+ while (siblingToSet && siblingToSet.firstChild) siblingToSet = siblingToSet.firstChild;
7794
7821
  }
7795
7822
 
7796
7823
  if (siblingToSet) {
@@ -7841,7 +7868,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7841
7868
  }
7842
7869
 
7843
7870
  let selectionNodeDeepestFirstChild = selectionNode;
7844
- while (selectionNodeDeepestFirstChild.firstChild) selectionNodeDeepestFirstChild = selectionNodeDeepestFirstChild.firstChild;
7871
+ while (selectionNodeDeepestFirstChild && selectionNodeDeepestFirstChild.firstChild) selectionNodeDeepestFirstChild = selectionNodeDeepestFirstChild.firstChild;
7845
7872
 
7846
7873
  const selectedComponentInfo = core.getFileComponent(selectionNodeDeepestFirstChild);
7847
7874
  if (!(e.keyCode === 16 || e.shiftKey) && selectedComponentInfo) core.selectComponent(selectedComponentInfo.target, selectedComponentInfo.pluginName);
@@ -8363,7 +8390,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
8363
8390
  }
8364
8391
  },
8365
8392
 
8366
- _enterPrevent(e) {
8393
+ _enterPrevent: function (e) {
8367
8394
  e.preventDefault();
8368
8395
  if (!util.isMobile) return;
8369
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) {
@@ -25,6 +25,11 @@ export default {
25
25
  context.math.focusElement = math_dialog.querySelector('.se-math-exp');
26
26
  context.math.previewElement = math_dialog.querySelector('.se-math-preview');
27
27
  context.math.fontSizeElement = math_dialog.querySelector('.se-math-size');
28
+ context.math.focusElement.addEventListener('paste', function (e) {
29
+ if (typeof core.functions.onPasteMath === 'function') {
30
+ core.functions.onPasteMath(e, core);
31
+ }
32
+ }, false);
28
33
  context.math.focusElement.addEventListener(core.util.isIE ? 'textinput' : 'input', this._renderMathExp.bind(core, context.math), false);
29
34
  context.math.fontSizeElement.addEventListener('change', function (e) { this.fontSize = e.target.value; }.bind(context.math.previewElement.style), false);
30
35
 
@@ -29,6 +29,7 @@ export default {
29
29
  _align: 'none',
30
30
  _floatClassRegExp: '__se__float\\-[a-z]+',
31
31
  _youtubeQuery: options.youtubeQuery,
32
+ _vimeoQuery: options.vimeoQuery,
32
33
  _videoRatio: (options.videoRatio * 100) + '%',
33
34
  _defaultRatio: (options.videoRatio * 100) + '%',
34
35
  _linkValue: '',
@@ -540,6 +541,15 @@ export default {
540
541
  url = url.slice(0, -1);
541
542
  }
542
543
  url = 'https://player.vimeo.com/video/' + url.slice(url.lastIndexOf('/') + 1);
544
+
545
+ if (contextVideo._vimeoQuery.length > 0) {
546
+ if (/\?/.test(url)) {
547
+ const splitUrl = url.split('?');
548
+ url = splitUrl[0] + '?' + contextVideo._vimeoQuery + '&' + splitUrl[1];
549
+ } else {
550
+ url += '?' + contextVideo._vimeoQuery;
551
+ }
552
+ }
543
553
  }
544
554
 
545
555
  this.plugins.video.create_video.call(this, this.plugins.video[(!/embed|iframe|player|\/e\/|\.php|\.html?/.test(url) && !/vimeo\.com/.test(url) ? "createVideoTag" : "createIframeTag")].call(this), url, contextVideo.inputX.value, contextVideo.inputY.value, contextVideo._align, null, this.context.dialog.updateModal);
@@ -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;