@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.
Files changed (2) hide show
  1. package/README.md +136 -0
  2. 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.20260915143617",
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.20260915143617"
61
+ "@localess/model": "4.0.1-dev.20260916085732"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@tiptap/extension-bold": "^3.22.5",