@olddevs/ui 4.5.1 → 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/editor/RichTextEditor.d.ts +2 -2
- package/dist/editor/RichTextEditor.d.ts.map +1 -1
- package/dist/editor/RichTextEditor.js +3 -0
- package/dist/editor/RichTextEditor.js.map +1 -1
- package/dist/editor/core/blockTypes.d.ts +2 -8
- package/dist/editor/core/blockTypes.d.ts.map +1 -1
- package/dist/editor/core/blockTypes.js +11 -54
- package/dist/editor/core/blockTypes.js.map +1 -1
- package/dist/editor/core/containers.d.ts.map +1 -1
- package/dist/editor/core/containers.js +1 -0
- package/dist/editor/core/containers.js.map +1 -1
- package/dist/editor/core/customMarkdown.js +1 -1
- package/dist/editor/core/customMarkdown.js.map +1 -1
- package/dist/editor/core/customTool.d.ts +6 -6
- package/dist/editor/core/customTool.d.ts.map +1 -1
- package/dist/editor/core/customToolEngine.d.ts +2 -2
- package/dist/editor/core/extensions.d.ts +7 -2
- package/dist/editor/core/extensions.d.ts.map +1 -1
- package/dist/editor/core/extensions.js +96 -64
- package/dist/editor/core/extensions.js.map +1 -1
- package/dist/editor/core/fieldNotice.d.ts +1 -1
- package/dist/editor/core/insertItems.d.ts +5 -28
- package/dist/editor/core/insertItems.d.ts.map +1 -1
- package/dist/editor/core/insertItems.js +3 -94
- package/dist/editor/core/insertItems.js.map +1 -1
- package/dist/editor/core/insertMenuTool.d.ts +13 -0
- package/dist/editor/core/insertMenuTool.d.ts.map +1 -0
- package/dist/editor/core/insertMenuTool.js +20 -0
- package/dist/editor/core/insertMenuTool.js.map +1 -0
- package/dist/editor/core/schema.d.ts +20 -5
- package/dist/editor/core/schema.d.ts.map +1 -1
- package/dist/editor/core/schema.js +18 -3
- package/dist/editor/core/schema.js.map +1 -1
- package/dist/editor/core/shortcuts.d.ts +56 -5
- package/dist/editor/core/shortcuts.d.ts.map +1 -1
- package/dist/editor/core/shortcuts.js +91 -156
- package/dist/editor/core/shortcuts.js.map +1 -1
- package/dist/editor/core/skeleton.d.ts +1 -1
- package/dist/editor/core/skeleton.d.ts.map +1 -1
- package/dist/editor/core/skeleton.js +1 -0
- package/dist/editor/core/skeleton.js.map +1 -1
- package/dist/editor/core/toolButtons.d.ts +3 -3
- package/dist/editor/core/toolDefinition.d.ts +3 -3
- package/dist/editor/core/toolDescriptor.d.ts +49 -30
- package/dist/editor/core/toolDescriptor.d.ts.map +1 -1
- package/dist/editor/core/toolDescriptor.js +2 -5
- package/dist/editor/core/toolDescriptor.js.map +1 -1
- package/dist/editor/core/toolLoader.d.ts +1 -1
- package/dist/editor/core/toolLoader.d.ts.map +1 -1
- package/dist/editor/core/toolOptions.d.ts +19 -2
- package/dist/editor/core/toolOptions.d.ts.map +1 -1
- package/dist/editor/core/toolOptions.js.map +1 -1
- package/dist/editor/core/tools.d.ts +10 -2
- package/dist/editor/core/tools.d.ts.map +1 -1
- package/dist/editor/core/tools.js +1 -0
- package/dist/editor/core/tools.js.map +1 -1
- package/dist/editor/features/builtins.d.ts +6 -8
- package/dist/editor/features/builtins.d.ts.map +1 -1
- package/dist/editor/features/builtins.js +48 -21
- package/dist/editor/features/builtins.js.map +1 -1
- package/dist/editor/features/code/codeBlockTool.d.ts +5 -6
- package/dist/editor/features/code/codeBlockTool.d.ts.map +1 -1
- package/dist/editor/features/code/codeBlockTool.js +10 -1
- package/dist/editor/features/code/codeBlockTool.js.map +1 -1
- package/dist/editor/features/color/colorTools.d.ts +14 -0
- package/dist/editor/features/color/colorTools.d.ts.map +1 -0
- package/dist/editor/features/color/colorTools.js +39 -0
- package/dist/editor/features/color/colorTools.js.map +1 -0
- package/dist/editor/features/details/details.d.ts +68 -0
- package/dist/editor/features/details/details.d.ts.map +1 -0
- package/dist/editor/features/details/details.js +237 -0
- package/dist/editor/features/details/details.js.map +1 -0
- package/dist/editor/features/details/detailsMarkdown.d.ts +17 -0
- package/dist/editor/features/details/detailsMarkdown.d.ts.map +1 -0
- package/dist/editor/features/details/detailsMarkdown.js +84 -0
- package/dist/editor/features/details/detailsMarkdown.js.map +1 -0
- package/dist/editor/features/details/detailsTool.d.ts +16 -0
- package/dist/editor/features/details/detailsTool.d.ts.map +1 -0
- package/dist/editor/features/details/detailsTool.js +39 -0
- package/dist/editor/features/details/detailsTool.js.map +1 -0
- package/dist/editor/features/emoji/emojiTool.d.ts +1 -1
- package/dist/editor/features/heading/headingTool.d.ts +18 -0
- package/dist/editor/features/heading/headingTool.d.ts.map +1 -0
- package/dist/editor/features/heading/headingTool.js +59 -0
- package/dist/editor/features/heading/headingTool.js.map +1 -0
- package/dist/editor/features/history/historyTool.d.ts +15 -0
- package/dist/editor/features/history/historyTool.d.ts.map +1 -0
- package/dist/editor/features/history/historyTool.js +28 -0
- package/dist/editor/features/history/historyTool.js.map +1 -0
- package/dist/editor/features/image/imageTool.d.ts +19 -0
- package/dist/editor/features/image/imageTool.d.ts.map +1 -0
- package/dist/editor/features/image/imageTool.js +28 -0
- package/dist/editor/features/image/imageTool.js.map +1 -0
- package/dist/editor/features/layout/layoutTool.d.ts +11 -0
- package/dist/editor/features/layout/layoutTool.d.ts.map +1 -0
- package/dist/editor/features/layout/layoutTool.js +20 -0
- package/dist/editor/features/layout/layoutTool.js.map +1 -0
- package/dist/editor/features/lists/listTools.d.ts +15 -0
- package/dist/editor/features/lists/listTools.d.ts.map +1 -0
- package/dist/editor/features/lists/listTools.js +78 -0
- package/dist/editor/features/lists/listTools.js.map +1 -0
- package/dist/editor/features/marks/markTools.d.ts +16 -0
- package/dist/editor/features/marks/markTools.d.ts.map +1 -0
- package/dist/editor/features/marks/markTools.js +43 -0
- package/dist/editor/features/marks/markTools.js.map +1 -0
- package/dist/editor/features/search/searchTool.d.ts +1 -1
- package/dist/editor/features/table/tableTool.d.ts +3 -3
- package/dist/editor/features/wrapper/wrapperTool.d.ts +11 -0
- package/dist/editor/features/wrapper/wrapperTool.d.ts.map +1 -0
- package/dist/editor/features/wrapper/wrapperTool.js +32 -0
- package/dist/editor/features/wrapper/wrapperTool.js.map +1 -0
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js.map +1 -1
- package/dist/editor/schema-entry.d.ts +1 -1
- package/dist/editor/styles/prose.d.ts.map +1 -1
- package/dist/editor/styles/prose.js +26 -0
- package/dist/editor/styles/prose.js.map +1 -1
- package/dist/editor/ui/EditorToolbar.d.ts.map +1 -1
- package/dist/editor/ui/EditorToolbar.js +5 -38
- package/dist/editor/ui/EditorToolbar.js.map +1 -1
- package/dist/editor/ui/SelectionBubble.d.ts.map +1 -1
- package/dist/editor/ui/SelectionBubble.js +1 -1
- package/dist/editor/ui/SelectionBubble.js.map +1 -1
- package/dist/editor/ui/ToolButtonView.d.ts.map +1 -1
- package/dist/editor/ui/ToolButtonView.js +1 -1
- package/dist/editor/ui/ToolButtonView.js.map +1 -1
- package/dist/i18n/defaults.d.ts.map +1 -1
- package/dist/i18n/defaults.js +3 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/messages.d.ts +16 -1
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/icons.d.ts +8 -1
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +1 -1
- package/dist/icons.js.map +1 -1
- package/dist/locales/ru.d.ts.map +1 -1
- package/dist/locales/ru.js +3 -0
- package/dist/locales/ru.js.map +1 -1
- package/docs/api/editor-extension.en.md +68 -68
- package/docs/api/editor-extension.md +55 -55
- package/docs/api/messages.en.md +4 -1
- package/docs/api/messages.md +3 -0
- package/docs/api/migration-notes.en.md +11 -11
- package/docs/api/migration-notes.md +2 -2
- package/docs/api/utilities.en.md +6 -6
- package/docs/components/actions/split-button.en.md +9 -9
- package/docs/components/content/data-display.en.md +1 -1
- package/docs/components/content/layout-primitives.en.md +3 -3
- package/docs/components/content/rich-text-toc.en.md +1 -1
- package/docs/components/content/rich-text-view.en.md +23 -17
- package/docs/components/content/rich-text-view.md +17 -11
- package/docs/components/content/virtual-grid.en.md +2 -2
- package/docs/components/date-time/date-picker.en.md +1 -1
- package/docs/components/inputs/rich-text-editor.en.md +283 -230
- package/docs/components/inputs/rich-text-editor.md +139 -86
- package/docs/components/navigation/mega-menu.en.md +10 -10
- package/docs/components/overlays/menu.en.md +2 -2
- package/docs/components/overlays/shortcuts-help.en.md +1 -1
- package/docs/components/table/data-table.en.md +3 -3
- package/docs/components/table/filter-bar.en.md +1 -1
- package/docs/components/table/toolbar.en.md +1 -1
- package/docs/conventions.en.md +2 -2
- package/docs/conventions.md +1 -1
- package/docs/getting-started.en.md +9 -9
- package/docs/getting-started.md +8 -8
- package/docs/glossary.en.md +193 -133
- package/docs/glossary.md +191 -132
- package/docs/icons.en.md +12 -9
- package/docs/icons.md +3 -0
- package/docs/palette.en.md +3 -3
- package/docs/theming.en.md +6 -6
- package/package.json +2 -1
- package/src/editor/RichTextEditor.tsx +9 -6
- package/src/editor/core/atomText.ts +2 -2
- package/src/editor/core/blockTypes.ts +19 -83
- package/src/editor/core/containers.ts +4 -2
- package/src/editor/core/customMarkdown.ts +2 -2
- package/src/editor/core/customTool.ts +6 -6
- package/src/editor/core/customToolEngine.ts +9 -9
- package/src/editor/core/extensions.ts +165 -110
- package/src/editor/core/fieldNotice.ts +1 -1
- package/src/editor/core/handle.ts +2 -2
- package/src/editor/core/insertItems.ts +12 -158
- package/src/editor/core/insertMenuTool.ts +32 -0
- package/src/editor/core/jsonDocument.ts +1 -1
- package/src/editor/core/schema.ts +42 -7
- package/src/editor/core/shortcuts.ts +116 -177
- package/src/editor/core/skeleton.ts +4 -3
- package/src/editor/core/toolButtons.ts +3 -3
- package/src/editor/core/toolDefinition.ts +4 -4
- package/src/editor/core/toolDescriptor.ts +65 -52
- package/src/editor/core/toolLoader.ts +3 -3
- package/src/editor/core/toolOptions.ts +21 -2
- package/src/editor/core/tools.ts +10 -1
- package/src/editor/core/viewIsland.ts +1 -1
- package/src/editor/features/builtins.ts +76 -41
- package/src/editor/features/code/codeBlockTool.ts +16 -8
- package/src/editor/features/color/colorTools.ts +68 -0
- package/src/editor/features/details/details.ts +389 -0
- package/src/editor/features/details/detailsMarkdown.ts +158 -0
- package/src/editor/features/details/detailsTool.ts +69 -0
- package/src/editor/features/emoji/emojiTool.tsx +1 -1
- package/src/editor/features/heading/headingTool.ts +96 -0
- package/src/editor/features/history/historyTool.ts +42 -0
- package/src/editor/features/image/imageTool.ts +49 -0
- package/src/editor/features/layout/layoutTool.ts +40 -0
- package/src/editor/features/lists/listTools.ts +112 -0
- package/src/editor/features/marks/markTools.ts +78 -0
- package/src/editor/features/search/searchTool.ts +1 -1
- package/src/editor/features/table/tableTool.ts +3 -3
- package/src/editor/features/toc/tocTool.ts +1 -1
- package/src/editor/features/wrapper/wrapperTool.ts +52 -0
- package/src/editor/index.ts +1 -0
- package/src/editor/schema-entry.ts +1 -1
- package/src/editor/styles/prose.ts +49 -0
- package/src/editor/ui/EditorToolbar.tsx +7 -50
- package/src/editor/ui/SelectionBubble.tsx +2 -5
- package/src/editor/ui/ToolButtonView.tsx +2 -1
- package/src/i18n/defaults.ts +3 -0
- package/src/i18n/messages.ts +14 -1
- package/src/icons.ts +7 -0
- package/src/locales/ru.ts +3 -0
|
@@ -19,7 +19,7 @@ category: content
|
|
|
19
19
|
status: stable
|
|
20
20
|
source: src/editor/RichTextView.tsx
|
|
21
21
|
story: src/editor/RichTextView.stories.tsx
|
|
22
|
-
sourceHash:
|
|
22
|
+
sourceHash: bceb9f235ef0
|
|
23
23
|
---
|
|
24
24
|
|
|
25
25
|
# RichTextView
|
|
@@ -93,17 +93,17 @@ The allowlist of tags and attributes (the table mirrors `src/editor/core/schema.
|
|
|
93
93
|
|
|
94
94
|
<!-- allow-list -->
|
|
95
95
|
|
|
96
|
-
| What | Allowed
|
|
97
|
-
| ---------- |
|
|
98
|
-
| Blocks | `p`, `h1`, `h2`, `h3`, `h4`, `ul`, `ol`, `li`, `blockquote`, `hr`, `pre`, `table`, `tbody`, `tr`, `th`, `td`; `label`, `input`, `span` — only inside a checklist item; `div` — there too, plus the
|
|
99
|
-
| Marks | `strong`, `em`, `u`, `s`, `code`, `sup`, `sub`, `mark` (only with a color), `br`, `a`
|
|
100
|
-
| Attributes | `ol[start]` — only when numbering does not start at 1; `ul[data-type]`, `li[data-type]`, `li[data-checked]` — checklist; `input[type]`, `input[checked]` — its checkbox; `code[class]` — the code block language, only "language-python", "language-typescript", "language-javascript", "language-go", "language-html", "language-json", "language-yaml" or "language-markdown" (no language, no class); `span[data-color]`, `mark[data-color]` — text color and marker, only names from that tag's palette: text colors on `span`, markers on `mark`; `span[style]`, `mark[style]` — the author's custom color, only "color:#rrggbb" and "background-color:#rrggbb", and not together with "data-color"; `p[data-align]`, `h1[data-align]`, `h2[data-align]`, `h3[data-align]`, `h4[data-align]` — Block alignment, center or right; `th[colspan]`, `td[colspan]` — horizontal merge, an integer from 2 to 20; `th[rowspan]`, `td[rowspan]` — vertical, from 2 to 50; `th[data-align]`, `td[data-align]` — column alignment, center or right, on each of its cells; `a[href]` — only "http", "https" or "mailto"; `a[rel]` — always "noopener noreferrer"; `img[src]` — only absolute "http" or "https", no "data:" or "blob:"; `img[alt]` — always, empty for an image without a description; `img[data-width]` — 50, 75 or 100; `div[data-block]` — the
|
|
96
|
+
| What | Allowed |
|
|
97
|
+
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
98
|
+
| Blocks | `p`, `h1`, `h2`, `h3`, `h4`, `ul`, `ol`, `li`, `blockquote`, `hr`, `pre`, `table`, `tbody`, `tr`, `th`, `td`; `label`, `input`, `span` — only inside a checklist item; `div` — there too, plus the Column layout and its columns (a Column layout only at the top level, a column only directly inside it) and the Wrapper (at the top level and directly in a column, not in another Wrapper); `details` — a collapsible Block, with `summary` and a content `div` inside it (both only directly in `details`; outside it the tags are removed); `figure` — an image, with `img` and an optional `figcaption` inside (they do not exist outside `figure`) |
|
|
99
|
+
| Marks | `strong`, `em`, `u`, `s`, `code`, `sup`, `sub`, `mark` (only with a color), `br`, `a` |
|
|
100
|
+
| Attributes | `ol[start]` — only when numbering does not start at 1; `ul[data-type]`, `li[data-type]`, `li[data-checked]` — checklist; `input[type]`, `input[checked]` — its checkbox; `code[class]` — the code block language, only "language-python", "language-typescript", "language-javascript", "language-go", "language-html", "language-json", "language-yaml" or "language-markdown" (no language, no class); `span[data-color]`, `mark[data-color]` — text color and marker, only names from that tag's palette: text colors on `span`, markers on `mark`; `span[style]`, `mark[style]` — the author's custom color, only "color:#rrggbb" and "background-color:#rrggbb", and not together with "data-color"; `p[data-align]`, `h1[data-align]`, `h2[data-align]`, `h3[data-align]`, `h4[data-align]` — Block alignment, center or right; `th[colspan]`, `td[colspan]` — horizontal merge, an integer from 2 to 20; `th[rowspan]`, `td[rowspan]` — vertical, from 2 to 50; `th[data-align]`, `td[data-align]` — column alignment, center or right, on each of its cells; `a[href]` — only "http", "https" or "mailto"; `a[rel]` — always "noopener noreferrer"; `img[src]` — only absolute "http" or "https", no "data:" or "blob:"; `img[alt]` — always, empty for an image without a description; `img[data-width]` — 50, 75 or 100; `div[data-block]` — the Column layout ("layout"), its column ("column") and the Wrapper ("wrapper"); `div[data-layout]` — the Column layout variant, column shares in percent: "100", "50-50", "33-33-33", "25-25-25-25", "60-40" or "40-60"; `div[data-tone]` — the Callout tone, only on it: "neutral", "accent", "success", "warning" or "danger" (no tone, no attribute); `div[data-type]` — the content wrapper of a collapsible Block, only the value "detailsContent" and only directly in `details`; `details[open]` — an open Block, only an empty "open" |
|
|
101
101
|
|
|
102
102
|
Tiptap writes a checklist item like this: `<li data-type="taskItem" data-checked="true"><label><input type="checkbox" checked><span></span></label><div><p>…</p></div></li>`. `sanitizeRichText` lets `input`, `label`, `span` and `div` through only inside such an item, and `input` only with `type="checkbox"`.
|
|
103
103
|
|
|
104
104
|
The Editor writes a table like this: `<table><tbody><tr><th><p>…</p></th></tr><tr><td colspan="2" data-align="center"><p>…</p></td></tr></tbody></table>`. The header row is the first row made of `th`, and a cell has only three attributes: `colspan` and `rowspan` (merging, an integer from 2 to `RICH_TEXT_SCHEMA.tableSpans`: 20 horizontally and 50 vertically; 1 is not written) and `data-align` (column alignment, `center` or `right`, on every cell of the column). A row fully covered by a merge from above stays an empty `<tr></tr>`. `thead`, `tfoot`, `colgroup` and `caption` are not in the Schema, and **by default DOMPurify removes the contents of `thead` and `colgroup` along with the tags**: the header of a third-party table written in `thead` disappears. The Editor does not write `thead`, so for its HTML this is no loss. `tbody`, `tr`, `th` and `td` outside a table are dropped by the HTML parser before sanitizing (the text stays), `style` and `colwidth` are not allowed through, and `colspan` and `rowspan` are let through only by the `sanitizeRichText` hook: on `th` and `td`, and only as digits within the Schema's limits; the bare config does not know them at all. Without such a check, `colspan="10000"` in third-party HTML would expand into 10,000 empty cells for everyone who opens the Document.
|
|
105
105
|
|
|
106
|
-
There are no other classes, `style` (except the custom color, see below), `aria-*` or `data-*` (except eight Schema names: `data-type` and `data-checked` of the checklist, `data-color` for color, `data-align` for alignment, `data-width` for image width, `data-block` and `data-layout` of the
|
|
106
|
+
There are no other classes, `style` (except the custom color, see below), `aria-*` or `data-*` (except eight Schema names: `data-type` of the checklist and of the content wrapper of a collapsible Block, `data-checked` of the checklist, `data-color` for color, `data-align` for alignment, `data-width` for image width, `data-block` and `data-layout` of the Column layout, and `data-tone` of the Wrapper) in the output HTML. An `h1` appears in a Document only if the Editor has the `heading1` Tool (on by default in `full`); a screen that puts an H1 above the Editor turns this Tool off.
|
|
107
107
|
|
|
108
108
|
**An image** is written by the Editor like this: `<figure><img src="https://…" alt="…" data-width="50"><figcaption>…</figcaption></figure>`. `figcaption` is present only when the caption is not empty, `data-width` only on an image with a set width (`50`, `75` or `100` percent of the Document width, `RICH_TEXT_SCHEMA.imageWidths`; without it, the image keeps its own size, no wider than the Document), and `alt` is always present and empty for an image without a description. The URL must be an absolute `http` or `https` URL (`RICH_TEXT_SCHEMA.imageProtocols`): the Editor never writes `data:` (base64) or `blob:`, and an image never has `style`, `width`, `height` or `class` — neither on `img` nor on `figure`. The Editor does not output a bare `<img>` outside `figure`; `sanitizeRichText` removes it.
|
|
109
109
|
|
|
@@ -153,7 +153,7 @@ Checked with the real DOMPurify 3.4.16 on jsdom in `tests/editor-sanitize.test.t
|
|
|
153
153
|
|
|
154
154
|
#### Custom Tools: the third argument
|
|
155
155
|
|
|
156
|
-
If the Editor works with custom Tools ([RichTextEditor, "Custom Tool"](../inputs/rich-text-editor.md#свой-инструмент)), the server passes **the same `tools` array** to `sanitizeRichText`. The description of a custom Tool is data, and the module with it is imported on the server: sanitizing reads only the Schema rules (`blocks`, `marks`) and does not touch the
|
|
156
|
+
If the Editor works with custom Tools ([RichTextEditor, "Custom Tool"](../inputs/rich-text-editor.md#свой-инструмент)), the server passes **the same `tools` array** to `sanitizeRichText`. The description of a custom Tool is data, and the module with it is imported on the server: sanitizing reads only the Schema rules (`blocks`, `marks`) and does not touch the Extensions. To keep Tiptap out of the server graph too, keep the description in a module with no extension imports — the Extensions are then supplied by a Loader ([RichTextEditor, "Extensions Loader"](../inputs/rich-text-editor.md#загрузчик-extensions)) or built next to the Editor.
|
|
157
157
|
|
|
158
158
|
```ts
|
|
159
159
|
import { sanitizeRichText } from "@olddevs/ui/editor/schema";
|
|
@@ -167,7 +167,7 @@ export function sanitizeDocument(html: string): string {
|
|
|
167
167
|
- **A custom Block** comes through as `div[data-block="<name>"]`, an inline Atom as `span[data-block="<name>"]`, a custom Mark as `span[data-mark="<name>"]`. A Block of the wrong kind (an Atom in a `div`, a block-level Block in a `span`) and an unknown name lose their tag; the text stays.
|
|
168
168
|
- **Custom attributes** `data-<name>` — only on their own element and only with a value that follows the rule: from a list, matching a pattern, or a URL that follows the link policy (absolute `http`, `https`, `mailto`). A value with markup (`<`) is never allowed through. An invalid value is stripped together with the attribute, and the element itself stays — the Editor does the same.
|
|
169
169
|
- **Anything extra on a custom Block's element** — `class`, `style`, `data-color`, handlers, an attribute of another Block — are stripped.
|
|
170
|
-
- **The `top` slot** — only the Document's top level: a nested Block (in a quote, a Wrapper, another Block) loses its tag, like a
|
|
170
|
+
- **The `top` slot** — only the Document's top level: a nested Block (in a quote, a Wrapper, another Block) loses its tag, like a Column layout.
|
|
171
171
|
- **Names of the kit's Tools do not narrow the allowlist.** `["bold", acmeCard]` sanitizes the same as `[...RICH_TEXT_PRESETS.full, acmeCard]`: screens with different sets write one Document, and the server must not strip a table that a `full` screen wrote. The server ignores rules with a name that lacks a Consumer prefix: the Editor rejects such a set with a field error, and on the server it must not create a way around the kit's rules.
|
|
172
172
|
- **Without the third argument** sanitizing is the same as before custom Tools: custom Blocks are stripped like any other third-party HTML.
|
|
173
173
|
|
|
@@ -231,11 +231,11 @@ The cost of this choice:
|
|
|
231
231
|
- **Table semantics.** On a `table` with `display: block`, some screen readers lose rows and columns. This was fixed long ago in Chrome and Firefox; in Safari with VoiceOver it depends on the version; check with a manual run on your versions. The alternative — `table-layout: fixed` without scrolling — keeps the semantics, but makes all columns equal and narrow and breaks long words, which is worse on a phone.
|
|
232
232
|
- **Focus on a scrollable region.** The axe rule `scrollable-region-focusable` requires that a scrollable block be reachable from the keyboard. In the Editor every cell has a caret, but `RichTextView` has no focusable content: the rule fires on a table that is wider than the screen. This can be fixed only with a container that has `tabindex="0"`, and such a container cannot be put into the saved HTML. If such tables occur on your screen, wrap `RichTextView` in your own container with `overflow-x: auto` and `tabindex="0"`, or keep tables narrow.
|
|
233
233
|
|
|
234
|
-
##
|
|
234
|
+
## Column layout
|
|
235
235
|
|
|
236
|
-
The Editor's
|
|
236
|
+
The Editor's Column layout (`div[data-block="layout"]` with the variant in `data-layout` and columns `div[data-block="column"]`, section "Column layout" of the [RichTextEditor](../inputs/rich-text-editor.md#раскладка) page) is laid out on the same grid as in the Editor, by `prose.ts` classes on the container, with no `style` in the markup. Columns have no borders: the dashed outline is a cue for the author in the Editor, and the reader needs no boundary. Columns sit side by side when the Document itself is wide (two from 30 rem, three from 40 rem, four from 48 rem), and in a single column in reading order, left to right, when it is narrower. The threshold is a container query on the Document's width: the `RichTextView` root with a Column layout becomes a container (`container-type: inline-size`), so in a shrinking context (a flex row item with no width) give it a width. On Paper it is the same grid.
|
|
237
237
|
|
|
238
|
-
`sanitizeRichText` keeps a
|
|
238
|
+
`sanitizeRichText` keeps a Column layout where the Editor writes it: only at the top level, with a variant from `RICH_TEXT_SCHEMA.layouts`, and columns only directly inside it; otherwise the wrappers are stripped and the Blocks stay one after another.
|
|
239
239
|
|
|
240
240
|
## Wrapper
|
|
241
241
|
|
|
@@ -243,7 +243,13 @@ The Editor's Wrapper (`div[data-block="wrapper"]` with Blocks inside, section "W
|
|
|
243
243
|
|
|
244
244
|
**A Wrapper with a tone** (`data-tone`: `accent`, `success`, `neutral`, `warning` or `danger`) is visible to the reader too — **in the same look as in the Editor**: a soft tone backing and a 3 px stripe on the left, the text is ordinary Document text, with inner padding like `Alert`. The colors are the `--rte-tone-*` roles of the `prose.ts` classes, with no `style` in the markup; on the Paper they are the light theme's values. The tone adds no ARIA role or `aria-*`: the meaning of the note is carried by a word in its text ("Warning: …"), not by color. `RichTextView` does not style a tone outside these five names — `sanitizeRichText` strips it on the server. The palette, contrast and Markdown are in the section ["Wrapper", "Tone"](../inputs/rich-text-editor.md#тон).
|
|
245
245
|
|
|
246
|
-
`sanitizeRichText` keeps a Wrapper where the Editor writes it: at the top level and directly in a
|
|
246
|
+
`sanitizeRichText` keeps a Wrapper where the Editor writes it: at the top level and directly in a column of a Column layout, not in another Wrapper, list, quote or table; otherwise the Wrapper is stripped and the Blocks stay one after another. A Column layout inside a Wrapper is unwrapped into its Blocks.
|
|
247
|
+
|
|
248
|
+
## Collapsible block
|
|
249
|
+
|
|
250
|
+
A collapsible Block of the Editor (the `details` Tool, the "Collapsible block" section of the [RichTextEditor](../inputs/rich-text-editor.md#сворачиваемый-блок) page) is shown as **a native `details` and `summary`**: it needs neither the engine nor JavaScript, the browser itself opens and closes it, from the mouse and from the keyboard (Tab, Enter, Space), and a screen reader reads it as a disclosure element — the kit adds no roles or `aria-*` to the markup. The Block arrives closed; it is open only if it was saved with `open` (the Editor's `persist` option). The frame, the padding and the semibold title are the same as in the Editor, the `summary` marker is the browser's native one, and the Blocks in the content keep the Document's rhythm. The content wrapper `div[data-type="detailsContent"]` has no look of its own.
|
|
251
|
+
|
|
252
|
+
`sanitizeRichText` keeps the Block in the shape the Editor writes: `summary` and the content wrapper only directly in `details` (otherwise the tag is removed and the Blocks stay in a row), `open` only empty and only on `details`; `iframe` is not opened this way. A Column layout or a Wrapper inside the content unwraps into Blocks.
|
|
247
253
|
|
|
248
254
|
## Custom Block
|
|
249
255
|
|
|
@@ -259,7 +265,7 @@ The look comes from the consumer's CSS, which targets the `data-block` selector
|
|
|
259
265
|
}
|
|
260
266
|
```
|
|
261
267
|
|
|
262
|
-
A node view from the
|
|
268
|
+
A node view from the Extensions (a React view) is not used in `RichTextView`: there is no engine here. An inline Atom is shown as whatever is in the HTML: an empty `span` with no text is not visible; a Block can be brought to life in the View with an Island (the next section). The rhythm is checked by `tests/rich-text-paper-css.test.tsx` on the real cascade.
|
|
263
269
|
|
|
264
270
|
## Custom Block's Island
|
|
265
271
|
|
|
@@ -295,18 +301,18 @@ export const acmePlayer: CustomTool = {
|
|
|
295
301
|
|
|
296
302
|
**How it works and why `RichTextView` stays a server component.** It works the same way as for "Copy" (the section above): the component itself has no state, and the client piece is a separate file with its own directive.
|
|
297
303
|
|
|
298
|
-
1. The whole Tool description (
|
|
304
|
+
1. The whole Tool description (Extensions, functions) cannot cross the RSC boundary, so `RichTextView` picks only the Islands from `tools`, and only those of Blocks whose `data-block="<name>"` is in the Document (`islandsFor`, `src/editor/core/viewIsland.ts`), and passes references to client components and attribute names to the Island. If there is no `tools`, no `island` on the Block, or no such Block in the Document, there is no Island at all: no anchor `<template>` in the markup, no effect, no loading of the consumer's Island code (the server serializes a reference to a client component only when the Block is there). `island` must be a component from a module with `"use client"`: a server component or a function with a dynamic import is not passed to a client Island.
|
|
299
305
|
2. The Block's server HTML is always in the page. Before hydration, before the Island loads and without JS, the reader sees it — that is, the Block's content as it was saved.
|
|
300
306
|
3. After mounting, the Island (`src/editor/ui/BlockIsland.tsx` and the implementation in `BlockIslandSlots.tsx`, with its own `"use client"`) finds the Blocks of **its own** View the same way "Copy" finds its Mount points: going left from its anchor `<template>`, not through the global `document` — a View in a shadow root or an `iframe` works. In every occurrence of the Block, the content is replaced by the Island; when `tools` changes (the Island is gone) or the Block goes away, the content comes back. If a Block with an Island sits inside another Block with an Island, the outer one comes to life: the component gets its whole text, and the inner one stays as its content.
|
|
301
307
|
4. The Island's state is not reset by a parent re-render: the Island looks for Blocks again only when the Document changed, not `tools` (an inline array literal in the parent does not remount the Island). For this, `RichTextView` keeps the `dangerouslySetInnerHTML` object stable (`useMemo` on the Document): a new object on every re-render would make React rewrite the markup together with the inserted Islands and "Copy" buttons.
|
|
302
308
|
|
|
303
|
-
**The Island is a separate component**, not the
|
|
309
|
+
**The Island is a separate component**, not the React view from the Extensions; the Editor does not read the `island` field.
|
|
304
310
|
|
|
305
311
|
**Cost.** The Island implementation (`BlockIslandSlots.tsx`) is loaded lazily (`React.lazy`) and is not part of the View's first load: while the chunk is in flight, the Block's server HTML is visible. The static `editorView` measurement includes only the shell and the choice of Islands — about 0.2 kB gzip; Tiptap is still not in the View's graph, which `tests/rich-text-view.test.tsx` checks.
|
|
306
312
|
|
|
307
313
|
## Accessibility
|
|
308
314
|
|
|
309
|
-
The component adds no roles: it is a regular `div` with semantic HTML inside — headings, lists, a quote. The Document's headings start with H2 if the Editor worked without `heading1`: the page keeps the H1 itself. A Document with `heading1` contains its own `h1`, and then the page does not put in a second one. An H1 in `RichTextView` is styled with the same typography as in the Editor. If the Document is a separate area of the page, give the container a name through `aria-label` and `role="region"`.
|
|
315
|
+
The component adds no roles: it is a regular `div` with semantic HTML inside — headings, lists, a quote. A collapsible Block is a native `details` and `summary`: opening from the keyboard (Tab, Enter, Space) and announcing the state are the browser's job. The Document's headings start with H2 if the Editor worked without `heading1`: the page keeps the H1 itself. A Document with `heading1` contains its own `h1`, and then the page does not put in a second one. An H1 in `RichTextView` is styled with the same typography as in the Editor. If the Document is a separate area of the page, give the container a name through `aria-label` and `role="region"`.
|
|
310
316
|
|
|
311
317
|
## Anti-patterns
|
|
312
318
|
|
|
@@ -92,17 +92,17 @@ export default async function ArticlePage({ id }: { id: string }) {
|
|
|
92
92
|
|
|
93
93
|
<!-- allow-list -->
|
|
94
94
|
|
|
95
|
-
| Что | Разрешено
|
|
96
|
-
| -------- |
|
|
97
|
-
| Блоки | `p`, `h1`, `h2`, `h3`, `h4`, `ul`, `ol`, `li`, `blockquote`, `hr`, `pre`, `table`, `tbody`, `tr`, `th`, `td`; `label`, `input`, `span` — только внутри пункта чек-листа; `div` — там же, а ещё Раскладка и её колонки (Раскладка — только на верхнем уровне, колонка — только прямо в ней) и Обёртка (на верхнем уровне и прямо в колонке, не в другой Обёртке); `figure` — картинка, внутри неё `img` и необязательная `figcaption` (вне `figure` их нет)
|
|
98
|
-
| Метки | `strong`, `em`, `u`, `s`, `code`, `sup`, `sub`, `mark` (только с цветом), `br`, `a`
|
|
99
|
-
| Атрибуты | `ol[start]` — только когда нумерация не с 1; `ul[data-type]`, `li[data-type]`, `li[data-checked]` — чек-лист; `input[type]`, `input[checked]` — его флажок; `code[class]` — язык блока кода, только «language-python», «language-typescript», «language-javascript», «language-go», «language-html», «language-json», «language-yaml» или «language-markdown» (без языка класса нет); `span[data-color]`, `mark[data-color]` — цвет текста и маркер, только имена палитры этого тега: на `span` цвета текста, на `mark` маркера; `span[style]`, `mark[style]` — произвольный цвет автора, только «color:#rrggbb» и «background-color:#rrggbb», и не вместе с «data-color»; `p[data-align]`, `h1[data-align]`, `h2[data-align]`, `h3[data-align]`, `h4[data-align]` — выравнивание Блока, по центру или справа; `th[colspan]`, `td[colspan]` — слияние по горизонтали, целое от 2 до 20; `th[rowspan]`, `td[rowspan]` — по вертикали, от 2 до 50; `th[data-align]`, `td[data-align]` — выравнивание столбца, по центру или справа, на каждой его ячейке; `a[href]` — только «http», «https» или «mailto»; `a[rel]` — всегда «noopener noreferrer»; `img[src]` — только абсолютный «http» или «https», без «data:» и «blob:»; `img[alt]` — всегда, пустой у картинки без описания; `img[data-width]` — 50, 75 или 100; `div[data-block]` — Раскладка («layout»), её колонка («column») и Обёртка («wrapper»); `div[data-layout]` — вариант Раскладки, доли колонок в процентах: «100», «50-50», «33-33-33», «25-25-25-25», «60-40» или «40-60»; `div[data-tone]` — тон Обёртки, только на ней: «neutral», «accent», «success», «warning» или «danger» (без тона атрибута нет) |
|
|
95
|
+
| Что | Разрешено |
|
|
96
|
+
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
97
|
+
| Блоки | `p`, `h1`, `h2`, `h3`, `h4`, `ul`, `ol`, `li`, `blockquote`, `hr`, `pre`, `table`, `tbody`, `tr`, `th`, `td`; `label`, `input`, `span` — только внутри пункта чек-листа; `div` — там же, а ещё Раскладка и её колонки (Раскладка — только на верхнем уровне, колонка — только прямо в ней) и Обёртка (на верхнем уровне и прямо в колонке, не в другой Обёртке); `details` — сворачиваемый Блок, внутри него `summary` и `div` содержимого (оба — только прямо в `details`; вне него теги снимаются); `figure` — картинка, внутри неё `img` и необязательная `figcaption` (вне `figure` их нет) |
|
|
98
|
+
| Метки | `strong`, `em`, `u`, `s`, `code`, `sup`, `sub`, `mark` (только с цветом), `br`, `a` |
|
|
99
|
+
| Атрибуты | `ol[start]` — только когда нумерация не с 1; `ul[data-type]`, `li[data-type]`, `li[data-checked]` — чек-лист; `input[type]`, `input[checked]` — его флажок; `code[class]` — язык блока кода, только «language-python», «language-typescript», «language-javascript», «language-go», «language-html», «language-json», «language-yaml» или «language-markdown» (без языка класса нет); `span[data-color]`, `mark[data-color]` — цвет текста и маркер, только имена палитры этого тега: на `span` цвета текста, на `mark` маркера; `span[style]`, `mark[style]` — произвольный цвет автора, только «color:#rrggbb» и «background-color:#rrggbb», и не вместе с «data-color»; `p[data-align]`, `h1[data-align]`, `h2[data-align]`, `h3[data-align]`, `h4[data-align]` — выравнивание Блока, по центру или справа; `th[colspan]`, `td[colspan]` — слияние по горизонтали, целое от 2 до 20; `th[rowspan]`, `td[rowspan]` — по вертикали, от 2 до 50; `th[data-align]`, `td[data-align]` — выравнивание столбца, по центру или справа, на каждой его ячейке; `a[href]` — только «http», «https» или «mailto»; `a[rel]` — всегда «noopener noreferrer»; `img[src]` — только абсолютный «http» или «https», без «data:» и «blob:»; `img[alt]` — всегда, пустой у картинки без описания; `img[data-width]` — 50, 75 или 100; `div[data-block]` — Раскладка («layout»), её колонка («column») и Обёртка («wrapper»); `div[data-layout]` — вариант Раскладки, доли колонок в процентах: «100», «50-50», «33-33-33», «25-25-25-25», «60-40» или «40-60»; `div[data-tone]` — тон Обёртки, только на ней: «neutral», «accent», «success», «warning» или «danger» (без тона атрибута нет); `div[data-type]` — обёртка содержимого сворачиваемого Блока, только значение «detailsContent» и только прямо в `details`; `details[open]` — открытый Блок, только пустой «open» |
|
|
100
100
|
|
|
101
101
|
Пункт чек-листа Tiptap пишет так: `<li data-type="taskItem" data-checked="true"><label><input type="checkbox" checked><span></span></label><div><p>…</p></div></li>`. `sanitizeRichText` пускает `input`, `label`, `span` и `div` только внутри такого пункта, а `input` — только с `type="checkbox"`.
|
|
102
102
|
|
|
103
103
|
Таблицу Редактор пишет так: `<table><tbody><tr><th><p>…</p></th></tr><tr><td colspan="2" data-align="center"><p>…</p></td></tr></tbody></table>`. Строка-заголовок — первая строка из `th`, а у ячейки бывают только три атрибута: `colspan` и `rowspan` (слияние, целое от 2 до `RICH_TEXT_SCHEMA.tableSpans`: 20 по горизонтали и 50 по вертикали; 1 не пишется) и `data-align` (выравнивание столбца, `center` или `right`, на каждой ячейке столбца). Строка, целиком накрытая слиянием сверху, остаётся пустой `<tr></tr>`. `thead`, `tfoot`, `colgroup`, `caption` в Схеме нет, и **DOMPurify по умолчанию вместе с `thead` и `colgroup` убирает и их содержимое**: заголовок таблицы, пришедшей не из Редактора, записанный в `thead`, пропадёт. Редактор `thead` не пишет, так что для его HTML это не потеря. `tbody`, `tr`, `th` и `td` вне таблицы парсер HTML выбрасывает до очистки (текст остаётся), `style` и `colwidth` не проходят, а `colspan` и `rowspan` пропускает только хук `sanitizeRichText`: на `th` и `td` и только цифрами в пределах Схемы; голый конфиг их не знает вовсе. Без такой проверки `colspan="10000"` из HTML не от Редактора развернётся в 10 тыс. пустых ячеек у каждого, кто откроет Документ.
|
|
104
104
|
|
|
105
|
-
Других классов, `style` (кроме произвольного цвета, см. ниже), `aria-*` и `data-*` (кроме восьми имён Схемы: `data-type` и `data-checked` чек-листа, `data-color` цвета, `data-align` выравнивания, `data-width` ширины картинки, `data-block` и `data-layout` Раскладки и `data-tone` Обёртки) в отдаваемом HTML нет. `h1` в Документе бывает только у Редактора с Инструментом `heading1` (по умолчанию в `full`); экран, у которого H1 стоит над Редактором, этот Инструмент выключает.
|
|
105
|
+
Других классов, `style` (кроме произвольного цвета, см. ниже), `aria-*` и `data-*` (кроме восьми имён Схемы: `data-type` чек-листа и обёртки содержимого сворачиваемого Блока, `data-checked` чек-листа, `data-color` цвета, `data-align` выравнивания, `data-width` ширины картинки, `data-block` и `data-layout` Раскладки и `data-tone` Обёртки) в отдаваемом HTML нет. `h1` в Документе бывает только у Редактора с Инструментом `heading1` (по умолчанию в `full`); экран, у которого H1 стоит над Редактором, этот Инструмент выключает.
|
|
106
106
|
|
|
107
107
|
**Картинку** Редактор пишет так: `<figure><img src="https://…" alt="…" data-width="50"><figcaption>…</figcaption></figure>`. `figcaption` есть, только когда подпись не пуста, `data-width` есть только у картинки с заданной шириной (`50`, `75` или `100` процентов ширины Документа, `RICH_TEXT_SCHEMA.imageWidths`; без него размер у картинки свой, не шире Документа), `alt` есть всегда и пуст у картинки без описания. Адрес может быть только абсолютным `http` или `https` (`RICH_TEXT_SCHEMA.imageProtocols`): `data:` (base64) и `blob:` Редактор не пишет никогда, а `style`, `width`, `height` и `class` у картинки не бывает ни у `img`, ни у `figure`. Голый `<img>` вне `figure` Редактор не отдаёт; `sanitizeRichText` его удаляет.
|
|
108
108
|
|
|
@@ -152,7 +152,7 @@ export function sanitizeDocument(html: string): string {
|
|
|
152
152
|
|
|
153
153
|
#### Свои Инструменты: третий аргумент
|
|
154
154
|
|
|
155
|
-
Если Редактор работает со своими Инструментами ([RichTextEditor, «Свой Инструмент»](../inputs/rich-text-editor.md#свой-инструмент)), сервер передаёт очистке **тот же массив `tools`**. Описание своего Инструмента — данные, и модуль с ним импортируется на сервере: очистка читает только правила Схемы (`blocks`, `marks`),
|
|
155
|
+
Если Редактор работает со своими Инструментами ([RichTextEditor, «Свой Инструмент»](../inputs/rich-text-editor.md#свой-инструмент)), сервер передаёт очистке **тот же массив `tools`**. Описание своего Инструмента — данные, и модуль с ним импортируется на сервере: очистка читает только правила Схемы (`blocks`, `marks`), Extensions не трогает. Чтобы в графе сервера не оказался и Tiptap, держите описание в модуле без импорта расширений — Extensions тогда подаются Загрузчиком ([RichTextEditor, «Загрузчик Extensions»](../inputs/rich-text-editor.md#загрузчик-extensions)) или собираются рядом с Редактором.
|
|
156
156
|
|
|
157
157
|
```ts
|
|
158
158
|
import { sanitizeRichText } from "@olddevs/ui/editor/schema";
|
|
@@ -244,6 +244,12 @@ export default async function ArticlePage({ id }: { id: string }) {
|
|
|
244
244
|
|
|
245
245
|
`sanitizeRichText` держит Обёртку там, где её пишет Редактор: на верхнем уровне и прямо в колонке Раскладки, не в другой Обёртке, списке, цитате и таблице; иначе Обёртка снимается, Блоки остаются подряд. Раскладка внутри Обёртки разворачивается в Блоки.
|
|
246
246
|
|
|
247
|
+
## Сворачиваемый Блок
|
|
248
|
+
|
|
249
|
+
Сворачиваемый Блок Редактора (Инструмент `details`, раздел «Сворачиваемый Блок» страницы [RichTextEditor](../inputs/rich-text-editor.md#сворачиваемый-блок)) в показе — **родные `details` и `summary`**: движка и JavaScript для него не нужно, раскрывает и сворачивает сам браузер, с мыши и с клавиатуры (Tab, Enter, пробел), а скринридер читает его как раскрывающийся элемент — ни ролей, ни `aria-*` кит в разметку не добавляет. Блок приходит закрытым; открытым — только тот, что сохранён с `open` (Опция `persist` Редактора). Рамка, отступ и полужирный заголовок — те же, что в Редакторе, маркер `summary` — родной маркер браузера, а Блоки содержимого идут с тем же ритмом, что в Документе. Обёртка содержимого `div[data-type="detailsContent"]` вида не имеет.
|
|
250
|
+
|
|
251
|
+
`sanitizeRichText` держит Блок в форме, которую пишет Редактор: `summary` и обёртка содержимого — только прямо в `details` (иначе тег снимается, Блоки остаются подряд), `open` — только пустой и только на `details`; `iframe` так не открывается. Раскладка или Обёртка внутри содержимого разворачивается в Блоки.
|
|
252
|
+
|
|
247
253
|
## Свой Блок
|
|
248
254
|
|
|
249
255
|
Свой Блок потребителя (`div[data-block="acme_card"]`, раздел «Свой Инструмент» страницы [RichTextEditor](../inputs/rich-text-editor.md#свой-инструмент)) `RichTextView` показывает очищенным HTML и даёт ему **тот же вертикальный ритм, что у абзаца, и больше ничего**: отступ до следующего Блока такой же, как у абзаца на том же месте (в Документе, цитате, ячейке, колонке). Блоки внутри своего Блока-контейнера стоят с ритмом Документа, как в Обёртке; строчное содержимое (текст, Атомы, Метки) отступов не получает. На Листе `surface="paper"` — то же.
|
|
@@ -258,7 +264,7 @@ export default async function ArticlePage({ id }: { id: string }) {
|
|
|
258
264
|
}
|
|
259
265
|
```
|
|
260
266
|
|
|
261
|
-
Вид узла из
|
|
267
|
+
Вид узла из Extensions (React-вид) в `RichTextView` не используется: движка здесь нет. Строчный Атом показывается тем, что лежит в HTML: пустой `span` без текста не виден; оживить Блок в показе можно Островом (раздел ниже). Ритм сверяет `tests/rich-text-paper-css.test.tsx` на настоящем каскаде.
|
|
262
268
|
|
|
263
269
|
## Остров своего Блока
|
|
264
270
|
|
|
@@ -294,18 +300,18 @@ export const acmePlayer: CustomTool = {
|
|
|
294
300
|
|
|
295
301
|
**Как это устроено и почему `RichTextView` остаётся серверным.** Так же, как у «Копировать» (раздел выше): сам компонент без состояния, клиентский кусок — отдельный файл со своей директивой.
|
|
296
302
|
|
|
297
|
-
1. Описание Инструмента целиком (
|
|
303
|
+
1. Описание Инструмента целиком (Extensions, функции) через границу RSC не пройдёт, поэтому `RichTextView` выбирает из `tools` только Острова, причём только тех Блоков, чей `data-block="<имя>"` есть в Документе (`islandsFor`, `src/editor/core/viewIsland.ts`), и отдаёт Острову ссылки на клиентские компоненты и имена атрибутов. Если нет `tools`, нет `island` у Блока или самого Блока в Документе, Острова нет вовсе: ни опорного `<template>` в разметке, ни эффекта, ни загрузки кода Острова потребителя (сервер сериализует ссылку на клиентский компонент, только когда Блок есть). `island` обязан быть компонентом из модуля с `"use client"`: серверный компонент или функция с динамическим импортом в клиентский Остров не передаются.
|
|
298
304
|
2. Серверный HTML Блока приходит всегда. До гидрации, до загрузки Острова и без JS читатель видит его — то есть содержимое Блока как оно сохранено.
|
|
299
305
|
3. После монтирования Остров (`src/editor/ui/BlockIsland.tsx` и тело `BlockIslandSlots.tsx`, свой `"use client"`) находит Блоки **своего** Просмотра так же, как «Копировать» находит гнёзда: от своего опорного `<template>` влево, а не по глобальному `document` — Просмотр в теневом корне или `iframe` работает. В каждом вхождении Блока содержимое заменяется Островом; при смене `tools` (Остров пропал) или уходе Блока содержимое возвращается. Если Блок с Островом стоит внутри другого Блока с Островом, оживает внешний: компонент получает его текст целиком, а внутренний остаётся его содержимым.
|
|
300
306
|
4. Состояние Острова не сбрасывается перерисовкой родителя: Остров ищет Блоки заново только когда сменился Документ, а не `tools` (инлайновый литерал массива у родителя не перемонтирует Остров). Для этого `RichTextView` держит объект `dangerouslySetInnerHTML` стабильным (`useMemo` по Документу): новый объект на каждой перерисовке заставил бы React переписать разметку вместе со вставленными Островами и кнопками «Копировать».
|
|
301
307
|
|
|
302
|
-
**Остров — отдельный компонент**, а не React-вид
|
|
308
|
+
**Остров — отдельный компонент**, а не React-вид из Extensions; Редактор поле `island` не читает.
|
|
303
309
|
|
|
304
310
|
**Цена.** Тело Острова (`BlockIslandSlots.tsx`) подключается лениво (`React.lazy`) и в первую загрузку Просмотра не входит: пока чанк идёт, виден серверный HTML Блока. В статический замер `editorView` попадает только оболочка и выбор Островов — около 0,2 кБ gzip; Tiptap в графе Просмотра по-прежнему нет, это проверяет `tests/rich-text-view.test.tsx`.
|
|
305
311
|
|
|
306
312
|
## Доступность
|
|
307
313
|
|
|
308
|
-
Компонент не добавляет ролей: это обычный `div` с семантическим HTML внутри — заголовки, списки, цитата. Заголовки Документа начинаются с H2, если Редактор работал без `heading1`: страница сама держит H1. Документ с `heading1` содержит собственный `h1`, и тогда страница своего второго не ставит. H1 в `RichTextView` рисуется той же типографикой, что в Редакторе. Если Документ — отдельная область страницы, дайте контейнеру имя через `aria-label` и `role="region"`.
|
|
314
|
+
Компонент не добавляет ролей: это обычный `div` с семантическим HTML внутри — заголовки, списки, цитата. Сворачиваемый Блок — родные `details` и `summary`: раскрытие с клавиатуры (Tab, Enter, пробел) и озвучивание состояния даёт браузер. Заголовки Документа начинаются с H2, если Редактор работал без `heading1`: страница сама держит H1. Документ с `heading1` содержит собственный `h1`, и тогда страница своего второго не ставит. H1 в `RichTextView` рисуется той же типографикой, что в Редакторе. Если Документ — отдельная область страницы, дайте контейнеру имя через `aria-label` и `role="region"`.
|
|
309
315
|
|
|
310
316
|
## Антипаттерны
|
|
311
317
|
|
|
@@ -234,7 +234,7 @@ leaves the cell selected.
|
|
|
234
234
|
|
|
235
235
|
The cell's `contextMenu` field (`items[n].contextMenu`) holds the menu items: `ContextMenuItem`,
|
|
236
236
|
`ContextMenuSeparator`, `ContextMenuSub` and the rest of the
|
|
237
|
-
[`ContextMenu`](../overlays/context-menu.md) family. The grid sets the panel, the trigger and the
|
|
237
|
+
[`ContextMenu`](../overlays/context-menu.md) component family. The grid sets the panel, the trigger and the
|
|
238
238
|
isolation from selection itself, so there is no need to wrap the cell in `ContextMenu` by hand.
|
|
239
239
|
|
|
240
240
|
```tsx
|
|
@@ -276,7 +276,7 @@ and the drag does not start.
|
|
|
276
276
|
|
|
277
277
|
**Why not `ContextMenu` around `content`.** That is how it was done before the field appeared, and it
|
|
278
278
|
produced two failures. First: a right click works, but Shift+F10 does not, because focus is on the
|
|
279
|
-
cell while the menu
|
|
279
|
+
cell while the menu button is inside it. Second: events from the panel, which is rendered in a
|
|
280
280
|
portal, bubble up the React tree to the cell and select it. Both failures are now closed inside the
|
|
281
281
|
grid.
|
|
282
282
|
|
|
@@ -44,7 +44,7 @@ sourceHash: 389a684cb62f
|
|
|
44
44
|
- **The `FormField` hint and error are announced together with the field.** The
|
|
45
45
|
description is passed through context, and `aria-describedby` is set on the
|
|
46
46
|
trigger; the consumer's `aria-describedby` value is not overridden but merged
|
|
47
|
-
with it. Before version 3.0.0 the react-aria families did not pick up the form description — it was on
|
|
47
|
+
with it. Before version 3.0.0 the react-aria component families did not pick up the form description — it was on
|
|
48
48
|
screen but never reached the screen reader
|
|
49
49
|
(ADR-0031).
|
|
50
50
|
- The popover uses `Calendar` with its grid and keyboard contract; it closes on selection.
|