diff --git a/modules/tinymce/CHANGELOG.md b/modules/tinymce/CHANGELOG.md index 7776e956108..15579b22ce0 100644 --- a/modules/tinymce/CHANGELOG.md +++ b/modules/tinymce/CHANGELOG.md @@ -60,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Popups were not constrained within the scrollable container when in a shadow root. #TINY-9743 - Pressing arrow keys inside RTL elements would move the caret in an incorrect direction when moving over elements with the `contenteditable` attribute set to `false`. #TINY-9565 - Inserting table consecutively without focus in the editor would result in the table being inserted at the wrong position. #TINY-3909 +- In some cases, the exiting a `blockquote` element could fail when the cursor was positioned at the end of the `blockquote`. #TINY-9794 ## 6.4.2 - 2023-04-26 diff --git a/modules/tinymce/src/core/main/ts/api/dom/DOMUtils.ts b/modules/tinymce/src/core/main/ts/api/dom/DOMUtils.ts index d1ff538207d..e4db8f9906d 100644 --- a/modules/tinymce/src/core/main/ts/api/dom/DOMUtils.ts +++ b/modules/tinymce/src/core/main/ts/api/dom/DOMUtils.ts @@ -6,7 +6,7 @@ import * as NodeType from '../../dom/NodeType'; import * as Position from '../../dom/Position'; import * as StyleSheetLoaderRegistry from '../../dom/StyleSheetLoaderRegistry'; import * as TrimNode from '../../dom/TrimNode'; -import { isWhitespaceText } from '../../text/Whitespace'; +import { isWhitespaceText, isZwsp } from '../../text/Whitespace'; import { GeomRect } from '../geom/Rect'; import Entities from '../html/Entities'; import Schema from '../html/Schema'; @@ -181,7 +181,7 @@ interface DOMUtils { findCommonAncestor: (a: Node, b: Node) => Node | null; run (this: DOMUtils, elm: T | T[], func: (node: T) => R, scope?: any): typeof elm extends Array ? R[] : R; run (this: DOMUtils, elm: RunArguments, func: (node: T) => R, scope?: any): RunResult; - isEmpty: (node: Node, elements?: Record) => boolean; + isEmpty: (node: Node, elements?: Record, options?: ({ includeZwsp?: boolean })) => boolean; createRng: () => Range; nodeIndex: (node: Node, normalized?: boolean) => number; split: { @@ -937,7 +937,7 @@ const DOMUtils = (doc: Document, settings: Partial = {}): DOMU return false; }; - const isEmpty = (node: Node, elements?: Record) => { + const isEmpty = (node: Node, elements?: Record, options?: ({ includeZwsp?: boolean })) => { let brCount = 0; // Keep elements with data-bookmark attributes, name attributes or are named anchors @@ -986,7 +986,7 @@ const DOMUtils = (doc: Document, settings: Partial = {}): DOMU } // Keep non whitespace text nodes - if (NodeType.isText(tempNode) && !isWhitespaceText(tempNode.data)) { + if (NodeType.isText(tempNode) && !isWhitespaceText(tempNode.data) && (!options?.includeZwsp || !isZwsp(tempNode.data))) { return false; } diff --git a/modules/tinymce/src/core/main/ts/newline/InsertBlock.ts b/modules/tinymce/src/core/main/ts/newline/InsertBlock.ts index 8e78183141e..b1b883e0ac6 100644 --- a/modules/tinymce/src/core/main/ts/newline/InsertBlock.ts +++ b/modules/tinymce/src/core/main/ts/newline/InsertBlock.ts @@ -361,7 +361,7 @@ const insert = (editor: Editor, evt?: EditorEvent): void => { } // Split the current container block element if enter is pressed inside an empty inner block element - if (shouldEndContainer(editor, containerBlock) && canSplitBlock(dom, containerBlock) && dom.isEmpty(parentBlock)) { + if (shouldEndContainer(editor, containerBlock) && canSplitBlock(dom, containerBlock) && dom.isEmpty(parentBlock, undefined, { includeZwsp: true })) { // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P block = dom.split(containerBlock, parentBlock) as Element; } else { diff --git a/modules/tinymce/src/core/main/ts/text/Whitespace.ts b/modules/tinymce/src/core/main/ts/text/Whitespace.ts index 1a7cdda30df..740c213fe63 100644 --- a/modules/tinymce/src/core/main/ts/text/Whitespace.ts +++ b/modules/tinymce/src/core/main/ts/text/Whitespace.ts @@ -3,6 +3,16 @@ import { Arr, Strings, Unicode } from '@ephox/katamari'; const whiteSpaceRegExp = /^[ \t\r\n]*$/; const isWhitespaceText = (text: string): boolean => whiteSpaceRegExp.test(text); + +const isZwsp = (text: string): boolean => { + for (const c of text) { + if (!Unicode.isZwsp(c)) { + return false; + } + } + return true; +}; + // Don't compare other unicode spaces here, as we're only concerned about whitespace the browser would collapse const isCollapsibleWhitespace = (c: string): boolean => ' \f\t\v'.indexOf(c) !== -1; const isNewLineChar = (c: string): boolean => c === '\n' || c === '\r'; @@ -37,6 +47,7 @@ const normalize = (text: string, tabSpaces: number = 4, isStartOfContent: boolea export { isWhitespaceText, + isZwsp, isNewline, normalize }; diff --git a/modules/tinymce/src/core/test/ts/browser/newline/InsertNewLineTest.ts b/modules/tinymce/src/core/test/ts/browser/newline/InsertNewLineTest.ts index 36eabe4ada6..7302a86f734 100644 --- a/modules/tinymce/src/core/test/ts/browser/newline/InsertNewLineTest.ts +++ b/modules/tinymce/src/core/test/ts/browser/newline/InsertNewLineTest.ts @@ -4,6 +4,7 @@ import { TinyAssertions, TinyHooks, TinySelections, TinyState } from '@ephox/wra import Editor from 'tinymce/core/api/Editor'; import { EditorEvent } from 'tinymce/core/api/util/EventDispatcher'; +import * as CaretFormat from 'tinymce/core/fmt/CaretFormat'; import * as InsertNewLine from 'tinymce/core/newline/InsertNewLine'; describe('browser.tinymce.core.newline.InsertNewLineTest', () => { @@ -755,4 +756,15 @@ describe('browser.tinymce.core.newline.InsertNewLineTest', () => { }); }); }); + + it('TINY-9794: Press Enter in a blockquote and then add format and then press Enter again should exit from the blockquote', () => { + const editor = hook.editor(); + editor.setContent('

A

'); + TinySelections.setCursor(editor, [ 0, 0 ], 1); + insertNewline(editor, { }); + CaretFormat.applyCaretFormat(editor, 'bold'); + insertNewline(editor, { }); + TinyAssertions.assertContent(editor, '

A

 

'); + TinyAssertions.assertCursor(editor, [ 1, 0, 0, 0 ], 0); + }); });