From e39f2924991a0bc56f547616748862768aa587dd Mon Sep 17 00:00:00 2001 From: Aitor Moreno Date: Mon, 3 Nov 2025 11:37:07 +0100 Subject: [PATCH] :books: Add more info about text editor v2 --- frontend/text-editor/README.md | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/frontend/text-editor/README.md b/frontend/text-editor/README.md index 763f4424a9..705c2277bd 100644 --- a/frontend/text-editor/README.md +++ b/frontend/text-editor/README.md @@ -37,7 +37,7 @@ This command is going to search for the file located in `frontend/src/app/main/u ## How it works? -The text editor divides the content in three elements: `root`, `paragraph` and `inline`. An `inline` in terms of content is a styled element that it is displayed in a line inside a block and an `inline` only can have one child (a Text node). A `paragraph` is a **block** element that can contain multiple `inline`s (**inline** elements). +The text editor divides the content in three elements: `root`, `paragraph` and `textSpan`. In terms of content, a `textSpan` is a styled element displayed on a line within a block. A `textSpan` can only have one child (a Text node). A `paragraph` is a **block** element that can contain multiple `textSpan`s (**textSpan** elements). ```html
@@ -53,10 +53,10 @@ This way we only need to deal with a structure like this, where circular nodes a ```mermaid flowchart TB root((root)) --> paragraph((paragraph)) - paragraph --> inline_1((inline)) - paragraph --> inline_2((inline)) - inline_1 --> text_1[Hello, ] - inline_2 --> text_2[World!] + paragraph --> text_span_1((textSpan)) + paragraph --> text_span_2((textSpan)) + text_span_1 --> text_1[Hello, ] + text_span_2 --> text_2[World!] ``` This is compatible with the way Penpot stores text content. @@ -68,6 +68,26 @@ flowchart TB paragraph --> text((text)) ``` +## How the TextEditor works? + +```mermaid +flowchart TB + TextEditor -->|handles `selectionchange` events| SelectionController + TextEditor -->|handles how the editor dispatches changes| ChangeController +``` + +The `TextEditor` contains a series of references to DOM elements, one of them is a `contenteditable` element that keeps the sub-elements explained before (root, paragraphs and textspans). + +`SelectionController` listens to the `document` event called `selectionchange`. This event is triggered everytime the focus/selection of the browser changes. + +`ChangeController` is called by the `TextEditor` instance everytime a change is performed on the content of the `contenteditable` element. + +### Events + +- `change`: This event is dispatched every time a change is made in the editor. All changes are debounced to prevent dispatching too many change events. This event is also dispatched when there are pending change events and the user blurs the textarea element. + +- `stylechange`: This event is dispatched every time the `currentStyle` changes. This normally happens when the user changes the caret position or the selection and the `currentStyle` is re-computed. + ## How the code is organized? - `editor`: contains everything related to the TextEditor. Where `TextEditor.js` is the main file where all the basic code of the editor is handled. This has been designed so that in the future, when the Web Components API is more stable and has features such as handling selection events within shadow roots we will be able to update this class with little effort.