diff --git a/raw/rich-text__blocks.md b/raw/_processed/rich-text__blocks.md similarity index 100% rename from raw/rich-text__blocks.md rename to raw/_processed/rich-text__blocks.md diff --git a/wiki/_master-index.md b/wiki/_master-index.md index 53592b9..3884777 100644 --- a/wiki/_master-index.md +++ b/wiki/_master-index.md @@ -35,7 +35,7 @@ This 3-hop pattern works for hundreds of articles without vector search. | [[wiki/reports/_index\|reports/]] | Weekly and monthly summaries — generate: `uv run python scripts/report-generator.py --weekly` | 1 | | [[wiki/infrastructure/_index\|infrastructure/]] | Server inventory: all 10 SSH hosts — optical, optical-dev, optical-prod, baic, librechat, modocmms, box-cli, aimpress, pve | 12 | | [[wiki/testing/_index\|testing/]] | Web app testing: functional, performance, security, UI types; TDD/BDD/Agile methodologies; Selenium/Cypress/Playwright/JMeter/OWASP ZAP tools | 1 | -| [[wiki/payloadcms/_index\|payloadcms/]] | Full Payload CMS reference — getting started, config, database (Postgres/MongoDB/SQLite), all 22 field types, access control, hooks, authentication (cookies, JWT, API keys, custom strategies, token data), admin UI, custom components, Lexical rich text, live preview, versions/drafts, Local/REST/GraphQL APIs, queries, plugins, jobs queue, upload, ecommerce, production deploy, TypeScript, migration guides, i18n, localization, hierarchy | 130 | +| [[wiki/payloadcms/_index\|payloadcms/]] | Full Payload CMS reference — getting started, config, database (Postgres/MongoDB/SQLite), all 22 field types, access control, hooks, authentication (cookies, JWT, API keys, custom strategies, token data), admin UI, custom components, Lexical rich text, live preview, versions/drafts, Local/REST/GraphQL APIs, queries, plugins, jobs queue, upload, ecommerce, production deploy, TypeScript, migration guides, i18n, localization, hierarchy | 131 | | [[wiki/shared-patterns/_index\|shared-patterns/]] | Oliver Agency standard library patterns: httpx, structlog, pydantic-settings, alembic — reuse before writing from scratch | 4 | | [[wiki/mistakes/_index\|mistakes/]] | Anti-patterns extracted from sessions — per-stack running lists (fastapi, react, docker, postgres, general) — injected at session start | 5 | diff --git a/wiki/payloadcms/_index.md b/wiki/payloadcms/_index.md index 63997cb..a0b6202 100644 --- a/wiki/payloadcms/_index.md +++ b/wiki/payloadcms/_index.md @@ -131,3 +131,4 @@ | [[wiki/payloadcms/queries-select\|Queries — Select & Populate]] | Include/exclude field selection at DB level, entity-level `select` function for hooks/access, `defaultPopulate` for relationship optimization, `populate` override | raw/queries__select.md | 2026-05-15 | | [[wiki/payloadcms/queries-sort\|Queries — Sort]] | Sort by any stored top-level field: `-field` descending, array (Local API) or comma-separated string (REST/GraphQL), index tip | raw/queries__sort.md | 2026-05-15 | | [[wiki/payloadcms/query-presets\|Query Presets]] | Save & share List View filters/columns/sort as DB records; collection-level + document-level access control; custom RBAC constraints; filterConstraints | raw/query-presets__overview.md | 2026-05-15 | +| [[wiki/payloadcms/rich-text-blocks\|Rich Text — Blocks (Lexical BlocksFeature)]] | Embed Payload Blocks in Lexical rich text: block vs inline blocks, data structure, custom admin components (BlockCollapsible/BlockEditButton), pre-built CodeBlock with TS IntelliSense, frontend rendering | raw/rich-text__blocks.md | 2026-05-15 | diff --git a/wiki/payloadcms/rich-text-blocks.md b/wiki/payloadcms/rich-text-blocks.md new file mode 100644 index 0000000..69c8f7a --- /dev/null +++ b/wiki/payloadcms/rich-text-blocks.md @@ -0,0 +1,213 @@ +--- +title: "Rich Text — Blocks (Lexical BlocksFeature)" +aliases: [lexical-blocks, rich-text-blocks, blocksfeature] +tags: [payloadcms, rich-text, lexical, blocks, inline-blocks] +sources: [raw/rich-text__blocks.md] +created: 2026-05-15 +updated: 2026-05-15 +--- + +## Overview + +`BlocksFeature` embeds Payload's [[wiki/payloadcms/fields-blocks|Blocks Field]] directly inside the Lexical rich text editor. Blocks share the same config schema as standard Payload blocks — all field types, hooks, validation, access control, and conditional logic are supported. The only difference is that data is stored inside the rich text JSON, not as separate DB fields. + +## Basic Setup + +```ts +import { lexicalEditor, BlocksFeature } from '@payloadcms/richtext-lexical' + +{ + name: 'content', + type: 'richText', + editor: lexicalEditor({ + features: ({ defaultFeatures }) => [ + ...defaultFeatures, + BlocksFeature({ + blocks: [ + { + slug: 'banner', + fields: [ + { name: 'style', type: 'select', options: ['info', 'warning', 'error', 'success'], defaultValue: 'info' }, + { name: 'content', type: 'textarea', required: true }, + ], + }, + ], + }), + ], + }), +} +``` + +## Blocks vs Inline Blocks + +| | **Blocks** | **Inline Blocks** | +|--|--|--| +| Position | Block-level — occupy a full line | Flow within text (same paragraph) | +| Use cases | CTAs, galleries, code snippets, embeds | Mentions, badges, icons, placeholders, footnotes | +| Config key | `blocks: []` | `inlineBlocks: []` | + +```ts +BlocksFeature({ + blocks: [ + { slug: 'callout', fields: [{ name: 'content', type: 'textarea' }] }, + ], + inlineBlocks: [ + { slug: 'mention', fields: [{ name: 'user', type: 'relationship', relationTo: 'users', required: true }] }, + ], +}) +``` + +## Data Structure + +Block nodes in the Lexical JSON contain a `fields` object. Three fields are auto-included: + +```json +{ + "type": "block", + "version": 2, + "fields": { + "id": "65298b13db4ef8c744a7faaa", + "blockType": "banner", + "blockName": "Important Notice", + "style": "warning", + "content": "This is the block content..." + } +} +``` + +Inline blocks use `"type": "inlineBlock"` but follow the same shape. + +## Custom Block Components + +Override how a block renders in the admin editor via `admin.components.Block`. + +### Block Component (client) + +```ts +{ + slug: 'myCustomBlock', + admin: { + components: { + Block: '/path/to/MyBlockComponent#MyBlockComponent', + }, + }, + fields: [{ name: 'style', type: 'select', options: ['primary', 'secondary'] }], +} +``` + +Composable primitives from `@payloadcms/richtext-lexical/client`: + +| Primitive | Purpose | +|-----------|---------| +| `BlockCollapsible` | Collapsible wrapper with built-in edit drawer trigger | +| `BlockEditButton` | Opens field edit drawer | +| `BlockRemoveButton` | Removes the block from the editor | + +```tsx +'use client' +import type { LexicalBlockClientProps } from '@payloadcms/richtext-lexical' +import { BlockCollapsible, BlockEditButton, BlockRemoveButton } from '@payloadcms/richtext-lexical/client' +import { useFormFields } from '@payloadcms/ui' + +export const MyBlockComponent: React.FC = () => { + const style = useFormFields(([fields]) => fields.style) + return ( + +
Style: {(style?.value as string) ?? 'none'}
+
+ + +
+
+ ) +} +``` + +### Inline Block Component (client) + +Primitives: `InlineBlockContainer`, `InlineBlockEditButton`, `InlineBlockLabel`, `InlineBlockRemoveButton` — all from `@payloadcms/richtext-lexical/client`. + +### Label Components + +Customize the block header label via `admin.components.Label`. Use `useFormFields` to render dynamic content from block field values. + +## TypeScript Types + +```ts +import type { + LexicalBlockClientProps, + LexicalBlockServerProps, + LexicalBlockLabelClientProps, + LexicalBlockLabelServerProps, + LexicalInlineBlockClientProps, + LexicalInlineBlockServerProps, + LexicalInlineBlockLabelClientProps, + LexicalInlineBlockLabelServerProps, +} from '@payloadcms/richtext-lexical' +``` + +## Pre-built CodeBlock + +Payload ships a ready-made `CodeBlock` with syntax highlighting, language selection, and optional TypeScript IntelliSense: + +```ts +import { BlocksFeature, CodeBlock } from '@payloadcms/richtext-lexical' + +BlocksFeature({ + blocks: [ + CodeBlock({ + defaultLanguage: 'ts', + languages: { plaintext: 'Plain Text', js: 'JavaScript', ts: 'TypeScript', tsx: 'TSX', jsx: 'JSX' }, + }), + ], +}) +``` + +### CodeBlock Options + +| Option | Description | +|--------|-------------| +| `slug` | Override slug (default: `'Code'`) | +| `defaultLanguage` | Default language (default: first key) | +| `languages` | `{ key: label }` map | +| `typescript` | TypeScript IntelliSense config (see below) | +| `fieldOverrides` | Partial block config to extend defaults | + +### TypeScript IntelliSense + +```ts +CodeBlock({ + typescript: { + fetchTypes: [ + { url: 'https://unpkg.com/payload@latest/dist/index.bundled.d.ts', filePath: 'file:///node_modules/payload/index.d.ts' }, + ], + paths: { payload: ['file:///node_modules/payload/index.d.ts'] }, + typeRoots: ['node_modules/@types', 'node_modules/payload'], + enableSemanticValidation: true, + }, +}) +``` + +## Rendering Blocks on the Frontend + +Blocks must be handled in your converter config: + +- **JSX Converters** — React/Next.js: [[wiki/payloadcms/rich-text|Rich Text (Lexical)]] converting-jsx guide +- **HTML Converters** — static HTML output +- **Markdown Converters** — define custom block renderers + +Each converter lets you define per-block-type custom renderers. + +## Key Takeaways + +- `BlocksFeature` accepts `blocks` (block-level) and `inlineBlocks` (inline) arrays — same config schema as the standard [[wiki/payloadcms/fields-blocks|Blocks Field]] +- Block data lives inside Lexical JSON; three auto-fields are always present: `id`, `blockType`, `blockName` +- Custom admin components use composable primitives (`BlockCollapsible`, `BlockEditButton`, etc.) from `@payloadcms/richtext-lexical/client` +- Use `useFormFields` (from `@payloadcms/ui`) to read live block field values inside custom components +- Pre-built `CodeBlock` covers most code-display needs, with optional TS IntelliSense via `fetchTypes` +- Frontend rendering requires explicit block handlers in your JSX/HTML/Markdown converter config +- See [Payload's CodeBlock source](https://github.com/payloadcms/payload/blob/main/packages/richtext-lexical/src/features/blocks/premade/CodeBlock/index.ts) for a real-world custom block example + +## Sources + +- `raw/rich-text__blocks.md` (https://payloadcms.com/docs/rich-text/blocks) diff --git a/wiki/payloadcms/rich-text-converters.md b/wiki/payloadcms/rich-text-converters.md new file mode 100644 index 0000000..a36c57d --- /dev/null +++ b/wiki/payloadcms/rich-text-converters.md @@ -0,0 +1,120 @@ +--- +title: "Rich Text — Converters (Lexical)" +aliases: [lexical-converters, richtext-converters, lexical-export] +tags: [payloadcms, lexical, rich-text, converters, html, markdown, jsx, plaintext] +sources: [raw/rich-text__converters.md] +created: 2026-05-15 +updated: 2026-05-15 +--- + +## Overview + +Lexical rich text fields store data as **JSON**. Payload provides converters to transform that JSON into other formats for rendering or export. + +## Supported Output Formats + +| Format | Reference | +|--------|-----------| +| JSX | `/docs/rich-text/converting-jsx` | +| HTML | `/docs/rich-text/converting-html` | +| Plaintext | `/docs/rich-text/converting-plaintext` | +| Markdown / MDX | `/docs/rich-text/converting-markdown` | + +## Retrieving the Editor Config + +Some converters need access to the **Lexical editor config** (defines available features/nodes). Use `editorConfigFactory` from `@payloadcms/richtext-lexical`. + +```ts +import { editorConfigFactory } from '@payloadcms/richtext-lexical' +``` + +### Option 1 — Default config + +```ts +const editorConfig = await editorConfigFactory.default({ config }) +``` + +### Option 2 — Extract from a field + +When you have a sanitized field reference (e.g. inside a hook on a sibling field): + +```ts +const editorConfig = editorConfigFactory.fromField({ field }) +``` + +### Option 3 — Custom features config + +```ts +import { FixedToolbarFeature } from '@payloadcms/richtext-lexical' + +const editorConfig = await editorConfigFactory.fromFeatures({ + config, + features: ({ defaultFeatures }) => [...defaultFeatures, FixedToolbarFeature()], +}) +``` + +### Option 4 — From an instantiated editor (not recommended) + +```ts +const editorConfig = await editorConfigFactory.fromEditor({ config, editor }) +``` + +> Prefer `fromFeatures` — extract features into a variable and reuse. + +## Practical Pattern — Get Config in a Hook + +Access the lexical editor config of a sibling `richText` field from an `afterRead` hook on another field: + +```ts +import type { CollectionConfig, RichTextField } from 'payload' +import { editorConfigFactory, lexicalEditor } from '@payloadcms/richtext-lexical' + +export const MyCollection: CollectionConfig = { + slug: 'slug', + fields: [ + { + name: 'text', + type: 'text', + hooks: { + afterRead: [ + ({ siblingFields, value }) => { + const richTextField = siblingFields.find( + (f) => 'name' in f && f.name === 'richText', + ) as RichTextField + + const editorConfig = editorConfigFactory.fromField({ field: richTextField }) + // use editorConfig to run a converter + return value + }, + ], + }, + }, + { + name: 'richText', + type: 'richText', + editor: lexicalEditor(), + }, + ], +} +``` + +## Key Takeaways + +- Lexical stores rich text as **JSON** — converters transform it to HTML/JSX/Markdown/Plaintext. +- Always import `editorConfigFactory` from `@payloadcms/richtext-lexical`. +- Four factory methods: `default`, `fromField`, `fromFeatures`, `fromEditor` — prefer `fromFeatures` for custom setups. +- Access a sibling field's editor config via `editorConfigFactory.fromField({ field })` inside hooks. +- `fromEditor` is least efficient — avoid unless you already have an instantiated editor. + +## Related + +- [[wiki/payloadcms/rich-text|Rich Text (Lexical) — Overview]] +- [[wiki/payloadcms/rich-text-blocks|Rich Text — Blocks (BlocksFeature)]] +- [[wiki/payloadcms/fields-rich-text|Rich Text Field Config]] +- [[wiki/payloadcms/hooks-fields|Field Hooks]] +- [[wiki/payloadcms/hooks-collections|Collection Hooks]] + +## Sources + +- `raw/rich-text__converters.md` +- https://payloadcms.com/docs/rich-text/converters