tinymce-rails 8.9.3 → 8.10.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/source/tinymce/tinymce.js +274 -219
  3. data/lib/tinymce/rails/version.rb +2 -2
  4. data/vendor/assets/javascripts/tinymce/icons/default/icons.js +1 -1
  5. data/vendor/assets/javascripts/tinymce/models/dom/model.js +1 -1
  6. data/vendor/assets/javascripts/tinymce/plugins/advlist/plugin.js +1 -1
  7. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.css +1 -1
  8. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.inline.css +1 -1
  9. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.inline.js +1 -1
  10. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.inline.min.css +1 -1
  11. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.js +1 -1
  12. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.min.css +1 -1
  13. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.css +1 -1
  14. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.js +1 -1
  15. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.min.css +1 -1
  16. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.css +1 -1
  17. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.inline.css +1 -1
  18. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.inline.js +1 -1
  19. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.inline.min.css +1 -1
  20. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.js +1 -1
  21. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.min.css +1 -1
  22. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.css +1 -1
  23. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.js +1 -1
  24. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.min.css +1 -1
  25. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.css +1 -1
  26. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.inline.css +1 -1
  27. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.inline.js +1 -1
  28. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.inline.min.css +1 -1
  29. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.js +1 -1
  30. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.min.css +1 -1
  31. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.css +1 -1
  32. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.js +1 -1
  33. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.min.css +1 -1
  34. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.css +1 -1
  35. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.inline.css +1 -1
  36. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.inline.js +1 -1
  37. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.inline.min.css +1 -1
  38. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.js +1 -1
  39. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.min.css +1 -1
  40. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.css +1 -1
  41. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.js +1 -1
  42. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.min.css +1 -1
  43. data/vendor/assets/javascripts/tinymce/themes/silver/theme.js +1 -1
  44. data/vendor/assets/javascripts/tinymce/tinymce.d.ts +2 -1
  45. data/vendor/assets/javascripts/tinymce/tinymce.js +3 -3
  46. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54b14ed8570aa7ab902e0b2abf90c2a9c2d4198407c22a27d0089bae5b7c2175
4
- data.tar.gz: 2e19733bc1406ca4940884fae634b89e88d17279971391467ff8aa98cc3de092
3
+ metadata.gz: 0aa0b46f0c60b3f5299265cf18434083cc20c90edca680e7aea3d4a51a7eb845
4
+ data.tar.gz: eea41881902fe912d5802a770b797817b44f6ec21d196ce40c72dc4a2862ddf0
5
5
  SHA512:
6
- metadata.gz: eaec7a999a4a5364971ddedbd8e5b637fdf528bae8ba7ed3563f80c9e8f080ad34b756aa5e734cc9a702ed64b28eab05137222db7d0542cc5dd91c44dfe6a2f3
7
- data.tar.gz: a558e0db2b1f6cf62625fec20d4fb49a329a2917ecaf8aa339cdf2263dda3eab9568f411b65c8ad2e23813bae03c6def8cac6fb185f08dbe97202bf6232c4c88
6
+ metadata.gz: 51ef53c4b01237071b3e711594fe9ed2e3762f8fd0eec78166ad33a2473aae1a197f09ff2c30866b1e354723fc097b52768a04245d828854f43827d364749e3c
7
+ data.tar.gz: 07df40aa209cf71d6e976945167463bc56219b5c8ea6265c9800b5b631f5553a42a0f1ae67f7131c99bedb4ea60e338792018e815c8ca16e3074b96ebd32a8e4
@@ -1,5 +1,5 @@
1
1
  /**
2
- * TinyMCE version 8.9.3 (2026-10-06)
2
+ * TinyMCE version 8.10.1 (2026-10-28)
3
3
  */
4
4
 
5
5
  (function () {
@@ -1155,6 +1155,35 @@
1155
1155
  return isNaN(num) ? Optional.none() : Optional.some(num);
1156
1156
  };
1157
1157
 
1158
+ // Run a function fn after rate ms. If another invocation occurs
1159
+ // during the time it is waiting, update the arguments f will run
1160
+ // with (but keep the current schedule)
1161
+ const adaptable = (fn, rate) => {
1162
+ let timer = null;
1163
+ let args = null;
1164
+ const cancel = () => {
1165
+ if (!isNull(timer)) {
1166
+ clearTimeout(timer);
1167
+ timer = null;
1168
+ args = null;
1169
+ }
1170
+ };
1171
+ const throttle = (...newArgs) => {
1172
+ args = newArgs;
1173
+ if (isNull(timer)) {
1174
+ timer = setTimeout(() => {
1175
+ const tempArgs = args;
1176
+ timer = null;
1177
+ args = null;
1178
+ fn.apply(null, tempArgs);
1179
+ }, rate);
1180
+ }
1181
+ };
1182
+ return {
1183
+ cancel,
1184
+ throttle
1185
+ };
1186
+ };
1158
1187
  // Run a function fn after rate ms. If another invocation occurs
1159
1188
  // during the time it is waiting, ignore it completely.
1160
1189
  const first$1 = (fn, rate) => {
@@ -3374,47 +3403,6 @@
3374
3403
  }
3375
3404
  }
3376
3405
 
3377
- const whiteSpaceRegExp = /^[ \t\r\n]*$/;
3378
- const isWhitespaceText = (text) => whiteSpaceRegExp.test(text);
3379
- const isZwsp$1 = (text) => {
3380
- for (const c of text) {
3381
- if (!isZwsp$2(c)) {
3382
- return false;
3383
- }
3384
- }
3385
- return true;
3386
- };
3387
- // Don't compare other unicode spaces here, as we're only concerned about whitespace the browser would collapse
3388
- const isCollapsibleWhitespace$1 = (c) => ' \f\t\v'.indexOf(c) !== -1;
3389
- const isNewLineChar = (c) => c === '\n' || c === '\r';
3390
- const isNewline = (text, idx) => (idx < text.length && idx >= 0) ? isNewLineChar(text[idx]) : false;
3391
- // Converts duplicate whitespace to alternating space/nbsps and tabs to spaces
3392
- const normalize$4 = (text, tabSpaces = 4, isStartOfContent = true, isEndOfContent = true) => {
3393
- // Replace tabs with a variable amount of spaces
3394
- // Note: We don't use an actual tab character here, as it only works when in a "whitespace: pre" element,
3395
- // which will cause other issues, such as trying to type the content will also be treated as being in a pre.
3396
- const tabSpace = repeat(' ', tabSpaces);
3397
- const normalizedText = text.replace(/\t/g, tabSpace);
3398
- const result = foldl(normalizedText, (acc, c) => {
3399
- // Are we dealing with a char other than some collapsible whitespace or nbsp? if so then just use it as is
3400
- if (isCollapsibleWhitespace$1(c) || c === nbsp) {
3401
- // If the previous char is a space, we are at the start or end, or if the next char is a new line char, then we need
3402
- // to convert the space to a nbsp
3403
- if (acc.pcIsSpace || (acc.str === '' && isStartOfContent) || (acc.str.length === normalizedText.length - 1 && isEndOfContent) || isNewline(normalizedText, acc.str.length + 1)) {
3404
- return { pcIsSpace: false, str: acc.str + nbsp };
3405
- }
3406
- else {
3407
- return { pcIsSpace: true, str: acc.str + ' ' };
3408
- }
3409
- }
3410
- else {
3411
- // Treat newlines as being a space, since we'll need to convert any leading spaces to nsbps
3412
- return { pcIsSpace: isNewLineChar(c), str: acc.str + c };
3413
- }
3414
- }, { pcIsSpace: false, str: '' });
3415
- return result.str;
3416
- };
3417
-
3418
3406
  const isNodeType = (type) => {
3419
3407
  return (node) => {
3420
3408
  return !!node && node.nodeType === type;
@@ -3502,6 +3490,47 @@
3502
3490
  const ucVideoNodeName = 'uc-video';
3503
3491
  const isUcVideo = matchNodeName$1(ucVideoNodeName);
3504
3492
 
3493
+ const whiteSpaceRegExp = /^[ \t\r\n]*$/;
3494
+ const isWhitespaceText = (text) => whiteSpaceRegExp.test(text);
3495
+ const isZwsp$1 = (text) => {
3496
+ for (const c of text) {
3497
+ if (!isZwsp$2(c)) {
3498
+ return false;
3499
+ }
3500
+ }
3501
+ return true;
3502
+ };
3503
+ // Don't compare other unicode spaces here, as we're only concerned about whitespace the browser would collapse
3504
+ const isCollapsibleWhitespace$1 = (c) => ' \f\t\v'.indexOf(c) !== -1;
3505
+ const isNewLineChar = (c) => c === '\n' || c === '\r';
3506
+ const isNewline = (text, idx) => (idx < text.length && idx >= 0) ? isNewLineChar(text[idx]) : false;
3507
+ // Converts duplicate whitespace to alternating space/nbsps and tabs to spaces
3508
+ const normalize$4 = (text, tabSpaces = 4, isStartOfContent = true, isEndOfContent = true) => {
3509
+ // Replace tabs with a variable amount of spaces
3510
+ // Note: We don't use an actual tab character here, as it only works when in a "whitespace: pre" element,
3511
+ // which will cause other issues, such as trying to type the content will also be treated as being in a pre.
3512
+ const tabSpace = repeat(' ', tabSpaces);
3513
+ const normalizedText = text.replace(/\t/g, tabSpace);
3514
+ const result = foldl(normalizedText, (acc, c) => {
3515
+ // Are we dealing with a char other than some collapsible whitespace or nbsp? if so then just use it as is
3516
+ if (isCollapsibleWhitespace$1(c) || c === nbsp) {
3517
+ // If the previous char is a space, we are at the start or end, or if the next char is a new line char, then we need
3518
+ // to convert the space to a nbsp
3519
+ if (acc.pcIsSpace || (acc.str === '' && isStartOfContent) || (acc.str.length === normalizedText.length - 1 && isEndOfContent) || isNewline(normalizedText, acc.str.length + 1)) {
3520
+ return { pcIsSpace: false, str: acc.str + nbsp };
3521
+ }
3522
+ else {
3523
+ return { pcIsSpace: true, str: acc.str + ' ' };
3524
+ }
3525
+ }
3526
+ else {
3527
+ // Treat newlines as being a space, since we'll need to convert any leading spaces to nsbps
3528
+ return { pcIsSpace: isNewLineChar(c), str: acc.str + c };
3529
+ }
3530
+ }, { pcIsSpace: false, str: '' });
3531
+ return result.str;
3532
+ };
3533
+
3505
3534
  const defaultOptionValues = {
3506
3535
  skipBogus: true,
3507
3536
  includeZwsp: false,
@@ -13765,6 +13794,12 @@
13765
13794
  editor.nodeChanged();
13766
13795
  };
13767
13796
  const showResizeRect = (targetElm) => {
13797
+ // The throttled call can fire after setContent has replaced the target, leaving
13798
+ // handles bound to the zero-sized geometry of a detached element
13799
+ if (!targetElm.isConnected) {
13800
+ hideResizeRect();
13801
+ return;
13802
+ }
13768
13803
  unbindResizeHandleEvents();
13769
13804
  // Get position and size of target
13770
13805
  const position = dom.getPos(targetElm, rootElement);
@@ -13863,7 +13898,9 @@
13863
13898
  hideResizeRect(false);
13864
13899
  }
13865
13900
  };
13866
- const throttledShowResizeRect = first$1(showResizeRect, 0);
13901
+ // Adaptable so a burst of update requests shows the rect for the most recently
13902
+ // requested element rather than the first, which may since have been detached
13903
+ const throttledShowResizeRect = adaptable(showResizeRect, 0);
13867
13904
  const hideResizeRect = (removeSelected = true) => {
13868
13905
  throttledShowResizeRect.cancel();
13869
13906
  unbindResizeHandleEvents();
@@ -16532,156 +16569,6 @@
16532
16569
  };
16533
16570
  };
16534
16571
 
16535
- const getNormalizedPoint$1 = (container, offset) => {
16536
- if (isText$b(container)) {
16537
- return { container, offset };
16538
- }
16539
- const node = RangeUtils.getNode(container, offset);
16540
- if (isText$b(node)) {
16541
- return {
16542
- container: node,
16543
- offset: offset >= container.childNodes.length ? node.data.length : 0
16544
- };
16545
- }
16546
- else if (node.previousSibling && isText$b(node.previousSibling)) {
16547
- return {
16548
- container: node.previousSibling,
16549
- offset: node.previousSibling.data.length
16550
- };
16551
- }
16552
- else if (node.nextSibling && isText$b(node.nextSibling)) {
16553
- return {
16554
- container: node.nextSibling,
16555
- offset: 0
16556
- };
16557
- }
16558
- return { container, offset };
16559
- };
16560
- const normalizeRange$1 = (rng) => {
16561
- const outRng = rng.cloneRange();
16562
- const rangeStart = getNormalizedPoint$1(rng.startContainer, rng.startOffset);
16563
- outRng.setStart(rangeStart.container, rangeStart.offset);
16564
- const rangeEnd = getNormalizedPoint$1(rng.endContainer, rng.endOffset);
16565
- outRng.setEnd(rangeEnd.container, rangeEnd.offset);
16566
- return outRng;
16567
- };
16568
-
16569
- const DOM$b = DOMUtils.DOM;
16570
- const defaultMarker = () => DOM$b.create('span', { 'data-mce-type': 'bookmark' });
16571
- const setupEndPoint = (container, offset, createMarker) => {
16572
- if (isElement$7(container)) {
16573
- const offsetNode = createMarker();
16574
- if (container.hasChildNodes()) {
16575
- if (offset === container.childNodes.length) {
16576
- container.appendChild(offsetNode);
16577
- }
16578
- else {
16579
- container.insertBefore(offsetNode, container.childNodes[offset]);
16580
- }
16581
- }
16582
- else {
16583
- container.appendChild(offsetNode);
16584
- }
16585
- return { container: offsetNode, offset: 0 };
16586
- }
16587
- else {
16588
- return { container, offset };
16589
- }
16590
- };
16591
- const restoreEndPoint = (container, offset) => {
16592
- const nodeIndex = (container) => {
16593
- let node = container.parentNode?.firstChild;
16594
- let idx = 0;
16595
- while (node) {
16596
- if (node === container) {
16597
- return idx;
16598
- }
16599
- // Skip data-mce-type=bookmark nodes
16600
- if (!isElement$7(node) || node.getAttribute('data-mce-type') !== 'bookmark') {
16601
- idx++;
16602
- }
16603
- node = node.nextSibling;
16604
- }
16605
- return -1;
16606
- };
16607
- if (isElement$7(container) && isNonNullable(container.parentNode)) {
16608
- const node = container;
16609
- offset = nodeIndex(container);
16610
- container = container.parentNode;
16611
- DOM$b.remove(node);
16612
- if (!container.hasChildNodes() && DOM$b.isBlock(container)) {
16613
- container.appendChild(DOM$b.create('br'));
16614
- }
16615
- }
16616
- return { container, offset };
16617
- };
16618
- const createNormalizedRange = (startContainer, startOffset, endContainer, endOffset) => {
16619
- const rng = DOM$b.createRng();
16620
- rng.setStart(startContainer, startOffset);
16621
- rng.setEnd(endContainer, endOffset);
16622
- return normalizeRange$1(rng);
16623
- };
16624
- /**
16625
- * Returns a range bookmark. This will convert indexed bookmarks into temporary span elements with
16626
- * index 0 so that they can be restored properly after the DOM has been modified. Text bookmarks will not have spans
16627
- * added to them since they can be restored after a dom operation.
16628
- *
16629
- * So this: <p><b>|</b><b>|</b></p>
16630
- * becomes: <p><b><span data-mce-type="bookmark">|</span></b><b data-mce-type="bookmark">|</span></b></p>
16631
- */
16632
- const createBookmark = (rng, createMarker = defaultMarker) => {
16633
- const { container: startContainer, offset: startOffset } = setupEndPoint(rng.startContainer, rng.startOffset, createMarker);
16634
- if (rng.collapsed) {
16635
- return { startContainer, startOffset };
16636
- }
16637
- else {
16638
- const { container: endContainer, offset: endOffset } = setupEndPoint(rng.endContainer, rng.endOffset, createMarker);
16639
- return { startContainer, startOffset, endContainer, endOffset };
16640
- }
16641
- };
16642
- const resolveBookmark = (bookmark) => {
16643
- const { container: startContainer, offset: startOffset } = restoreEndPoint(bookmark.startContainer, bookmark.startOffset);
16644
- if (!isUndefined(bookmark.endContainer) && !isUndefined(bookmark.endOffset)) {
16645
- const { container: endContainer, offset: endOffset } = restoreEndPoint(bookmark.endContainer, bookmark.endOffset);
16646
- return createNormalizedRange(startContainer, startOffset, endContainer, endOffset);
16647
- }
16648
- else {
16649
- return createNormalizedRange(startContainer, startOffset, startContainer, startOffset);
16650
- }
16651
- };
16652
-
16653
- const applyStyles = (dom, elm, format, vars) => {
16654
- Tools.each(format.styles, (value, name) => {
16655
- dom.setStyle(elm, name, replaceVars(value, vars));
16656
- });
16657
- // Needed for the WebKit span spam bug
16658
- // TODO: Remove this once WebKit/Blink fixes this
16659
- if (format.styles) {
16660
- const styleVal = dom.getAttrib(elm, 'style');
16661
- if (styleVal) {
16662
- dom.setAttrib(elm, 'data-mce-style', styleVal);
16663
- }
16664
- }
16665
- };
16666
- const setElementFormat = (ed, elm, fmt, vars, node) => {
16667
- const dom = ed.dom;
16668
- if (isFunction(fmt.onformat)) {
16669
- fmt.onformat(elm, fmt, vars, node);
16670
- }
16671
- applyStyles(dom, elm, fmt, vars);
16672
- Tools.each(fmt.attributes, (value, name) => {
16673
- dom.setAttrib(elm, name, replaceVars(value, vars));
16674
- });
16675
- Tools.each(fmt.classes, (value) => {
16676
- const newValue = replaceVars(value, vars);
16677
- if (!dom.hasClass(elm, newValue)) {
16678
- dom.addClass(elm, newValue);
16679
- }
16680
- });
16681
- };
16682
-
16683
- const isApplyFormat = (format) => !isArray$1(format.attributes) && !isArray$1(format.styles);
16684
-
16685
16572
  const isEq$3 = isEq$5;
16686
16573
  const matchesUnInheritedFormatSelector = (ed, node, name) => {
16687
16574
  const formatList = ed.formatter.get(name);
@@ -16935,14 +16822,18 @@
16935
16822
  selection.setRng(rng);
16936
16823
  }
16937
16824
  };
16825
+ const isAdjacent = (node, sibling) => node.nextSibling === sibling || node.previousSibling === sibling;
16826
+ const isCaretContainerNodeAdjacentToSelection = (node, startContainer, endContainer) => node === startContainer || node === endContainer || isAdjacent(node, startContainer) || isAdjacent(node, endContainer);
16938
16827
  // Removes the caret container for the specified node or all on the current document
16939
16828
  const removeCaretContainer = (editor, node, moveCaret) => {
16940
16829
  const dom = editor.dom, selection = editor.selection;
16941
16830
  if (!node) {
16942
16831
  node = getParentCaretContainer(editor.getBody(), selection.getStart());
16943
16832
  if (!node) {
16833
+ const startContainer = editor.selection.getRng().startContainer;
16834
+ const endContainer = editor.selection.getRng().endContainer;
16944
16835
  while ((node = dom.get(CARET_ID))) {
16945
- removeCaretContainerNode(editor, node, moveCaret);
16836
+ removeCaretContainerNode(editor, node, isCaretContainerNodeAdjacentToSelection(node, startContainer, endContainer));
16946
16837
  }
16947
16838
  }
16948
16839
  }
@@ -17145,9 +17036,9 @@
17145
17036
  }
17146
17037
  }
17147
17038
  };
17148
- const disableCaretContainer = (editor, keyCode, moveCaret) => {
17039
+ const disableCaretContainer = (editor, keyCode) => {
17149
17040
  const selection = editor.selection, body = editor.getBody();
17150
- removeCaretContainer(editor, null, moveCaret);
17041
+ removeCaretContainer(editor, null, false);
17151
17042
  // Remove caret container if it's empty
17152
17043
  if ((keyCode === 8 || keyCode === 46) && selection.isCollapsed() && selection.getStart().innerHTML === ZWSP) {
17153
17044
  removeCaretContainer(editor, getParentCaretContainer(body, selection.getStart()), true);
@@ -17157,10 +17048,9 @@
17157
17048
  removeCaretContainer(editor, getParentCaretContainer(body, selection.getStart()), true);
17158
17049
  }
17159
17050
  };
17160
- const endsWithNbsp = (element) => isText$b(element) && endsWith(element.data, nbsp);
17161
17051
  const setup$B = (editor) => {
17162
17052
  editor.on('mouseup keydown', (e) => {
17163
- disableCaretContainer(editor, e.keyCode, endsWithNbsp(editor.selection.getRng().endContainer));
17053
+ disableCaretContainer(editor, e.keyCode);
17164
17054
  });
17165
17055
  };
17166
17056
  const createCaretFormat = (formatNodes) => {
@@ -17187,6 +17077,156 @@
17187
17077
  return has$2(inlineElements, name(element)) && !isCaretNode(element.dom) && !isBogus$1(element.dom);
17188
17078
  };
17189
17079
 
17080
+ const getNormalizedPoint$1 = (container, offset) => {
17081
+ if (isText$b(container)) {
17082
+ return { container, offset };
17083
+ }
17084
+ const node = RangeUtils.getNode(container, offset);
17085
+ if (isText$b(node)) {
17086
+ return {
17087
+ container: node,
17088
+ offset: offset >= container.childNodes.length ? node.data.length : 0
17089
+ };
17090
+ }
17091
+ else if (node.previousSibling && isText$b(node.previousSibling)) {
17092
+ return {
17093
+ container: node.previousSibling,
17094
+ offset: node.previousSibling.data.length
17095
+ };
17096
+ }
17097
+ else if (node.nextSibling && isText$b(node.nextSibling)) {
17098
+ return {
17099
+ container: node.nextSibling,
17100
+ offset: 0
17101
+ };
17102
+ }
17103
+ return { container, offset };
17104
+ };
17105
+ const normalizeRange$1 = (rng) => {
17106
+ const outRng = rng.cloneRange();
17107
+ const rangeStart = getNormalizedPoint$1(rng.startContainer, rng.startOffset);
17108
+ outRng.setStart(rangeStart.container, rangeStart.offset);
17109
+ const rangeEnd = getNormalizedPoint$1(rng.endContainer, rng.endOffset);
17110
+ outRng.setEnd(rangeEnd.container, rangeEnd.offset);
17111
+ return outRng;
17112
+ };
17113
+
17114
+ const DOM$b = DOMUtils.DOM;
17115
+ const defaultMarker = () => DOM$b.create('span', { 'data-mce-type': 'bookmark' });
17116
+ const setupEndPoint = (container, offset, createMarker) => {
17117
+ if (isElement$7(container)) {
17118
+ const offsetNode = createMarker();
17119
+ if (container.hasChildNodes()) {
17120
+ if (offset === container.childNodes.length) {
17121
+ container.appendChild(offsetNode);
17122
+ }
17123
+ else {
17124
+ container.insertBefore(offsetNode, container.childNodes[offset]);
17125
+ }
17126
+ }
17127
+ else {
17128
+ container.appendChild(offsetNode);
17129
+ }
17130
+ return { container: offsetNode, offset: 0 };
17131
+ }
17132
+ else {
17133
+ return { container, offset };
17134
+ }
17135
+ };
17136
+ const restoreEndPoint = (container, offset) => {
17137
+ const nodeIndex = (container) => {
17138
+ let node = container.parentNode?.firstChild;
17139
+ let idx = 0;
17140
+ while (node) {
17141
+ if (node === container) {
17142
+ return idx;
17143
+ }
17144
+ // Skip data-mce-type=bookmark nodes
17145
+ if (!isElement$7(node) || node.getAttribute('data-mce-type') !== 'bookmark') {
17146
+ idx++;
17147
+ }
17148
+ node = node.nextSibling;
17149
+ }
17150
+ return -1;
17151
+ };
17152
+ if (isElement$7(container) && isNonNullable(container.parentNode)) {
17153
+ const node = container;
17154
+ offset = nodeIndex(container);
17155
+ container = container.parentNode;
17156
+ DOM$b.remove(node);
17157
+ if (!container.hasChildNodes() && DOM$b.isBlock(container)) {
17158
+ container.appendChild(DOM$b.create('br'));
17159
+ }
17160
+ }
17161
+ return { container, offset };
17162
+ };
17163
+ const createNormalizedRange = (startContainer, startOffset, endContainer, endOffset) => {
17164
+ const rng = DOM$b.createRng();
17165
+ rng.setStart(startContainer, startOffset);
17166
+ rng.setEnd(endContainer, endOffset);
17167
+ return normalizeRange$1(rng);
17168
+ };
17169
+ /**
17170
+ * Returns a range bookmark. This will convert indexed bookmarks into temporary span elements with
17171
+ * index 0 so that they can be restored properly after the DOM has been modified. Text bookmarks will not have spans
17172
+ * added to them since they can be restored after a dom operation.
17173
+ *
17174
+ * So this: <p><b>|</b><b>|</b></p>
17175
+ * becomes: <p><b><span data-mce-type="bookmark">|</span></b><b data-mce-type="bookmark">|</span></b></p>
17176
+ */
17177
+ const createBookmark = (rng, createMarker = defaultMarker) => {
17178
+ const { container: startContainer, offset: startOffset } = setupEndPoint(rng.startContainer, rng.startOffset, createMarker);
17179
+ if (rng.collapsed) {
17180
+ return { startContainer, startOffset };
17181
+ }
17182
+ else {
17183
+ const { container: endContainer, offset: endOffset } = setupEndPoint(rng.endContainer, rng.endOffset, createMarker);
17184
+ return { startContainer, startOffset, endContainer, endOffset };
17185
+ }
17186
+ };
17187
+ const resolveBookmark = (bookmark) => {
17188
+ const { container: startContainer, offset: startOffset } = restoreEndPoint(bookmark.startContainer, bookmark.startOffset);
17189
+ if (!isUndefined(bookmark.endContainer) && !isUndefined(bookmark.endOffset)) {
17190
+ const { container: endContainer, offset: endOffset } = restoreEndPoint(bookmark.endContainer, bookmark.endOffset);
17191
+ return createNormalizedRange(startContainer, startOffset, endContainer, endOffset);
17192
+ }
17193
+ else {
17194
+ return createNormalizedRange(startContainer, startOffset, startContainer, startOffset);
17195
+ }
17196
+ };
17197
+
17198
+ const applyStyles = (dom, elm, format, vars) => {
17199
+ Tools.each(format.styles, (value, name) => {
17200
+ dom.setStyle(elm, name, replaceVars(value, vars));
17201
+ });
17202
+ // Needed for the WebKit span spam bug
17203
+ // TODO: Remove this once WebKit/Blink fixes this
17204
+ if (format.styles) {
17205
+ const styleVal = dom.getAttrib(elm, 'style');
17206
+ if (styleVal) {
17207
+ dom.setAttrib(elm, 'data-mce-style', styleVal);
17208
+ }
17209
+ }
17210
+ };
17211
+ const setElementFormat = (ed, elm, fmt, vars, node) => {
17212
+ const dom = ed.dom;
17213
+ if (isFunction(fmt.onformat)) {
17214
+ fmt.onformat(elm, fmt, vars, node);
17215
+ }
17216
+ applyStyles(dom, elm, fmt, vars);
17217
+ Tools.each(fmt.attributes, (value, name) => {
17218
+ dom.setAttrib(elm, name, replaceVars(value, vars));
17219
+ });
17220
+ Tools.each(fmt.classes, (value) => {
17221
+ const newValue = replaceVars(value, vars);
17222
+ if (!dom.hasClass(elm, newValue)) {
17223
+ dom.addClass(elm, newValue);
17224
+ }
17225
+ });
17226
+ };
17227
+
17228
+ const isApplyFormat = (format) => !isArray$1(format.attributes) && !isArray$1(format.styles);
17229
+
17190
17230
  const listItemStyles = ['fontWeight', 'fontStyle', 'color', 'fontSize', 'fontFamily'];
17191
17231
  const hasListStyles = (fmt) => isObject(fmt.styles) && exists(keys(fmt.styles), (name) => contains$2(listItemStyles, name));
17192
17232
  const findExpandedListItemFormat = (formats) => find$2(formats, (fmt) => isInlineFormat(fmt) && fmt.inline === 'span' && hasListStyles(fmt));
@@ -22837,6 +22877,18 @@
22837
22877
  }
22838
22878
  selection.setRng(rng);
22839
22879
  };
22880
+ const isWholeBlockContentInInlineFormat = (editor, rng) => {
22881
+ const dom = editor.dom;
22882
+ const ancestor = rng.commonAncestorContainer;
22883
+ const elm = isText$b(ancestor) ? ancestor.parentNode : ancestor;
22884
+ if (!isElement$7(elm) || !dom.isEditable(elm) || !isFormatElement(editor, SugarElement.fromDom(elm))) {
22885
+ return false;
22886
+ }
22887
+ const block = dom.getParent(elm, dom.isBlock);
22888
+ return isNonNullable(block)
22889
+ && prevPosition(block, CaretPosition.fromRangeStart(rng)).isNone()
22890
+ && nextPosition(block, CaretPosition.fromRangeEnd(rng)).isNone();
22891
+ };
22840
22892
  const deleteSelectedContent = (editor) => {
22841
22893
  const dom = editor.dom;
22842
22894
  // Fix for #2595 seems that delete removes one extra character on
@@ -22850,8 +22902,9 @@
22850
22902
  deleteCellContents(editor, rng, SugarElement.fromDom(startCell));
22851
22903
  // TINY-9193: If the selection is over the whole text node in an element then Firefox incorrectly moves the caret to the previous line
22852
22904
  // TINY-11953: If the selection is over the whole anchor node, then Chrome incorrectly removes parent node alongside with it's child - anchor
22905
+ // TINY-11317: If the whole block content is selected inside a common inline format element, native delete removes the format element
22853
22906
  }
22854
- else if (isSelectionOverWholeAnchor(rng) || isSelectionOverWholeTextNode(rng)) {
22907
+ else if (isSelectionOverWholeAnchor(rng) || isSelectionOverWholeTextNode(rng) || isWholeBlockContentInInlineFormat(editor, rng)) {
22855
22908
  rng.deleteContents();
22856
22909
  }
22857
22910
  else {
@@ -28952,6 +29005,16 @@
28952
29005
  }
28953
29006
  };
28954
29007
 
29008
+ const clearShadowingItemStyles = (editor) => {
29009
+ each$e(getSelectedListItems(editor), (item) => {
29010
+ const styleType = editor.dom.getStyle(item, 'list-style-type');
29011
+ // Keep none intact as TinyMCE uses it for nested lists
29012
+ if (isNonNullable(styleType) && styleType !== 'none') {
29013
+ editor.dom.setStyle(item, 'list-style-type', '');
29014
+ }
29015
+ });
29016
+ };
29017
+
28955
29018
  const updateListStyle = (dom, el, detail) => {
28956
29019
  const type = detail['list-style-type'] ? detail['list-style-type'] : null;
28957
29020
  dom.setStyle(el, 'list-style-type', type);
@@ -29286,6 +29349,7 @@
29286
29349
  else {
29287
29350
  toggleSingleList(editor, parentList, listName, detail);
29288
29351
  }
29352
+ clearShadowingItemStyles(editor);
29289
29353
  };
29290
29354
 
29291
29355
  const findNextCaretContainer = (editor, rng, isForward, root) => {
@@ -37597,16 +37661,6 @@
37597
37661
  }
37598
37662
  }, true);
37599
37663
  };
37600
- /**
37601
- * Forces Gecko to render a broken image icon if it fails to load an image.
37602
- */
37603
- const showBrokenImageIcon = () => {
37604
- editor.contentStyles.push('img:-moz-broken {' +
37605
- '-moz-force-broken-image-icon:1;' +
37606
- 'min-width:24px;' +
37607
- 'min-height:24px' +
37608
- '}');
37609
- };
37610
37664
  /**
37611
37665
  * iOS has a bug where it's impossible to type if the document has a touchstart event
37612
37666
  * bound and the user touches the document while having the on screen keyboard visible.
@@ -37783,8 +37837,8 @@
37783
37837
  const clickAfterEl = (clientX, clientY, el) => get$c(getClientRects([el.dom]), 0).exists((rect) => clientX >= rect.right && clientY >= rect.top && clientY <= rect.bottom);
37784
37838
  const selectPos = (editor, e, pos) => {
37785
37839
  e.preventDefault();
37786
- editor.focus();
37787
37840
  editor.selection.setRng(pos.toRange());
37841
+ editor.focus();
37788
37842
  };
37789
37843
  /**
37790
37844
  * In Chrome in a `LI` that contains a block element and where the first child is an inline element
@@ -37792,7 +37846,7 @@
37792
37846
  * issue: https://issues.chromium.org/issues/40767343
37793
37847
  **/
37794
37848
  const fixInLISelection = () => {
37795
- editor.on('mousedown', (e) => {
37849
+ editor.on('click', (e) => {
37796
37850
  const target = SugarElement.fromDom(e.target);
37797
37851
  if (isListItem$2(target)) {
37798
37852
  firstBlockChildOrNewLine(target).fold(() => {
@@ -37836,7 +37890,6 @@
37836
37890
  if (isGecko) {
37837
37891
  focusBody();
37838
37892
  setGeckoEditingOptions();
37839
- showBrokenImageIcon();
37840
37893
  blockCmdArrowNavigation();
37841
37894
  }
37842
37895
  };
@@ -37880,7 +37933,6 @@
37880
37933
  removeStylesWhenDeletingAcrossBlockElements();
37881
37934
  setGeckoEditingOptions();
37882
37935
  addBrAfterLastLinks();
37883
- showBrokenImageIcon();
37884
37936
  blockCmdArrowNavigation();
37885
37937
  disableBackspaceIntoATable();
37886
37938
  }
@@ -38399,7 +38451,7 @@
38399
38451
  id: id + '_ifr',
38400
38452
  frameBorder: '0',
38401
38453
  allowTransparency: 'true',
38402
- ...Env.browser.isFirefox() ? { title } : {}
38454
+ title
38403
38455
  });
38404
38456
  add$2(iframe, 'tox-edit-area__iframe');
38405
38457
  return iframe;
@@ -38415,12 +38467,15 @@
38415
38467
  const bodyId = getBodyId(editor);
38416
38468
  const bodyClass = getBodyClass(editor);
38417
38469
  const translatedAriaText = editor.translate(getIframeAriaText(editor));
38418
- const iframeBodyAriaLabel = Env.browser.isFirefox() ? '' : `aria-label="${translatedAriaText}"`;
38470
+ // TINYMCE-13100: VoiceOver does not read the iframe title, so the body is labelled
38471
+ // for it. VoiceOver only runs on macOS, and elsewhere the title is announced.
38472
+ // The role goes with it, since aria-label on a generic role is a violation.
38473
+ const iframeBodyAriaAttrs = Env.os.isMacOS() || Env.os.isiOS() ? `role="textbox" aria-multiline="true" aria-label="${translatedAriaText}"` : '';
38419
38474
  if (getContentSecurityPolicy(editor)) {
38420
38475
  iframeHTML += '<meta http-equiv="Content-Security-Policy" content="' + getContentSecurityPolicy(editor) + '" />';
38421
38476
  }
38422
38477
  iframeHTML += '</head>' +
38423
- `<body id="${bodyId}" class="mce-content-body ${bodyClass}" data-id="${editor.id}" ${iframeBodyAriaLabel}>` +
38478
+ `<body id="${bodyId}" class="mce-content-body ${bodyClass}" data-id="${editor.id}" ${iframeBodyAriaAttrs}>` +
38424
38479
  '<br>' +
38425
38480
  '</body></html>';
38426
38481
  return iframeHTML;
@@ -39725,7 +39780,7 @@
39725
39780
  return false;
39726
39781
  }
39727
39782
  if (lowerCaseCommand !== 'mcefocus') {
39728
- if (!/^(mceAddUndoLevel|mceEndUndoLevel)$/i.test(lowerCaseCommand) && !skipFocus) {
39783
+ if (!/^(mceAddUndoLevel|mceEndUndoLevel|toggleview)$/i.test(lowerCaseCommand) && !skipFocus) {
39729
39784
  editor.focus();
39730
39785
  }
39731
39786
  else {
@@ -42102,14 +42157,14 @@
42102
42157
  * @property minorVersion
42103
42158
  * @type String
42104
42159
  */
42105
- minorVersion: '9.3',
42160
+ minorVersion: '10.1',
42106
42161
  /**
42107
42162
  * Release date of TinyMCE build.
42108
42163
  *
42109
42164
  * @property releaseDate
42110
42165
  * @type String
42111
42166
  */
42112
- releaseDate: '2026-10-06',
42167
+ releaseDate: '2026-10-28',
42113
42168
  /**
42114
42169
  * Collection of language pack data.
42115
42170
  *