@localess/richtext 4.0.1-dev.20260915143617 → 4.0.1-dev.20260916085732
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/README.md +136 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<br/>
|
|
2
|
+
<br/>
|
|
3
|
+
<img src="https://github.com/Lessify/localess/wiki/img/logo-adaptive.svg" alt="logo">
|
|
4
|
+
<br/>
|
|
5
|
+
<br/>
|
|
6
|
+
|
|
7
|
+
----
|
|
8
|
+
|
|
9
|
+
# @localess/richtext
|
|
10
|
+
|
|
11
|
+
The framework-neutral rich text layer for [Localess](https://github.com/Lessify/localess): a precise node/mark model for the JSON the Localess Studio editor produces, an HTML renderer, and HTML/Markdown parsers that turn existing content back into that model.
|
|
12
|
+
|
|
13
|
+
Every framework package (`@localess/react`, `@localess/angular`, `@localess/vue`, `@localess/svelte`, `@localess/astro`) renders rich text through this package and ships its own `<LocalessRichText>` component on top of it. Use `@localess/richtext` directly when you need HTML on the server, are integrating a framework we don't ship, or are importing content from elsewhere.
|
|
14
|
+
|
|
15
|
+
**No TipTap at runtime.** The editor's JSON format is modelled here as plain types; TipTap appears only as a `devDependency`, used by a parity test that proves this renderer agrees with the editor. Your bundle never sees it.
|
|
16
|
+
|
|
17
|
+
**Zero external dependencies** — `@localess/model` is the only entry in `dependencies`. See [ADR 007](../../docs/decisions/007-shared-richtext-package.md).
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- Node.js >= 24.0.0
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# npm
|
|
27
|
+
npm install @localess/richtext
|
|
28
|
+
|
|
29
|
+
# yarn
|
|
30
|
+
yarn add @localess/richtext
|
|
31
|
+
|
|
32
|
+
# pnpm
|
|
33
|
+
pnpm add @localess/richtext
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Rendering to HTML
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { renderRichTextToHtml } from '@localess/richtext';
|
|
42
|
+
|
|
43
|
+
const html = renderRichTextToHtml(content.data.content);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`renderRichTextToHtml(input, options?)` accepts a whole document, a single node, an array of nodes, or the loose `ContentRichText` from `@localess/model` — and returns `''` for `null`/`undefined`, so you don't need to guard an empty field.
|
|
47
|
+
|
|
48
|
+
**Supported:** headings (h1–h6), paragraphs, bold, italic, strikethrough, underline, code, code blocks, ordered and unordered lists, and links. Link `href`s pass a protocol allowlist — `javascript:` and `data:` are stripped. Unknown node types are skipped rather than throwing.
|
|
49
|
+
|
|
50
|
+
### Overriding a node type
|
|
51
|
+
|
|
52
|
+
Pass `renderers` to replace how one type is rendered — node types and mark types alike. A renderer receives the node's own `attrs`, `text` and `marks` plus its already-rendered `children`, so you only describe the wrapper:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const html = renderRichTextToHtml(content.data.content, {
|
|
56
|
+
renderers: {
|
|
57
|
+
heading: ({ attrs, children }) => `<h${attrs?.level} class="font-bold">${children}</h${attrs?.level}>`,
|
|
58
|
+
link: ({ attrs, children }) => `<a href="${attrs?.href}" rel="noopener">${children}</a>`,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## The document model
|
|
66
|
+
|
|
67
|
+
`LocalessRichTextDocument` is the exact shape the Studio editor emits. Typing a field with it — rather than `@localess/model`'s deliberately loose `ContentRichText` — gives you real autocomplete over nodes and marks. From the [schema playground](../../playgrounds/schema):
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import type { LocalessRichTextDocument } from '@localess/richtext';
|
|
71
|
+
|
|
72
|
+
const doc: LocalessRichTextDocument = {
|
|
73
|
+
type: 'doc',
|
|
74
|
+
content: [
|
|
75
|
+
{ type: 'heading', attrs: { level: 1 }, content: [{ type: 'text', text: 'H1' }] },
|
|
76
|
+
{ type: 'paragraph', content: [{ type: 'text', text: 'paragraph' }] },
|
|
77
|
+
{ type: 'paragraph', content: [{ type: 'text', marks: [{ type: 'bold' }], text: 'Bold' }] },
|
|
78
|
+
{
|
|
79
|
+
type: 'orderedList',
|
|
80
|
+
attrs: { start: 1 },
|
|
81
|
+
content: [
|
|
82
|
+
{ type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'First' }] }] },
|
|
83
|
+
{ type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Second' }] }] },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
{ type: 'codeBlock', content: [{ type: 'text', text: 'This is Code Block' }] },
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
| Type | Purpose |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `LocalessRichTextDocument` | A whole document — `{ type: 'doc'; content: [...] }` |
|
|
94
|
+
| `LocalessRichTextNode` | The node union: heading, paragraph, text, lists, code block, … |
|
|
95
|
+
| `LocalessRichTextMark` | The mark union: bold, italic, strike, underline, code, link |
|
|
96
|
+
| `LocalessRichTextInput` | What the renderer accepts — document, node, node array, `ContentRichText`, `null` or `undefined` |
|
|
97
|
+
| `LocalessRichTextRenderers<TOut>` | The `renderers` override map, generic over the output type |
|
|
98
|
+
|
|
99
|
+
`LocalessRichTextRenderers<TOut>` is generic because the framework packages reuse it: React renders to `ReactNode`, Vue to `VNode`, this package to `string`.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Importing existing content
|
|
104
|
+
|
|
105
|
+
Two parsers convert into the model, each behind its own subpath so you only pay for the one you use:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { parseHtmlToRichText } from '@localess/richtext/html-parser';
|
|
109
|
+
import { parseMarkdownToRichText } from '@localess/richtext/markdown-parser';
|
|
110
|
+
|
|
111
|
+
const { doc } = parseHtmlToRichText('<h1>Title</h1><p>Body</p>');
|
|
112
|
+
const { doc: fromMarkdown } = parseMarkdownToRichText('# Title\n\nBody');
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Both return `{ doc, unsupported }`. The model is closed — it matches the Studio editor exactly — so a construct with nowhere to go, like a `<table>` or a blockquote, is **reported** rather than silently lost:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const { doc, unsupported } = parseHtmlToRichText(legacyHtml);
|
|
119
|
+
// unsupported → [{ element: 'table', action: 'unwrapped', count: 347 }]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The `unsupported` option chooses the handling: `'unwrap'` keeps the inner text and drops the wrapper (the default), `'skip'` drops the element and its contents, and `'throw'` raises `RichTextParseError` naming the element. Getting a counted report back rather than only a console warning is what lets a migration surface "347 tables were unwrapped" and decide whether to proceed *before* committing a write.
|
|
123
|
+
|
|
124
|
+
`@localess/richtext/test-utils` exports `richTextFixtures`, the same fixture set this package tests against, for asserting your own renderers.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Related
|
|
129
|
+
|
|
130
|
+
- [`@localess/model`](../model) — where `ContentRichText` and the content types live
|
|
131
|
+
- [docs/richtext.md](../../docs/richtext.md) — full reference, overrides, and per-framework usage
|
|
132
|
+
- Framework components: [react](../react), [angular](../angular), [vue](../vue), [svelte](../svelte), [astro](../astro)
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
See the [Localess](https://github.com/Lessify/localess) repository.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@localess/richtext",
|
|
3
|
-
"version": "4.0.1-dev.
|
|
3
|
+
"version": "4.0.1-dev.20260916085732",
|
|
4
4
|
"description": "Framework-neutral rich text model and renderer for Localess's TipTap JSON content.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"localess",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
},
|
|
59
59
|
"license": "MIT",
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@localess/model": "4.0.1-dev.
|
|
61
|
+
"@localess/model": "4.0.1-dev.20260916085732"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@tiptap/extension-bold": "^3.22.5",
|