From b5714752114e6a7f06eb704edb8bba055a63fe7a Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Thu, 21 May 2026 22:44:47 +0300 Subject: [PATCH 01/25] [add] document Markdown encoder support - index.md: list Markdown alongside HTML and plain text in features and Supported formats; add example with richtext.markdown encoders and a note on the supported subset - get-value.md / set-value.md: include markdown in the encoder list and show richtext.markdown.toMarkdown / fromMarkdown usage --- docs/api/methods/get-value.md | 7 ++++--- docs/api/methods/set-value.md | 7 ++++--- docs/index.md | 24 ++++++++++++++++++++++-- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/docs/api/methods/get-value.md b/docs/api/methods/get-value.md index 4ed50ae..971bd9d 100644 --- a/docs/api/methods/get-value.md +++ b/docs/api/methods/get-value.md @@ -18,13 +18,14 @@ getValue(encoder?: any): string; ### Parameters -- `encoder` - (optional) a parser used to encode the RichText's content into a custom format. The following formats are available: `html` (default) and `text` +- `encoder` - (optional) a parser used to encode the RichText's content into a custom format. The following formats are available: `html` (default), `text`, and `markdown` You can get the required encoder in the following way: ```jsx -const toTextEncoder = richtext.text.toText; // text encoder -const toHTMLEncoder = richtext.html.toHTML; // html encoder +const toTextEncoder = richtext.text.toText; // text encoder +const toHTMLEncoder = richtext.html.toHTML; // html encoder +const toMarkdownEncoder = richtext.markdown.toMarkdown; // markdown encoder ``` ### Example diff --git a/docs/api/methods/set-value.md b/docs/api/methods/set-value.md index 5d40f94..29ff858 100644 --- a/docs/api/methods/set-value.md +++ b/docs/api/methods/set-value.md @@ -19,13 +19,14 @@ setValue: (value: string, encoder?: any): void; ### Parameters - `value` - (required) a value to be inserted into the RichText -- `encoder` - (optional) a custom parser used to encode the RichText's content into a custom format. The following formats are available: `html` (default) and `text` +- `encoder` - (optional) a custom parser used to encode the RichText's content into a custom format. The following formats are available: `html` (default), `text`, and `markdown` You can get the required encoder in the following way: ```jsx -const fromTextEncoder = richtext.text.fromText; // text encoder -const fromHTMLEncoder = richtext.html.fromHTML; // html encoder +const fromTextEncoder = richtext.text.fromText; // text encoder +const fromHTMLEncoder = richtext.html.fromHTML; // html encoder +const fromMarkdownEncoder = richtext.markdown.fromMarkdown; // markdown encoder ``` ### Example diff --git a/docs/index.md b/docs/index.md index d6a84fe..abc4e40 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,7 +13,7 @@ description: You can have an overview of DHTMLX JavaScript RichText library in t - Two [**layout modes**](api/config/layout-mode.md) -- Content serialization to both plain text and HTML +- Content serialization to HTML, plain text, and Markdown - Configurable [**toolbar**](api/config/toolbar.md) with built-in and custom buttons @@ -71,7 +71,7 @@ DHTMLX RichText can work with content in "classic" and "document" modes. You can ## Supported formats -The RichText editor supports [parsing](api/methods/set-value.md) and [serialization](api/methods/get-value.md) of content in the **HTML** and plain text formats. +The RichText editor supports [parsing](api/methods/set-value.md) and [serialization](api/methods/get-value.md) of content in the **HTML**, **plain text**, and **Markdown** formats. #### HTML format @@ -85,6 +85,26 @@ The RichText editor supports [parsing](api/methods/set-value.md) and [serializat ![Text format](./assets/richtext/text_format.png) +#### Markdown format + +Pass the built-in `markdown` encoders to [`setValue()`](api/methods/set-value.md) / [`getValue()`](api/methods/get-value.md) to load or serialize content as Markdown: + +~~~jsx +const editor = new richtext.Richtext("#root", { + value: "# Hello\n\nSome **bold** text", +}); + +// load Markdown into the editor +editor.setValue("# Title\n\nParagraph", richtext.markdown.fromMarkdown); + +// read editor content as Markdown +const md = editor.getValue(richtext.markdown.toMarkdown); +~~~ + +:::note +Markdown support covers a limited subset of the syntax — common block and inline elements such as headings, paragraphs, line breaks, emphasis, blockquotes, lists, and links. Formatting that has no Markdown equivalent (font family, font size, colors, alignment, line height) is dropped on serialization. +::: + ## Keyboard shortcuts The RichText editor supports a set of common keyboard shortcuts for faster formatting and editing. The shortcuts follow platform conventions and are available on both **Windows/Linux** (`Ctrl`) and **macOS** (`⌘`). From 5fb98f16dc40ea6182173aceb306b2d32fd54042 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Fri, 22 May 2026 11:36:16 +0300 Subject: [PATCH 02/25] [add] document insertValue() method - new article docs/api/methods/insert-value.md covering description, parameters (value, optional encoder), encoder snippet (text/html/markdown), example and v2.1 change log - register the method in sidebars.js and in main_overview.md / methods_overview.md tables --- docs/api/methods/insert-value.md | 45 +++++++++++++++++++++++++++ docs/api/overview/main_overview.md | 1 + docs/api/overview/methods_overview.md | 1 + sidebars.js | 1 + 4 files changed, 48 insertions(+) create mode 100644 docs/api/methods/insert-value.md diff --git a/docs/api/methods/insert-value.md b/docs/api/methods/insert-value.md new file mode 100644 index 0000000..9991ae9 --- /dev/null +++ b/docs/api/methods/insert-value.md @@ -0,0 +1,45 @@ +--- +sidebar_label: insertValue() +title: insertValue Method +description: You can learn about the insertValue method in the documentation of the DHTMLX JavaScript RichText library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX RichText. +--- + +# insertValue() + +### Description + +@short: Inserts a value at the current cursor position or replaces the selected text + +### Usage + +~~~jsx {} +insertValue: (value: string, encoder?: any): void; +~~~ + +### Parameters + +- `value` - (required) a value to be inserted into the RichText at the current cursor position. If a selection is active, the selection is replaced with the new value +- `encoder` - (optional) a parser used to decode the inserted value from a custom format. The following formats are available: `html` (default), `text`, and `markdown` + +You can get the required encoder in the following way: + +```jsx +const fromTextEncoder = richtext.text.fromText; // text encoder +const fromHTMLEncoder = richtext.html.fromHTML; // html encoder +const fromMarkdownEncoder = richtext.markdown.fromMarkdown; // markdown encoder +``` + +### Example + +~~~jsx {6} +const editor = new richtext.Richtext("#root", { + // configuration properties +}); + +// inserts an HTML link with custom attributes at the cursor position +editor.insertValue("link"); +~~~ + +The inserted content is added as a single history entry and can be reverted with one **Undo** step. + +**Change log:** The method was added in v2.1 diff --git a/docs/api/overview/main_overview.md b/docs/api/overview/main_overview.md index c89b2fc..9337cae 100644 --- a/docs/api/overview/main_overview.md +++ b/docs/api/overview/main_overview.md @@ -25,6 +25,7 @@ new richtext.RichText("#root", { | ----------------------------------------------|-------------------------------------------| | [](api/methods/get-value.md) | @getshort(api/methods/get-value.md) | | [](api/methods/set-value.md) | @getshort(api/methods/set-value.md) | +| [](api/methods/insert-value.md) | @getshort(api/methods/insert-value.md) | | [](api/methods/set-config.md) | @getshort(api/methods/set-config.md) | | [](api/methods/set-locale.md) | @getshort(api/methods/set-locale.md) | | [](api/methods/destructor.md) | @getshort(api/methods/destructor.md) | diff --git a/docs/api/overview/methods_overview.md b/docs/api/overview/methods_overview.md index b6b8f67..6c5c41b 100644 --- a/docs/api/overview/methods_overview.md +++ b/docs/api/overview/methods_overview.md @@ -14,5 +14,6 @@ Use this reference to quickly navigate to detailed descriptions of each method, | [](api/methods/destructor.md) | @getshort(api/methods/destructor.md) | | [](api/methods/get-value.md) | @getshort(api/methods/get-value.md) | | [](api/methods/set-value.md) | @getshort(api/methods/set-value.md) | +| [](api/methods/insert-value.md) | @getshort(api/methods/insert-value.md) | | [](api/methods/set-config.md) | @getshort(api/methods/set-config.md) | | [](api/methods/set-locale.md) | @getshort(api/methods/set-locale.md) | diff --git a/sidebars.js b/sidebars.js index 4be1cb8..64bd0b7 100644 --- a/sidebars.js +++ b/sidebars.js @@ -49,6 +49,7 @@ module.exports = { "api/methods/destructor", "api/methods/get-value", "api/methods/set-value", + "api/methods/insert-value", "api/methods/set-config", "api/methods/set-locale" ] From 4620b963937386df60d3c40b10c9649d7d6647de Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Fri, 22 May 2026 11:56:51 +0300 Subject: [PATCH 03/25] [add] document copy and paste behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - new "Copy and paste" section in index.md describing system shortcuts, toolbar/menubar entries, plain-text + HTML clipboard payload, paste fallback to plain text, and behavior across RichText instances and external sources - note that the toolbar Paste button uses the async Clipboard API (plain text only), while Ctrl+V / ⌘V receives full HTML --- docs/index.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/index.md b/docs/index.md index d6a84fe..ca37622 100644 --- a/docs/index.md +++ b/docs/index.md @@ -85,6 +85,25 @@ The RichText editor supports [parsing](api/methods/set-value.md) and [serializat ![Text format](./assets/richtext/text_format.png) +## Copy and paste + +The RichText editor supports clipboard operations through standard system shortcuts (`Ctrl+C` / `Ctrl+X` / `Ctrl+V` on Windows/Linux, `⌘C` / `⌘X` / `⌘V` on macOS), the corresponding [toolbar](api/config/toolbar.md) buttons, and the [menubar](api/config/menubar.md) entries. + +When content is copied or cut, RichText writes two representations to the system clipboard: + +- a **plain text** version for compatibility with simple targets (terminals, code editors, plain inputs) +- an **HTML** version that carries all inline and block formatting (bold, italic, underline, strikethrough, font family, font size, text and background color, headings, blockquotes, lists, alignment, indentation, line height, links, and images) + +On paste, the editor reads the HTML version when it is available and falls back to plain text otherwise. This means that: + +- Copying between two RichText instances (in the same document or on different pages) preserves the original formatting. +- Pasting from external sources that publish HTML to the clipboard (most browsers, word processors, and editors) also preserves the recognized formatting. Styles that have no direct counterpart in RichText are dropped during normalization. +- Pasting from a plain-text source produces a plain paragraph, as expected. + +:::note +The toolbar **Paste** button uses the asynchronous Clipboard API, which exposes plain text only. To paste formatted content, use the `Ctrl+V` / `⌘V` shortcut, which receives the full HTML payload directly from the browser's clipboard event. +::: + ## Keyboard shortcuts The RichText editor supports a set of common keyboard shortcuts for faster formatting and editing. The shortcuts follow platform conventions and are available on both **Windows/Linux** (`Ctrl`) and **macOS** (`⌘`). From a51b41bca2110dfcd4a6bd75ed54d644f3961164 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Fri, 22 May 2026 12:16:34 +0300 Subject: [PATCH 04/25] [add] document inline base64 image insertion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - image-upload-url.md: describe optional behavior — when imageUploadUrl is omitted, images are inserted as base64 data URLs with 1024x800 downscaling; add upload vs inline examples and note caveats around DOCX/PDF export and payload size - guides/configuration.md: rename "Configure the image upload URL" to "Configure image insertion" and split into server-upload vs inline base64 modes - insert-image.md: clarify that IImageContext.value holds either an external URL or a base64 data URL - index.md: mention server upload and inline base64 in the features list --- docs/api/config/image-upload-url.md | 27 +++++++++++++++++++++++++-- docs/api/events/insert-image.md | 6 +++++- docs/guides/configuration.md | 25 +++++++++++++++++++++++-- docs/index.md | 2 +- 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/docs/api/config/image-upload-url.md b/docs/api/config/image-upload-url.md index 761b8c8..9a43aa3 100644 --- a/docs/api/config/image-upload-url.md +++ b/docs/api/config/image-upload-url.md @@ -10,6 +10,18 @@ description: You can learn about the imageUploadUrl config in the documentation @short: Optional. Specifies the URL which will be used for image upload +When the property is set, RichText uploads each inserted image to the given endpoint (via toolbar, menubar, paste, or drag-and-drop) and inserts the URL returned by the server. + +When the property is omitted or set to a falsy value (`""`, `null`, `undefined`), RichText switches to **inline mode**: the image file is read on the client and embedded directly into the content as a base64 data URL — no server is required. Inline images larger than 1024×800 are proportionally downscaled to fit within these limits. + +:::note +Inline (base64) images are not preserved by the built-in DOCX / PDF [export](api/events/export.md). If you rely on export, supply an `imageUploadUrl` so that images reference an external location. +::: + +:::caution +Base64 encoding increases the encoded payload by roughly one third relative to the original file. A document with several large inline images grows accordingly, which affects the size of the value returned by [`getValue()`](api/methods/get-value.md), the memory footprint of the editor, and the cost of persisting or transferring the content. Prefer a server `imageUploadUrl` for documents that contain many or large images. +::: + ### Usage ~~~jsx {} @@ -18,15 +30,26 @@ imageUploadUrl?: string; ### Example +Upload images to a server endpoint: + ~~~jsx {3} // initialize RichText new richtext.Richtext("#root", { - imageUploadUrl: "some URL" + imageUploadUrl: "https://example.com/upload" + // other configuration properties +}); +~~~ + +Insert images inline as base64 (no server required) — omit the property or pass an empty string: + +~~~jsx {2} +new richtext.Richtext("#root", { + // imageUploadUrl is not set, images are inserted as base64 data URLs // other configuration properties }); ~~~ -**Change log:** The property was added in v2.0 +**Change log:** The property was added in v2.0. Starting with v2.1, the property is fully optional: when omitted, images are inserted inline as base64 data URLs. **Related articles:** [Configuration](guides/configuration.md) diff --git a/docs/api/events/insert-image.md b/docs/api/events/insert-image.md index 7898511..99c5dde 100644 --- a/docs/api/events/insert-image.md +++ b/docs/api/events/insert-image.md @@ -17,7 +17,7 @@ description: You can learn about the insert-image event in the documentation of interface IImageContext { id: TID; - value: string; + value: string; // image source: server URL when imageUploadUrl is set, or a base64 data URL when the image is inlined width: number; height: number; // extra props from uploader ctx, not required for the actual action @@ -29,6 +29,10 @@ interface IImageContext { } ~~~ +:::note +The `value` field holds either an external URL (when [`imageUploadUrl`](api/config/image-upload-url.md) is configured and the upload succeeds) or a base64 data URL (when `imageUploadUrl` is omitted and the image is inlined on the client). Handlers that process the source — for example, to rewrite the URL or validate the host — must account for both formats. +::: + :::info For handling inner events you can use [**Event Bus methods**](api/overview/event_bus_methods_overview.md) ::: diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index 855ae27..3d3d2ff 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -238,9 +238,13 @@ new richtext.Richtext("#root", { }); ~~~ -## Configure the image upload URL +## Configure image insertion -Pass a URL to the [`imageUploadUrl`](api/config/image-upload-url.md) property to set the server endpoint for toolbar image uploads: +RichText supports two modes for inserting images via the toolbar, menubar, paste, or drag-and-drop. The mode is selected automatically based on the [`imageUploadUrl`](api/config/image-upload-url.md) property. + +### Upload images to a server + +Pass a URL to the [`imageUploadUrl`](api/config/image-upload-url.md) property to upload each inserted image to your endpoint. RichText sends the file as `multipart/form-data` (field name `upload`) and inserts the URL returned by the server: ~~~jsx {2} new richtext.Richtext("#root", { @@ -249,6 +253,23 @@ new richtext.Richtext("#root", { }); ~~~ +### Insert images inline as base64 + +Omit [`imageUploadUrl`](api/config/image-upload-url.md) (or set it to an empty string) to embed images directly into the document content as base64 data URLs. No server is required: + +~~~jsx {2} +new richtext.Richtext("#root", { + // imageUploadUrl is not set, images are inserted inline + // other configuration properties +}); +~~~ + +Inline images larger than 1024×800 are proportionally downscaled to fit within these limits. + +:::note +Inline (base64) images are not preserved by the built-in DOCX / PDF [export](api/events/export.md). If you rely on export, supply an `imageUploadUrl` so that images reference an external location. +::: + ## Configure default styles Use the [`defaultStyles`](api/config/default-styles.md) property to set default styles per block type. diff --git a/docs/index.md b/docs/index.md index d6a84fe..25c556b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -19,7 +19,7 @@ description: You can have an overview of DHTMLX JavaScript RichText library in t - Static [**menubar**](api/config/menubar.md) that can be shown or hidden -- Image uploading, rich formatting, custom styling, and full screen mode +- Image uploading with optional server [upload](api/config/image-upload-url.md) or inline base64 embedding, rich formatting, custom styling, and full screen mode - [Full API access](api/overview/main_overview.md) for [event handling](api/overview/event_bus_methods_overview.md), [content manipulation](api/overview/methods_overview.md), and [reactive state management](api/overview/state_methods_overview.md) From ab426ba907f61ce34457b720f7692e0cfa8cf55f Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Fri, 22 May 2026 12:31:13 +0300 Subject: [PATCH 05/25] [update] note paste and drag-and-drop as image upload sources - image-upload-url.md @short and configuration guide now state that image uploads can be triggered from the toolbar, menubar, clipboard paste, or drag-and-drop, not just the toolbar - insert-image.md @short updated to match --- docs/api/config/image-upload-url.md | 2 +- docs/api/events/insert-image.md | 2 +- docs/guides/configuration.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api/config/image-upload-url.md b/docs/api/config/image-upload-url.md index 761b8c8..aa461bd 100644 --- a/docs/api/config/image-upload-url.md +++ b/docs/api/config/image-upload-url.md @@ -8,7 +8,7 @@ description: You can learn about the imageUploadUrl config in the documentation ### Description -@short: Optional. Specifies the URL which will be used for image upload +@short: Optional. Specifies the URL which will be used for image upload (from the toolbar, menubar, clipboard paste, or drag-and-drop) ### Usage diff --git a/docs/api/events/insert-image.md b/docs/api/events/insert-image.md index 7898511..d8357a9 100644 --- a/docs/api/events/insert-image.md +++ b/docs/api/events/insert-image.md @@ -8,7 +8,7 @@ description: You can learn about the insert-image event in the documentation of ### Description -@short: Fires when inserting image +@short: Fires when inserting an image (via the toolbar, menubar, clipboard paste, or drag-and-drop) ### Usage diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index 855ae27..69428b5 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -240,7 +240,7 @@ new richtext.Richtext("#root", { ## Configure the image upload URL -Pass a URL to the [`imageUploadUrl`](api/config/image-upload-url.md) property to set the server endpoint for toolbar image uploads: +Pass a URL to the [`imageUploadUrl`](api/config/image-upload-url.md) property to set the server endpoint for image uploads triggered from the toolbar, menubar, clipboard paste, or drag-and-drop: ~~~jsx {2} new richtext.Richtext("#root", { From 575a11d6a02912d0a36479d71d7f338d657345fc Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Fri, 22 May 2026 17:41:55 +0300 Subject: [PATCH 06/25] [update] address review on copy and paste section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - macOS shortcuts now follow Ctrl+C style: ⌘+C / ⌘+X / ⌘+V (incl. the note about ⌘+V) - clarify that pasting from any external source is processed as plain text; previous wording about HTML-aware external paste was incorrect - drop the separate plain-text-source bullet; external-source behavior is now one line --- docs/index.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/index.md b/docs/index.md index ca37622..7ab48e4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -87,21 +87,20 @@ The RichText editor supports [parsing](api/methods/set-value.md) and [serializat ## Copy and paste -The RichText editor supports clipboard operations through standard system shortcuts (`Ctrl+C` / `Ctrl+X` / `Ctrl+V` on Windows/Linux, `⌘C` / `⌘X` / `⌘V` on macOS), the corresponding [toolbar](api/config/toolbar.md) buttons, and the [menubar](api/config/menubar.md) entries. +The RichText editor supports clipboard operations through standard system shortcuts (`Ctrl+C` / `Ctrl+X` / `Ctrl+V` on Windows/Linux, `⌘+C` / `⌘+X` / `⌘+V` on macOS), the corresponding [toolbar](api/config/toolbar.md) buttons, and the [menubar](api/config/menubar.md) entries. When content is copied or cut, RichText writes two representations to the system clipboard: - a **plain text** version for compatibility with simple targets (terminals, code editors, plain inputs) - an **HTML** version that carries all inline and block formatting (bold, italic, underline, strikethrough, font family, font size, text and background color, headings, blockquotes, lists, alignment, indentation, line height, links, and images) -On paste, the editor reads the HTML version when it is available and falls back to plain text otherwise. This means that: +Paste behavior depends on the source of the clipboard payload: -- Copying between two RichText instances (in the same document or on different pages) preserves the original formatting. -- Pasting from external sources that publish HTML to the clipboard (most browsers, word processors, and editors) also preserves the recognized formatting. Styles that have no direct counterpart in RichText are dropped during normalization. -- Pasting from a plain-text source produces a plain paragraph, as expected. +- Pasting between two RichText instances (in the same document or on different pages) uses the HTML representation and preserves the original formatting. +- Pasting from any external source — including browsers, word processors, and other editors — is processed as plain text. The inserted content is added as text without external formatting. :::note -The toolbar **Paste** button uses the asynchronous Clipboard API, which exposes plain text only. To paste formatted content, use the `Ctrl+V` / `⌘V` shortcut, which receives the full HTML payload directly from the browser's clipboard event. +The toolbar **Paste** button uses the asynchronous Clipboard API, which exposes plain text only. To paste content copied from another RichText with its formatting preserved, use the `Ctrl+V` / `⌘+V` shortcut, which receives the full HTML payload directly from the browser's clipboard event. ::: ## Keyboard shortcuts From 1c76f3186f4bb77944c7b38e077b9307bfc8e58b Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Tue, 26 May 2026 18:27:46 +0300 Subject: [PATCH 07/25] [update] tighten wording in imageUploadUrl change log - drop "fully" from "fully optional" in the v2.1 change-log note --- docs/api/config/image-upload-url.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/config/image-upload-url.md b/docs/api/config/image-upload-url.md index 9a43aa3..f67fc08 100644 --- a/docs/api/config/image-upload-url.md +++ b/docs/api/config/image-upload-url.md @@ -49,7 +49,7 @@ new richtext.Richtext("#root", { }); ~~~ -**Change log:** The property was added in v2.0. Starting with v2.1, the property is fully optional: when omitted, images are inserted inline as base64 data URLs. +**Change log:** The property was added in v2.0. Starting with v2.1, the property is optional: when omitted, images are inserted inline as base64 data URLs. **Related articles:** [Configuration](guides/configuration.md) From 5940afb934d7ee5909f30464226985b05da275d3 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Tue, 26 May 2026 20:00:35 +0300 Subject: [PATCH 08/25] [update] address review on insertValue docs - @short now describes the inserted content as "text, Markdown, or HTML" instead of the abstract "a value" - drop "from a custom format" from the encoder description; the supported formats are listed on the next line --- docs/api/methods/insert-value.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/methods/insert-value.md b/docs/api/methods/insert-value.md index 9991ae9..7f2a43b 100644 --- a/docs/api/methods/insert-value.md +++ b/docs/api/methods/insert-value.md @@ -8,7 +8,7 @@ description: You can learn about the insertValue method in the documentation of ### Description -@short: Inserts a value at the current cursor position or replaces the selected text +@short: Inserts text, Markdown, or HTML content at the current cursor position or replaces the selected text ### Usage @@ -19,7 +19,7 @@ insertValue: (value: string, encoder?: any): void; ### Parameters - `value` - (required) a value to be inserted into the RichText at the current cursor position. If a selection is active, the selection is replaced with the new value -- `encoder` - (optional) a parser used to decode the inserted value from a custom format. The following formats are available: `html` (default), `text`, and `markdown` +- `encoder` - (optional) a parser used to decode the inserted value. The following formats are available: `html` (default), `text`, and `markdown` You can get the required encoder in the following way: From 94d4605cb39d073f5ffa6c2b15a3c7186ce475ed Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Tue, 26 May 2026 20:09:51 +0300 Subject: [PATCH 09/25] [update] note nested-structure limits in Markdown - extend Markdown support note in docs/index.md - document that nested inline structures are unsupported - call out bold-inside-italic as the only exception - list bold-in-link, italic-in-list, multi-level lists as cases that won't render correctly --- docs/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.md b/docs/index.md index abc4e40..6883635 100644 --- a/docs/index.md +++ b/docs/index.md @@ -103,6 +103,8 @@ const md = editor.getValue(richtext.markdown.toMarkdown); :::note Markdown support covers a limited subset of the syntax — common block and inline elements such as headings, paragraphs, line breaks, emphasis, blockquotes, lists, and links. Formatting that has no Markdown equivalent (font family, font size, colors, alignment, line height) is dropped on serialization. + +Nested inline structures are not supported, with the only exception of **bold inside italic**. Combinations such as bold inside a link, italic inside a list item, or multi-level (nested) lists will not render correctly. ::: ## Keyboard shortcuts From 3d4da9d6969c1443dccaca9a8afc094815b508cb Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Wed, 27 May 2026 09:16:34 +0300 Subject: [PATCH 10/25] [add] hide-suggest event docs - new API reference page for the hide-suggest event - documents when the dropdown closes (selection, Escape, cursor leaves trigger, empty result) --- docs/api/events/hide-suggest.md | 47 +++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 docs/api/events/hide-suggest.md diff --git a/docs/api/events/hide-suggest.md b/docs/api/events/hide-suggest.md new file mode 100644 index 0000000..e458359 --- /dev/null +++ b/docs/api/events/hide-suggest.md @@ -0,0 +1,47 @@ +--- +sidebar_label: hide-suggest +title: hide-suggest Event +description: You can learn about the hide-suggest event in the documentation of the DHTMLX JavaScript RichText library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX RichText. +--- + +# hide-suggest + +### Description + +@short: Fires when the suggestion dropdown closes + +The event fires when any of these happen: + +- the user picks an item from the dropdown +- the user presses `Escape` +- the cursor leaves the trigger context (for example, on `Backspace` past the trigger character) +- the current query returns no matches + +### Usage + +~~~jsx {} +"hide-suggest": () => boolean | void; +~~~ + +### Parameters + +The `hide-suggest` event callback does not receive any parameters. + +:::info +To handle internal events, use [**Event Bus methods**](api/overview/event_bus_methods_overview.md). +::: + +### Example + +~~~jsx {5-7} +// initialize RichText +const editor = new richtext.Richtext("#root", { + // configuration properties +}); +// subscribe to the "hide-suggest" event +editor.api.on("hide-suggest", () => { + console.log("Suggestion dropdown closed"); +}); +~~~ + +**Change log:** The event was added in v2.1 From 6eac5771c560c997cd762ce55e6ad82fcb4b27e9 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Wed, 27 May 2026 09:16:54 +0300 Subject: [PATCH 11/25] [add] insert-token event docs - new API reference page for the insert-token event - documents callback fields (id, label, url, trigger, showTrigger) --- docs/api/events/insert-token.md | 55 +++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 docs/api/events/insert-token.md diff --git a/docs/api/events/insert-token.md b/docs/api/events/insert-token.md new file mode 100644 index 0000000..1632a65 --- /dev/null +++ b/docs/api/events/insert-token.md @@ -0,0 +1,55 @@ +--- +sidebar_label: insert-token +title: insert-token Event +description: You can learn about the insert-token event in the documentation of the DHTMLX JavaScript RichText library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX RichText. +--- + +# insert-token + +### Description + +@short: Fires after the user picks a suggestion item and RichText inserts it as a token + +The event fires after the user picks an item from a trigger dropdown (mentions, tags, or any custom trigger you set up through the [`triggers`](api/config/triggers.md) property). + +### Usage + +~~~jsx {} +"insert-token": ({ + id: string, + label: string, + url: string, + trigger: string, + showTrigger?: boolean +}) => boolean | void; +~~~ + +### Parameters + +The callback of the `insert-token` event receives an object with the following fields: + +- `id` - the id of the picked suggestion item +- `label` - the label of the picked item (the text inserted into the document) +- `url` - the url associated with the item (becomes the `href` attribute of the inserted token) +- `trigger` - the trigger character that opened the dropdown (for example, `"@"` or `"#"`) +- `showTrigger` - when `false`, RichText inserts only `label`; otherwise it also shows the trigger character (default) + +:::info +To handle internal events, use [**Event Bus methods**](api/overview/event_bus_methods_overview.md). +::: + +### Example + +~~~jsx {5-9} +// initialize RichText +const editor = new richtext.Richtext("#root", { + // configuration properties +}); +// subscribe to the "insert-token" event +editor.api.on("insert-token", (obj) => { + console.log(obj); + console.log(`Inserted ${obj.trigger}${obj.label} (id: ${obj.id})`); +}); +~~~ + +**Change log:** The event was added in v2.1 From 34f4c798550533247fba21584dfcfb00af4ea242 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Wed, 27 May 2026 09:17:06 +0300 Subject: [PATCH 12/25] [add] show-suggest event docs - new API reference page for the show-suggest event - documents callback fields (trigger, query, items, pos) and intercept example --- docs/api/events/show-suggest.md | 54 +++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docs/api/events/show-suggest.md diff --git a/docs/api/events/show-suggest.md b/docs/api/events/show-suggest.md new file mode 100644 index 0000000..f191ba0 --- /dev/null +++ b/docs/api/events/show-suggest.md @@ -0,0 +1,54 @@ +--- +sidebar_label: show-suggest +title: show-suggest Event +description: You can learn about the show-suggest event in the documentation of the DHTMLX JavaScript RichText library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX RichText. +--- + +# show-suggest + +### Description + +@short: Fires when the suggestion dropdown opens for a configured trigger + +The event fires after RichText resolves a non-empty list of items for the current trigger, just before the dropdown opens. Intercept the event to adjust the items, move the dropdown, or cancel it. + +### Usage + +~~~jsx {} +"show-suggest": ({ + trigger: string, + query: string, + items: Array<{ id: string, label: string, url: string }>, + pos: DOMRect +}) => boolean | void; +~~~ + +### Parameters + +The callback of the `show-suggest` event receives an object with the following fields: + +- `trigger` - the trigger character that opened the dropdown +- `query` - the text typed after the trigger character (used to filter `items`) +- `items` - the resolved (and already filtered) list of suggestion items +- `pos` - a `DOMRect` describing the cursor position; used to place the dropdown on screen + +:::info +To handle internal events, use [**Event Bus methods**](api/overview/event_bus_methods_overview.md). +::: + +### Example + +~~~jsx {5-10} +// initialize RichText +const editor = new richtext.Richtext("#root", { + // configuration properties +}); +// override the suggestion list before the dropdown opens +editor.api.intercept("show-suggest", (state) => { + if (state.trigger === "@" && state.query === "") { + return { ...state, items: state.items.slice(0, 5) }; + } +}); +~~~ + +**Change log:** The event was added in v2.1 From 44b66d8f35e474f64ffc981b02afc087ae1be2b5 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Wed, 27 May 2026 09:17:20 +0300 Subject: [PATCH 13/25] [add] triggers config docs - new API reference page for the triggers property - covers static array, sync function, async function data sources - documents suggestion item fields and rendered token CSS targeting --- docs/api/config/triggers.md | 130 ++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 docs/api/config/triggers.md diff --git a/docs/api/config/triggers.md b/docs/api/config/triggers.md new file mode 100644 index 0000000..0ef565e --- /dev/null +++ b/docs/api/config/triggers.md @@ -0,0 +1,130 @@ +--- +sidebar_label: triggers +title: triggers Config +description: You can learn about the triggers config in the documentation of the DHTMLX JavaScript RichText library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX RichText. +--- + +# triggers + +### Description + +@short: Optional. Defines dropdown triggers for inserting mentions, tags, and other tokens + +When the user types a configured character (for example, `@` or `#`), RichText opens a suggestion dropdown. When the user selects an item, RichText inserts it into the document as a non-editable token (``). + +### Usage + +~~~jsx {} +triggers?: Array<{ + trigger: string, + data: Array<{ id?: string; label?: string; url?: string }> + | ((query: string) => + Array<{ id?: string; label?: string; url?: string }> + | Promise>), + showTrigger?: boolean +}>; +~~~ + +### Parameters + +Each entry of the `triggers` array accepts the following fields: + +- `trigger` - (required) the character that opens the suggestion dropdown (for example, `"@"`, `"#"`, `"/"`, `"$"`) +- `data` - (required) the data source for the dropdown; can be an array, a sync function, or an async function. See [Data source forms](#data-source-forms) +- `showTrigger` - (optional) when `true` (default), RichText keeps the trigger character in the inserted token (`@Alice`); when `false`, RichText inserts only `label` (`Alice`) + +### Data source forms + +**Static array** — RichText filters the array automatically by matching the query against `label` (case-insensitive, `startsWith`): + +~~~jsx {3-7} +new richtext.Richtext("#root", { + triggers: [{ + trigger: "@", + data: [ + { id: "alice", label: "Alice" }, + { id: "bob", label: "Bob" } + ] + }] +}); +~~~ + +**Sync function** — RichText calls your function with the current `query` string; you do the filtering and return the matching array: + +~~~jsx {3-6} +new richtext.Richtext("#root", { + triggers: [{ + trigger: "#", + data: query => tags.filter(t => + t.label.toLowerCase().startsWith(query.toLowerCase()) + ) + }] +}); +~~~ + +**Async function** — RichText calls your function with the current `query` string; return a `Promise` that resolves to the matching array. Useful for server-side search: + +~~~jsx {3-8} +new richtext.Richtext("#root", { + triggers: [{ + trigger: "+", + data: async query => { + const res = await fetch(`/api/users?q=${encodeURIComponent(query)}`); + const users = await res.json(); + return users.map(u => ({ id: String(u.id), label: u.name, url: u.website })); + } + }] +}); +~~~ + +### Suggestion item fields + +Each item in `data` (or each item returned by a function) has the following fields: + +- `id` - (optional) unique identifier saved on the inserted token. If omitted, RichText generates an ID automatically. +- `label` - (required) the text shown in the dropdown and inserted into the document +- `url` - (optional) URL associated with the item. RichText stores it on the inserted token as the `href` attribute. + +### Rendered token + +When a user selects an item, RichText inserts a non-editable token element into the document: + +~~~html {} +@Alice +~~~ + +Use the `data-token` and `data-token-id` attributes to target tokens with CSS, for example, to highlight all mentions of a user: + +~~~css {} +.wx-editor-content a[data-token="@"][data-token-id="alice"] { + background: #fb8500; + color: #fff; +} +~~~ + +### Example + +~~~jsx {2-13} +new richtext.Richtext("#root", { + triggers: [ + { + trigger: "@", + data: [ + { id: "alice", label: "Alice", url: "mailto:alice@example.com" }, + { id: "bob", label: "Bob", url: "mailto:bob@example.com" } + ] + }, + { + trigger: "#", + data: [ + { id: "css", label: "CSS" }, + { id: "html", label: "HTML" } + ] + } + ] +}); +~~~ + +**Change log:** The property was added in v2.1 + +**Related articles:** [Mentions and tags](guides/mentions_and_tags.md) From a49929befabc5d8c8bb1b0a0992f98f5e9e6ea69 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Wed, 27 May 2026 09:17:44 +0300 Subject: [PATCH 14/25] [add] triggerTemplate config docs - new API reference page for the triggerTemplate property - documents callback parameters (data, trigger) and template usage - includes tip on overriding the dropdown anchor width --- docs/api/config/trigger-template.md | 62 +++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 docs/api/config/trigger-template.md diff --git a/docs/api/config/trigger-template.md b/docs/api/config/trigger-template.md new file mode 100644 index 0000000..0ade570 --- /dev/null +++ b/docs/api/config/trigger-template.md @@ -0,0 +1,62 @@ +--- +sidebar_label: triggerTemplate +title: triggerTemplate Config +description: You can learn about the triggerTemplate config in the documentation of the DHTMLX JavaScript RichText library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX RichText. +--- + +# triggerTemplate + +### Description + +@short: Optional. Customizes how RichText renders items in the suggestion dropdown opened by a [`triggers`](api/config/triggers.md) entry + +By default, the dropdown shows each item's `label` as plain text. Use `triggerTemplate` to render richer rows — for example, an avatar plus a name and an email. + +### Usage + +~~~jsx {} +function triggerTemplate({ data, trigger }) { + return "HTML template of the suggestion item"; +}; +~~~ + +### Parameters + +The callback function takes an object with the following parameters: + +- `data` - the current suggestion item (`{ id, label, url }`, plus any custom fields you add to the trigger's `data` source) +- `trigger` - the trigger character that opened the dropdown (`"@"`, `"#"`, etc.) + +:::tip +The dropdown anchor has a fixed default width of `160px`. To make room for a wider template, override the width from a parent stylesheet (`!important` is needed because the widget sets the default via its own scoped CSS): + +~~~css {} +.wx-suggest-anchor { + width: 220px !important; +} +~~~ +::: + +### Example + +~~~jsx {1,4-9} +const { template } = richtext; + +new richtext.Richtext("#root", { + triggers: [{ trigger: "@", data: people }], + triggerTemplate: template(obj => { + if (obj.trigger === "@") { + return `
+ +
${obj.data.label}
+
${obj.data.name}
+
`; + } + return obj.data.label; + }) +}); +~~~ + +**Change log:** The property was added in v2.1 + +**Related articles:** [Mentions and tags](guides/mentions_and_tags.md) From d1adb47a0760744d0b41cf7bb969ffad4e2ae417 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Wed, 27 May 2026 09:18:02 +0300 Subject: [PATCH 15/25] [add] Mentions and tags guide - new guide covering trigger characters, data source forms, token rendering, and event handling - examples for static array, sync function, async function, and custom dropdown templates --- docs/guides/mentions_and_tags.md | 144 +++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 docs/guides/mentions_and_tags.md diff --git a/docs/guides/mentions_and_tags.md b/docs/guides/mentions_and_tags.md new file mode 100644 index 0000000..c2494e0 --- /dev/null +++ b/docs/guides/mentions_and_tags.md @@ -0,0 +1,144 @@ +--- +sidebar_label: Mentions and tags +title: Mentions and tags +description: 'Learn how to configure @mentions, #tags, and custom dropdown triggers in DHTMLX RichText. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX RichText.' +--- + +# Mentions and tags + +RichText supports user-defined trigger characters that open a suggestion dropdown inside the editor. When the user picks an item, RichText inserts a non-editable token into the document. Typical use cases: + +- `@` — mention a person +- `#` — apply a tag +- `/` — insert a command or template +- `$` — insert a financial ticker or variable + +Configure the behavior through the [`triggers`](api/config/triggers.md) array. Each entry binds one character to a data source. + +## Configure triggers + +Each trigger is an object `{ trigger, data, showTrigger? }` within the [`triggers`](api/config/triggers.md) array. The [`data`](api/config/triggers.md/#data-source-forms) field can take three forms: + +- A static array — RichText filters it automatically by `label` (case-insensitive, `startsWith`): + +~~~jsx {} +{ trigger: "@", data: people } +~~~ + +- A sync function — use it to filter results yourself: + +~~~jsx {} +{ + trigger: "#", + data: query => tags.filter(t => + t.label.toLowerCase().startsWith(query.toLowerCase()) + ) +} +~~~ + +- An async function — use it for server-side search: + +~~~jsx {} +{ + trigger: "+", + data: async query => { + const res = await fetch(`/api/users?q=${encodeURIComponent(query)}`); + const users = await res.json(); + return users.map(u => ({ + id: String(u.id), + label: u.name, + url: u.website + })); + } +} +~~~ + +## Token rendering + +When the user picks an item, RichText inserts it as an `` element with two data attributes: + +~~~html {2-3} +@Alice +~~~ + +The token is a single non-editable node. `Backspace` deletes it in one step. RichText stores the `url` field in `href`, so `Ctrl+Click` on the token follows the link. + +You can style tokens with the `data-token` selector: + +~~~css {} +.wx-editor-content a[data-token="@"][data-token-id="alice"] { + background: #fb8500; + color: #fff; + border-radius: 3px; + padding: 0 2px; +} +~~~ + +## Hide the trigger character + +Set `showTrigger: false` on a trigger to insert only the item label, without the trigger symbol: + +~~~jsx {4} +{ + trigger: "/", + data: commands, + showTrigger: false +} +~~~ + +## Keyboard interaction + +Inside the suggestion dropdown you can use the following shortcuts: + +- `↑` / `↓` — move between items +- `Enter` — insert the active item +- `Escape` — close the dropdown without inserting + +## Listen to suggestion events + +Three events expose the dropdown lifecycle through the Event Bus: + +- [`insert-token`](api/events/insert-token.md) — fires when a user picks an item +- [`show-suggest`](api/events/show-suggest.md) — fires when the dropdown opens +- [`hide-suggest`](api/events/hide-suggest.md) — fires when the dropdown closes + +~~~jsx {5-7} +const editor = new richtext.Richtext("#root", { + triggers: [{ trigger: "@", data: people }] +}); + +editor.api.on("insert-token", ({ trigger, label, id }) => { + console.log(`Inserted ${trigger}${label} (id: ${id})`); +}); +~~~ + +## Customize the dropdown item + +By default the dropdown shows the `label` of each item. To render custom suggestions (for example, an avatar plus a name and an email) pass a template via the [`triggerTemplate`](api/config/trigger-template.md) property. + +### Example + +~~~jsx {1,4-9} +const { template } = richtext; + +new richtext.Richtext("#root", { + triggers: [{ trigger: "@", data: people }], + triggerTemplate: template(({ data, trigger }) => ` +
+
${trigger}${data.label}
+
${data.url || ""}
+
+ `) +}); +~~~ + +## Related API + +- [`triggers`](api/config/triggers.md) +- [`triggerTemplate`](api/config/trigger-template.md) +- [`insert-token`](api/events/insert-token.md) +- [`show-suggest`](api/events/show-suggest.md) +- [`hide-suggest`](api/events/hide-suggest.md) From 6948c9cccdd81b9fa983511b49672ee516c62d59 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Wed, 27 May 2026 09:18:15 +0300 Subject: [PATCH 16/25] [update] add suggestion events to events overview - list hide-suggest, insert-token, and show-suggest in the events table --- docs/api/overview/events_overview.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/api/overview/events_overview.md b/docs/api/overview/events_overview.md index 957e228..449c945 100644 --- a/docs/api/overview/events_overview.md +++ b/docs/api/overview/events_overview.md @@ -18,12 +18,14 @@ You can use these events to extend functionality, track user interaction, or tri | [](api/events/cut.md) | @getshort(api/events/cut.md) | | [](api/events/delete-link.md) | @getshort(api/events/delete-link.md) | | [](api/events/export.md) | @getshort(api/events/export.md) | +| [](api/events/hide-suggest.md) | @getshort(api/events/hide-suggest.md) | | [](api/events/import.md) | @getshort(api/events/import.md) | | [](api/events/indent.md) | @getshort(api/events/indent.md) | | [](api/events/insert-image.md) | @getshort(api/events/insert-image.md) | | [](api/events/insert-line.md) | @getshort(api/events/insert-line.md) | | [](api/events/insert-link.md) | @getshort(api/events/insert-link.md) | | [](api/events/insert-list.md) | @getshort(api/events/insert-list.md) | +| [](api/events/insert-token.md) | @getshort(api/events/insert-token.md) | | [](api/events/outdent.md) | @getshort(api/events/outdent.md) | | [](api/events/paste.md) | @getshort(api/events/paste.md) | | [](api/events/print.md) | @getshort(api/events/print.md) | @@ -36,6 +38,7 @@ You can use these events to extend functionality, track user interaction, or tri | [](api/events/set-text-format.md) | @getshort(api/events/set-text-format.md) | | [](api/events/set-text-style.md) | @getshort(api/events/set-text-style.md) | | [](api/events/show-popup.md) | @getshort(api/events/show-popup.md) | +| [](api/events/show-suggest.md) | @getshort(api/events/show-suggest.md) | | [](api/events/subscript.md) | @getshort(api/events/subscript.md) | | [](api/events/superscript.md) | @getshort(api/events/superscript.md) | | [](api/events/toggle-fullscreen-mode.md) | @getshort(api/events/toggle-fullscreen-mode.md)| From ce748b791f2a9236c6140850a986fa3e0f039847 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Wed, 27 May 2026 09:18:26 +0300 Subject: [PATCH 17/25] [update] add trigger configs to properties overview - list triggers and triggerTemplate in the properties table --- docs/api/overview/properties_overview.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api/overview/properties_overview.md b/docs/api/overview/properties_overview.md index 255a158..e545112 100644 --- a/docs/api/overview/properties_overview.md +++ b/docs/api/overview/properties_overview.md @@ -18,4 +18,6 @@ They help you control layout, toolbar, value, localization, and other aspects of | [](api/config/locale.md) | @getshort(api/config/locale.md) | | [](api/config/menubar.md) | @getshort(api/config/menubar.md) | | [](api/config/toolbar.md) | @getshort(api/config/toolbar.md) | +| [](api/config/trigger-template.md) | @getshort(api/config/trigger-template.md) | +| [](api/config/triggers.md) | @getshort(api/config/triggers.md) | | [](api/config/value.md) | @getshort(api/config/value.md) | From 90c074d73a4f44e3f917bb5430e455bed33e72e7 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Wed, 27 May 2026 09:18:45 +0300 Subject: [PATCH 18/25] [update] mention triggers in configuration guide - list triggers in the property overview with a link to the Mentions and tags guide --- docs/guides/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index 855ae27..d98eb9f 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -16,6 +16,7 @@ You can configure the RichText appearance and behavior with the following proper - [`locale`](api/config/locale.md) — apply a localization object on initialization - [`defaultStyles`](api/config/default-styles.md) — set default styles for specific block types - [`imageUploadUrl`](api/config/image-upload-url.md) — set the endpoint for image uploads +- [`triggers`](api/config/triggers.md) — enable @mentions, #tags, and custom dropdown triggers (see the [Mentions and tags](guides/mentions_and_tags.md) guide) ## Layout modes From d7646923231d91cbefb9b5d3581e01293a94de97 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Wed, 27 May 2026 09:19:02 +0300 Subject: [PATCH 19/25] [fix] correct anchor for custom toolbar controls link - update anchor in whats_new from #custom-toolbar-controls to #add-custom-toolbar-controls to match the heading in configuration.md --- docs/news/whats_new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/news/whats_new.md b/docs/news/whats_new.md index 27dc4f3..2e96ab1 100644 --- a/docs/news/whats_new.md +++ b/docs/news/whats_new.md @@ -83,7 +83,7 @@ API of v1.2 is not compatible with v2.0. Refer to the [**migration guide**](news - **Granular toolbar configuration** Take full control of the toolbar: - Define [individual toolbar controls](guides/configuration.md/#default-toolbar-controls) and their order - - Add [custom controls](guides/configuration.md/#custom-toolbar-controls) + - Add [custom controls](guides/configuration.md/#add-custom-toolbar-controls) - **Optional [menubar](api/config/menubar.md)** Enable a classic menu-style interface on the top of the editor From f888bf746732d328ae42c607452253ff2d36a479 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Wed, 27 May 2026 09:19:19 +0300 Subject: [PATCH 20/25] [update] register trigger pages in sidebar - add hide-suggest, insert-token, show-suggest under events - add triggers and trigger-template under properties - add mentions_and_tags under guides --- sidebars.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sidebars.js b/sidebars.js index 4be1cb8..683ed3d 100644 --- a/sidebars.js +++ b/sidebars.js @@ -117,12 +117,14 @@ module.exports = { "api/events/cut", "api/events/delete-link", "api/events/export", + "api/events/hide-suggest", "api/events/import", "api/events/indent", "api/events/insert-image", "api/events/insert-line", "api/events/insert-link", "api/events/insert-list", + "api/events/insert-token", "api/events/outdent", "api/events/paste", "api/events/print", @@ -135,6 +137,7 @@ module.exports = { "api/events/set-text-format", "api/events/set-text-style", "api/events/show-popup", + "api/events/show-suggest", "api/events/subscript", "api/events/superscript", "api/events/toggle-fullscreen-mode", @@ -162,7 +165,9 @@ module.exports = { "api/config/locale", "api/config/menubar", "api/config/toolbar", - "api/config/value" + "api/config/trigger-template", + "api/config/triggers", + "api/config/value" ] } ] @@ -198,6 +203,7 @@ module.exports = { items: [ "guides/initialization", "guides/configuration", + "guides/mentions_and_tags", "guides/localization", "guides/stylization", "guides/typescript_support" From b77eef15b55a7da8750111b77188950195c285c1 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Wed, 3 Jun 2026 18:27:15 +0300 Subject: [PATCH 21/25] [update] document trigger action callback - add `action` field to triggers config with Custom action section (emoji insertion and slash-style command menu examples) - update insert-token event payload: id/label/url replaced by `data` object carrying the picked item plus custom fields; add `action` - note that `action` takes priority over `showTrigger` - show-suggest items now documented as optional fields + custom keys - extend mentions guide with custom action examples - fix typos, a broken anchor, and align wording across pages --- docs/api/config/triggers.md | 79 +++++++++++++++++++++++---- docs/api/events/insert-token.md | 30 ++++++----- docs/api/events/show-suggest.md | 18 +++++-- docs/guides/mentions_and_tags.md | 93 +++++++++++++++++++++++++++++--- 4 files changed, 184 insertions(+), 36 deletions(-) diff --git a/docs/api/config/triggers.md b/docs/api/config/triggers.md index 0ef565e..69fdcf0 100644 --- a/docs/api/config/triggers.md +++ b/docs/api/config/triggers.md @@ -10,7 +10,7 @@ description: You can learn about the triggers config in the documentation of the @short: Optional. Defines dropdown triggers for inserting mentions, tags, and other tokens -When the user types a configured character (for example, `@` or `#`), RichText opens a suggestion dropdown. When the user selects an item, RichText inserts it into the document as a non-editable token (``). +When a user types a configured character (for example, `@` or `#`), RichText opens a dropdown with predefined items. When the user selects an item, RichText inserts it into the document as a non-editable token (``). ### Usage @@ -21,7 +21,8 @@ triggers?: Array<{ | ((query: string) => Array<{ id?: string; label?: string; url?: string }> | Promise>), - showTrigger?: boolean + showTrigger?: boolean, + action?: (item) => void }>; ~~~ @@ -31,11 +32,12 @@ Each entry of the `triggers` array accepts the following fields: - `trigger` - (required) the character that opens the suggestion dropdown (for example, `"@"`, `"#"`, `"/"`, `"$"`) - `data` - (required) the data source for the dropdown; can be an array, a sync function, or an async function. See [Data source forms](#data-source-forms) -- `showTrigger` - (optional) when `true` (default), RichText keeps the trigger character in the inserted token (`@Alice`); when `false`, RichText inserts only `label` (`Alice`) +- `showTrigger` - (optional) when `true` (default), RichText keeps the trigger character in the inserted token (for example, `@Alice`); when `false`, RichText inserts only `label` (for example, `Alice`) +- `action` - (optional) a custom callback called when a user selects an item. When set, RichText removes the typed trigger text (the trigger character plus the query) and calls `action(item)` **instead of** inserting a token. The callback receives the picked item and can insert any content instead of selected one. The `action` parameter takes priority over `showTrigger`, which has no effect when `action` is set. See [Custom action](#custom-action) ### Data source forms -**Static array** — RichText filters the array automatically by matching the query against `label` (case-insensitive, `startsWith`): +* **Static array** — RichText filters the array automatically by matching the query against `label` (case-insensitive, `startsWith`): ~~~jsx {3-7} new richtext.Richtext("#root", { @@ -49,7 +51,7 @@ new richtext.Richtext("#root", { }); ~~~ -**Sync function** — RichText calls your function with the current `query` string; you do the filtering and return the matching array: +* **Sync function** — RichText calls your function with the current `query` string; you do the filtering and return the matching array: ~~~jsx {3-6} new richtext.Richtext("#root", { @@ -62,7 +64,7 @@ new richtext.Richtext("#root", { }); ~~~ -**Async function** — RichText calls your function with the current `query` string; return a `Promise` that resolves to the matching array. Useful for server-side search: +* **Async function** — RichText calls your function with the current `query` string; return a `Promise` that resolves to the matching array. Useful for server-side search: ~~~jsx {3-8} new richtext.Richtext("#root", { @@ -79,15 +81,17 @@ new richtext.Richtext("#root", { ### Suggestion item fields -Each item in `data` (or each item returned by a function) has the following fields: +Each item in the `data` object (or each item returned by a function) has the following fields: -- `id` - (optional) unique identifier saved on the inserted token. If omitted, RichText generates an ID automatically. +- `id` - (optional) unique identifier saved on the inserted token. If omitted, RichText generates an ID automatically - `label` - (required) the text shown in the dropdown and inserted into the document -- `url` - (optional) URL associated with the item. RichText stores it on the inserted token as the `href` attribute. +- `url` - (optional) URL associated with the item. RichText stores the inserted token URL as the `href` attribute. + +An item may also include any number of custom fields beyond `id`, `label`, and `url` (for example, `code` for an emoji, or `image` and `name` for an avatar). These extra fields are passed through to the [`triggerTemplate`](api/config/trigger-template.md) callback and to the `action` callback. ### Rendered token -When a user selects an item, RichText inserts a non-editable token element into the document: +When a user selects an item in the dropdown, RichText inserts a non-editable token element into the document: ~~~html {} @Alice @@ -102,9 +106,62 @@ Use the `data-token` and `data-token-id` attributes to target tokens with CSS, f } ~~~ +### Custom action + +By default, when a user picks an item, RichText inserts it into the document as a token. Set the `action` parameter to run your own code instead: RichText removes the typed trigger string (the trigger character and the query) and calls the `action(item)` callback with the picked item. No token is inserted, so you can decide what to add to the document. The `action` parameter takes priority over `showTrigger`. When `action` is set, `showTrigger` is ignored. + +#### Add emoji + +A common use case is inserting an emoji from a `:` trigger, where each item contains a custom `code` field. Pair `action` with [`triggerTemplate`](api/config/trigger-template.md) so the dropdown shows the emoji itself instead of just its label: + +~~~jsx {8,12} +const { template } = richtext; + +const editor = new richtext.Richtext("#root", { + triggers: [ + { + trigger: ":", + data: emoji, // [{ id: "apple", label: "apple", code: "1F34E" }, ...] + action: item => editor.insertValue(`${emojiFromCode(item.code)} `) + } + ], + // render the emoji itself (not just its label) in the dropdown + triggerTemplate: template(({ data }) => `${emojiFromCode(data.code)} ${data.label}`) +}); + +function emojiFromCode(code) { + return String.fromCodePoint(parseInt(code, 16)); +} +~~~ + +#### Add slash-style command menu + +You can use `action` to build a slash-style command menu (like `/` in Notion or Slack). Store a command name in each item's `id`, its options in a custom `config` field, and let the callback run it with [`api.exec`](api/internal/exec.md): + +~~~jsx {13} +// each item stores an api.exec action name in `id` and its parameters in `config` +const commands = [ + { id: "set-text-style", label: "Heading 1", config: { tag: "h1" } }, + { id: "insert-list", label: "Bulleted list", config: { type: "bulleted" } }, + { id: "insert-line", label: "Divider" } // no config → `|| {}` applies +]; + +const editor = new richtext.Richtext("#root", { + triggers: [ + { + trigger: "/", + data: commands, + action: item => editor.api.exec(item.id, item.config || {}) + } + ] +}); +~~~ + ### Example -~~~jsx {2-13} +The following example sets up two triggers: `@` for mentions (each item carries a `url` that becomes the token's `href`) and `#` for tags (label only): + +~~~jsx {4,11} new richtext.Richtext("#root", { triggers: [ { diff --git a/docs/api/events/insert-token.md b/docs/api/events/insert-token.md index 1632a65..dea71c5 100644 --- a/docs/api/events/insert-token.md +++ b/docs/api/events/insert-token.md @@ -10,17 +10,21 @@ description: You can learn about the insert-token event in the documentation of @short: Fires after the user picks a suggestion item and RichText inserts it as a token -The event fires after the user picks an item from a trigger dropdown (mentions, tags, or any custom trigger you set up through the [`triggers`](api/config/triggers.md) property). +The `insert-token` event fires after the user picks an item from a trigger dropdown (mentions, tags, or any custom trigger you set up through the [`triggers`](api/config/triggers.md) property). ### Usage ~~~jsx {} "insert-token": ({ - id: string, - label: string, - url: string, + data: { + id?: string | number, + label?: string, + url?: string, + // ...any custom fields from the trigger's data source + }, trigger: string, - showTrigger?: boolean + showTrigger?: boolean, + action?: (item) => void }) => boolean | void; ~~~ @@ -28,11 +32,10 @@ The event fires after the user picks an item from a trigger dropdown (mentions, The callback of the `insert-token` event receives an object with the following fields: -- `id` - the id of the picked suggestion item -- `label` - the label of the picked item (the text inserted into the document) -- `url` - the url associated with the item (becomes the `href` attribute of the inserted token) +- `data` - the picked suggestion item. Contains the `id`, `label`, and `url` of the item, as well as any custom fields you added to the trigger's `data` source - `trigger` - the trigger character that opened the dropdown (for example, `"@"` or `"#"`) -- `showTrigger` - when `false`, RichText inserts only `label`; otherwise it also shows the trigger character (default) +- `showTrigger` - when `false`, RichText inserts only `label`; otherwise the widget also shows the trigger character (default) +- `action` - a custom action defined for the matching [trigger](api/config/triggers.md). When set, the parameter takes priority over token insertion: RichText removes the typed text (the trigger character and the query) and calls `action(data)` instead of inserting a token. The `showTrigger` parameter has no effect in this case :::info To handle internal events, use [**Event Bus methods**](api/overview/event_bus_methods_overview.md). @@ -40,16 +43,17 @@ To handle internal events, use [**Event Bus methods**](api/overview/event_bus_me ### Example -~~~jsx {5-9} +~~~jsx {5-8} // initialize RichText const editor = new richtext.Richtext("#root", { // configuration properties }); // subscribe to the "insert-token" event -editor.api.on("insert-token", (obj) => { - console.log(obj); - console.log(`Inserted ${obj.trigger}${obj.label} (id: ${obj.id})`); +editor.api.on("insert-token", ({ data, trigger, showTrigger }) => { + console.log(`Inserted ${trigger}${data.label} (id: ${data.id})`); }); ~~~ **Change log:** The event was added in v2.1 + +**Related articles:** [Mentions and tags](guides/mentions_and_tags.md) diff --git a/docs/api/events/show-suggest.md b/docs/api/events/show-suggest.md index f191ba0..666bc8b 100644 --- a/docs/api/events/show-suggest.md +++ b/docs/api/events/show-suggest.md @@ -10,7 +10,7 @@ description: You can learn about the show-suggest event in the documentation of @short: Fires when the suggestion dropdown opens for a configured trigger -The event fires after RichText resolves a non-empty list of items for the current trigger, just before the dropdown opens. Intercept the event to adjust the items, move the dropdown, or cancel it. +The `show-suggest` event fires after RichText resolves a non-empty list of items for the current trigger, just before the dropdown opens. Intercept the event to adjust the items, move the dropdown, or cancel it. ### Usage @@ -18,7 +18,12 @@ The event fires after RichText resolves a non-empty list of items for the curren "show-suggest": ({ trigger: string, query: string, - items: Array<{ id: string, label: string, url: string }>, + items: Array<{ + id?: string | number, + label?: string, + url?: string, + // ...any custom fields from the trigger's data source + }>, pos: DOMRect }) => boolean | void; ~~~ @@ -29,7 +34,7 @@ The callback of the `show-suggest` event receives an object with the following f - `trigger` - the trigger character that opened the dropdown - `query` - the text typed after the trigger character (used to filter `items`) -- `items` - the resolved (and already filtered) list of suggestion items +- `items` - the resolved (and already filtered) list of suggestion items. Each item follows the [suggestion item shape](api/config/triggers.md#suggestion-item-fields): optional `id`, `label`, and `url`, plus any custom fields (such as `image` or `name`) used by [`triggerTemplate`](api/config/trigger-template.md) - `pos` - a `DOMRect` describing the cursor position; used to place the dropdown on screen :::info @@ -38,10 +43,11 @@ To handle internal events, use [**Event Bus methods**](api/overview/event_bus_me ### Example -~~~jsx {5-10} +~~~jsx {6-11} // initialize RichText const editor = new richtext.Richtext("#root", { - // configuration properties + triggers: [{ trigger: "@", data: people }] + // other configuration properties }); // override the suggestion list before the dropdown opens editor.api.intercept("show-suggest", (state) => { @@ -52,3 +58,5 @@ editor.api.intercept("show-suggest", (state) => { ~~~ **Change log:** The event was added in v2.1 + +**Related articles:** [Mentions and tags](guides/mentions_and_tags.md) diff --git a/docs/guides/mentions_and_tags.md b/docs/guides/mentions_and_tags.md index c2494e0..8611fd1 100644 --- a/docs/guides/mentions_and_tags.md +++ b/docs/guides/mentions_and_tags.md @@ -6,18 +6,19 @@ description: 'Learn how to configure @mentions, #tags, and custom dropdown trigg # Mentions and tags -RichText supports user-defined trigger characters that open a suggestion dropdown inside the editor. When the user picks an item, RichText inserts a non-editable token into the document. Typical use cases: +RichText supports user-defined trigger characters that open a suggestion dropdown inside the document. When the user picks an item, RichText inserts a non-editable token into the document. Typical use cases: - `@` — mention a person - `#` — apply a tag - `/` — insert a command or template - `$` — insert a financial ticker or variable +- `:` - insert an emoji -Configure the behavior through the [`triggers`](api/config/triggers.md) array. Each entry binds one character to a data source. +Configure the behavior through the [`triggers`](api/config/triggers.md) property. Each entry binds one character to a data source. ## Configure triggers -Each trigger is an object `{ trigger, data, showTrigger? }` within the [`triggers`](api/config/triggers.md) array. The [`data`](api/config/triggers.md/#data-source-forms) field can take three forms: +Each trigger is an object `{ trigger, data, showTrigger?, action? }` within the [`triggers`](api/config/triggers.md) array. The [`data`](api/config/triggers.md#data-source-forms) field can take three forms: - A static array — RichText filters it automatically by `label` (case-insensitive, `startsWith`): @@ -55,7 +56,7 @@ Each trigger is an object `{ trigger, data, showTrigger? }` within the [`trigger ## Token rendering -When the user picks an item, RichText inserts it as an `` element with two data attributes: +When the user picks an item from the dropdown, RichText inserts it as an `` element with two data attributes: ~~~html {2-3} { - console.log(`Inserted ${trigger}${label} (id: ${id})`); +editor.api.on("insert-token", ({ trigger, data }) => { + console.log(`Inserted ${trigger}${data.label} (id: ${data.id})`); }); ~~~ ## Customize the dropdown item -By default the dropdown shows the `label` of each item. To render custom suggestions (for example, an avatar plus a name and an email) pass a template via the [`triggerTemplate`](api/config/trigger-template.md) property. +By default the dropdown shows the `label` of each item. To render custom suggestions (for example, avatar, name and email) pass a template via the [`triggerTemplate`](api/config/trigger-template.md) property. ### Example @@ -135,6 +136,84 @@ new richtext.Richtext("#root", { }); ~~~ +## Custom action on select + +By default, picking an item inserts it into the document as a token. To run your own code instead, add an `action` callback to the trigger. RichText removes the typed trigger text and calls `action(item)` with the picked item — no token is inserted, so you can decide what to add. + +:::note + `action` takes priority over `showTrigger`. When `action` is set, `showTrigger` is ignored. +::: + +### Add emoji + +A `:` trigger can insert an emoji, where each item includes a custom `code` field. Pair `action` with [`triggerTemplate`](api/config/trigger-template.md) so the dropdown shows the emoji instead of just its label: + +~~~jsx {8,12} +const { template } = richtext; + +const editor = new richtext.Richtext("#root", { + triggers: [ + { + trigger: ":", + data: emoji, // [{ id: "apple", label: "apple", code: "1F34E" }, ...] + action: item => editor.insertValue(`${emojiFromCode(item.code)} `) + } + ], + // render the emoji itself (not just its label) in the dropdown + triggerTemplate: template(({ data }) => `${emojiFromCode(data.code)} ${data.label}`) +}); + +function emojiFromCode(code) { + return String.fromCodePoint(parseInt(code, 16)); +} + +const emoji = [ + { + id: "apple", + label: "apple", + code: "1F34E", + }, + { + id: "blue_car", + label: "blue_car", + code: "1F699", + }, + { + id: "computer", + label: "computer", + code: "1F4BB", + }, + { + id: "dvd", + label: "dvd", + code: "1F4C0", + }, +]; +~~~ + +### Add slash-style command menu + +You can use `action` to build a slash-style command menu (like `/` in Notion or Slack). Store a command name in each item's `id`, its options in a custom `config` field, and let the callback run it with [`api.exec`](api/internal/exec.md): + +~~~jsx {13} +// each item stores an api.exec action name in `id` and its parameters in `config` +const commands = [ + { id: "set-text-style", label: "Heading 1", config: { tag: "h1" } }, + { id: "insert-list", label: "Bulleted list", config: { type: "bulleted" } }, + { id: "insert-line", label: "Divider" } // no config → `|| {}` applies +]; + +const editor = new richtext.Richtext("#root", { + triggers: [ + { + trigger: "/", + data: commands, + action: item => editor.api.exec(item.id, item.config || {}) + } + ] +}); +~~~ + ## Related API - [`triggers`](api/config/triggers.md) From 2f2a6b74354e701ea9dea6423dc2408a0afe06bb Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Wed, 3 Jun 2026 18:41:14 +0300 Subject: [PATCH 22/25] [update] align insert-token handler in guide - destructure ({ data, trigger, showTrigger }) to match the insert-token event docs and the PR review suggestion --- docs/guides/mentions_and_tags.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/mentions_and_tags.md b/docs/guides/mentions_and_tags.md index 8611fd1..f2b0b52 100644 --- a/docs/guides/mentions_and_tags.md +++ b/docs/guides/mentions_and_tags.md @@ -111,7 +111,7 @@ const editor = new richtext.Richtext("#root", { triggers: [{ trigger: "@", data: people }] }); -editor.api.on("insert-token", ({ trigger, data }) => { +editor.api.on("insert-token", ({ data, trigger, showTrigger }) => { console.log(`Inserted ${trigger}${data.label} (id: ${data.id})`); }); ~~~ From 5ae4c900053805cda505ce3313e259d28e9cc7c5 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Wed, 3 Jun 2026 22:34:01 +0300 Subject: [PATCH 23/25] [update] address review on triggers docs - mark suggestion `label` as optional (custom triggerTemplate case) - note Ctrl+Click opens token url, explain mailto behavior - break down rendered token attributes (trigger/id/url/label) - clarify custom action can run code without inserting content - add "Group emoji by category" example with custom filtering, category headers and api.intercept guard - triggerTemplate: add second trigger so trigger check is meaningful, replace !important width tip with more specific selector --- docs/api/config/trigger-template.md | 20 ++++--- docs/api/config/triggers.md | 66 ++++++++++++++++++++--- docs/guides/mentions_and_tags.md | 84 +++++++++++++++++++++-------- 3 files changed, 134 insertions(+), 36 deletions(-) diff --git a/docs/api/config/trigger-template.md b/docs/api/config/trigger-template.md index 0ade570..574a437 100644 --- a/docs/api/config/trigger-template.md +++ b/docs/api/config/trigger-template.md @@ -28,22 +28,27 @@ The callback function takes an object with the following parameters: - `trigger` - the trigger character that opened the dropdown (`"@"`, `"#"`, etc.) :::tip -The dropdown anchor has a fixed default width of `160px`. To make room for a wider template, override the width from a parent stylesheet (`!important` is needed because the widget sets the default via its own scoped CSS): +The dropdown default width is `160px`. If you need more space for your template, add the `.wx-editor` parent in front of the selector: ~~~css {} -.wx-suggest-anchor { - width: 220px !important; +.wx-editor .wx-suggest-anchor { + width: 220px; } ~~~ ::: ### Example -~~~jsx {1,4-9} -const { template } = richtext; +The following code snippet configures two triggers: `@` for mentions and `#` for tags. Use `triggerTemplate` to expand the `trigger` value to render each dropdown differently. For the `@` dropdown the template returns a custom HTML row with an avatar (`data.image`), a nickname (`data.label`), and a full name (`data.name`). For the `#` trigger the template use the `label`: -new richtext.Richtext("#root", { - triggers: [{ trigger: "@", data: people }], +~~~jsx {5-6,8-15} +const { template, Richtext } = richtext; + +new Richtext("#root", { + triggers: [ + { trigger: "@", data: people }, + { trigger: "#", data: tags } + ], triggerTemplate: template(obj => { if (obj.trigger === "@") { return `
@@ -52,6 +57,7 @@ new richtext.Richtext("#root", {
${obj.data.name}
`; } + // other triggers (for example, "#") use the plain label return obj.data.label; }) }); diff --git a/docs/api/config/triggers.md b/docs/api/config/triggers.md index 69fdcf0..3f916d5 100644 --- a/docs/api/config/triggers.md +++ b/docs/api/config/triggers.md @@ -81,11 +81,11 @@ new richtext.Richtext("#root", { ### Suggestion item fields -Each item in the `data` object (or each item returned by a function) has the following fields: +Each item in `data` (or each item returned by a function) has the following fields: - `id` - (optional) unique identifier saved on the inserted token. If omitted, RichText generates an ID automatically -- `label` - (required) the text shown in the dropdown and inserted into the document -- `url` - (optional) URL associated with the item. RichText stores the inserted token URL as the `href` attribute. +- `label` - (optional) the text shown in the dropdown and inserted into the document. Required only for the default rendering; with a custom [`triggerTemplate`](api/config/trigger-template.md) you can render items from other fields (for example, `template(({ data }) => data.id)`) and omit `label` +- `url` - (optional) URL associated with the item. RichText stores the URL as the inserted token's `href` attribute. `Ctrl+Click` on the token opens the link An item may also include any number of custom fields beyond `id`, `label`, and `url` (for example, `code` for an emoji, or `image` and `name` for an avatar). These extra fields are passed through to the [`triggerTemplate`](api/config/trigger-template.md) callback and to the `action` callback. @@ -97,6 +97,11 @@ When a user selects an item in the dropdown, RichText inserts a non-editable tok
@Alice ~~~ +- `@` (in `data-token="@"`) - the item's `trigger` +- `alice` (in `data-token-id="alice"`) - the item's `id` +- `mailto:alice@example.com` (in `href="mailto:alice@example.com"`) - the item's `url` +- `@Alice` - the combination of `trigger` and `label`; with `showTrigger: false` it would be just `Alice` + Use the `data-token` and `data-token-id` attributes to target tokens with CSS, for example, to highlight all mentions of a user: ~~~css {} @@ -108,16 +113,16 @@ Use the `data-token` and `data-token-id` attributes to target tokens with CSS, f ### Custom action -By default, when a user picks an item, RichText inserts it into the document as a token. Set the `action` parameter to run your own code instead: RichText removes the typed trigger string (the trigger character and the query) and calls the `action(item)` callback with the picked item. No token is inserted, so you can decide what to add to the document. The `action` parameter takes priority over `showTrigger`. When `action` is set, `showTrigger` is ignored. +By default, when a user picks an item, RichText inserts the item into the document as a token. Set the `action` parameter to run your code instead: RichText removes the typed trigger string (the trigger character and the query) and calls the `action(item)` callback with the picked item. No token is inserted, so you can decide what to add to the document (or run your custom code). The `action` parameter takes priority over `showTrigger`. When `action` is set, `showTrigger` is ignored. #### Add emoji A common use case is inserting an emoji from a `:` trigger, where each item contains a custom `code` field. Pair `action` with [`triggerTemplate`](api/config/trigger-template.md) so the dropdown shows the emoji itself instead of just its label: ~~~jsx {8,12} -const { template } = richtext; +const { template, Richtext } = richtext; -const editor = new richtext.Richtext("#root", { +const editor = new Richtext("#root", { triggers: [ { trigger: ":", @@ -134,6 +139,55 @@ function emojiFromCode(code) { } ~~~ +#### Group emoji by categories + +When the `data` parameter is a function, you are not limited to the built-in `label` matching. You can run your own filtering and keep category headers in the dropdown. Add header items that include a `label` field and do not include `code`. The `data` function first finds the emoji that match the query, then returns emoji together with the headers of the categories that still have matches: + +~~~jsx {18-26,31-33,41} +const { template, Richtext } = richtext; + +// header items carry no `code` field; emoji items include one +const emoji = [ + { id: "$smileys", label: "Smileys", category: 1 }, // category + { id: "grinning", label: "grinning", code: "1F600", category: 1 }, + { id: "smile", label: "smile", code: "1F604", category: 1 }, + { id: "$animals", label: "Animals", category: 2 }, // category + { id: "dog", label: "dog", code: "1F436", category: 2 }, + { id: "cat", label: "cat", code: "1F431", category: 2 } +]; + +const editor = new Richtext("#root", { + triggers: [ + { + trigger: ":", + data: query => { + const matched = emoji.filter(item => + item.code && + item.label.toLowerCase().startsWith(query.toLowerCase().trim()) + ); + const categories = new Set(matched.map(item => item.category)); + // keep matching emoji plus the headers of categories that still match + return emoji.filter(item => + item.code ? matched.includes(item) : categories.has(item.category) + ); + }, + action: item => editor.insertValue(`${emojiFromCode(item.code)} `) + } + ], + // render emoji rows normally and category headers in bold + triggerTemplate: template(({ data }) => + data.code ? `${emojiFromCode(data.code)} ${data.label}` : `${data.label}` + ) +}); + +function emojiFromCode(code) { + return String.fromCodePoint(parseInt(code, 16)); +} + +// headers have no `code` — ignore picks on them so they are never inserted +editor.api.intercept("insert-token", ({ data }) => !!data.code); +~~~ + #### Add slash-style command menu You can use `action` to build a slash-style command menu (like `/` in Notion or Slack). Store a command name in each item's `id`, its options in a custom `config` field, and let the callback run it with [`api.exec`](api/internal/exec.md): diff --git a/docs/guides/mentions_and_tags.md b/docs/guides/mentions_and_tags.md index f2b0b52..d6a449b 100644 --- a/docs/guides/mentions_and_tags.md +++ b/docs/guides/mentions_and_tags.md @@ -148,10 +148,22 @@ By default, picking an item inserts it into the document as a token. To run your A `:` trigger can insert an emoji, where each item includes a custom `code` field. Pair `action` with [`triggerTemplate`](api/config/trigger-template.md) so the dropdown shows the emoji instead of just its label: -~~~jsx {8,12} -const { template } = richtext; +~~~jsx {18-20,24} +const { template, Richtext } = richtext; -const editor = new richtext.Richtext("#root", { +const emoji = [ + { + id: "apple", label: "apple", code: "1F34E" + }, + { + id: "blue_car", label: "blue_car", code: "1F699" + }, + { + id: "computer", label: "computer", code: "1F4BB" + } +]; + +const editor = new Richtext("#root", { triggers: [ { trigger: ":", @@ -166,29 +178,55 @@ const editor = new richtext.Richtext("#root", { function emojiFromCode(code) { return String.fromCodePoint(parseInt(code, 16)); } +~~~ + +### Group emoji by categories + +When the `data` parameter is a function, you are not limited to the built-in `label` matching. You can run your own filtering and keep category headers in the dropdown. Add header items that include a `label` field and do not include `code`. The `data` function first finds the emoji that match the query, then returns emoji together with the headers of the categories that still have matches: + +~~~jsx {17-26,31-33} +const { template, Richtext } = richtext; +// header items carry no `code` field; emoji items include one const emoji = [ - { - id: "apple", - label: "apple", - code: "1F34E", - }, - { - id: "blue_car", - label: "blue_car", - code: "1F699", - }, - { - id: "computer", - label: "computer", - code: "1F4BB", - }, - { - id: "dvd", - label: "dvd", - code: "1F4C0", - }, + { id: "$smileys", label: "Smileys", category: 1 }, // category + { id: "grinning", label: "grinning", code: "1F600", category: 1 }, + { id: "smile", label: "smile", code: "1F604", category: 1 }, + { id: "$animals", label: "Animals", category: 2 }, // category + { id: "dog", label: "dog", code: "1F436", category: 2 }, + { id: "cat", label: "cat", code: "1F431", category: 2 } ]; + +const editor = new Richtext("#root", { + triggers: [ + { + trigger: ":", + data: query => { + const matched = emoji.filter(item => + item.code && + item.label.toLowerCase().startsWith(query.toLowerCase().trim()) + ); + const categories = new Set(matched.map(item => item.category)); + // keep matching emoji plus the headers of categories that still match + return emoji.filter(item => + item.code ? matched.includes(item) : categories.has(item.category) + ); + }, + action: item => editor.insertValue(`${emojiFromCode(item.code)} `) + } + ], + // render emoji rows normally and category headers in bold + triggerTemplate: template(({ data }) => + data.code ? `${emojiFromCode(data.code)} ${data.label}` : `${data.label}` + ) +}); + +function emojiFromCode(code) { + return String.fromCodePoint(parseInt(code, 16)); +} + +// headers have no `code` — ignore picks on them so they are never inserted +editor.api.intercept("insert-token", ({ data }) => !!data.code); ~~~ ### Add slash-style command menu From cf2c7f678966b16566f8242978742d99f9944e7a Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Thu, 4 Jun 2026 20:29:09 +0300 Subject: [PATCH 24/25] [fix] resolve index.md conflict with base, fix Markdown example - merge the Copy and paste section from the base branch with the new Markdown format section - pull in the expanded image uploading feature line from base - fix misleading example: Markdown cannot be passed via the value config (always parsed as HTML), use setValue with the markdown encoder instead --- docs/index.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 6883635..f624c15 100644 --- a/docs/index.md +++ b/docs/index.md @@ -19,7 +19,7 @@ description: You can have an overview of DHTMLX JavaScript RichText library in t - Static [**menubar**](api/config/menubar.md) that can be shown or hidden -- Image uploading, rich formatting, custom styling, and full screen mode +- Image uploading with optional server [upload](api/config/image-upload-url.md) or inline base64 embedding, rich formatting, custom styling, and full screen mode - [Full API access](api/overview/main_overview.md) for [event handling](api/overview/event_bus_methods_overview.md), [content manipulation](api/overview/methods_overview.md), and [reactive state management](api/overview/state_methods_overview.md) @@ -91,7 +91,8 @@ Pass the built-in `markdown` encoders to [`setValue()`](api/methods/set-value.md ~~~jsx const editor = new richtext.Richtext("#root", { - value: "# Hello\n\nSome **bold** text", + value: "Hello world" + // other configuration properties }); // load Markdown into the editor @@ -107,6 +108,24 @@ Markdown support covers a limited subset of the syntax — common block and inli Nested inline structures are not supported, with the only exception of **bold inside italic**. Combinations such as bold inside a link, italic inside a list item, or multi-level (nested) lists will not render correctly. ::: +## Copy and paste + +The RichText editor supports clipboard operations through standard system shortcuts (`Ctrl+C` / `Ctrl+X` / `Ctrl+V` on Windows/Linux, `⌘+C` / `⌘+X` / `⌘+V` on macOS), the corresponding [toolbar](api/config/toolbar.md) buttons, and the [menubar](api/config/menubar.md) entries. + +When content is copied or cut, RichText writes two representations to the system clipboard: + +- a **plain text** version for compatibility with simple targets (terminals, code editors, plain inputs) +- an **HTML** version that carries all inline and block formatting (bold, italic, underline, strikethrough, font family, font size, text and background color, headings, blockquotes, lists, alignment, indentation, line height, links, and images) + +Paste behavior depends on the source of the clipboard payload: + +- Pasting between two RichText instances (in the same document or on different pages) uses the HTML representation and preserves the original formatting. +- Pasting from any external source — including browsers, word processors, and other editors — is processed as plain text. The inserted content is added as text without external formatting. + +:::note +The toolbar **Paste** button uses the asynchronous Clipboard API, which exposes plain text only. To paste content copied from another RichText with its formatting preserved, use the `Ctrl+V` / `⌘+V` shortcut, which receives the full HTML payload directly from the browser's clipboard event. +::: + ## Keyboard shortcuts The RichText editor supports a set of common keyboard shortcuts for faster formatting and editing. The shortcuts follow platform conventions and are available on both **Windows/Linux** (`Ctrl`) and **macOS** (`⌘`). From 6ef88360765f5d13115476a38253e1ead46fbc58 Mon Sep 17 00:00:00 2001 From: Serhii Pylypchuk Date: Fri, 5 Jun 2026 13:59:20 +0300 Subject: [PATCH 25/25] [fix] correct typos and wording in v2.1 docs - trigger-template: "the template use" -> "uses" - triggers: "instead of selected one" -> "instead of the selected one" - mentions_and_tags: align dash in trigger list, drop stray space in note - set-value: encoder describes decoding the value from a format (consistent with insertValue) --- docs/api/config/trigger-template.md | 2 +- docs/api/config/triggers.md | 2 +- docs/api/methods/set-value.md | 6 +++--- docs/guides/mentions_and_tags.md | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/api/config/trigger-template.md b/docs/api/config/trigger-template.md index 574a437..aed2bb4 100644 --- a/docs/api/config/trigger-template.md +++ b/docs/api/config/trigger-template.md @@ -39,7 +39,7 @@ The dropdown default width is `160px`. If you need more space for your template, ### Example -The following code snippet configures two triggers: `@` for mentions and `#` for tags. Use `triggerTemplate` to expand the `trigger` value to render each dropdown differently. For the `@` dropdown the template returns a custom HTML row with an avatar (`data.image`), a nickname (`data.label`), and a full name (`data.name`). For the `#` trigger the template use the `label`: +The following code snippet configures two triggers: `@` for mentions and `#` for tags. Use `triggerTemplate` to expand the `trigger` value to render each dropdown differently. For the `@` dropdown the template returns a custom HTML row with an avatar (`data.image`), a nickname (`data.label`), and a full name (`data.name`). For the `#` trigger the template uses the `label`: ~~~jsx {5-6,8-15} const { template, Richtext } = richtext; diff --git a/docs/api/config/triggers.md b/docs/api/config/triggers.md index 3f916d5..e679aa2 100644 --- a/docs/api/config/triggers.md +++ b/docs/api/config/triggers.md @@ -33,7 +33,7 @@ Each entry of the `triggers` array accepts the following fields: - `trigger` - (required) the character that opens the suggestion dropdown (for example, `"@"`, `"#"`, `"/"`, `"$"`) - `data` - (required) the data source for the dropdown; can be an array, a sync function, or an async function. See [Data source forms](#data-source-forms) - `showTrigger` - (optional) when `true` (default), RichText keeps the trigger character in the inserted token (for example, `@Alice`); when `false`, RichText inserts only `label` (for example, `Alice`) -- `action` - (optional) a custom callback called when a user selects an item. When set, RichText removes the typed trigger text (the trigger character plus the query) and calls `action(item)` **instead of** inserting a token. The callback receives the picked item and can insert any content instead of selected one. The `action` parameter takes priority over `showTrigger`, which has no effect when `action` is set. See [Custom action](#custom-action) +- `action` - (optional) a custom callback called when a user selects an item. When set, RichText removes the typed trigger text (the trigger character plus the query) and calls `action(item)` **instead of** inserting a token. The callback receives the picked item and can insert any content instead of the selected one. The `action` parameter takes priority over `showTrigger`, which has no effect when `action` is set. See [Custom action](#custom-action) ### Data source forms diff --git a/docs/api/methods/set-value.md b/docs/api/methods/set-value.md index 29ff858..bb80841 100644 --- a/docs/api/methods/set-value.md +++ b/docs/api/methods/set-value.md @@ -19,13 +19,13 @@ setValue: (value: string, encoder?: any): void; ### Parameters - `value` - (required) a value to be inserted into the RichText -- `encoder` - (optional) a custom parser used to encode the RichText's content into a custom format. The following formats are available: `html` (default), `text`, and `markdown` +- `encoder` - (optional) a custom parser used to decode the value from a custom format. The following formats are available: `html` (default), `text`, and `markdown` You can get the required encoder in the following way: ```jsx -const fromTextEncoder = richtext.text.fromText; // text encoder -const fromHTMLEncoder = richtext.html.fromHTML; // html encoder +const fromTextEncoder = richtext.text.fromText; // text encoder +const fromHTMLEncoder = richtext.html.fromHTML; // html encoder const fromMarkdownEncoder = richtext.markdown.fromMarkdown; // markdown encoder ``` diff --git a/docs/guides/mentions_and_tags.md b/docs/guides/mentions_and_tags.md index d6a449b..b0a95d2 100644 --- a/docs/guides/mentions_and_tags.md +++ b/docs/guides/mentions_and_tags.md @@ -12,7 +12,7 @@ RichText supports user-defined trigger characters that open a suggestion dropdow - `#` — apply a tag - `/` — insert a command or template - `$` — insert a financial ticker or variable -- `:` - insert an emoji +- `:` — insert an emoji Configure the behavior through the [`triggers`](api/config/triggers.md) property. Each entry binds one character to a data source. @@ -141,7 +141,7 @@ new richtext.Richtext("#root", { By default, picking an item inserts it into the document as a token. To run your own code instead, add an `action` callback to the trigger. RichText removes the typed trigger text and calls `action(item)` with the picked item — no token is inserted, so you can decide what to add. :::note - `action` takes priority over `showTrigger`. When `action` is set, `showTrigger` is ignored. +`action` takes priority over `showTrigger`. When `action` is set, `showTrigger` is ignored. ::: ### Add emoji