tinymce-rails 8.0.1 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -0
  3. data/app/assets/source/tinymce/tinymce.js +323 -347
  4. data/lib/tinymce/rails/version.rb +2 -2
  5. data/vendor/assets/javascripts/tinymce/icons/default/icons.js +1 -1
  6. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.css +1 -1
  7. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.inline.css +1 -1
  8. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.inline.js +1 -1
  9. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.inline.min.css +1 -1
  10. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.js +1 -1
  11. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.min.css +1 -1
  12. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.css +1 -1
  13. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.js +1 -1
  14. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.min.css +1 -1
  15. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.css +1 -1
  16. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.inline.css +1 -1
  17. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.inline.js +1 -1
  18. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.inline.min.css +1 -1
  19. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.js +1 -1
  20. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.min.css +1 -1
  21. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.css +1 -1
  22. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.js +1 -1
  23. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.min.css +1 -1
  24. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.css +1 -1
  25. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.inline.css +1 -1
  26. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.inline.js +1 -1
  27. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.inline.min.css +1 -1
  28. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.js +1 -1
  29. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.min.css +1 -1
  30. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.css +1 -1
  31. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.js +1 -1
  32. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.min.css +1 -1
  33. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.css +1 -1
  34. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.inline.css +1 -1
  35. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.inline.js +1 -1
  36. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.inline.min.css +1 -1
  37. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.js +1 -1
  38. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.min.css +1 -1
  39. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.css +1 -1
  40. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.js +1 -1
  41. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.min.css +1 -1
  42. data/vendor/assets/javascripts/tinymce/themes/silver/theme.js +1 -1
  43. data/vendor/assets/javascripts/tinymce/tinymce.d.ts +405 -399
  44. data/vendor/assets/javascripts/tinymce/tinymce.js +2 -2
  45. metadata +1 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * TinyMCE version 8.0.1 (2025-07-28)
2
+ * TinyMCE version 8.1.0 (2025-09-17)
3
3
  */
4
4
 
5
5
  (function () {
@@ -580,7 +580,7 @@
580
580
  }
581
581
  return Optional.none();
582
582
  };
583
- const flatten = (xs) => {
583
+ const flatten$1 = (xs) => {
584
584
  // Note, this is possible because push supports multiple arguments:
585
585
  // http://jsperf.com/concat-push/6
586
586
  // Note that in the past, concat() would silently work (very slowly) for array-like objects.
@@ -595,7 +595,7 @@
595
595
  }
596
596
  return r;
597
597
  };
598
- const bind$3 = (xs, f) => flatten(map$3(xs, f));
598
+ const bind$3 = (xs, f) => flatten$1(map$3(xs, f));
599
599
  const forall = (xs, pred) => {
600
600
  for (let i = 0, len = xs.length; i < len; ++i) {
601
601
  const x = xs[i];
@@ -719,7 +719,7 @@
719
719
  * Generates a church encoded ADT (https://en.wikipedia.org/wiki/Church_encoding)
720
720
  * For syntax and use, look at the test code.
721
721
  */
722
- const generate$2 = (cases) => {
722
+ const generate$1 = (cases) => {
723
723
  // validation
724
724
  if (!isArray$1(cases)) {
725
725
  throw new Error('cases must be an array');
@@ -800,7 +800,7 @@
800
800
  return adt;
801
801
  };
802
802
  const Adt = {
803
- generate: generate$2
803
+ generate: generate$1
804
804
  };
805
805
 
806
806
  const Cell = (initial) => {
@@ -945,7 +945,7 @@
945
945
  * generate :: String -> String
946
946
  */
947
947
  let unique = 0;
948
- const generate$1 = (prefix) => {
948
+ const generate = (prefix) => {
949
949
  const date = new Date();
950
950
  const time = date.getTime();
951
951
  const random$1 = Math.floor(random() * 1000000000);
@@ -1019,6 +1019,7 @@
1019
1019
  */
1020
1020
  const lift2 = (oa, ob, f) => oa.isSome() && ob.isSome() ? Optional.some(f(oa.getOrDie(), ob.getOrDie())) : Optional.none();
1021
1021
  const lift3 = (oa, ob, oc, f) => oa.isSome() && ob.isSome() && oc.isSome() ? Optional.some(f(oa.getOrDie(), ob.getOrDie(), oc.getOrDie())) : Optional.none();
1022
+ const flatten = (oot) => oot.bind(identity);
1022
1023
  // This can help with type inference, by specifying the type param on the none case, so the caller doesn't have to.
1023
1024
  const someIf = (b, a) => b ? Optional.some(a) : Optional.none();
1024
1025
 
@@ -7565,7 +7566,7 @@
7565
7566
  * alert('All scripts are now loaded.');
7566
7567
  * });
7567
7568
  */
7568
- const DOM$f = DOMUtils.DOM;
7569
+ const DOM$e = DOMUtils.DOM;
7569
7570
  const QUEUED = 0;
7570
7571
  const LOADING = 1;
7571
7572
  const LOADED = 2;
@@ -7594,7 +7595,7 @@
7594
7595
  */
7595
7596
  loadScript(url) {
7596
7597
  return new Promise((resolve, reject) => {
7597
- const dom = DOM$f;
7598
+ const dom = DOM$e;
7598
7599
  let elm;
7599
7600
  const cleanup = () => {
7600
7601
  dom.remove(id);
@@ -9443,7 +9444,7 @@
9443
9444
  selection.moveToBookmark({ id, keep: true, forward });
9444
9445
  return { id, forward };
9445
9446
  };
9446
- const getBookmark$3 = (selection, type, normalized = false) => {
9447
+ const getBookmark$2 = (selection, type, normalized = false) => {
9447
9448
  if (type === 2) {
9448
9449
  return getOffsetBookmark(trim$2, normalized, selection);
9449
9450
  }
@@ -9459,6 +9460,15 @@
9459
9460
  };
9460
9461
  const getUndoBookmark = curry(getOffsetBookmark, identity, true);
9461
9462
 
9463
+ /**
9464
+ * Checks if the direction is forwards.
9465
+ */
9466
+ const isForwards = (direction) => direction === 1 /* HDirection.Forwards */;
9467
+ /**
9468
+ * Checks if the direction is backwards.
9469
+ */
9470
+ const isBackwards = (direction) => direction === -1 /* HDirection.Backwards */;
9471
+
9462
9472
  const isInlinePattern = (pattern) => pattern.type === 'inline-command' || pattern.type === 'inline-format';
9463
9473
  const isBlockPattern = (pattern) => pattern.type === 'block-command' || pattern.type === 'block-format';
9464
9474
  const hasBlockTrigger = (pattern, trigger) => (pattern.type === 'block-command' || pattern.type === 'block-format') && pattern.trigger === trigger;
@@ -9640,7 +9650,7 @@
9640
9650
 
9641
9651
  const deviceDetection$1 = detect$1().deviceType;
9642
9652
  const isTouch = deviceDetection$1.isTouch();
9643
- const DOM$e = DOMUtils.DOM;
9653
+ const DOM$d = DOMUtils.DOM;
9644
9654
  const getHash = (value) => {
9645
9655
  const items = value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(',');
9646
9656
  return foldl(items, (output, item) => {
@@ -10422,7 +10432,7 @@
10422
10432
  registerOption('placeholder', {
10423
10433
  processor: 'string',
10424
10434
  // Fallback to the original elements placeholder if not set in the settings
10425
- default: DOM$e.getAttrib(editor.getElement(), 'placeholder')
10435
+ default: DOM$d.getAttrib(editor.getElement(), 'placeholder')
10426
10436
  });
10427
10437
  });
10428
10438
  registerOption('lists_indent_on_tab', {
@@ -10806,8 +10816,6 @@
10806
10816
  const isElement$3 = isElement$7;
10807
10817
  const isText$4 = isText$b;
10808
10818
  const isCaretCandidate$1 = isCaretCandidate$3;
10809
- const isForwards = (direction) => direction === 1 /* HDirection.Forwards */;
10810
- const isBackwards = (direction) => direction === -1 /* HDirection.Backwards */;
10811
10819
  const skipCaretContainers = (walk, shallow) => {
10812
10820
  let node;
10813
10821
  while ((node = walk(shallow))) {
@@ -11024,6 +11032,20 @@
11024
11032
  return inSameBlock;
11025
11033
  };
11026
11034
 
11035
+ /**
11036
+ * This module contains logic for moving around a virtual caret in logical order within a DOM element.
11037
+ *
11038
+ * It ignores the most obvious invalid caret locations such as within a script element or within a
11039
+ * contentEditable=false element but it will return locations that isn't possible to render visually.
11040
+ *
11041
+ * @private
11042
+ * @class tinymce.caret.CaretWalker
11043
+ * @example
11044
+ * const caretWalker = CaretWalker(rootElm);
11045
+ *
11046
+ * const prevLogicalCaretPosition = caretWalker.prev(CaretPosition.fromRangeStart(range));
11047
+ * const nextLogicalCaretPosition = caretWalker.next(CaretPosition.fromRangeEnd(range));
11048
+ */
11027
11049
  const isContentEditableFalse$5 = isContentEditableFalse$a;
11028
11050
  const isText$3 = isText$b;
11029
11051
  const isElement$2 = isElement$7;
@@ -11375,7 +11397,7 @@
11375
11397
  return true;
11376
11398
  };
11377
11399
  const isValidTextNode = (node) => isText$b(node) && node.data.length > 0;
11378
- const restoreEndPoint = (dom, suffix, bookmark) => {
11400
+ const restoreEndPoint$1 = (dom, suffix, bookmark) => {
11379
11401
  const marker = dom.get(bookmark.id + '_' + suffix);
11380
11402
  const markerParent = marker === null || marker === void 0 ? void 0 : marker.parentNode;
11381
11403
  const keep = bookmark.keep;
@@ -11469,8 +11491,8 @@
11469
11491
  }
11470
11492
  };
11471
11493
  const resolveId = (dom, bookmark) => {
11472
- const startPos = restoreEndPoint(dom, 'start', bookmark);
11473
- const endPos = restoreEndPoint(dom, 'end', bookmark);
11494
+ const startPos = restoreEndPoint$1(dom, 'start', bookmark);
11495
+ const endPos = restoreEndPoint$1(dom, 'end', bookmark);
11474
11496
  return lift2(startPos, endPos.or(startPos), (spos, epos) => {
11475
11497
  const range = dom.createRng();
11476
11498
  range.setStart(addBogus(dom, spos.container()), spos.offset());
@@ -11505,8 +11527,8 @@
11505
11527
  return Optional.none();
11506
11528
  };
11507
11529
 
11508
- const getBookmark$2 = (selection, type, normalized) => {
11509
- return getBookmark$3(selection, type, normalized);
11530
+ const getBookmark$1 = (selection, type, normalized) => {
11531
+ return getBookmark$2(selection, type, normalized);
11510
11532
  };
11511
11533
  const moveToBookmark = (selection, bookmark) => {
11512
11534
  resolve(selection, bookmark).each(({ range, forward }) => {
@@ -12428,7 +12450,7 @@
12428
12450
  const selection = editor.selection;
12429
12451
  const initialRng = selection.getRng();
12430
12452
  const hasFakeSelection = getCellsFromEditor(editor).length > 0;
12431
- const masterUid = generate$1('mce-annotation');
12453
+ const masterUid = generate('mce-annotation');
12432
12454
  if (initialRng.collapsed && !hasFakeSelection) {
12433
12455
  applyWordGrab(editor, initialRng);
12434
12456
  }
@@ -12576,7 +12598,7 @@
12576
12598
  * // Restore the selection bookmark
12577
12599
  * tinymce.activeEditor.selection.moveToBookmark(bm);
12578
12600
  */
12579
- getBookmark: curry(getBookmark$2, selection),
12601
+ getBookmark: curry(getBookmark$1, selection),
12580
12602
  /**
12581
12603
  * Restores the selection to the specified bookmark.
12582
12604
  *
@@ -12634,7 +12656,7 @@
12634
12656
  const rng = !selection || selection.rangeCount === 0 ? Optional.none() : Optional.from(selection.getRangeAt(0));
12635
12657
  return rng.map(nativeRangeToSelectionRange);
12636
12658
  };
12637
- const getBookmark$1 = (root) => {
12659
+ const getBookmark = (root) => {
12638
12660
  const win = defaultView(root);
12639
12661
  return readRange(win.dom)
12640
12662
  .filter(isRngInRoot(root));
@@ -12655,7 +12677,7 @@
12655
12677
  }
12656
12678
  };
12657
12679
  const store = (editor) => {
12658
- const newBookmark = shouldStore(editor) ? getBookmark$1(SugarElement.fromDom(editor.getBody())) : Optional.none();
12680
+ const newBookmark = shouldStore(editor) ? getBookmark(SugarElement.fromDom(editor.getBody())) : Optional.none();
12659
12681
  editor.bookmark = newBookmark.isSome() ? newBookmark : editor.bookmark;
12660
12682
  };
12661
12683
  const getRng = (editor) => {
@@ -12836,7 +12858,7 @@
12836
12858
  };
12837
12859
 
12838
12860
  let documentFocusInHandler;
12839
- const DOM$d = DOMUtils.DOM;
12861
+ const DOM$c = DOMUtils.DOM;
12840
12862
  const isEditorUIElement = (elm) => {
12841
12863
  // Since this can be overridden by third party we need to use the API reference here
12842
12864
  return isElement$7(elm) && FocusManager.isEditorUIElement(elm);
@@ -12854,7 +12876,7 @@
12854
12876
  };
12855
12877
  const isUIElement = (editor, elm) => {
12856
12878
  const customSelector = getCustomUiSelector(editor);
12857
- const parent = DOM$d.getParent(elm, (elm) => {
12879
+ const parent = DOM$c.getParent(elm, (elm) => {
12858
12880
  return (isEditorUIElement(elm) ||
12859
12881
  (customSelector ? editor.dom.is(elm, customSelector) : false));
12860
12882
  });
@@ -12944,7 +12966,7 @@
12944
12966
  });
12945
12967
  }
12946
12968
  };
12947
- DOM$d.bind(document, 'focusin', documentFocusInHandler);
12969
+ DOM$c.bind(document, 'focusin', documentFocusInHandler);
12948
12970
  }
12949
12971
  };
12950
12972
  const unregisterDocumentEvents = (editorManager, e) => {
@@ -12952,7 +12974,7 @@
12952
12974
  editorManager.focusedEditor = null;
12953
12975
  }
12954
12976
  if (!editorManager.activeEditor && documentFocusInHandler) {
12955
- DOM$d.unbind(document, 'focusin', documentFocusInHandler);
12977
+ DOM$c.unbind(document, 'focusin', documentFocusInHandler);
12956
12978
  documentFocusInHandler = null;
12957
12979
  }
12958
12980
  };
@@ -16067,90 +16089,90 @@
16067
16089
  return outRng;
16068
16090
  };
16069
16091
 
16070
- // TODO: This is a clone of the list bookmark code if we move lists to core then de-duplicate this #TINY-12172
16071
- const DOM$c = DOMUtils.DOM;
16072
- /**
16073
- * Returns a range bookmark. This will convert indexed bookmarks into temporary span elements with
16074
- * index 0 so that they can be restored properly after the DOM has been modified. Text bookmarks will not have spans
16075
- * added to them since they can be restored after a dom operation.
16076
- *
16077
- * So this: <p><b>|</b><b>|</b></p>
16078
- * becomes: <p><b><span data-mce-type="bookmark">|</span></b><b data-mce-type="bookmark">|</span></b></p>
16079
- */
16080
- const createBookmark$1 = (rng) => {
16081
- const bookmark = {};
16082
- const setupEndPoint = (start) => {
16083
- let container = rng[start ? 'startContainer' : 'endContainer'];
16084
- let offset = rng[start ? 'startOffset' : 'endOffset'];
16085
- if (isElement$7(container)) {
16086
- const offsetNode = DOM$c.create('span', { 'data-mce-type': 'bookmark' });
16087
- if (container.hasChildNodes()) {
16088
- if (offset === container.childNodes.length) {
16089
- container.appendChild(offsetNode);
16090
- }
16091
- else {
16092
- container.insertBefore(offsetNode, container.childNodes[offset]);
16093
- }
16092
+ const DOM$b = DOMUtils.DOM;
16093
+ const defaultMarker = () => DOM$b.create('span', { 'data-mce-type': 'bookmark' });
16094
+ const setupEndPoint = (container, offset, createMarker) => {
16095
+ if (isElement$7(container)) {
16096
+ const offsetNode = createMarker();
16097
+ if (container.hasChildNodes()) {
16098
+ if (offset === container.childNodes.length) {
16099
+ container.appendChild(offsetNode);
16094
16100
  }
16095
16101
  else {
16096
- container.appendChild(offsetNode);
16102
+ container.insertBefore(offsetNode, container.childNodes[offset]);
16097
16103
  }
16098
- container = offsetNode;
16099
- offset = 0;
16100
16104
  }
16101
- bookmark[start ? 'startContainer' : 'endContainer'] = container;
16102
- bookmark[start ? 'startOffset' : 'endOffset'] = offset;
16103
- };
16104
- setupEndPoint(true);
16105
- if (!rng.collapsed) {
16106
- setupEndPoint();
16105
+ else {
16106
+ container.appendChild(offsetNode);
16107
+ }
16108
+ return { container: offsetNode, offset: 0 };
16109
+ }
16110
+ else {
16111
+ return { container, offset };
16107
16112
  }
16108
- return bookmark;
16109
16113
  };
16110
- const resolveBookmark$2 = (bookmark) => {
16111
- const restoreEndPoint = (start) => {
16112
- const nodeIndex = (container) => {
16113
- var _a;
16114
- let node = (_a = container.parentNode) === null || _a === void 0 ? void 0 : _a.firstChild;
16115
- let idx = 0;
16116
- while (node) {
16117
- if (node === container) {
16118
- return idx;
16119
- }
16120
- // Skip data-mce-type=bookmark nodes
16121
- if (!isElement$7(node) || node.getAttribute('data-mce-type') !== 'bookmark') {
16122
- idx++;
16123
- }
16124
- node = node.nextSibling;
16114
+ const restoreEndPoint = (container, offset) => {
16115
+ const nodeIndex = (container) => {
16116
+ var _a;
16117
+ let node = (_a = container.parentNode) === null || _a === void 0 ? void 0 : _a.firstChild;
16118
+ let idx = 0;
16119
+ while (node) {
16120
+ if (node === container) {
16121
+ return idx;
16125
16122
  }
16126
- return -1;
16127
- };
16128
- let container = bookmark[start ? 'startContainer' : 'endContainer'];
16129
- let offset = bookmark[start ? 'startOffset' : 'endOffset'];
16130
- if (!container) {
16131
- return;
16132
- }
16133
- if (isElement$7(container) && container.parentNode) {
16134
- const node = container;
16135
- offset = nodeIndex(container);
16136
- container = container.parentNode;
16137
- DOM$c.remove(node);
16138
- if (!container.hasChildNodes() && DOM$c.isBlock(container)) {
16139
- container.appendChild(DOM$c.create('br'));
16123
+ // Skip data-mce-type=bookmark nodes
16124
+ if (!isElement$7(node) || node.getAttribute('data-mce-type') !== 'bookmark') {
16125
+ idx++;
16140
16126
  }
16127
+ node = node.nextSibling;
16141
16128
  }
16142
- bookmark[start ? 'startContainer' : 'endContainer'] = container;
16143
- bookmark[start ? 'startOffset' : 'endOffset'] = offset;
16129
+ return -1;
16144
16130
  };
16145
- restoreEndPoint(true);
16146
- restoreEndPoint();
16147
- const rng = DOM$c.createRng();
16148
- rng.setStart(bookmark.startContainer, bookmark.startOffset);
16149
- if (bookmark.endContainer) {
16150
- rng.setEnd(bookmark.endContainer, bookmark.endOffset);
16131
+ if (isElement$7(container) && isNonNullable(container.parentNode)) {
16132
+ const node = container;
16133
+ offset = nodeIndex(container);
16134
+ container = container.parentNode;
16135
+ DOM$b.remove(node);
16136
+ if (!container.hasChildNodes() && DOM$b.isBlock(container)) {
16137
+ container.appendChild(DOM$b.create('br'));
16138
+ }
16151
16139
  }
16140
+ return { container, offset };
16141
+ };
16142
+ const createNormalizedRange = (startContainer, startOffset, endContainer, endOffset) => {
16143
+ const rng = DOM$b.createRng();
16144
+ rng.setStart(startContainer, startOffset);
16145
+ rng.setEnd(endContainer, endOffset);
16152
16146
  return normalizeRange$1(rng);
16153
16147
  };
16148
+ /**
16149
+ * Returns a range bookmark. This will convert indexed bookmarks into temporary span elements with
16150
+ * index 0 so that they can be restored properly after the DOM has been modified. Text bookmarks will not have spans
16151
+ * added to them since they can be restored after a dom operation.
16152
+ *
16153
+ * So this: <p><b>|</b><b>|</b></p>
16154
+ * becomes: <p><b><span data-mce-type="bookmark">|</span></b><b data-mce-type="bookmark">|</span></b></p>
16155
+ */
16156
+ const createBookmark = (rng, createMarker = defaultMarker) => {
16157
+ const { container: startContainer, offset: startOffset } = setupEndPoint(rng.startContainer, rng.startOffset, createMarker);
16158
+ if (rng.collapsed) {
16159
+ return { startContainer, startOffset };
16160
+ }
16161
+ else {
16162
+ const { container: endContainer, offset: endOffset } = setupEndPoint(rng.endContainer, rng.endOffset, createMarker);
16163
+ return { startContainer, startOffset, endContainer, endOffset };
16164
+ }
16165
+ };
16166
+ const resolveBookmark = (bookmark) => {
16167
+ const { container: startContainer, offset: startOffset } = restoreEndPoint(bookmark.startContainer, bookmark.startOffset);
16168
+ if (!isUndefined(bookmark.endContainer) && !isUndefined(bookmark.endOffset)) {
16169
+ const { container: endContainer, offset: endOffset } = restoreEndPoint(bookmark.endContainer, bookmark.endOffset);
16170
+ return createNormalizedRange(startContainer, startOffset, endContainer, endOffset);
16171
+ }
16172
+ else {
16173
+ return createNormalizedRange(startContainer, startOffset, startContainer, startOffset);
16174
+ }
16175
+ };
16154
16176
 
16155
16177
  const applyStyles = (dom, elm, format, vars) => {
16156
16178
  Tools.each(format.styles, (value, name) => {
@@ -17398,9 +17420,9 @@
17398
17420
  return Optional.some(el);
17399
17421
  }
17400
17422
  };
17401
- const bookmark = createBookmark$1(editor.selection.getRng());
17423
+ const bookmark = createBookmark(editor.selection.getRng());
17402
17424
  normalizeFontSizeElementsInternal(editor.dom, fontSizeElements, hasFormat, createFormatElement, removeFormatFromElement);
17403
- editor.selection.setRng(resolveBookmark$2(bookmark));
17425
+ editor.selection.setRng(resolveBookmark(bookmark));
17404
17426
  };
17405
17427
  const collectFontSizeElements = (formatter, wrappers) => bind$3(wrappers, (wrapper) => {
17406
17428
  const fontSizeDescendants = descendants$1(wrapper, (el) => isFontSizeAlteringElement(formatter, el));
@@ -20056,7 +20078,7 @@
20056
20078
  // Deliberately ban all tags and attributes by default, and then un-ban them on demand in hooks
20057
20079
  // #comment and #cdata-section are always allowed as they aren't controlled via the schema
20058
20080
  // body is also allowed due to the DOMPurify checking the root node before sanitizing
20059
- ALLOWED_TAGS: ['#comment', '#cdata-section', 'body'],
20081
+ ALLOWED_TAGS: ['#comment', '#cdata-section', 'body', 'html'],
20060
20082
  ALLOWED_ATTR: []
20061
20083
  };
20062
20084
  const config = { ...basePurifyConfig };
@@ -20393,6 +20415,7 @@
20393
20415
  return name;
20394
20416
  }
20395
20417
  };
20418
+ const xhtmlAttribte = ' xmlns="http://www.w3.org/1999/xhtml"';
20396
20419
  const DomParser = (settings = {}, schema = Schema()) => {
20397
20420
  const nodeFilterRegistry = create$8();
20398
20421
  const attributeFilterRegistry = create$8();
@@ -20406,25 +20429,28 @@
20406
20429
  };
20407
20430
  const parser = new DOMParser();
20408
20431
  const sanitizer = getSanitizer(defaultedSettings, schema);
20409
- const parseAndSanitizeWithContext = (html, rootName, format = 'html') => {
20410
- const mimeType = format === 'xhtml' ? 'application/xhtml+xml' : 'text/html';
20432
+ const parseAndSanitizeWithContext = (html, rootName, format = 'html', useDocumentNotBody = false) => {
20433
+ const isxhtml = format === 'xhtml';
20434
+ const mimeType = isxhtml ? 'application/xhtml+xml' : 'text/html';
20411
20435
  // Determine the root element to wrap the HTML in when parsing. If we're dealing with a
20412
20436
  // special element then we need to wrap it so the internal content is handled appropriately.
20413
20437
  const isSpecialRoot = has$2(schema.getSpecialElements(), rootName.toLowerCase());
20414
20438
  const content = isSpecialRoot ? `<${rootName}>${html}</${rootName}>` : html;
20415
20439
  const makeWrap = () => {
20416
- if (format === 'xhtml') {
20417
- // If parsing XHTML then the content must contain the xmlns declaration, see https://www.w3.org/TR/xhtml1/normative.html#strict
20418
- return `<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>${content}</body></html>`;
20419
- }
20420
- else if (/^[\s]*<head/i.test(html) || /^[\s]*<html/i.test(html) || /^[\s]*<!DOCTYPE/i.test(html)) {
20421
- return `<html>${content}</html>`;
20440
+ if (/^[\s]*<head/i.test(html) || /^[\s]*<html/i.test(html) || /^[\s]*<!DOCTYPE/i.test(html)) {
20441
+ return `<html${isxhtml ? xhtmlAttribte : ''}>${content}</html>`;
20422
20442
  }
20423
20443
  else {
20424
- return `<body>${content}</body>`;
20444
+ if (isxhtml) {
20445
+ return `<html${xhtmlAttribte}><head></head><body>${content}</body></html>`;
20446
+ }
20447
+ else {
20448
+ return `<body>${content}</body>`;
20449
+ }
20425
20450
  }
20426
20451
  };
20427
- const body = parser.parseFromString(makeWrap(), mimeType).body;
20452
+ const document = parser.parseFromString(makeWrap(), mimeType);
20453
+ const body = useDocumentNotBody ? document.documentElement : document.body;
20428
20454
  sanitizer.sanitizeHtmlElement(body, mimeType);
20429
20455
  return isSpecialRoot ? body.firstChild : body;
20430
20456
  };
@@ -20555,11 +20581,12 @@
20555
20581
  * const rootNode = tinymce.html.DomParser({...}).parse('<b>text</b>');
20556
20582
  */
20557
20583
  const parse = (html, args = {}) => {
20558
- var _a;
20584
+ var _a, _b;
20559
20585
  const validate = defaultedSettings.validate;
20560
- const rootName = (_a = args.context) !== null && _a !== void 0 ? _a : defaultedSettings.root_name;
20586
+ const preferFullDocument = ((_a = args.context) !== null && _a !== void 0 ? _a : defaultedSettings.root_name) === '#document';
20587
+ const rootName = (_b = args.context) !== null && _b !== void 0 ? _b : (preferFullDocument ? 'html' : defaultedSettings.root_name);
20561
20588
  // Parse and sanitize the content
20562
- const element = parseAndSanitizeWithContext(html, rootName, args.format);
20589
+ const element = parseAndSanitizeWithContext(html, rootName, args.format, preferFullDocument);
20563
20590
  updateChildren(schema, element);
20564
20591
  // Create the AST representation
20565
20592
  const rootNode = new AstNode(rootName, 11);
@@ -20694,8 +20721,7 @@
20694
20721
  'tab_focus,tabfocus_elements,table_responsive_width,text_block_elements,text_inline_elements,toolbar_drawer,types,validate,whitespace_elements,' +
20695
20722
  'paste_enable_default_filters,paste_filter_drop,paste_word_valid_elements,paste_retain_style_properties,paste_convert_word_fake_lists,' +
20696
20723
  'template_cdate_classes,template_mdate_classes,template_selected_content_classes,template_preview_replace_values,template_replace_values,templates,template_cdate_format,template_mdate_format').split(',');
20697
- // const deprecatedOptions: string[] = ('').split(',');
20698
- const deprecatedOptions = [];
20724
+ const deprecatedOptions = ['content_css_cors'];
20699
20725
  const removedPlugins = 'bbcode,colorpicker,contextmenu,fullpage,legacyoutput,spellchecker,template,textcolor,rtc'.split(',');
20700
20726
  const deprecatedPlugins = [
20701
20727
  {
@@ -23746,12 +23772,21 @@
23746
23772
  const schema = editor && editor.schema ? editor.schema : Schema(defaultedSettings);
23747
23773
  const htmlParser = DomParser(defaultedSettings, schema);
23748
23774
  register$3(htmlParser, defaultedSettings, dom);
23749
- const serialize = (node, parserArgs = {}) => {
23750
- const args = { format: 'html', ...parserArgs };
23751
- const targetNode = process$1(editor, node, args);
23752
- const html = getHtmlFromNode(dom, targetNode, args);
23753
- const rootNode = parseHtml(htmlParser, html, args);
23754
- return args.format === 'tree' ? rootNode : toHtml(editor, defaultedSettings, schema, rootNode, args);
23775
+ const serialize = (node, domSerializerArgs = {}) => {
23776
+ const { indent, entity_encoding, ...rest } = domSerializerArgs;
23777
+ const parserArgs = { format: 'html', ...rest };
23778
+ const targetNode = process$1(editor, node, parserArgs);
23779
+ const html = getHtmlFromNode(dom, targetNode, parserArgs);
23780
+ const rootNode = parseHtml(htmlParser, html, parserArgs);
23781
+ if (parserArgs.format === 'tree') {
23782
+ return rootNode;
23783
+ }
23784
+ const serializerSettings = {
23785
+ ...defaultedSettings,
23786
+ ...(isNonNullable(indent) ? { indent } : {}),
23787
+ ...(isNonNullable(entity_encoding) ? { entity_encoding } : {}),
23788
+ };
23789
+ return toHtml(editor, serializerSettings, schema, rootNode, parserArgs);
23755
23790
  };
23756
23791
  return {
23757
23792
  schema,
@@ -23921,9 +23956,9 @@
23921
23956
  });
23922
23957
  };
23923
23958
 
23924
- const DOM$b = DOMUtils.DOM;
23959
+ const DOM$a = DOMUtils.DOM;
23925
23960
  const restoreOriginalStyles = (editor) => {
23926
- DOM$b.setStyle(editor.id, 'display', editor.orgDisplay);
23961
+ DOM$a.setStyle(editor.id, 'display', editor.orgDisplay);
23927
23962
  };
23928
23963
  const safeDestroy = (x) => Optional.from(x).each((x) => x.destroy());
23929
23964
  const clearDomReferences = (editor) => {
@@ -23944,7 +23979,7 @@
23944
23979
  form.submit = form._mceOldSubmit;
23945
23980
  delete form._mceOldSubmit;
23946
23981
  }
23947
- DOM$b.unbind(form, 'submit reset', editor.formEventDelegate);
23982
+ DOM$a.unbind(form, 'submit reset', editor.formEventDelegate);
23948
23983
  }
23949
23984
  };
23950
23985
  const remove$1 = (editor) => {
@@ -23959,7 +23994,7 @@
23959
23994
  editor.unbindAllNativeEvents();
23960
23995
  // Remove any hidden input
23961
23996
  if (editor.hasHiddenInput && isNonNullable(element === null || element === void 0 ? void 0 : element.nextSibling)) {
23962
- DOM$b.remove(element.nextSibling);
23997
+ DOM$a.remove(element.nextSibling);
23963
23998
  }
23964
23999
  fireRemove(editor);
23965
24000
  editor.editorManager.remove(editor);
@@ -23967,7 +24002,7 @@
23967
24002
  restoreOriginalStyles(editor);
23968
24003
  }
23969
24004
  fireDetach(editor);
23970
- DOM$b.remove(editor.getContainer());
24005
+ DOM$a.remove(editor.getContainer());
23971
24006
  safeDestroy(_selectionOverrides);
23972
24007
  safeDestroy(editorUpload);
23973
24008
  editor.destroy();
@@ -26633,6 +26668,37 @@
26633
26668
  };
26634
26669
  const isChildOfBody = (dom, elm) => dom.isChildOf(elm, dom.getRoot());
26635
26670
 
26671
+ const DOM$9 = DOMUtils.DOM;
26672
+ const normalizeList = (dom, list) => {
26673
+ const parentNode = list.parentElement;
26674
+ // Move UL/OL to previous LI if it's the only child of a LI
26675
+ if (parentNode && parentNode.nodeName === 'LI' && parentNode.firstChild === list) {
26676
+ const sibling = parentNode.previousSibling;
26677
+ if (sibling && sibling.nodeName === 'LI') {
26678
+ sibling.appendChild(list);
26679
+ if (isEmpty$1(dom, parentNode)) {
26680
+ DOM$9.remove(parentNode);
26681
+ }
26682
+ }
26683
+ else {
26684
+ DOM$9.setStyle(parentNode, 'listStyleType', 'none');
26685
+ }
26686
+ }
26687
+ // Append OL/UL to previous LI if it's in a parent OL/UL i.e. old HTML4
26688
+ if (isListNode(parentNode)) {
26689
+ const sibling = parentNode.previousSibling;
26690
+ if (sibling && sibling.nodeName === 'LI') {
26691
+ sibling.appendChild(list);
26692
+ }
26693
+ }
26694
+ };
26695
+ const normalizeLists = (dom, element) => {
26696
+ const lists = Tools.grep(dom.select('ol,ul', element));
26697
+ Tools.each(lists, (list) => {
26698
+ normalizeList(dom, list);
26699
+ });
26700
+ };
26701
+
26636
26702
  const getNormalizedPoint = (container, offset) => {
26637
26703
  if (isTextNode$1(container)) {
26638
26704
  return { container, offset };
@@ -26667,123 +26733,6 @@
26667
26733
  return outRng;
26668
26734
  };
26669
26735
 
26670
- const DOM$a = DOMUtils.DOM;
26671
- /**
26672
- * Returns a range bookmark. This will convert indexed bookmarks into temporary span elements with
26673
- * index 0 so that they can be restored properly after the DOM has been modified. Text bookmarks will not have spans
26674
- * added to them since they can be restored after a dom operation.
26675
- *
26676
- * So this: <p><b>|</b><b>|</b></p>
26677
- * becomes: <p><b><span data-mce-type="bookmark">|</span></b><b data-mce-type="bookmark">|</span></b></p>
26678
- *
26679
- */
26680
- const createBookmark = (rng) => {
26681
- const bookmark = {};
26682
- const setupEndPoint = (start) => {
26683
- let container = rng[start ? 'startContainer' : 'endContainer'];
26684
- let offset = rng[start ? 'startOffset' : 'endOffset'];
26685
- if (isElement$1(container)) {
26686
- const offsetNode = DOM$a.create('span', { 'data-mce-type': 'bookmark' });
26687
- if (container.hasChildNodes()) {
26688
- offset = Math.min(offset, container.childNodes.length - 1);
26689
- if (start) {
26690
- container.insertBefore(offsetNode, container.childNodes[offset]);
26691
- }
26692
- else {
26693
- DOM$a.insertAfter(offsetNode, container.childNodes[offset]);
26694
- }
26695
- }
26696
- else {
26697
- container.appendChild(offsetNode);
26698
- }
26699
- container = offsetNode;
26700
- offset = 0;
26701
- }
26702
- bookmark[start ? 'startContainer' : 'endContainer'] = container;
26703
- bookmark[start ? 'startOffset' : 'endOffset'] = offset;
26704
- };
26705
- setupEndPoint(true);
26706
- if (!rng.collapsed) {
26707
- setupEndPoint();
26708
- }
26709
- return bookmark;
26710
- };
26711
- const resolveBookmark$1 = (bookmark) => {
26712
- const restoreEndPoint = (start) => {
26713
- const nodeIndex = (container) => {
26714
- var _a;
26715
- let node = (_a = container.parentNode) === null || _a === void 0 ? void 0 : _a.firstChild;
26716
- let idx = 0;
26717
- while (node) {
26718
- if (node === container) {
26719
- return idx;
26720
- }
26721
- // Skip data-mce-type=bookmark nodes
26722
- if (!isElement$1(node) || node.getAttribute('data-mce-type') !== 'bookmark') {
26723
- idx++;
26724
- }
26725
- node = node.nextSibling;
26726
- }
26727
- return -1;
26728
- };
26729
- let container = bookmark[start ? 'startContainer' : 'endContainer'];
26730
- let offset = bookmark[start ? 'startOffset' : 'endOffset'];
26731
- if (!container) {
26732
- return;
26733
- }
26734
- if (isElement$1(container) && container.parentNode) {
26735
- const node = container;
26736
- offset = nodeIndex(container);
26737
- container = container.parentNode;
26738
- DOM$a.remove(node);
26739
- if (!container.hasChildNodes() && DOM$a.isBlock(container)) {
26740
- container.appendChild(DOM$a.create('br'));
26741
- }
26742
- }
26743
- bookmark[start ? 'startContainer' : 'endContainer'] = container;
26744
- bookmark[start ? 'startOffset' : 'endOffset'] = offset;
26745
- };
26746
- restoreEndPoint(true);
26747
- restoreEndPoint();
26748
- const rng = DOM$a.createRng();
26749
- rng.setStart(bookmark.startContainer, bookmark.startOffset);
26750
- if (bookmark.endContainer) {
26751
- rng.setEnd(bookmark.endContainer, bookmark.endOffset);
26752
- }
26753
- return normalizeRange(rng);
26754
- };
26755
-
26756
- const DOM$9 = DOMUtils.DOM;
26757
- const normalizeList = (dom, list) => {
26758
- const parentNode = list.parentElement;
26759
- // Move UL/OL to previous LI if it's the only child of a LI
26760
- if (parentNode && parentNode.nodeName === 'LI' && parentNode.firstChild === list) {
26761
- const sibling = parentNode.previousSibling;
26762
- if (sibling && sibling.nodeName === 'LI') {
26763
- sibling.appendChild(list);
26764
- if (isEmpty$1(dom, parentNode)) {
26765
- DOM$9.remove(parentNode);
26766
- }
26767
- }
26768
- else {
26769
- DOM$9.setStyle(parentNode, 'listStyleType', 'none');
26770
- }
26771
- }
26772
- // Append OL/UL to previous LI if it's in a parent OL/UL i.e. old HTML4
26773
- if (isListNode(parentNode)) {
26774
- const sibling = parentNode.previousSibling;
26775
- if (sibling && sibling.nodeName === 'LI') {
26776
- sibling.appendChild(list);
26777
- }
26778
- }
26779
- };
26780
- const normalizeLists = (dom, element) => {
26781
- const lists = Tools.grep(dom.select('ol,ul', element));
26782
- Tools.each(lists, (list) => {
26783
- normalizeList(dom, list);
26784
- });
26785
- };
26786
-
26787
26736
  const listNames = ['OL', 'UL', 'DL'];
26788
26737
  const listSelector = listNames.join(',');
26789
26738
  const getParentList = (editor, node) => {
@@ -27492,7 +27441,7 @@
27492
27441
  mergeWithAdjacentLists(editor.dom, listBlock);
27493
27442
  }
27494
27443
  });
27495
- editor.selection.setRng(resolveBookmark$1(bookmark));
27444
+ editor.selection.setRng(resolveBookmark(bookmark));
27496
27445
  };
27497
27446
  const isValidLists = (list1, list2) => {
27498
27447
  return isListNode(list1) && list1.nodeName === (list2 === null || list2 === void 0 ? void 0 : list2.nodeName);
@@ -27574,7 +27523,7 @@
27574
27523
  Tools.each(allLists, (elm) => {
27575
27524
  updateFunction(editor, elm, listName, detail);
27576
27525
  });
27577
- editor.selection.setRng(resolveBookmark$1(bookmark));
27526
+ editor.selection.setRng(resolveBookmark(bookmark));
27578
27527
  }
27579
27528
  };
27580
27529
  const hasListStyleDetail = (detail) => {
@@ -27603,7 +27552,7 @@
27603
27552
  updateListWithDetails(editor.dom, parentList, detail);
27604
27553
  const newList = editor.dom.rename(parentList, listName);
27605
27554
  mergeWithAdjacentLists(editor.dom, newList);
27606
- editor.selection.setRng(resolveBookmark$1(bookmark));
27555
+ editor.selection.setRng(resolveBookmark(bookmark));
27607
27556
  applyList(editor, listName, detail);
27608
27557
  fireListEvent(editor, listToggleActionFromListName(listName), newList);
27609
27558
  }
@@ -27729,13 +27678,13 @@
27729
27678
  else {
27730
27679
  const bookmark = createBookmark(rng);
27731
27680
  mergeLiElements(dom, fromLi, toLi);
27732
- editor.selection.setRng(resolveBookmark$1(bookmark));
27681
+ editor.selection.setRng(resolveBookmark(bookmark));
27733
27682
  }
27734
27683
  };
27735
27684
  const mergeBackward = (editor, rng, fromLi, toLi) => {
27736
27685
  const bookmark = createBookmark(rng);
27737
27686
  mergeLiElements(editor.dom, fromLi, toLi);
27738
- const resolvedBookmark = resolveBookmark$1(bookmark);
27687
+ const resolvedBookmark = resolveBookmark(bookmark);
27739
27688
  editor.selection.setRng(resolvedBookmark);
27740
27689
  };
27741
27690
  const backspaceDeleteFromListToListCaret = (editor, isForward) => {
@@ -27776,7 +27725,7 @@
27776
27725
  const bookmark = createBookmark(rng);
27777
27726
  moveChildren(dom, commonAncestorParent, otherLi);
27778
27727
  commonAncestorParent.remove();
27779
- const resolvedBookmark = resolveBookmark$1(bookmark);
27728
+ const resolvedBookmark = resolveBookmark(bookmark);
27780
27729
  editor.selection.setRng(resolvedBookmark);
27781
27730
  });
27782
27731
  return true;
@@ -28311,65 +28260,6 @@
28311
28260
  }
28312
28261
  };
28313
28262
 
28314
- const isTextEndpoint = (endpoint) => endpoint.hasOwnProperty('text');
28315
- const isElementEndpoint = (endpoint) => endpoint.hasOwnProperty('marker');
28316
- const getBookmark = (range, createMarker) => {
28317
- const getEndpoint = (container, offset) => {
28318
- if (isText$b(container)) {
28319
- return { text: container, offset };
28320
- }
28321
- else {
28322
- const marker = createMarker();
28323
- const children = container.childNodes;
28324
- if (offset < children.length) {
28325
- container.insertBefore(marker, children[offset]);
28326
- return { marker, before: true };
28327
- }
28328
- else {
28329
- container.appendChild(marker);
28330
- return { marker, before: false };
28331
- }
28332
- }
28333
- };
28334
- const end = getEndpoint(range.endContainer, range.endOffset);
28335
- const start = getEndpoint(range.startContainer, range.startOffset);
28336
- return { start, end };
28337
- };
28338
- const resolveBookmark = (bm) => {
28339
- var _a, _b;
28340
- const { start, end } = bm;
28341
- const rng = new window.Range();
28342
- if (isTextEndpoint(start)) {
28343
- rng.setStart(start.text, start.offset);
28344
- }
28345
- else {
28346
- if (isElementEndpoint(start)) {
28347
- if (start.before) {
28348
- rng.setStartBefore(start.marker);
28349
- }
28350
- else {
28351
- rng.setStartAfter(start.marker);
28352
- }
28353
- (_a = start.marker.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(start.marker);
28354
- }
28355
- }
28356
- if (isTextEndpoint(end)) {
28357
- rng.setEnd(end.text, end.offset);
28358
- }
28359
- else {
28360
- if (isElementEndpoint(end)) {
28361
- if (end.before) {
28362
- rng.setEndBefore(end.marker);
28363
- }
28364
- else {
28365
- rng.setEndAfter(end.marker);
28366
- }
28367
- (_b = end.marker.parentNode) === null || _b === void 0 ? void 0 : _b.removeChild(end.marker);
28368
- }
28369
- }
28370
- return rng;
28371
- };
28372
-
28373
28263
  const backspaceDelete$7 = (editor, forward) => {
28374
28264
  var _a;
28375
28265
  const dom = editor.dom;
@@ -28389,7 +28279,7 @@
28389
28279
  }
28390
28280
  // Div was deleted by delete operation then lets restore it
28391
28281
  if (body.firstChild !== startBlock) {
28392
- const bookmark = getBookmark(editor.selection.getRng(), () => document.createElement('span'));
28282
+ const bookmark = createBookmark(editor.selection.getRng(), () => document.createElement('span'));
28393
28283
  Array.from(body.childNodes).forEach((node) => wrapper.appendChild(node));
28394
28284
  body.appendChild(wrapper);
28395
28285
  editor.selection.setRng(resolveBookmark(bookmark));
@@ -28932,7 +28822,7 @@
28932
28822
  }
28933
28823
  return Optional.none();
28934
28824
  };
28935
- const moveVertically = (editor, direction, range, isBefore, isAfter, isElement) => {
28825
+ const moveVertically$1 = (editor, direction, range, isBefore, isAfter, isElement) => {
28936
28826
  const caretPosition = getNormalizedRangeEndPoint(direction, editor.getBody(), range);
28937
28827
  const caretClientRect = last(caretPosition.getClientRects());
28938
28828
  const forwards = direction === VDirection.Down;
@@ -29595,7 +29485,7 @@
29595
29485
  }
29596
29486
  if (!rootBlockNode) {
29597
29487
  if (!bm && editor.hasFocus()) {
29598
- bm = getBookmark(editor.selection.getRng(), () => document.createElement('span'));
29488
+ bm = createBookmark(editor.selection.getRng(), () => document.createElement('span'));
29599
29489
  }
29600
29490
  // Firefox will remove the last BR element if you insert nodes next to it using DOM APIs like insertBefore
29601
29491
  // so for that weird edge case we stop processing.
@@ -29762,7 +29652,7 @@
29762
29652
  const moveToCeFalseVertically = (direction, editor, range) => {
29763
29653
  const isBefore = (caretPosition) => isBeforeContentEditableFalse(caretPosition) || isBeforeTable(caretPosition);
29764
29654
  const isAfter = (caretPosition) => isAfterContentEditableFalse(caretPosition) || isAfterTable(caretPosition);
29765
- return moveVertically(editor, direction, range, isBefore, isAfter, isContentEditableFalse$3);
29655
+ return moveVertically$1(editor, direction, range, isBefore, isAfter, isContentEditableFalse$3);
29766
29656
  };
29767
29657
  const createTextBlock = (editor) => {
29768
29658
  const textBlock = editor.dom.create(getForcedRootBlock(editor));
@@ -29815,7 +29705,7 @@
29815
29705
  moveToRange(editor, newRange);
29816
29706
  return true;
29817
29707
  });
29818
- const moveV$4 = (editor, down) => getVerticalRange(editor, down).exists((newRange) => {
29708
+ const moveV$5 = (editor, down) => getVerticalRange(editor, down).exists((newRange) => {
29819
29709
  moveToRange(editor, newRange);
29820
29710
  return true;
29821
29711
  });
@@ -29840,6 +29730,39 @@
29840
29730
  return true;
29841
29731
  });
29842
29732
 
29733
+ const getClosestCetBlock = (position, root) => {
29734
+ const isRoot = (el) => eq(el, root);
29735
+ const isCet = (el) => isContentEditableTrue$3(el.dom);
29736
+ const startNode = SugarElement.fromDom(position.container());
29737
+ const closestCetBlock = closest$4(startNode, isCet, isRoot);
29738
+ return closestCetBlock.filter((b) => !isRoot(b));
29739
+ };
29740
+ const moveVertically = (editor, position, down) => {
29741
+ const getNextPosition = down ? getClosestPositionBelow : getClosestPositionAbove;
29742
+ return getNextPosition(editor.getBody(), position).map((nextPosition) => nextPosition.toRange());
29743
+ };
29744
+ const moveToNextOrPreviousLine = (editor, down) => {
29745
+ const startPosition = CaretPosition.fromRangeStart(editor.selection.getRng());
29746
+ const endPosition = CaretPosition.fromRangeEnd(editor.selection.getRng());
29747
+ const root = SugarElement.fromDom(editor.getBody());
29748
+ /* I wasn't able to find a way to create a selection between two different contenteditable elements.
29749
+ However I can't rule out that it is possible. So I am checking if both positions are in the same contenteditable element.
29750
+ This is a defensive check to ensure selection integrity.
29751
+ */
29752
+ const closestCetBlock = flatten(lift2(getClosestCetBlock(startPosition, root), getClosestCetBlock(endPosition, root), (c1, c2) => eq(c1, c2) ? Optional.some(c1) : Optional.none()));
29753
+ return closestCetBlock.fold(never, (cetBlock) => {
29754
+ if ((down && isAtLastLine(cetBlock.dom, endPosition)) ||
29755
+ (!down && isAtFirstLine(cetBlock.dom, startPosition))) {
29756
+ return moveVertically(editor, down ? endPosition : startPosition, down).exists((newRange) => {
29757
+ moveToRange(editor, newRange);
29758
+ return true;
29759
+ });
29760
+ }
29761
+ return false;
29762
+ });
29763
+ };
29764
+ const moveV$4 = (editor, down) => moveToNextOrPreviousLine(editor, down);
29765
+
29843
29766
  const isTarget = (node) => contains$2(['figcaption'], name(node));
29844
29767
  const getClosestTargetBlock = (pos, root, schema) => {
29845
29768
  const isRoot = curry(eq, root);
@@ -29945,7 +29868,7 @@
29945
29868
  const moveV$1 = (editor, down) => {
29946
29869
  const direction = down ? 1 : -1;
29947
29870
  const range = editor.selection.getRng();
29948
- return moveVertically(editor, direction, range, isBeforeMedia, isAfterMedia, isMedia$2).exists((newRange) => {
29871
+ return moveVertically$1(editor, direction, range, isBeforeMedia, isAfterMedia, isMedia$2).exists((newRange) => {
29949
29872
  moveToRange(editor, newRange);
29950
29873
  return true;
29951
29874
  });
@@ -30230,7 +30153,7 @@
30230
30153
  return toLeaf$2(universe, element, offset);
30231
30154
  };
30232
30155
 
30233
- const imageId = generate$1('image');
30156
+ const imageId = generate('image');
30234
30157
  const getDragImage = (transfer) => {
30235
30158
  const dt = transfer;
30236
30159
  return Optional.from(dt[imageId]);
@@ -30240,7 +30163,7 @@
30240
30163
  dt[imageId] = imageData;
30241
30164
  };
30242
30165
 
30243
- const eventId = generate$1('event');
30166
+ const eventId = generate('event');
30244
30167
  const getEvent = (transfer) => {
30245
30168
  const dt = transfer;
30246
30169
  return Optional.from(dt[eventId]);
@@ -30264,7 +30187,7 @@
30264
30187
  item: (_) => null
30265
30188
  });
30266
30189
 
30267
- const modeId = generate$1('mode');
30190
+ const modeId = generate('mode');
30268
30191
  const getMode = (transfer) => {
30269
30192
  const dt = transfer;
30270
30193
  return Optional.from(dt[modeId]);
@@ -30580,11 +30503,12 @@
30580
30503
 
30581
30504
  const executeKeydownOverride$4 = (editor, caret, evt) => {
30582
30505
  const isMac = Env.os.isMacOS() || Env.os.isiOS();
30506
+ const isFirefox = Env.browser.isFirefox();
30583
30507
  execute([
30584
30508
  { keyCode: VK.RIGHT, action: action(moveH$2, editor, true) },
30585
30509
  { keyCode: VK.LEFT, action: action(moveH$2, editor, false) },
30586
- { keyCode: VK.UP, action: action(moveV$4, editor, false) },
30587
- { keyCode: VK.DOWN, action: action(moveV$4, editor, true) },
30510
+ { keyCode: VK.UP, action: action(moveV$5, editor, false) },
30511
+ { keyCode: VK.DOWN, action: action(moveV$5, editor, true) },
30588
30512
  ...(isMac ? [
30589
30513
  { keyCode: VK.UP, action: action(selectToEndPoint, editor, false), metaKey: true, shiftKey: true },
30590
30514
  { keyCode: VK.DOWN, action: action(selectToEndPoint, editor, true), metaKey: true, shiftKey: true }
@@ -30605,7 +30529,11 @@
30605
30529
  { keyCode: VK.RIGHT, ctrlKey: !isMac, altKey: isMac, action: action(moveNextWord, editor, caret) },
30606
30530
  { keyCode: VK.LEFT, ctrlKey: !isMac, altKey: isMac, action: action(movePrevWord, editor, caret) },
30607
30531
  { keyCode: VK.UP, action: action(moveV$3, editor, false) },
30608
- { keyCode: VK.DOWN, action: action(moveV$3, editor, true) }
30532
+ { keyCode: VK.DOWN, action: action(moveV$3, editor, true) },
30533
+ ...(isFirefox ? [
30534
+ { keyCode: VK.UP, action: action(moveV$4, editor, false) },
30535
+ { keyCode: VK.DOWN, action: action(moveV$4, editor, true) }
30536
+ ] : [])
30609
30537
  ], evt).each((_) => {
30610
30538
  evt.preventDefault();
30611
30539
  });
@@ -30968,7 +30896,7 @@
30968
30896
  const mergeValues = (values, base) => {
30969
30897
  return SimpleResult.svalue(deepMerge(base, merge$1.apply(undefined, values)));
30970
30898
  };
30971
- const mergeErrors = (errors) => compose(SimpleResult.serror, flatten)(errors);
30899
+ const mergeErrors = (errors) => compose(SimpleResult.serror, flatten$1)(errors);
30972
30900
  const consolidateObj = (objects, base) => {
30973
30901
  const partition = SimpleResult.partition(objects);
30974
30902
  return partition.errors.length > 0 ? mergeErrors(partition.errors) : mergeValues(partition.values, base);
@@ -31569,7 +31497,7 @@
31569
31497
  }
31570
31498
  });
31571
31499
  };
31572
- const executeKeyupOverride = (editor, evt, isBackspaceKeydown) => execute([
31500
+ const executeKeyupOverride = (editor, evt, isBackspaceKeydown, formatNodes) => execute([
31573
31501
  { keyCode: VK.BACKSPACE, action: action(paddEmptyElement, editor) },
31574
31502
  { keyCode: VK.DELETE, action: action(paddEmptyElement, editor) },
31575
31503
  ...isMacOSOriOS ? [
@@ -31581,7 +31509,10 @@
31581
31509
  ...isBackspaceKeydown ? [{
31582
31510
  // Firefox detects macOS Command key code as "Command" not "Meta"
31583
31511
  keyCode: isFirefox ? 224 : 91,
31584
- action: action(refreshCaret, editor)
31512
+ action: action(() => {
31513
+ updateCaretFormat(editor, formatNodes);
31514
+ return refreshCaret(editor);
31515
+ })
31585
31516
  }] : []
31586
31517
  ] : [
31587
31518
  { keyCode: VK.BACKSPACE, ctrlKey: true, action: action(refreshCaret, editor) },
@@ -31591,15 +31522,18 @@
31591
31522
  const setup$o = (editor, caret) => {
31592
31523
  // track backspace keydown state for emulating Meta + Backspace keyup detection on macOS
31593
31524
  let isBackspaceKeydown = false;
31525
+ let formatNodes = [];
31594
31526
  editor.on('keydown', (evt) => {
31595
31527
  isBackspaceKeydown = evt.keyCode === VK.BACKSPACE;
31528
+ formatNodes = getFormatNodesAtStart(editor);
31596
31529
  if (!evt.isDefaultPrevented()) {
31597
31530
  executeKeydownOverride$3(editor, caret, evt);
31598
31531
  }
31599
31532
  });
31600
31533
  editor.on('keyup', (evt) => {
31601
31534
  if (!evt.isDefaultPrevented()) {
31602
- executeKeyupOverride(editor, evt, isBackspaceKeydown);
31535
+ executeKeyupOverride(editor, evt, isBackspaceKeydown, formatNodes);
31536
+ formatNodes.length = 0;
31603
31537
  }
31604
31538
  isBackspaceKeydown = false;
31605
31539
  });
@@ -31647,7 +31581,7 @@
31647
31581
  findFirstList(firstChild).fold(() => {
31648
31582
  if (isEmpty(firstChild)) {
31649
31583
  const element = toLeaf$1(firstChild, 0).element;
31650
- if (!isBr$6(element)) {
31584
+ if (isElement$8(element) && !isBr$6(element)) {
31651
31585
  append$1(element, SugarElement.fromText(nbsp));
31652
31586
  }
31653
31587
  }
@@ -34897,7 +34831,10 @@
34897
34831
  });
34898
34832
  editor.on('focusin', (e) => {
34899
34833
  // for medias the selection is already managed in `MediaFocus.ts`
34900
- if (editor.selection.isCollapsed() && !isMedia$2(e.target) && editor.getBody().contains(e.target) && e.target !== editor.getBody() && !editor.dom.isEditable(e.target.parentNode)) {
34834
+ if (isMedia$2(e.target)) {
34835
+ return;
34836
+ }
34837
+ if (editor.getBody().contains(e.target) && e.target !== editor.getBody() && !editor.dom.isEditable(e.target.parentNode)) {
34901
34838
  if (fakeCaret.isShowing()) {
34902
34839
  fakeCaret.hide();
34903
34840
  }
@@ -35415,7 +35352,7 @@
35415
35352
  applyPattern$2(editor, pattern, patternRange);
35416
35353
  };
35417
35354
  const addMarkers = (dom, matches) => {
35418
- const markerPrefix = generate$1('mce_textpattern');
35355
+ const markerPrefix = generate('mce_textpattern');
35419
35356
  // Add end markers
35420
35357
  const matchesWithEnds = foldr(matches, (acc, match) => {
35421
35358
  const endMarker = createMarker(dom, markerPrefix + `_end${acc.length}`, match.endRng);
@@ -35832,10 +35769,15 @@
35832
35769
  if (!editor.inline) {
35833
35770
  // Needs to be both down/up due to weird rendering bug on Chrome Windows
35834
35771
  dom.bind(editor.getDoc(), 'mousedown mouseup', (e) => {
35772
+ var _a;
35835
35773
  let rng;
35836
35774
  if (e.target === editor.getDoc().documentElement) {
35837
35775
  rng = selection.getRng();
35838
- editor.getBody().focus();
35776
+ // TINY-12245: this is needed to avoid the scroll back to the top when the content is scrolled, there is no selection and the user is clicking on a non selectable editor element
35777
+ // example content scrolled by browser search and user click on the horizontal scroll bar
35778
+ if (((_a = editor.getDoc().getSelection()) === null || _a === void 0 ? void 0 : _a.anchorNode) !== null) {
35779
+ editor.getBody().focus();
35780
+ }
35839
35781
  if (e.type === 'mousedown') {
35840
35782
  if (isCaretContainer$2(rng.startContainer)) {
35841
35783
  return;
@@ -35933,9 +35875,10 @@
35933
35875
  const isEditableImage = (node) => node.nodeName === 'IMG' && editor.dom.isEditable(node);
35934
35876
  editor.on('mousedown', (e) => {
35935
35877
  lift2(Optional.from(e.clientX), Optional.from(e.clientY), (clientX, clientY) => {
35878
+ var _a;
35936
35879
  const caretPos = editor.getDoc().caretPositionFromPoint(clientX, clientY);
35937
- const img = (caretPos === null || caretPos === void 0 ? void 0 : caretPos.offsetNode.childNodes[caretPos.offset - (caretPos.offset > 0 ? 1 : 0)]) || (caretPos === null || caretPos === void 0 ? void 0 : caretPos.offsetNode);
35938
- if (img && isEditableImage(img)) {
35880
+ const img = ((_a = caretPos === null || caretPos === void 0 ? void 0 : caretPos.offsetNode) === null || _a === void 0 ? void 0 : _a.childNodes[caretPos.offset - (caretPos.offset > 0 ? 1 : 0)]) || (caretPos === null || caretPos === void 0 ? void 0 : caretPos.offsetNode);
35881
+ if (isNonNullable(img) && isEditableImage(img)) {
35939
35882
  const rect = img.getBoundingClientRect();
35940
35883
  e.preventDefault();
35941
35884
  if (!editor.hasFocus()) {
@@ -36829,7 +36772,7 @@
36829
36772
  id: id + '_ifr',
36830
36773
  frameBorder: '0',
36831
36774
  allowTransparency: 'true',
36832
- title
36775
+ ...Env.browser.isFirefox() ? { title } : {}
36833
36776
  });
36834
36777
  add$2(iframe, 'tox-edit-area__iframe');
36835
36778
  return iframe;
@@ -36845,17 +36788,18 @@
36845
36788
  const bodyId = getBodyId(editor);
36846
36789
  const bodyClass = getBodyClass(editor);
36847
36790
  const translatedAriaText = editor.translate(getIframeAriaText(editor));
36791
+ const iframeBodyAriaLabel = Env.browser.isFirefox() ? '' : `aria-label="${translatedAriaText}"`;
36848
36792
  if (getContentSecurityPolicy(editor)) {
36849
36793
  iframeHTML += '<meta http-equiv="Content-Security-Policy" content="' + getContentSecurityPolicy(editor) + '" />';
36850
36794
  }
36851
36795
  iframeHTML += '</head>' +
36852
- `<body id="${bodyId}" class="mce-content-body ${bodyClass}" data-id="${editor.id}" aria-label="${translatedAriaText}">` +
36796
+ `<body id="${bodyId}" class="mce-content-body ${bodyClass}" data-id="${editor.id}" ${iframeBodyAriaLabel}>` +
36853
36797
  '<br>' +
36854
36798
  '</body></html>';
36855
36799
  return iframeHTML;
36856
36800
  };
36857
36801
  const createIframe = (editor, boxInfo) => {
36858
- const iframeTitle = Env.browser.isFirefox() ? getIframeAriaText(editor) : 'Rich Text Area';
36802
+ const iframeTitle = getIframeAriaText(editor);
36859
36803
  const translatedTitle = editor.translate(iframeTitle);
36860
36804
  const tabindex = getOpt(SugarElement.fromDom(editor.getElement()), 'tabindex').bind(toInt);
36861
36805
  const ifr = createIframeElement(editor.id, translatedTitle, getIframeAttrs(editor), tabindex).dom;
@@ -37318,23 +37262,55 @@
37318
37262
  return name.trim()[0];
37319
37263
  }
37320
37264
  };
37321
- const getRandomColor = () => {
37322
- const colorIdx = Math.floor(random() * AvatarColors.length);
37265
+ /* For a given string returns integer between 0 and maxValue (inclusive).
37266
+ This function is based on the djb2 hash algorithm reported by Dan Bernstein.
37267
+ You can find more informations here: http://www.cse.yorku.ca/~oz/hash.html
37268
+ The hashing algorithm is using bitwise operators to multiply by 32, and later to ensure a positive integer.
37269
+ The result is then reduced to the range of 0 to maxValue using modulo operation.
37270
+ */
37271
+ const djb2Hash = (key, maxValue) => {
37272
+ let hash = 5381;
37273
+ for (let i = 0; i < key.length; i++) {
37274
+ // eslint-disable-next-line no-bitwise
37275
+ hash = ((hash << 5) + hash) + key.charCodeAt(i);
37276
+ }
37277
+ // eslint-disable-next-line no-bitwise
37278
+ return (hash >>> 0) % (maxValue + 1);
37279
+ };
37280
+ const getColor = (id) => {
37281
+ const colorIdx = djb2Hash(id !== null && id !== void 0 ? id : '', AvatarColors.length - 1);
37323
37282
  return AvatarColors[colorIdx];
37324
37283
  };
37325
- const generate = (name, color, size = 36) => {
37284
+ const generateAvatarSvg = (content, color, size) => {
37326
37285
  const halfSize = size / 2;
37327
37286
  return `<svg height="${size}" width="${size}" xmlns="http://www.w3.org/2000/svg">` +
37328
37287
  `<circle cx="${halfSize}" cy="${halfSize}" r="${halfSize}" fill="${color}"/>` +
37329
37288
  `<text x="50%" y="50%" text-anchor="middle" dominant-baseline="central" fill="#FFF" font-family="sans-serif" font-size="${halfSize}">` +
37330
- getFirstChar(name) +
37289
+ content +
37331
37290
  `</text>` +
37332
37291
  '</svg>';
37333
37292
  };
37334
- const deriveAvatar = (name) => {
37335
- const avatarSvg = generate(name, getRandomColor());
37336
- return 'data:image/svg+xml,' + encodeURIComponent(avatarSvg);
37337
- };
37293
+ /**
37294
+ * Generates a data URL for an SVG avatar with the specified content, color, and size.
37295
+ *
37296
+ * @param content The text content to display in the avatar (typically a single character)
37297
+ * @param color The background color of the avatar (hex color string)
37298
+ * @param size The size of the avatar in pixels (width and height)
37299
+ * @returns A data URL string containing the encoded SVG avatar
37300
+ */
37301
+ const generateAvatar = (content, color, size) => 'data:image/svg+xml,' + encodeURIComponent(generateAvatarSvg(content, color, size));
37302
+ /**
37303
+ * Generates a user avatar based on the user's name and ID.
37304
+ *
37305
+ * @param user User object containing id and name properties
37306
+ * @param user.id Unique identifier used to determine the avatar color
37307
+ * @param user.name User's name, first character will be displayed in the avatar
37308
+ * @param config Configuration object for the avatar
37309
+ * @param config.size The size of the avatar in pixels (defaults to 36)
37310
+ * @returns A data URL string containing the encoded SVG avatar
37311
+ */
37312
+ const generateUserAvatar = (user, config = { size: 36 }) => generateAvatar(getFirstChar(user.name), getColor(user.id), config.size);
37313
+
37338
37314
  const userSchema = objOf([
37339
37315
  required('id'),
37340
37316
  optionString('name'),
@@ -37366,7 +37342,7 @@
37366
37342
  return {
37367
37343
  id,
37368
37344
  name: name.getOr(id),
37369
- avatar: avatar.getOr(deriveAvatar(name.getOr(id))),
37345
+ avatar: avatar.getOr(generateUserAvatar({ id, name: name.getOr(id) })),
37370
37346
  ...objectCat(rest),
37371
37347
  };
37372
37348
  });
@@ -37399,7 +37375,7 @@
37399
37375
  return mapToObject(userIds, (userId) => Promise.resolve({
37400
37376
  id: userId,
37401
37377
  name: userId,
37402
- avatar: deriveAvatar(userId)
37378
+ avatar: generateUserAvatar({ id: userId, name: userId })
37403
37379
  }));
37404
37380
  }
37405
37381
  const uncachedIds = unique$1(filter$5((userIds), (userId) => !lookup(userId).isSome()));
@@ -37431,7 +37407,7 @@
37431
37407
  acc[userId] = lookup(userId).getOr(Promise.resolve({
37432
37408
  id: userId,
37433
37409
  name: userId,
37434
- avatar: deriveAvatar(userId)
37410
+ avatar: generateUserAvatar({ id: userId, name: userId })
37435
37411
  }));
37436
37412
  return acc;
37437
37413
  }, {});
@@ -37946,7 +37922,7 @@
37946
37922
  const anchor = editor.dom.getParent(editor.selection.getNode(), 'a');
37947
37923
  if (isObject(linkDetails) && isString(linkDetails.href)) {
37948
37924
  // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
37949
- linkDetails.href = linkDetails.href.replace(/ /g, '%20');
37925
+ linkDetails.href = linkDetails.href.replace(/ /g, '%20').replace(/&amp;/g, '&');
37950
37926
  // Remove existing links if there could be child links or that the href isn't specified
37951
37927
  if (!anchor || !linkDetails.href) {
37952
37928
  editor.formatter.remove('link');
@@ -40330,14 +40306,14 @@
40330
40306
  * @property minorVersion
40331
40307
  * @type String
40332
40308
  */
40333
- minorVersion: '0.1',
40309
+ minorVersion: '1.0',
40334
40310
  /**
40335
40311
  * Release date of TinyMCE build.
40336
40312
  *
40337
40313
  * @property releaseDate
40338
40314
  * @type String
40339
40315
  */
40340
- releaseDate: '2025-07-28',
40316
+ releaseDate: '2025-09-17',
40341
40317
  /**
40342
40318
  * Collection of language pack data.
40343
40319
  *