vault backup: 2026-05-15 16:37:52

This commit is contained in:
Vadym Samoilenko 2026-05-15 16:37:52 +01:00
parent d18653ff63
commit f5b2c403c0
5 changed files with 335 additions and 1 deletions

View file

@ -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 |

View file

@ -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 |

View file

@ -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<LexicalBlockClientProps> = () => {
const style = useFormFields(([fields]) => fields.style)
return (
<BlockCollapsible removeButton={false}>
<div>Style: {(style?.value as string) ?? 'none'}</div>
<div style={{ display: 'flex' }}>
<BlockEditButton />
<BlockRemoveButton />
</div>
</BlockCollapsible>
)
}
```
### 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)

View file

@ -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