--- stage: Create group: Editor info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- # Content Editor development guidelines The Content 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 Content 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 Content Editor in a feature. 1. [Include the Content Editor component](#include-the-content-editor-component). 1. [Set and get Markdown](#set-and-get-markdown). 1. [Listen for changes](#listen-for-changes). ### Include the Content 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 Content 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 Content 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 Content 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 Content 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