suneditor 2.45.0 → 2.46.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.
package/src/lib/util.js CHANGED
@@ -17,7 +17,8 @@ const util = {
17
17
  isIE_Edge: null,
18
18
  isOSX_IOS: null,
19
19
  isChromium: null,
20
- isResizeObserverSupported: null,
20
+ isMobile: null,
21
+ isResizeObserverSupported: null,
21
22
  _propertiesInit: function () {
22
23
  if (this._d) return;
23
24
  this._d = document;
@@ -27,6 +28,7 @@ const util = {
27
28
  this.isOSX_IOS = /(Mac|iPhone|iPod|iPad)/.test(navigator.platform);
28
29
  this.isChromium = !!window.chrome;
29
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;
30
32
  },
31
33
 
32
34
  _allowedEmptyNodeList: '.se-component, pre, blockquote, hr, li, table, img, iframe, video, audio, canvas',
@@ -38,7 +40,7 @@ const util = {
38
40
  * @private
39
41
  */
40
42
  _HTMLConvertor: function (contents) {
41
- const ec = {'&': '&amp;', '\u00A0': '&nbsp;', '\'': '&apos;', '"': '&quot;', '<': '&lt;', '>': '&gt;'};
43
+ const ec = {'&': '&amp;', '\u00A0': '&nbsp;', '\'': '&apos;', '"': '&quot;', '<': '&lt;', '>': '&gt;'};
42
44
  return contents.replace(/&|\u00A0|'|"|<|>/g, function (m) {
43
45
  return (typeof ec[m] === 'string') ? ec[m] : m;
44
46
  });
@@ -170,7 +172,7 @@ const util = {
170
172
  * @returns {String}
171
173
  */
172
174
  HTMLEncoder: function (contents) {
173
- const ec = {'<': '$lt;', '>': '$gt;'};
175
+ const ec = {'<': '$lt;', '>': '$gt;'};
174
176
  return contents.replace(/<|>/g, function (m) {
175
177
  return (typeof ec[m] === 'string') ? ec[m] : m;
176
178
  });
@@ -184,7 +186,7 @@ const util = {
184
186
  * @returns {String}
185
187
  */
186
188
  HTMLDecoder: function (contents) {
187
- const ec = {'$lt;': '<', '$gt;': '>'};
189
+ const ec = {'$lt;': '<', '$gt;': '>'};
188
190
  return contents.replace(/\$lt;|\$gt;/g, function (m) {
189
191
  return (typeof ec[m] === 'string') ? ec[m] : m;
190
192
  });
@@ -214,7 +216,7 @@ const util = {
214
216
  const pathList = [];
215
217
  const tagName = extension === 'js' ? 'script' : 'link';
216
218
  const src = extension === 'js' ? 'src' : 'href';
217
-
219
+
218
220
  let fileName = '(?:';
219
221
  for (let i = 0, len = nameArray.length; i < len; i++) {
220
222
  fileName += nameArray[i] + (i < len - 1 ? '|' : ')');
@@ -222,7 +224,7 @@ const util = {
222
224
 
223
225
  const regExp = new this._w.RegExp('(^|.*[\\/])' + fileName + '(\\.[^\\/]+)?\.' + extension + '(?:\\?.*|;.*)?$', 'i');
224
226
  const extRegExp = new this._w.RegExp('.+\\.' + extension + '(?:\\?.*|;.*)?$', 'i');
225
-
227
+
226
228
  for (let c = this._d.getElementsByTagName(tagName), i = 0; i < c.length; i++) {
227
229
  if (extRegExp.test(c[i][src])) {
228
230
  pathList.push(c[i]);
@@ -255,14 +257,14 @@ const util = {
255
257
  getPageStyle: function (doc) {
256
258
  let cssText = '';
257
259
  const sheets = (doc || this._d).styleSheets;
258
-
260
+
259
261
  for (let i = 0, len = sheets.length, rules; i < len; i++) {
260
262
  try {
261
263
  rules = sheets[i].cssRules;
262
264
  } catch (e) {
263
265
  continue;
264
266
  }
265
-
267
+
266
268
  if (rules) {
267
269
  for (let c = 0, cLen = rules.length; c < cLen; c++) {
268
270
  cssText += rules[c].cssText;
@@ -488,7 +490,7 @@ const util = {
488
490
 
489
491
  element = element.parentNode;
490
492
  }
491
-
493
+
492
494
  return null;
493
495
  },
494
496
 
@@ -531,7 +533,7 @@ const util = {
531
533
 
532
534
  element = element.parentNode;
533
535
  }
534
-
536
+
535
537
  return null;
536
538
  },
537
539
 
@@ -553,7 +555,7 @@ const util = {
553
555
 
554
556
  element = element.parentNode;
555
557
  }
556
-
558
+
557
559
  return null;
558
560
  },
559
561
 
@@ -603,7 +605,7 @@ const util = {
603
605
 
604
606
  validation = validation || function () { return true; };
605
607
  const arr = [];
606
-
608
+
607
609
  for (let i = 0, len = array.length, a; i < len; i++) {
608
610
  a = array[i];
609
611
  if (validation(a)) {
@@ -733,7 +735,7 @@ const util = {
733
735
  }
734
736
  return false;
735
737
  }.bind(this));
736
-
738
+
737
739
  return path.map(this.getPositionIndex).reverse();
738
740
  },
739
741
 
@@ -899,7 +901,7 @@ const util = {
899
901
  */
900
902
  getNumber: function (text, maxDec) {
901
903
  if (!text) return 0;
902
-
904
+
903
905
  let number = (text + '').match(/-?\d+(\.\d+)?/);
904
906
  if (!number || !number[0]) return 0;
905
907
 
@@ -1054,6 +1056,54 @@ const util = {
1054
1056
  return element;
1055
1057
  },
1056
1058
 
1059
+ /**
1060
+ * @description Gets the previous sibling last child. If there is no sibling, then it'll take it from the closest ancestor with child
1061
+ * Returns null if not found.
1062
+ * @param {Node} node Reference element
1063
+ * @param {Node|null} ceiling Highest boundary allowed
1064
+ * @returns {Node|null}
1065
+ */
1066
+ getPreviousDeepestNode: function (node, ceiling) {
1067
+ let previousNode = node.previousSibling;
1068
+ if (!previousNode) {
1069
+ for (let parentNode = node.parentNode; parentNode; parentNode = parentNode.parentNode) {
1070
+ if (parentNode === ceiling) return null;
1071
+ if (parentNode.previousSibling) {
1072
+ previousNode = parentNode.previousSibling;
1073
+ break;
1074
+ }
1075
+ }
1076
+ if (!previousNode) return null;
1077
+ }
1078
+ while (previousNode.lastChild) previousNode = previousNode.lastChild;
1079
+
1080
+ return previousNode;
1081
+ },
1082
+
1083
+ /**
1084
+ * @description Gets the next sibling first child. If there is no sibling, then it'll take it from the closest ancestor with child
1085
+ * Returns null if not found.
1086
+ * @param {Node} node Reference element
1087
+ * @param {Node|null} ceiling Highest boundary allowed
1088
+ * @returns {Node|null}
1089
+ */
1090
+ getNextDeepestNode: function (node, ceiling) {
1091
+ let nextNode = node.nextSibling;
1092
+ if (!nextNode) {
1093
+ for (let parentNode = node.parentNode; parentNode; parentNode = parentNode.parentNode) {
1094
+ if (parentNode === ceiling) return null;
1095
+ if (parentNode.nextSibling) {
1096
+ nextNode = parentNode.nextSibling;
1097
+ break;
1098
+ }
1099
+ }
1100
+ if (!nextNode) return null;
1101
+ }
1102
+ while (nextNode.firstChild) nextNode = nextNode.firstChild;
1103
+
1104
+ return nextNode;
1105
+ },
1106
+
1057
1107
  /**
1058
1108
  * @description Get the child element of the argument value.
1059
1109
  * A tag that satisfies the query condition is imported.
@@ -1291,7 +1341,7 @@ const util = {
1291
1341
  let button = buttonList[i];
1292
1342
  if (important || !this.isImportantDisabled(button)) button.disabled = disabled;
1293
1343
  if (important) {
1294
- if (disabled) {
1344
+ if (disabled) {
1295
1345
  button.setAttribute('data-important-disabled', '');
1296
1346
  } else {
1297
1347
  button.removeAttribute('data-important-disabled');
@@ -1368,7 +1418,7 @@ const util = {
1368
1418
  } else {
1369
1419
  rangeElement = baseNode;
1370
1420
  }
1371
-
1421
+
1372
1422
  let rChildren;
1373
1423
  if (!all) {
1374
1424
  const depth = this.getElementDepth(baseNode) + 2;
@@ -1380,7 +1430,7 @@ const util = {
1380
1430
  for (let i = 0, len = rChildren.length; i < len; i++) {
1381
1431
  this._deleteNestedList(rChildren[i]);
1382
1432
  }
1383
-
1433
+
1384
1434
  if (rNode) {
1385
1435
  rNode.parentNode.insertBefore(rangeElement, rNode.nextSibling);
1386
1436
  if (cNodes && cNodes.length === 0) this.removeItem(rNode);
@@ -1398,7 +1448,7 @@ const util = {
1398
1448
  let sibling = baseParent;
1399
1449
  let parent = sibling.parentNode;
1400
1450
  let liSibling, liParent, child, index, c;
1401
-
1451
+
1402
1452
  while (this.isListCell(parent)) {
1403
1453
  index = this.getPositionIndex(baseNode);
1404
1454
  liSibling = parent.nextElementSibling;
@@ -1522,7 +1572,7 @@ const util = {
1522
1572
 
1523
1573
  this.mergeSameTags(newEl, null, false);
1524
1574
  this.mergeNestedTags(newEl, function (current) { return this.isList(current); }.bind(this));
1525
-
1575
+
1526
1576
  if (newEl.childNodes.length > 0) pElement.insertBefore(newEl, depthEl);
1527
1577
  else newEl = depthEl;
1528
1578
 
@@ -1548,14 +1598,14 @@ const util = {
1548
1598
  const inst = this;
1549
1599
  const nodePathLen = nodePathArray ? nodePathArray.length : 0;
1550
1600
  let offsets = null;
1551
-
1601
+
1552
1602
  if (nodePathLen) {
1553
1603
  offsets = this._w.Array.apply(null, new this._w.Array(nodePathLen)).map(this._w.Number.prototype.valueOf, 0);
1554
1604
  }
1555
1605
 
1556
1606
  (function recursionFunc(current, depth, depthIndex) {
1557
1607
  const children = current.childNodes;
1558
-
1608
+
1559
1609
  for (let i = 0, len = children.length, child, next; i < len; i++) {
1560
1610
  child = children[i];
1561
1611
  next = children[i + 1];
@@ -1628,7 +1678,7 @@ const util = {
1628
1678
  path = nodePathArray[n];
1629
1679
  if (path && path[depth] > i) {
1630
1680
  if (depth > 0 && path[depth - 1] !== depthIndex) continue;
1631
-
1681
+
1632
1682
  path[depth] -= 1;
1633
1683
  if (path[depth + 1] >= 0 && path[depth] === i) {
1634
1684
  path[depth + 1] += childLength;
@@ -1652,7 +1702,7 @@ const util = {
1652
1702
  path = nodePathArray[n];
1653
1703
  if (path && path[depth] > i) {
1654
1704
  if (depth > 0 && path[depth - 1] !== depthIndex) continue;
1655
-
1705
+
1656
1706
  path[depth] -= 1;
1657
1707
  if (path[depth + 1] >= 0 && path[depth] === i) {
1658
1708
  path[depth + 1] += childLength;
@@ -1664,7 +1714,7 @@ const util = {
1664
1714
  } else {
1665
1715
  child.innerHTML += next.innerHTML;
1666
1716
  }
1667
-
1717
+
1668
1718
  inst.removeItem(next);
1669
1719
  i--;
1670
1720
  } else if (child.nodeType === 1) {
@@ -1687,7 +1737,7 @@ const util = {
1687
1737
  } else if (typeof validation !== 'function') {
1688
1738
  validation = function () { return true; };
1689
1739
  }
1690
-
1740
+
1691
1741
  (function recursionFunc(current) {
1692
1742
  let children = current.children;
1693
1743
  if (children.length === 1 && children[0].nodeName === current.nodeName && validation(current)) {
@@ -1719,7 +1769,7 @@ const util = {
1719
1769
  return element === current.parentElement;
1720
1770
  });
1721
1771
  }
1722
-
1772
+
1723
1773
  (function recursionFunc(current) {
1724
1774
  if (inst._notTextNode(current) || current === notRemoveNode || inst.isNonEditable(current)) return 0;
1725
1775
  if (current !== element && inst.onlyZeroWidthSpace(current.textContent) && (!current.firstChild || !inst.isBreak(current.firstChild)) && !current.querySelector(inst._allowedEmptyNodeList)) {
@@ -1758,13 +1808,13 @@ const util = {
1758
1808
  },
1759
1809
 
1760
1810
  /**
1761
- * @description HTML code compression
1762
- * @param {string} html HTML string
1763
- * @returns {string} HTML string
1764
- */
1765
- htmlCompress: function (html) {
1766
- return html.replace(/\n/g, '').replace(/(>)(?:\s+)(<)/g, '$1$2');
1767
- },
1811
+ * @description HTML code compression
1812
+ * @param {string} html HTML string
1813
+ * @returns {string} HTML string
1814
+ */
1815
+ htmlCompress: function (html) {
1816
+ return html.replace(/\n/g, '').replace(/(>)(?:\s+)(<)/g, '$1$2');
1817
+ },
1768
1818
 
1769
1819
  /**
1770
1820
  * @description Sort a element array by depth of element.
@@ -1782,6 +1832,23 @@ const util = {
1782
1832
  return a > b ? t : a < b ? f : 0;
1783
1833
  }.bind(this));
1784
1834
  },
1835
+
1836
+ /**
1837
+ * @description Escape a string for safe use in regular expressions.
1838
+ * @param {String} string String to escape
1839
+ * @returns {String}
1840
+ */
1841
+ escapeStringRegexp: function (string) {
1842
+ if (typeof string !== 'string') {
1843
+ throw new TypeError('Expected a string');
1844
+ }
1845
+
1846
+ // Escape characters with special meaning either inside or outside character sets.
1847
+ // Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar.
1848
+ return string
1849
+ .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
1850
+ .replace(/-/g, '\\x2d');
1851
+ },
1785
1852
 
1786
1853
  _isExcludeSelectionElement: function (element) {
1787
1854
  return !/FIGCAPTION/i.test(element.nodeName) && (this.isComponent(element) || /FIGURE/i.test(element.nodeName));
@@ -1916,8 +1983,8 @@ const util = {
1916
1983
  }
1917
1984
 
1918
1985
  const result = current.parentNode !== documentFragment && nrtag &&
1919
- ((this.isListCell(current) && !this.isList(current.parentNode)) ||
1920
- ((this.isFormatElement(current) || this.isComponent(current)) && !this.isRangeFormatElement(current.parentNode) && !this.getParentElement(current, this.isComponent)));
1986
+ ((this.isListCell(current) && !this.isList(current.parentNode)) ||
1987
+ ((this.isFormatElement(current) || this.isComponent(current)) && !this.isRangeFormatElement(current.parentNode) && !this.getParentElement(current, this.isComponent)));
1921
1988
 
1922
1989
  return result;
1923
1990
  }.bind(this));
@@ -1925,7 +1992,7 @@ const util = {
1925
1992
  for (let i = 0, len = removeTags.length; i < len; i++) {
1926
1993
  this.removeItem(removeTags[i]);
1927
1994
  }
1928
-
1995
+
1929
1996
  const checkTags = [];
1930
1997
  for (let i = 0, len = wrongTags.length, t, p; i < len; i++) {
1931
1998
  t = wrongTags[i];
@@ -2029,6 +2096,7 @@ const util = {
2029
2096
  this._setIframeCssTags(options);
2030
2097
  frame.contentDocument.body.className = options._editableClass;
2031
2098
  frame.contentDocument.body.setAttribute('contenteditable', true);
2099
+ frame.contentDocument.body.setAttribute('autocorrect', "off");
2032
2100
  },
2033
2101
 
2034
2102
  _setIframeCssTags: function (options) {
@@ -208,8 +208,10 @@ export default {
208
208
  element = element || this.context.audio._element;
209
209
  const container = this.util.getParentElement(element, this.util.isComponent) || element;
210
210
  const dataIndex = element.getAttribute('data-index') * 1;
211
+
212
+ if (typeof this.functions.onAudioDeleteBefore === 'function' && (this.functions.onAudioDeleteBefore(element, container, dataIndex, this) === false)) return;
213
+
211
214
  const focusEl = (container.previousElementSibling || container.nextElementSibling);
212
-
213
215
  const emptyDiv = container.parentNode;
214
216
  this.util.removeItem(container);
215
217
  this.plugins.audio.init.call(this);
@@ -243,7 +243,7 @@ export default {
243
243
 
244
244
  _setUrlInput: function (target) {
245
245
  this.altText.value = target.alt;
246
- this._v_src._linkValue = this.previewSrc.textContent = this.imgUrlFile.value = target.src;
246
+ this._v_src._linkValue = this.previewSrc.textContent = this.imgUrlFile.value = target.getAttribute('data-value') || target.src;
247
247
  this.imgUrlFile.focus();
248
248
  },
249
249
 
@@ -273,6 +273,10 @@ export default {
273
273
  const imageEl = element || this.context.image._element;
274
274
  const imageContainer = this.util.getParentElement(imageEl, this.util.isMediaComponent) || imageEl;
275
275
  const dataIndex = imageEl.getAttribute('data-index') * 1;
276
+
277
+ // event
278
+ if (typeof this.functions.onImageDeleteBefore === 'function' && (this.functions.onImageDeleteBefore(imageEl, imageContainer, dataIndex, this) === false)) return;
279
+
276
280
  let focusEl = (imageContainer.previousElementSibling || imageContainer.nextElementSibling);
277
281
 
278
282
  const emptyDiv = imageContainer.parentNode;
@@ -516,6 +520,12 @@ export default {
516
520
 
517
521
  setup_reader: function (files, anchor, width, height, align, alt, filesLen, isUpdate) {
518
522
  try {
523
+ if (filesLen === 0) {
524
+ this.closeLoading();
525
+ console.warn('[SUNEDITOR.image.base64.fail] cause : No applicable files');
526
+ return;
527
+ }
528
+
519
529
  this.context.image.base64RenderIndex = filesLen;
520
530
  const wFileReader = this._w.FileReader;
521
531
  const filesStack = [filesLen];
@@ -793,6 +803,7 @@ export default {
793
803
  formats.parentNode.insertBefore(container, existElement.previousSibling ? formats.nextElementSibling : formats);
794
804
  if (contextImage.__updateTags.map(function (current) { return existElement.contains(current); }).length === 0) this.util.removeItem(existElement);
795
805
  } else {
806
+ existElement = this.util.isFigures(existElement.parentNode) ? existElement.parentNode : existElement;
796
807
  existElement.parentNode.replaceChild(container, existElement);
797
808
  }
798
809
  }
@@ -277,8 +277,10 @@ export default {
277
277
  const frame = element || this.context.video._element;
278
278
  const container = this.context.video._container;
279
279
  const dataIndex = frame.getAttribute('data-index') * 1;
280
- let focusEl = (container.previousElementSibling || container.nextElementSibling);
281
280
 
281
+ if (typeof this.functions.onVideoDeleteBefore === 'function' && (this.functions.onVideoDeleteBefore(frame, container, dataIndex, this) === false)) return;
282
+
283
+ let focusEl = (container.previousElementSibling || container.nextElementSibling);
282
284
  const emptyDiv = container.parentNode;
283
285
  this.util.removeItem(container);
284
286
  this.plugins.video.init.call(this);
@@ -568,7 +570,7 @@ export default {
568
570
  newTag.src = src;
569
571
  oFrame.parentNode.replaceChild(newTag, oFrame);
570
572
  contextVideo._element = oFrame = newTag;
571
- } else if (!isYoutube && !isVimeo && !/^videoo$/i.test(oFrame.nodeName)) {
573
+ } else if (!isYoutube && !isVimeo && !/^video$/i.test(oFrame.nodeName)) {
572
574
  const newTag = this.plugins.video.createVideoTag.call(this);
573
575
  newTag.src = src;
574
576
  oFrame.parentNode.replaceChild(newTag, oFrame);
@@ -303,7 +303,7 @@ export default {
303
303
  const protocol = this.options.linkProtocol;
304
304
  const noPrefix = this.options.linkNoPrefix;
305
305
  const reservedProtocol = /^(mailto\:|tel\:|sms\:|https*\:\/\/|#)/.test(value) || value.indexOf(protocol) === 0;
306
- const sameProtocol = !protocol ? false : this._w.RegExp('^' + value.substr(0, protocol.length)).test(protocol);
306
+ const sameProtocol = !protocol ? false : this._w.RegExp('^' + this.util.escapeStringRegexp(value.substr(0, protocol.length))).test(protocol);
307
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;
308
308
 
309
309
  if (this.plugins.anchor.selfPathBookmark.call(this, value)) {