--- stage: Plan group: Knowledge info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review. --- # Rich text editor development guidelines The rich text editor is a UI component that provides a WYSIWYG editing experience for [GitLab Flavored Markdown](../../user/markdown.md) in the GitLab application. It also serves as the foundation for implementing Markdown-focused editors that target other engines, like static site generators. We use [Tiptap 2.0](https://tiptap.dev/) and [ProseMirror](https://prosemirror.net/) to build the rich text editor. These frameworks provide a level of abstraction on top of the native [`contenteditable`](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content) web technology. ## Usage guide Follow these instructions to include the rich text editor in a feature. 1. [Include the rich text editor component](#include-the-rich-text-editor-component). 1. [Set and get Markdown](#set-and-get-markdown). 1. [Listen for changes](#listen-for-changes). ### Include the rich text editor component Import the `ContentEditor` Vue component. We recommend using asynchronous named imports to take advantage of caching, as the ContentEditor is a big dependency. ```html ``` The rich text editor requires two properties: - `renderMarkdown` is an asynchronous function that returns the response (String) of invoking the [Markdown API](../../api/markdown.md). - `uploadsPath` is a URL that points to a [GitLab upload service](../uploads/index.md) with `multipart/form-data` support. See the [`WikiForm.vue`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue#L207) component for a production example of these two properties. ### Set and get Markdown The `ContentEditor` Vue component doesn't implement Vue data binding flow (`v-model`) because setting and getting Markdown are expensive operations. Data binding would trigger these operations every time the user interacts with the component. Instead, you should obtain an instance of the `ContentEditor` class by listening to the `initialized` event: ```html ``` ### Listen for changes You can still react to changes in the rich text editor. Reacting to changes helps you know if the document is empty or dirty. Use the `@change` event handler for this purpose. ```html ``` ## Implementation guide The rich text editor is composed of three main layers: - **The editing tools UI**, like the toolbar and the table structure editor. They display the editor's state and mutate it by dispatching commands. - **The Tiptap Editor object** manages the editor's state, and exposes business logic as commands executed by the editing tools UI. - **The Markdown serializer** transforms a Markdown source string into a ProseMirror document and vice versa. ### Editing tools UI The editing tools UI are Vue components that display the editor's state and dispatch [commands](https://tiptap.dev/api/commands/#commands) to mutate it. They are located in the `~/content_editor/components` directory. For example, the **Bold** toolbar button displays the editor's state by becoming active when the user selects bold text. This button also dispatches the `toggleBold` command to format text as bold: ```mermaid sequenceDiagram participant A as Editing tools UI participant B as Tiptap object A->>B: queries state/dispatches commands B--)A: notifies state changes ``` #### Node views We implement [node views](https://tiptap.dev/guide/node-views/vue/#node-views-with-vue) to provide inline editing tools for some content types, like tables and images. Node views allow separating the presentation of a content type from its [model](https://prosemirror.net/docs/guide/#doc.data_structures). Using a Vue component in the presentation layer enables sophisticated editing experiences in the rich text editor. Node views are located in `~/content_editor/components/wrappers`. #### Dispatch commands You can inject the Tiptap Editor object to Vue components to dispatch commands. NOTE: Do not implement logic that changes the editor's state in Vue components. Encapsulate this logic in commands, and dispatch the command from the component's methods. ```html