diff --git a/docs/api/config/trigger-template.md b/docs/api/config/trigger-template.md new file mode 100644 index 0000000..574a437 --- /dev/null +++ b/docs/api/config/trigger-template.md @@ -0,0 +1,68 @@ +--- +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 default width is `160px`. If you need more space for your template, add the `.wx-editor` parent in front of the selector: + +~~~css {} +.wx-editor .wx-suggest-anchor { + width: 220px; +} +~~~ +::: + +### 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`: + +~~~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 `
+ +
${obj.data.label}
+
${obj.data.name}
+
`; + } + // other triggers (for example, "#") use the plain label + return obj.data.label; + }) +}); +~~~ + +**Change log:** The property was added in v2.1 + +**Related articles:** [Mentions and tags](guides/mentions_and_tags.md) diff --git a/docs/api/config/triggers.md b/docs/api/config/triggers.md new file mode 100644 index 0000000..3f916d5 --- /dev/null +++ b/docs/api/config/triggers.md @@ -0,0 +1,241 @@ +--- +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 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 + +~~~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, + action?: (item) => void +}>; +~~~ + +### 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 (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`): + +~~~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` - (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. + +### Rendered token + +When a user selects an item in the dropdown, RichText inserts a non-editable token element into the document: + +~~~html {} +@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 {} +.wx-editor-content a[data-token="@"][data-token-id="alice"] { + background: #fb8500; + color: #fff; +} +~~~ + +### Custom action + +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 } = richtext; + +const editor = new 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)); +} +~~~ + +#### 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): + +~~~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 + +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: [ + { + 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) 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 diff --git a/docs/api/events/insert-token.md b/docs/api/events/insert-token.md new file mode 100644 index 0000000..dea71c5 --- /dev/null +++ b/docs/api/events/insert-token.md @@ -0,0 +1,59 @@ +--- +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 `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": ({ + data: { + id?: string | number, + label?: string, + url?: string, + // ...any custom fields from the trigger's data source + }, + trigger: string, + showTrigger?: boolean, + action?: (item) => void +}) => boolean | void; +~~~ + +### Parameters + +The callback of the `insert-token` event receives an object with the following fields: + +- `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 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). +::: + +### Example + +~~~jsx {5-8} +// initialize RichText +const editor = new richtext.Richtext("#root", { + // configuration properties +}); +// subscribe to the "insert-token" event +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 new file mode 100644 index 0000000..666bc8b --- /dev/null +++ b/docs/api/events/show-suggest.md @@ -0,0 +1,62 @@ +--- +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 `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 + +~~~jsx {} +"show-suggest": ({ + trigger: string, + query: string, + items: Array<{ + id?: string | number, + label?: string, + url?: string, + // ...any custom fields from the trigger's data source + }>, + 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. 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 +To handle internal events, use [**Event Bus methods**](api/overview/event_bus_methods_overview.md). +::: + +### Example + +~~~jsx {6-11} +// initialize RichText +const editor = new richtext.Richtext("#root", { + triggers: [{ trigger: "@", data: people }] + // other 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 + +**Related articles:** [Mentions and tags](guides/mentions_and_tags.md) 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)| 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) | 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 diff --git a/docs/guides/mentions_and_tags.md b/docs/guides/mentions_and_tags.md new file mode 100644 index 0000000..d6a449b --- /dev/null +++ b/docs/guides/mentions_and_tags.md @@ -0,0 +1,261 @@ +--- +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 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) property. Each entry binds one character to a data source. + +## Configure triggers + +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`): + +~~~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 from the dropdown, 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", ({ data, trigger, showTrigger }) => { + 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, avatar, name and 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 || ""}
+
+ `) +}); +~~~ + +## 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 {18-20,24} +const { template, Richtext } = richtext; + +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: ":", + 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)); +} +~~~ + +### 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: "$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): + +~~~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) +- [`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) 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 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"