Rich text editor

View on GitHub

Rich text editor

Issue descriptions (and, in read-only/disabled mode, an integration's catalog overview copy) are edited/rendered through Tiptap, configured once in EditableMarkdown.tsx and stored as plain markdown text on the backend — there's no separate rich-content column or JSON document format, just a TEXT column holding whatever markdown Tiptap's tiptap-markdown extension serializes.

Guides, in the order you'd actually need them

  1. Add a new Tiptap extensionRich text editorAdd a new Tiptap extensionWorked example: adding text highlighting (@tiptap/extension-highlight) to the issue description editor — an extension not currently installed, following th… — worked example adding text highlighting (@tiptap/extension-highlight, not currently installed) to the editor's extension list.
  2. Add image paste & drop uploadsRich text editorAdd image paste & drop uploadsWalkthrough of the pipeline that lets an issue description accept an image pasted with Ctrl+V or dragged onto the page — every layer of it, in the order yo… — the full pipeline behind pasting or dragging an image into an issue description: the attachments table, repository, service and JSON upload endpoint, the NSFW screening it shares with avatar uploads, and the editor's paste/drop handlers.
  3. Add image uploads to another surfaceRich text editorAdd image uploads to another surfaceHow the upload pipeline from 02-add-image-paste-and-drop-uploads.md reaches the app's markdown fields that are not Tiptap: Part A issue comments and Part B… — how that pipeline reaches surfaces that are plain textareas rather than Tiptap: issue comments (composing, editing, rendering, and the mention-range trap) and an issue type's template body — the two shapes any further surface will take.
  4. Add mentions to a Tiptap surfaceRich text editorAdd mentions to a Tiptap surfaceWorked example: @member and #issue mentions in the issue description, the Tiptap editor behind EditableMarkdown. Issue comments already had both (see ../no… — how @member and #issue mentions work inside the Tiptap description editor: mention nodes that round-trip to the same markdown tokens comments use, the bridge from Tiptap's suggestion plugin to the shared MentionSuggestions menu, and the listener that notifies members named in a description.

The architecture in one paragraph

EditableMarkdown (resources/js/Components/Molecules/EditableMarkdown/EditableMarkdown.tsx) wraps a single useEditor() call from @tiptap/react, always mounted but toggling editable: false/true on click rather than conditionally rendering a separate read/edit component — clicking the rendered content calls startEditing() (sets editable: true, focuses the end), and losing focus (onBlur) or pressing Escape exits editing, calling onSave(markdown) only if the content actually changed (compared via tiptap-markdown's editor.storage.markdown.getMarkdown() against the last-saved value prop). The extension list — StarterKit (bold/italic/lists/headings/code/etc., with link configured not to open on click while editing), Markdown (the serializer itself, html: false so pasted/typed HTML never leaks into the stored markdown), Placeholder, TaskList/TaskItem, TableKit, and Image — is the single place that decides what formatting is possible; nothing else in the app has its own separate Tiptap configuration. A separate rendering path, react-markdown/remark-gfm (see ../architecture/01-tech-stack-and-project-structure.mdArchitecture & project technicalsTech stack and project structureThe root README.md has the headline stack table (PHP/Laravel, Inertia, React/TypeScript/Vite, Tailwind, SQLite, Pest/Vitest). This guide goes one level dee…), handles markdown that's never editable at all (the integrations catalog's static overview field) — don't reach for Tiptap for read-only markdown display; it's a heavier tool for a job react-markdown already does.

Mentions are the other exception, though they stay inside the TEXT column: when users/projectId are passed, two extra nodes (@member, #issue) are added to the list and serialize to the same @[Name](id) / #[Title](id:number) tokens comments store — guide 4.

Images are the one thing that doesn't live in that TEXT column. Pasting or dropping one uploads it to POST /projects/{project}/attachments (AttachmentController → AttachmentService → AttachmentRepository, screened by NsfwDetectionService exactly like an avatar), and only the returned URL goes into the markdown as a plain ![alt](url). Attachments are owned by the project, not by the issue, comment or template that references them, which is what lets one endpoint serve every editing surface in the app. Issue descriptions get images for free through Tiptap's Image node; comments — plain textareas, not Tiptap — do the same work by hand with insertMarkdownImage and splitMarkdownImages in resources/js/utils/imagePaste.ts, which is what guide 3 walks through.