Rich text editor
View on GitHubRich 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
- 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. - 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
attachmentstable, repository, service and JSON upload endpoint, the NSFW screening it shares with avatar uploads, and the editor's paste/drop handlers. - 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.
- 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
@memberand#issuementions 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 sharedMentionSuggestionsmenu, 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 .
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.
