@localess/richtext 3.4.1-dev.20260901200805 → 4.0.0-dev.20260905071322
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/SKILL.md +66 -17
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/model.d.ts +5 -8
- package/dist/normalize.d.ts +1 -1
- package/package.json +4 -1
package/SKILL.md
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: localess-richtext
|
|
3
|
-
description: Framework-neutral rich text model and renderer for Localess TipTap JSON content. Zero dependencies. Use when rendering Localess RICH_TEXT fields to HTML or building a framework-specific rich text walker.
|
|
3
|
+
description: Framework-neutral rich text model and renderer for Localess TipTap JSON content. Zero dependencies beyond the shared @localess/model types package. Use when rendering Localess RICH_TEXT fields to HTML or building a framework-specific rich text walker.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# @localess/richtext
|
|
7
7
|
|
|
8
8
|
Renders Localess rich text field values (TipTap/ProseMirror JSON produced by
|
|
9
|
-
the Localess Studio editor) without TipTap at runtime. Zero
|
|
10
|
-
dependencies
|
|
9
|
+
the Localess Studio editor) without TipTap at runtime. Zero external
|
|
10
|
+
dependencies — its only dependency is the in-monorepo, itself-zero-dependency
|
|
11
|
+
`@localess/model` types package; safe in browsers, SSR, and edge runtimes.
|
|
12
|
+
Requires Node.js >= 24.0.0 when used server-side.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @localess/richtext
|
|
16
|
+
```
|
|
11
17
|
|
|
12
18
|
Framework packages (`@localess/react`, `@localess/vue`, `@localess/svelte`,
|
|
13
19
|
`@localess/astro`, `@localess/angular`) ship idiomatic wrappers — prefer those
|
|
@@ -23,7 +29,7 @@ const html = renderRichTextToHtml(data.body);
|
|
|
23
29
|
```
|
|
24
30
|
|
|
25
31
|
- Input: `LocalessRichTextInput` — a `doc`, a node, a node array, `null`, or
|
|
26
|
-
|
|
32
|
+
`ContentRichText` (re-exported from `@localess/model`, no cast).
|
|
27
33
|
- Output is byte-identical to TipTap's `generateHTML` for the Studio's
|
|
28
34
|
extension set, except link `href`s pass a protocol allowlist
|
|
29
35
|
(`http:`/`https:`/`mailto:`/`tel:`/scheme-less); `javascript:`/`data:`
|
|
@@ -32,13 +38,22 @@ const html = renderRichTextToHtml(data.body);
|
|
|
32
38
|
|
|
33
39
|
## Supported node set
|
|
34
40
|
|
|
35
|
-
Nodes: `doc`, `paragraph
|
|
36
|
-
|
|
37
|
-
`<
|
|
38
|
-
`
|
|
41
|
+
Nodes: `doc`, `paragraph` → `<p>`, `heading` (`level` 1–6 → `<h1>`…`<h6>`;
|
|
42
|
+
any other level falls back to `<h1>`), `bulletList` → `<ul>`, `orderedList`
|
|
43
|
+
→ `<ol>` (`start` emitted only when present and not `1`), `listItem` → `<li>`,
|
|
44
|
+
`codeBlock` → `<pre><code>` (`language` → `class="language-x"` on `<code>`),
|
|
45
|
+
`text`. Marks: `bold` → `<strong>`, `italic` → `<em>`, `strike` → `<s>`,
|
|
46
|
+
`underline` → `<u>`, `code` → `<code>`, `link` → `<a>` (`target`, `rel`,
|
|
47
|
+
sanitized `href`, `class`, in that order; `null`/empty attrs are dropped).
|
|
48
|
+
|
|
49
|
+
Adjacent `text` nodes sharing outer marks are merged into one wrapper
|
|
50
|
+
(`<strong>a<em>b</em></strong>`, one `<a>` per link span), matching
|
|
51
|
+
ProseMirror's serializer. Text is escaped `& < >`; attribute values `& " < >`.
|
|
39
52
|
|
|
40
|
-
Unknown types are skipped
|
|
41
|
-
|
|
53
|
+
Unknown node/mark types are skipped (marks: their children are still emitted)
|
|
54
|
+
with a `console.warn` once per type per render — suppressed when
|
|
55
|
+
`process.env.NODE_ENV === 'production'` — unless a custom renderer is provided
|
|
56
|
+
for that type string.
|
|
42
57
|
|
|
43
58
|
## Custom renderers
|
|
44
59
|
|
|
@@ -54,13 +69,25 @@ renderRichTextToHtml(data.body, {
|
|
|
54
69
|
type unset — pass it to a nested `renderRichTextToHtml` call to re-render your
|
|
55
70
|
own node without infinite recursion.
|
|
56
71
|
|
|
72
|
+
Renderer props (`LocalessRichTextRendererProps<TOut>`): `type`, `attrs?`,
|
|
73
|
+
`text?`, `marks?`, `content?`, `children`, `context: { renderers? }`, `_key?`.
|
|
74
|
+
Options type: `LocalessRichTextHtmlOptions` (`{ renderers?:
|
|
75
|
+
LocalessRichTextRenderers<string> }`). A custom `text` renderer receives the
|
|
76
|
+
HTML-escaped text as `children` and disables adjacent-mark merging for that
|
|
77
|
+
render; a custom mark renderer receives `context.renderers` unchanged (marks
|
|
78
|
+
don't nest into themselves).
|
|
79
|
+
|
|
57
80
|
## Building a native walker
|
|
58
81
|
|
|
59
82
|
The helpers encode the algorithms once so walkers are mechanical translations:
|
|
60
|
-
`normalizeInput(input, { withKeys: true })` (keyed node list
|
|
61
|
-
`
|
|
83
|
+
`normalizeInput(input, { withKeys: true })` (keyed node list, `_key` =
|
|
84
|
+
`paragraph-1`, `text-3`, …; never throws, malformed input → `[]`),
|
|
85
|
+
`buildMarkTree(textRun)` → `MarkTreeSegment[]` (adjacent-mark merging;
|
|
86
|
+
`marksEqual(a, b)` is the comparison it uses), `processAttrs(type, attrs,
|
|
62
87
|
{ attrMap })` (attribute normalization; React passes `{ class: 'className' }`),
|
|
63
|
-
`NODE_RENDER_MAP` / `MARK_RENDER_MAP` / `resolveHeadingTag` (default table
|
|
88
|
+
`NODE_RENDER_MAP` / `MARK_RENDER_MAP` / `resolveHeadingTag` (default table;
|
|
89
|
+
`null` entry = transparent, missing key = unknown), `escapeHtml` /
|
|
90
|
+
`escapeAttr` / `sanitizeUrl` (escaping and URL allowlist).
|
|
64
91
|
See `@localess/react`'s `src/core/richtext.ts` for the reference walker.
|
|
65
92
|
|
|
66
93
|
## Test fixtures
|
|
@@ -69,7 +96,29 @@ See `@localess/react`'s `src/core/richtext.ts` for the reference walker.
|
|
|
69
96
|
import { richTextFixtures } from '@localess/richtext/test-utils';
|
|
70
97
|
```
|
|
71
98
|
|
|
72
|
-
`{ title, input, expected, parity }`
|
|
73
|
-
renderer. `parity: true` fixtures are
|
|
74
|
-
`generateHTML` — parity is normative;
|
|
75
|
-
`toContain`.
|
|
99
|
+
`richTextFixtures: RichTextFixture[]` — a `{ title, input, expected, parity }`
|
|
100
|
+
corpus asserted by every Localess renderer. `parity: true` fixtures are
|
|
101
|
+
additionally byte-compared to TipTap's `generateHTML` — parity is normative;
|
|
102
|
+
never weaken an assertion to `toContain`.
|
|
103
|
+
|
|
104
|
+
## Exports Reference
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// @localess/richtext
|
|
108
|
+
export { renderRichTextToHtml } // HTML string renderer
|
|
109
|
+
export { normalizeInput, buildMarkTree, marksEqual, processAttrs } // walker helpers
|
|
110
|
+
export { escapeHtml, escapeAttr, sanitizeUrl } // escaping / URL policy
|
|
111
|
+
export { NODE_RENDER_MAP, MARK_RENDER_MAP, resolveHeadingTag } // default render table
|
|
112
|
+
export type {
|
|
113
|
+
LocalessRichTextDocument, LocalessRichTextNode, LocalessRichTextNodeWithKey,
|
|
114
|
+
LocalessRichTextMark, LocalessRichTextLinkAttrs, LocalessRichTextElement,
|
|
115
|
+
LocalessRichTextInput, ContentRichText /* re-exported from @localess/model */,
|
|
116
|
+
LocalessRichTextRenderer, LocalessRichTextRenderers, LocalessRichTextRendererProps,
|
|
117
|
+
LocalessRichTextHtmlOptions, NormalizeInputOptions, ProcessAttrsOptions,
|
|
118
|
+
RichTextRenderSpec, MarkTreeSegment, MarkTreeText, MarkTreeMark,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// @localess/richtext/test-utils
|
|
122
|
+
export { richTextFixtures }
|
|
123
|
+
export type { RichTextFixture }
|
|
124
|
+
```
|
package/dist/index.js
CHANGED
|
@@ -106,7 +106,7 @@ function buildMarkTree(nodes) {
|
|
|
106
106
|
//#region src/normalize.ts
|
|
107
107
|
/**
|
|
108
108
|
* Flattens any accepted rich text input (document, node, node array, or the
|
|
109
|
-
* loose `ContentRichText` shape from `@localess/
|
|
109
|
+
* loose `ContentRichText` shape from `@localess/model`) into a node list.
|
|
110
110
|
* Never throws; malformed input yields `[]`.
|
|
111
111
|
*/
|
|
112
112
|
function normalizeInput(input, options = {}) {
|
package/dist/index.mjs
CHANGED
|
@@ -105,7 +105,7 @@ function buildMarkTree(nodes) {
|
|
|
105
105
|
//#region src/normalize.ts
|
|
106
106
|
/**
|
|
107
107
|
* Flattens any accepted rich text input (document, node, node array, or the
|
|
108
|
-
* loose `ContentRichText` shape from `@localess/
|
|
108
|
+
* loose `ContentRichText` shape from `@localess/model`) into a node list.
|
|
109
109
|
* Never throws; malformed input yields `[]`.
|
|
110
110
|
*/
|
|
111
111
|
function normalizeInput(input, options = {}) {
|
package/dist/model.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ContentRichText } from '@localess/model';
|
|
1
2
|
/**
|
|
2
3
|
* Attributes of a `link` mark as stored by the Localess Studio editor
|
|
3
4
|
* (TipTap Link extension JSON).
|
|
@@ -71,16 +72,12 @@ export interface LocalessRichTextDocument {
|
|
|
71
72
|
content?: LocalessRichTextNode[];
|
|
72
73
|
}
|
|
73
74
|
/**
|
|
74
|
-
*
|
|
75
|
-
* values
|
|
76
|
-
* zero dependencies.
|
|
75
|
+
* Re-exported from `@localess/model` so `LocalessRichTextInput` accepts
|
|
76
|
+
* `ContentRichText` values without casting.
|
|
77
77
|
*/
|
|
78
|
-
export
|
|
79
|
-
type?: string;
|
|
80
|
-
content?: ContentRichTextLike[];
|
|
81
|
-
}
|
|
78
|
+
export type { ContentRichText };
|
|
82
79
|
/** Anything a render function accepts. */
|
|
83
|
-
export type LocalessRichTextInput = LocalessRichTextDocument | LocalessRichTextNode | LocalessRichTextNode[] |
|
|
80
|
+
export type LocalessRichTextInput = LocalessRichTextDocument | LocalessRichTextNode | LocalessRichTextNode[] | ContentRichText | null | undefined;
|
|
84
81
|
/** Union of every known node and mark type name. */
|
|
85
82
|
export type LocalessRichTextElement = LocalessRichTextNode['type'] | LocalessRichTextMark['type'] | 'doc';
|
|
86
83
|
/**
|
package/dist/normalize.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface NormalizeInputOptions {
|
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* Flattens any accepted rich text input (document, node, node array, or the
|
|
8
|
-
* loose `ContentRichText` shape from `@localess/
|
|
8
|
+
* loose `ContentRichText` shape from `@localess/model`) into a node list.
|
|
9
9
|
* Never throws; malformed input yields `[]`.
|
|
10
10
|
*/
|
|
11
11
|
export declare function normalizeInput(input: LocalessRichTextInput, options?: NormalizeInputOptions): LocalessRichTextNodeWithKey[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@localess/richtext",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-dev.20260905071322",
|
|
4
4
|
"description": "Framework-neutral rich text model and renderer for Localess's TipTap JSON content.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"localess",
|
|
@@ -47,6 +47,9 @@
|
|
|
47
47
|
"test:coverage": "vitest run --coverage"
|
|
48
48
|
},
|
|
49
49
|
"license": "MIT",
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@localess/model": "4.0.0-dev.20260905071322"
|
|
52
|
+
},
|
|
50
53
|
"devDependencies": {
|
|
51
54
|
"@tiptap/extension-bold": "^3.22.5",
|
|
52
55
|
"@tiptap/extension-bullet-list": "^3.22.5",
|