diff --git a/content/guides/01.data-model/3.interfaces.md b/content/guides/01.data-model/3.interfaces.md index dc17218c..4e983a98 100644 --- a/content/guides/01.data-model/3.interfaces.md +++ b/content/guides/01.data-model/3.interfaces.md @@ -97,6 +97,7 @@ The What You See Is What You Get (WYSIWYG) editor provides a text area with rich | Soft Limit | Used to limit the number of characters within the Data Studio. | | Static Access Token | Token appended to asset URLs when displaying in the editor. | | Custom Formats | JSON array of custom formatting entries added to the editor's Formats menu. Supports inline, block, selector, and grouped entries. `wrapper` entries are not supported. See [Rich Text](/guides/data-model/rich-text#custom-formats). | +| Extensions | [Rich text extensions](/guides/extensions/app-extensions/richtext) this field can use. Shown only when at least one is installed. Markup from unselected extensions is removed on save. | | Options Override | Deprecated and no longer applied. See the note below. | ::callout{icon="i-lucide-info"} diff --git a/content/guides/09.extensions/3.app-extensions/7.richtext.md b/content/guides/09.extensions/3.app-extensions/7.richtext.md index df36617b..0514e33c 100644 --- a/content/guides/09.extensions/3.app-extensions/7.richtext.md +++ b/content/guides/09.extensions/3.app-extensions/7.richtext.md @@ -8,6 +8,278 @@ navigation: Rich text extensions add [Tiptap](https://tiptap.dev) nodes, marks, and toolbar buttons to the [WYSIWYG interface](/guides/data-model/interfaces#wysiwyg). A field uses an extension only when you select it in the interface's **Extensions** option. +You write nodes and marks with the standard Tiptap API. Directus adds a small config around them, and a set of rules that keep content intact when users save, compare, or remove an extension. + +:partial{content="extensions-app"} + +## Before You Start + +- You know how to write a Tiptap node or mark with `parseHTML` and `renderHTML`. +- You have a Directus project to test in. Set `EXTENSIONS_AUTO_RELOAD=true` while you develop. + +## Create an Extension + +Run the extensions CLI and select the `richtext` type: + +```shell +npx create-directus-extension@latest +``` + +The template contains one mark and one toolbar button. It adds `@tiptap/core` as a dev dependency so your editor can type-check Tiptap imports. The build does not bundle it. See [Shared Tiptap Packages](#shared-tiptap-packages). + +The generated `package.json` declares the extension type in `directus:extension`. Before you publish, set its `host` field as described in [Set the Host Range](#set-the-host-range). + +Rich text extensions load in the Data Studio only. You can also add one to a [bundle](/guides/extensions/bundles) as a `richtext` entry. + +## Rich Text Entrypoint + +The `index.js` or `index.ts` file exports the config. It lists the Tiptap extensions to add to the editor schema, and the buttons that act on them. + +### Entrypoint Example + +This example adds a note block and a toolbar button that wraps the selection in it. + +```js +import { defineRichText } from '@directus/extensions-sdk'; +import { Node, mergeAttributes } from '@tiptap/core'; + +const Note = Node.create({ + name: 'note', + group: 'block', + content: 'block+', + parseHTML: () => [{ tag: 'div[data-note]' }], + renderHTML: ({ HTMLAttributes }) => ['div', mergeAttributes(HTMLAttributes, { 'data-note': '' }), 0], +}); + +export default defineRichText({ + id: 'note', + name: 'Note', + extensions: [Note], + buttons: [ + { + key: 'note', + icon: 'sticky_note_2', + label: 'Note', + command: (editor) => editor.chain().focus().toggleWrap('note').run(), + isActive: (editor) => editor.isActive('note'), + }, + ], +}); +``` + +### Properties + +| Property | Type | Description | +| ------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------- | +| `id` | string | A unique identifier for this extension. Lowercase letters, digits, and hyphens only. | +| `name` | string | The name shown in the field's **Extensions** option. | +| `extensions` | array | Tiptap extensions, nodes, and marks to add to the editor. Each node and mark must follow the [round-trip rule](#round-trip-rule). | +| `buttons` | array | [Toolbar buttons](#toolbar-buttons). Admins add them to a field in the **Toolbar** option. | +| `bubbleMenus` | array | [Bubble menus](#bubble-menus). They show on every field that enables the extension. | + +:partial{content="extensions-uid"} + +### Toolbar Buttons + +A button runs a `command`, or opens a dropdown of `items`. It cannot have both. + +| Property | Type | Description | +| ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `key` | string | Unique inside the extension. Lowercase letters, digits, and hyphens only. | +| `icon` | string | An icon name from the [Google Material Icons set](https://fonts.google.com/icons). An unknown name shows the `extension` icon. | +| `label` | string | Plain text shown for the button. | +| `command` | function | Receives the Tiptap `editor` and runs when the user clicks the button. | +| `items` | array | Dropdown entries. Each has a `key`, `label`, `command`, and optional `icon`, `isActive`, and `isDisabled`. | +| `isActive` | function | Receives the `editor` and returns `true` to show the button as active. A dropdown without it is active when one of its items is. | +| `isDisabled` | function | Receives the `editor` and returns `true` to disable the button. A disabled or read-only field disables every button anyway. | + +### Bubble Menus + +A bubble menu floats next to the selection when `shouldShow` returns `true`. + +| Property | Type | Description | +| ------------ | -------- | --------------------------------------------------------------------------- | +| `key` | string | Unique inside the extension. | +| `shouldShow` | function | Receives the `editor` and returns `true` to show the menu. | +| `buttons` | array | Toolbar buttons with a `command`. Dropdowns with `items` are not supported. | + +The editor hides bubble menus while the field is not editable, and while the cursor is in a table. When several menus match, only the first one shows. See [Load Order](#load-order). + +## Enable an Extension on a Field + +An admin chooses which fields use an extension. Content in other fields is not affected. + +1. Open the settings of a field that uses the WYSIWYG interface. +2. In **Extensions**, select your extension. This option shows only when at least one rich text extension is installed. +3. In **Toolbar**, select your extension's buttons. The list shows them after you select the extension. +4. Save the field. + +The stored toolbar key of a contributed button is `: