Rich Text
CruzJS Pro includes a rich text module for storing and managing HTML content associated with entities. It handles HTML sanitization, @mention resolution, attachment tracking, and full-text search integration.
Register the RichTextModule in your application:
import { RichTextModule } from '@cruzjs/pro/rich-text/rich-text.module';
export default createCruzApp({ modules: [RichTextModule],});Entity-Scoped Content
Section titled “Entity-Scoped Content”Rich text content is associated with a specific entity through three identifiers: entityType, entityId, and fieldName. This allows multiple rich text fields per entity.
// Save rich text for an article's body fieldtrpc.richText.save.useMutation().mutate({ entityType: 'article', entityId: 'art_abc123', fieldName: 'body', htmlContent: '<p>This is the article body with <strong>formatting</strong>.</p>',});SaveRichTextInput
Section titled “SaveRichTextInput”type SaveRichTextInput = { entityType: string; // e.g. 'article', 'comment', 'page' entityId: string; // ID of the parent entity fieldName: string; // e.g. 'body', 'description', 'notes' htmlContent: string; // Raw HTML content};HTML Sanitization
Section titled “HTML Sanitization”All HTML content is sanitized on save using an allowlist of safe tags. The sanitizer is compatible with Cloudflare Workers (no DOM APIs required).
Allowed tags include standard formatting elements: p, strong, em, a, ul, ol, li, h1-h6, blockquote, code, pre, img, br, hr, table, thead, tbody, tr, th, td.
Script tags, event handlers, and other potentially dangerous content are stripped automatically.
@Mention Resolution
Section titled “@Mention Resolution”The rich text module supports @mentions that reference users or organizations. During save, mention tokens are resolved and replaced with linked anchor tags:
<!-- Input --><p>Hey @john, please review this.</p>
<!-- Output (after mention resolution) --><p>Hey <a href="/users/user_john" class="mention" data-mention-id="user_john">@John Smith</a>, please review this.</p>Attachment Tracking
Section titled “Attachment Tracking”Associate file attachments with rich text content:
// Add an attachmenttrpc.richText.addAttachment.useMutation().mutate({ entityType: 'article', entityId: 'art_abc123', fieldName: 'body', fileId: 'file_xyz789',});
// Remove an attachmenttrpc.richText.removeAttachment.useMutation().mutate({ entityType: 'article', entityId: 'art_abc123', fieldName: 'body', fileId: 'file_xyz789',});Attachment records track which files are referenced in which rich text fields, enabling cleanup when content is deleted or files are removed.
Full-Text Search
Section titled “Full-Text Search”If the SearchModule is also registered, rich text content is automatically indexed for full-text search:
const results = trpc.richText.search.useQuery({ query: 'typescript patterns', entityType: 'article', // optional filter});The search indexes the plain text extracted from the HTML content, stripping all tags.
Plain Text Extraction
Section titled “Plain Text Extraction”The module extracts plain text from HTML for use in notifications, previews, and search indexing. This is done server-side without DOM APIs.
// Stored HTML: <p>Hello <strong>world</strong></p>// Extracted text: "Hello world"tRPC Procedures
Section titled “tRPC Procedures”| Procedure | Type | Description |
|---|---|---|
richText.get | query | Get rich text content for an entity field |
richText.save | mutation | Save (create or update) rich text content |
richText.search | query | Full-text search across rich text content |
richText.addAttachment | mutation | Associate a file with rich text content |
richText.removeAttachment | mutation | Remove a file association |
Example: Article Editor
Section titled “Example: Article Editor”function ArticleEditor({ articleId }: { articleId: string }) { const { data } = trpc.richText.get.useQuery({ entityType: 'article', entityId: articleId, fieldName: 'body', });
const save = trpc.richText.save.useMutation();
return ( <RichTextEditor initialContent={data?.htmlContent ?? ''} onSave={(html) => { save.mutate({ entityType: 'article', entityId: articleId, fieldName: 'body', htmlContent: html, }); }} /> );}