@haklex/rich-static-renderer 0.0.38

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/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Innei
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+
24
+ Additional Terms and Conditions
25
+
26
+ ----------------
27
+
28
+ Use of this software is governed by the terms of MIT and, in addition, by the terms and conditions described in the additional file (ADDITIONAL_TERMS.md). By using this software, you agree to abide by these additional terms and conditions.
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # @haklex/rich-static-renderer
2
+
3
+ Lexical 富文本的只读渲染引擎(Headless + React 输出)。
4
+ 用于把 `SerializedEditorState` 渲染成可展示的 React 内容。
5
+
6
+ ## 包定位
7
+
8
+ - 不提供编辑能力
9
+ - 适合文章详情页、评论展示、SSR 输出
10
+ - 默认支持 `@haklex/rich-editor` 内置节点(`allNodes`)
11
+
12
+ ## 安装
13
+
14
+ ```bash
15
+ pnpm add @haklex/rich-static-renderer @haklex/rich-editor lexical react react-dom
16
+ ```
17
+
18
+ ## 快速开始
19
+
20
+ ```tsx
21
+ import { RichRenderer } from '@haklex/rich-static-renderer'
22
+ import type { SerializedEditorState } from 'lexical'
23
+ import '@haklex/rich-editor/style.css'
24
+
25
+ export function Article({
26
+ value,
27
+ }: {
28
+ value: SerializedEditorState
29
+ }) {
30
+ return <RichRenderer value={value} variant="article" theme="light" />
31
+ }
32
+ ```
33
+
34
+ ## API
35
+
36
+ ```ts
37
+ interface RichRendererProps {
38
+ value: SerializedEditorState
39
+ variant?: 'article' | 'comment' | 'note'
40
+ theme?: 'light' | 'dark'
41
+ className?: string
42
+ style?: CSSProperties
43
+ as?: keyof React.JSX.IntrinsicElements
44
+ rendererConfig?: RendererConfig
45
+ extraNodes?: Array<Klass<LexicalNode>>
46
+ }
47
+ ```
48
+
49
+ 字段说明:
50
+
51
+ - `value`:Lexical JSON(必填)
52
+ - `as`:渲染容器标签,默认 `div`
53
+ - `rendererConfig`:覆写某些节点的渲染组件(Image/CodeBlock/...)
54
+ - `extraNodes`:注册扩展节点(如 Tldraw、Embed、Gallery、CodeSnippet)
55
+
56
+ ## 与增强渲染器一起用
57
+
58
+ ```tsx
59
+ import { RichRenderer } from '@haklex/rich-static-renderer'
60
+ import {
61
+ codeSnippetNodes,
62
+ embedNodes,
63
+ enhancedRendererConfig,
64
+ galleryNodes,
65
+ TldrawNode,
66
+ } from '@haklex/rich-renderers'
67
+
68
+ import '@haklex/rich-editor/style.css'
69
+ import '@haklex/rich-renderers/style.css'
70
+ import '@haklex/rich-ext-code-snippet/style.css'
71
+ import '@haklex/rich-ext-embed/style.css'
72
+ import 'katex/dist/katex.min.css'
73
+ import 'tldraw/tldraw.css'
74
+
75
+ const extraNodes = [
76
+ TldrawNode,
77
+ ...embedNodes,
78
+ ...galleryNodes,
79
+ ...codeSnippetNodes,
80
+ ]
81
+
82
+ <RichRenderer
83
+ value={value}
84
+ rendererConfig={enhancedRendererConfig}
85
+ extraNodes={extraNodes}
86
+ />
87
+ ```
88
+
89
+ ## 渲染行为说明
90
+
91
+ - 内部使用 `@lexical/headless` 构建只读 editor,再将节点树转为 React
92
+ - 标题会自动生成 slug 与锚点(重复标题自动去重)
93
+ - 脚注会先预处理编号,再统一渲染引用与定义
94
+ - `Alert/Banner/Grid` 等嵌套内容通过 `NestedContentRendererProvider` 递归渲染
95
+
96
+ ## 设计模式
97
+
98
+ ### 1) 节点注册与渲染器覆写解耦
99
+
100
+ - 节点协议(JSON 结构)由 Node class 决定
101
+ - 显示层由 `rendererConfig` 决定
102
+ - 扩展节点只需通过 `extraNodes` 注册,不要求侵入核心渲染器
103
+
104
+ ### 2) Static / Edit 拆分协作
105
+
106
+ `@haklex/rich-static-renderer` 只消费静态节点。
107
+ 编辑态重依赖(Popover、Dialog 等)应该放在 edit 节点/编辑包里,不进入只读包。
108
+
109
+ ## 常见问题
110
+
111
+ ### Q1: 为什么有些节点不显示?
112
+
113
+ 通常是忘了注册扩展节点(`extraNodes`),例如 `embed`、`tldraw`、`gallery`、`code-snippet`。
114
+
115
+ ### Q2: 为什么公式样式不对?
116
+
117
+ 需要手动引入:
118
+
119
+ ```ts
120
+ import 'katex/dist/katex.min.css'
121
+ ```
122
+
123
+ ### Q3: 为什么 tldraw 画布样式错乱?
124
+
125
+ 需要手动引入:
126
+
127
+ ```ts
128
+ import 'tldraw/tldraw.css'
129
+ ```
130
+
131
+ ## License
132
+
133
+ MIT
@@ -0,0 +1,3 @@
1
+ import { RichRendererProps } from './types';
2
+ export declare function RichRenderer({ value, variant, theme, className, style, as: Component, rendererConfig, extraNodes, }: RichRendererProps): import("react/jsx-runtime").JSX.Element;
3
+ //# sourceMappingURL=RichRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RichRenderer.d.ts","sourceRoot":"","sources":["../src/RichRenderer.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AA+JhD,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,OAAmB,EACnB,KAAe,EACf,SAAS,EACT,KAAK,EACL,EAAE,EAAE,SAAiB,EACrB,cAAc,EACd,UAAU,GACX,EAAE,iBAAiB,2CAmCnB"}
@@ -0,0 +1,3 @@
1
+ import { ReactNode } from 'react';
2
+ export declare function renderBuiltinNode(node: any, key: string, children: ReactNode[] | null, headingSlugs: Map<string, number>): ReactNode;
3
+ //# sourceMappingURL=renderBuiltinNode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderBuiltinNode.d.ts","sourceRoot":"","sources":["../../src/engine/renderBuiltinNode.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAmBtC,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,GAAG,EACT,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,EAC5B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,SAAS,CAwLX"}
@@ -0,0 +1,3 @@
1
+ export { renderBuiltinNode } from './renderBuiltinNode';
2
+ export { renderTextNode } from './renderTextNode';
3
+ //# sourceMappingURL=renderNode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderNode.d.ts","sourceRoot":"","sources":["../../src/engine/renderNode.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,3 @@
1
+ import { ReactNode } from 'react';
2
+ export declare function renderTextNode(node: any, key: string): ReactNode;
3
+ //# sourceMappingURL=renderTextNode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderTextNode.d.ts","sourceRoot":"","sources":["../../src/engine/renderTextNode.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AA6BrD,wBAAgB,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAuBhE"}
@@ -0,0 +1,3 @@
1
+ export { RichRenderer } from './RichRenderer';
2
+ export type { RichRendererProps } from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,398 @@
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import { getVariantClass, allNodes, ColorSchemeProvider, RendererConfigProvider, FootnoteDefinitionsProvider, NestedContentRendererProvider, editorTheme } from "@haklex/rich-editor/static";
3
+ import { PortalThemeProvider } from "@haklex/rich-style-token";
4
+ import { createHeadlessEditor } from "@lexical/headless";
5
+ import { $getRoot } from "lexical";
6
+ import { isValidElement, cloneElement, createElement } from "react";
7
+ var tableWrapper = "_1v9yxw30";
8
+ var table = "_1v9yxw31";
9
+ var tableHead = "_1v9yxw32";
10
+ var tableCell = "_1v9yxw33";
11
+ function textToSlug(text) {
12
+ return text.toLowerCase().trim().replaceAll(/[^\w\s\u3001-\u9fff\uac00-\ud7af\uff00-\uffef-]/g, "").replaceAll(/[\s_]+/g, "-").replaceAll(/^-+|-+$/g, "");
13
+ }
14
+ function extractText(node) {
15
+ if (node.text) return node.text;
16
+ if (node.children) return node.children.map(extractText).join("");
17
+ return "";
18
+ }
19
+ function renderBuiltinNode(node, key, children, headingSlugs) {
20
+ switch (node.type) {
21
+ case "root":
22
+ return /* @__PURE__ */ jsx(Fragment, { children });
23
+ case "paragraph": {
24
+ const align = node.format ? { textAlign: node.format } : void 0;
25
+ return /* @__PURE__ */ jsx("p", { className: "rich-paragraph", style: align, children }, key);
26
+ }
27
+ case "heading": {
28
+ const Tag = node.tag;
29
+ const text = extractText(node);
30
+ const baseSlug = textToSlug(text);
31
+ let slug = baseSlug;
32
+ if (baseSlug) {
33
+ const count = headingSlugs.get(baseSlug);
34
+ if (count !== void 0) {
35
+ slug = `${baseSlug}-${count}`;
36
+ headingSlugs.set(baseSlug, count + 1);
37
+ } else {
38
+ headingSlugs.set(baseSlug, 1);
39
+ }
40
+ }
41
+ return /* @__PURE__ */ jsxs(Tag, { id: slug || void 0, className: `rich-heading-${Tag}`, children: [
42
+ slug && /* @__PURE__ */ jsx(
43
+ "a",
44
+ {
45
+ className: "rich-heading-anchor",
46
+ "aria-hidden": "true",
47
+ tabIndex: -1,
48
+ href: `#${slug}`
49
+ }
50
+ ),
51
+ children
52
+ ] }, key);
53
+ }
54
+ case "quote":
55
+ return /* @__PURE__ */ jsx("blockquote", { className: "rich-quote", children }, key);
56
+ case "list": {
57
+ const Tag = node.listType === "number" ? "ol" : "ul";
58
+ const cls = node.listType === "number" ? "rich-list-ol" : node.listType === "check" ? "rich-checklist rich-list-ul" : "rich-list-ul";
59
+ return /* @__PURE__ */ jsx(
60
+ Tag,
61
+ {
62
+ className: cls,
63
+ start: node.start !== 1 ? node.start : void 0,
64
+ children
65
+ },
66
+ key
67
+ );
68
+ }
69
+ case "listitem": {
70
+ const isChecklist = node.checked !== void 0;
71
+ const hasNestedList = node.children?.some((c) => c.type === "list");
72
+ let cls;
73
+ if (hasNestedList) {
74
+ cls = "rich-list-nested-item";
75
+ } else if (isChecklist) {
76
+ cls = node.checked ? "rich-list-item rich-list-item-checked" : "rich-list-item rich-list-item-unchecked";
77
+ } else {
78
+ cls = "rich-list-item";
79
+ }
80
+ return /* @__PURE__ */ jsx("li", { className: cls, value: node.value, children }, key);
81
+ }
82
+ case "link":
83
+ return /* @__PURE__ */ jsx(
84
+ "a",
85
+ {
86
+ className: "rich-link",
87
+ href: node.url,
88
+ target: node.target,
89
+ rel: node.rel,
90
+ children
91
+ },
92
+ key
93
+ );
94
+ case "autolink":
95
+ return /* @__PURE__ */ jsx(
96
+ "a",
97
+ {
98
+ className: "rich-link",
99
+ href: node.url,
100
+ target: "_blank",
101
+ rel: "noopener noreferrer",
102
+ children
103
+ },
104
+ key
105
+ );
106
+ case "horizontalrule":
107
+ return /* @__PURE__ */ jsx("hr", { className: "rich-hr" }, key);
108
+ case "table":
109
+ return /* @__PURE__ */ jsx("div", { className: tableWrapper, children: /* @__PURE__ */ jsx("table", { className: table, children }) }, key);
110
+ case "tablerow":
111
+ return /* @__PURE__ */ jsx("tr", { children }, key);
112
+ case "tablecell": {
113
+ const CellTag = node.headerState ? "th" : "td";
114
+ const cls = node.headerState ? tableHead : tableCell;
115
+ return /* @__PURE__ */ jsx(
116
+ CellTag,
117
+ {
118
+ className: cls,
119
+ colSpan: node.colSpan > 1 ? node.colSpan : void 0,
120
+ children
121
+ },
122
+ key
123
+ );
124
+ }
125
+ case "details": {
126
+ const summary = node.summary || "";
127
+ return /* @__PURE__ */ jsxs(
128
+ "details",
129
+ {
130
+ className: "rich-details",
131
+ open: node.open || void 0,
132
+ children: [
133
+ /* @__PURE__ */ jsxs("summary", { className: "rich-details-summary", children: [
134
+ /* @__PURE__ */ jsx("span", { className: "rich-details-summary-text", children: summary }),
135
+ /* @__PURE__ */ jsx("span", { className: "rich-details-chevron", children: /* @__PURE__ */ jsx(
136
+ "svg",
137
+ {
138
+ width: "20",
139
+ height: "20",
140
+ viewBox: "0 0 20 20",
141
+ fill: "none",
142
+ stroke: "currentColor",
143
+ strokeWidth: "1.5",
144
+ strokeLinecap: "round",
145
+ strokeLinejoin: "round",
146
+ children: /* @__PURE__ */ jsx("path", { d: "M6 8L10 12L14 8" })
147
+ }
148
+ ) })
149
+ ] }),
150
+ /* @__PURE__ */ jsx("div", { className: "rich-details-content", children })
151
+ ]
152
+ },
153
+ key
154
+ );
155
+ }
156
+ case "spoiler":
157
+ return /* @__PURE__ */ jsx("span", { className: "rich-spoiler", role: "button", tabIndex: 0, children }, key);
158
+ case "code":
159
+ return /* @__PURE__ */ jsx("pre", { className: "rich-code-block", children: /* @__PURE__ */ jsx("code", { children }) }, key);
160
+ case "code-highlight":
161
+ return /* @__PURE__ */ jsx("span", { children: node.text }, key);
162
+ case "linebreak":
163
+ return /* @__PURE__ */ jsx("br", {}, key);
164
+ case "tab":
165
+ return /* @__PURE__ */ jsx("span", { children: " " }, key);
166
+ default:
167
+ return null;
168
+ }
169
+ }
170
+ const FORMAT_FLAGS = [
171
+ [1, "rich-text-bold"],
172
+ [2, "rich-text-italic"],
173
+ [4, "rich-text-strikethrough"],
174
+ [8, "rich-text-underline"],
175
+ [16, "rich-text-code"],
176
+ [32, "rich-text-subscript"],
177
+ [64, "rich-text-superscript"],
178
+ [128, "rich-text-highlight"]
179
+ ];
180
+ function parseCSSText(cssText) {
181
+ const style = {};
182
+ for (const part of cssText.split(";")) {
183
+ const colonIndex = part.indexOf(":");
184
+ if (colonIndex === -1) continue;
185
+ const prop = part.slice(0, colonIndex).trim();
186
+ const value = part.slice(colonIndex + 1).trim();
187
+ if (!prop || !value) continue;
188
+ const camelProp = prop.replaceAll(
189
+ /-([a-z])/g,
190
+ (_, c) => c.toUpperCase()
191
+ );
192
+ style[camelProp] = value;
193
+ }
194
+ return style;
195
+ }
196
+ function renderTextNode(node, key) {
197
+ let element = node.text;
198
+ const format = node.format || 0;
199
+ for (const [flag, className] of FORMAT_FLAGS) {
200
+ if (format & flag) {
201
+ element = /* @__PURE__ */ jsx("span", { className, children: element }, `${key}-${flag}`);
202
+ }
203
+ }
204
+ if (node.style) {
205
+ element = /* @__PURE__ */ jsx("span", { style: parseCSSText(node.style), children: element }, key);
206
+ }
207
+ return element;
208
+ }
209
+ function preprocessFootnotes(state) {
210
+ const definitions = {};
211
+ const seen = /* @__PURE__ */ new Set();
212
+ const displayNumberMap = {};
213
+ let counter = 1;
214
+ function walk(node) {
215
+ if (node.type === "footnote" && node.identifier) {
216
+ const id = node.identifier;
217
+ if (!seen.has(id)) {
218
+ seen.add(id);
219
+ displayNumberMap[id] = counter++;
220
+ }
221
+ }
222
+ if (node.type === "footnote-section" && node.definitions) {
223
+ Object.assign(definitions, node.definitions);
224
+ }
225
+ if (node.children) {
226
+ for (const child of node.children) walk(child);
227
+ }
228
+ if (node.root) walk(node.root);
229
+ if (node.content && typeof node.content === "object" && node.content.root) {
230
+ walk(node.content);
231
+ }
232
+ if (node.cells && Array.isArray(node.cells)) {
233
+ for (const cell of node.cells) {
234
+ if (cell && cell.root) walk(cell);
235
+ }
236
+ }
237
+ }
238
+ walk(state);
239
+ for (const id of seen) {
240
+ if (!(id in definitions)) {
241
+ definitions[id] = "";
242
+ }
243
+ }
244
+ return { definitions, displayNumberMap };
245
+ }
246
+ function wrapDecoration(serialized, key, decoration) {
247
+ switch (serialized.type) {
248
+ case "alert-quote":
249
+ return createElement(
250
+ "div",
251
+ { key, className: `rich-alert rich-alert-${serialized.alertType}` },
252
+ decoration
253
+ );
254
+ case "banner":
255
+ return createElement(
256
+ "div",
257
+ { key, className: `rich-banner rich-banner-${serialized.bannerType}` },
258
+ decoration
259
+ );
260
+ case "grid-container":
261
+ return createElement(
262
+ "div",
263
+ { key, className: "rich-grid-container" },
264
+ decoration
265
+ );
266
+ default:
267
+ if (isValidElement(decoration)) {
268
+ return cloneElement(decoration, { key });
269
+ }
270
+ return decoration;
271
+ }
272
+ }
273
+ function renderTree(node, editor, editorConfig, headingSlugs, key) {
274
+ const nodeKey = node.getKey ? node.getKey() : key;
275
+ if (typeof node.decorate === "function") {
276
+ try {
277
+ const decoration = node.decorate(editor, editorConfig);
278
+ if (decoration != null) {
279
+ const serialized2 = node.exportJSON ? node.exportJSON() : {};
280
+ return wrapDecoration(serialized2, nodeKey, decoration);
281
+ }
282
+ } catch {
283
+ }
284
+ }
285
+ const serialized = node.exportJSON ? node.exportJSON() : {};
286
+ if (serialized.type === "text") {
287
+ return renderTextNode(serialized, nodeKey);
288
+ }
289
+ let children = null;
290
+ if (node.getChildren) {
291
+ const childNodes = node.getChildren();
292
+ if (childNodes.length > 0) {
293
+ children = childNodes.map(
294
+ (child, i) => renderTree(
295
+ child,
296
+ editor,
297
+ editorConfig,
298
+ headingSlugs,
299
+ `${nodeKey}-${i}`
300
+ )
301
+ );
302
+ }
303
+ }
304
+ return renderBuiltinNode(serialized, nodeKey, children, headingSlugs);
305
+ }
306
+ function renderEditorToReact(value, nodes) {
307
+ const editor = createHeadlessEditor({
308
+ nodes,
309
+ theme: editorTheme,
310
+ editable: false,
311
+ onError: (error) => {
312
+ console.error("[RichRenderer]", error);
313
+ }
314
+ });
315
+ const editorConfig = { namespace: "ssr", theme: editorTheme };
316
+ const editorState = editor.parseEditorState(value);
317
+ editor.setEditorState(editorState);
318
+ const footnoteData = preprocessFootnotes(value);
319
+ let content = null;
320
+ editorState.read(() => {
321
+ const root = $getRoot();
322
+ const headingSlugs = /* @__PURE__ */ new Map();
323
+ const children = root.getChildren().map(
324
+ (child, i) => renderTree(child, editor, editorConfig, headingSlugs, `ssr-${i}`)
325
+ );
326
+ content = /* @__PURE__ */ jsx(Fragment, { children });
327
+ });
328
+ const renderNestedContent = (state) => {
329
+ const nestedEditor = createHeadlessEditor({
330
+ nodes,
331
+ theme: editorTheme,
332
+ editable: false,
333
+ onError: (error) => {
334
+ console.error("[RichRenderer:nested]", error);
335
+ }
336
+ });
337
+ const nestedEditorConfig = {
338
+ namespace: "ssr-nested",
339
+ theme: editorTheme
340
+ };
341
+ const nestedState = nestedEditor.parseEditorState(state);
342
+ nestedEditor.setEditorState(nestedState);
343
+ let nested = null;
344
+ nestedState.read(() => {
345
+ const root = $getRoot();
346
+ const headingSlugs = /* @__PURE__ */ new Map();
347
+ const ch = root.getChildren().map(
348
+ (child, i) => renderTree(
349
+ child,
350
+ nestedEditor,
351
+ nestedEditorConfig,
352
+ headingSlugs,
353
+ `nested-${i}`
354
+ )
355
+ );
356
+ nested = /* @__PURE__ */ jsx(Fragment, { children: ch });
357
+ });
358
+ return nested;
359
+ };
360
+ return { content, footnoteData, renderNestedContent };
361
+ }
362
+ function RichRenderer({
363
+ value,
364
+ variant = "article",
365
+ theme = "light",
366
+ className,
367
+ style,
368
+ as: Component = "div",
369
+ rendererConfig,
370
+ extraNodes
371
+ }) {
372
+ const variantClass = getVariantClass(variant);
373
+ const nodes = extraNodes ? [...allNodes, ...extraNodes] : allNodes;
374
+ const { content, footnoteData, renderNestedContent } = renderEditorToReact(
375
+ value,
376
+ nodes
377
+ );
378
+ const classes = ["rich-content", variantClass, className].filter(Boolean).join(" ");
379
+ return /* @__PURE__ */ jsx(PortalThemeProvider, { className: variantClass, theme, children: /* @__PURE__ */ jsx(ColorSchemeProvider, { colorScheme: theme, children: /* @__PURE__ */ jsx(
380
+ RendererConfigProvider,
381
+ {
382
+ config: rendererConfig,
383
+ mode: "renderer",
384
+ variant,
385
+ children: /* @__PURE__ */ jsx(
386
+ FootnoteDefinitionsProvider,
387
+ {
388
+ definitions: footnoteData.definitions,
389
+ displayNumberMap: footnoteData.displayNumberMap,
390
+ children: /* @__PURE__ */ jsx(NestedContentRendererProvider, { value: renderNestedContent, children: /* @__PURE__ */ jsx(Component, { className: classes, style, "data-theme": theme, children: content }) })
391
+ }
392
+ )
393
+ }
394
+ ) }) });
395
+ }
396
+ export {
397
+ RichRenderer
398
+ };
@@ -0,0 +1,7 @@
1
+ import { SerializedEditorState } from 'lexical';
2
+ export interface FootnoteData {
3
+ definitions: Record<string, string>;
4
+ displayNumberMap: Record<string, number>;
5
+ }
6
+ export declare function preprocessFootnotes(state: SerializedEditorState): FootnoteData;
7
+ //# sourceMappingURL=footnote.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"footnote.d.ts","sourceRoot":"","sources":["../../src/preprocess/footnote.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AAEpD,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACzC;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,qBAAqB,GAC3B,YAAY,CAwCd"}
@@ -0,0 +1,2 @@
1
+ export declare function textToSlug(text: string): string;
2
+ //# sourceMappingURL=headingSlug.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headingSlug.d.ts","sourceRoot":"","sources":["../../src/preprocess/headingSlug.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO/C"}
@@ -0,0 +1 @@
1
+ :root{--rc-text: #000;--rc-text-secondary: #27272a;--rc-text-tertiary: #71717a;--rc-text-quaternary: #a1a1aa;--rc-bg: #ffffff;--rc-bg-secondary: #fafafa;--rc-bg-tertiary: #f4f4f5;--rc-border: #f4f4f5;--rc-accent: #2563eb;--rc-accent-light: #2563eb20;--rc-link: #2563eb;--rc-code-text: #3f3f46;--rc-code-bg: #f4f4f5;--rc-hr-border: #e4e4e7;--rc-quote-border: #2563eb;--rc-quote-bg: #eff6ff;--rc-alert-info: #006bb7;--rc-alert-warning: #cc5500;--rc-alert-tip: #11cc00;--rc-alert-caution: #cc0011;--rc-alert-important: #5500cc;--rc-max-width: 700px;--rc-space-xs: 4px;--rc-space-sm: 8px;--rc-space-md: 16px;--rc-space-lg: 24px;--rc-space-xl: 32px;--rc-font-family-sans: "PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-font-family-serif: "Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-font-mono: "SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace;--rc-font-size-2xs: .625em;--rc-font-size-xs: .75em;--rc-font-size-sm: .8125em;--rc-font-size-md: .875em;--rc-font-size-lg: 1.25em;--rc-font-size-base: 16px;--rc-font-size-small: 14px;--rc-line-height: 1.7;--rc-line-height-tight: 1.4;--rc-font-family: "PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-radius-sm: 4px;--rc-radius-md: 8px;--rc-radius-lg: 12px}:root.dark{--rc-text: #fafafa;--rc-text-secondary: #a1a1aa;--rc-text-tertiary: #71717a;--rc-text-quaternary: #52525b;--rc-bg: #09090b;--rc-bg-secondary: #18181b;--rc-bg-tertiary: #27272a;--rc-border: #27272a;--rc-accent: #60a5fa;--rc-accent-light: #60a5fa20;--rc-link: #60a5fa;--rc-code-text: #e4e4e7;--rc-code-bg: #27272a;--rc-hr-border: #27272a;--rc-quote-border: #60a5fa;--rc-quote-bg: #1e3a5f;--rc-alert-info: #7db9e5;--rc-alert-warning: #da864a;--rc-alert-tip: #54da48;--rc-alert-caution: #e16973;--rc-alert-important: #9966e0;--rc-max-width: 700px;--rc-space-xs: 4px;--rc-space-sm: 8px;--rc-space-md: 16px;--rc-space-lg: 24px;--rc-space-xl: 32px;--rc-font-family-sans: "PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-font-family-serif: "Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-font-mono: "SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace;--rc-font-size-2xs: .625em;--rc-font-size-xs: .75em;--rc-font-size-sm: .8125em;--rc-font-size-md: .875em;--rc-font-size-lg: 1.25em;--rc-font-size-base: 16px;--rc-font-size-small: 14px;--rc-line-height: 1.7;--rc-line-height-tight: 1.4;--rc-font-family: "PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-radius-sm: 4px;--rc-radius-md: 8px;--rc-radius-lg: 12px}.km6fx0{--rc-text: #000;--rc-text-secondary: #27272a;--rc-text-tertiary: #71717a;--rc-text-quaternary: #a1a1aa;--rc-bg: #ffffff;--rc-bg-secondary: #fafafa;--rc-bg-tertiary: #f4f4f5;--rc-border: #f4f4f5;--rc-accent: #2563eb;--rc-accent-light: #2563eb20;--rc-link: #2563eb;--rc-code-text: #3f3f46;--rc-code-bg: #f4f4f5;--rc-hr-border: #e4e4e7;--rc-quote-border: #2563eb;--rc-quote-bg: #eff6ff;--rc-alert-info: #006bb7;--rc-alert-warning: #cc5500;--rc-alert-tip: #11cc00;--rc-alert-caution: #cc0011;--rc-alert-important: #5500cc;--rc-max-width: 700px;--rc-space-xs: 4px;--rc-space-sm: 8px;--rc-space-md: 16px;--rc-space-lg: 24px;--rc-space-xl: 32px;--rc-font-family-sans: "PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-font-family-serif: "Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-font-mono: "SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace;--rc-font-size-2xs: .625em;--rc-font-size-xs: .75em;--rc-font-size-sm: .8125em;--rc-font-size-md: .875em;--rc-font-size-lg: 1.25em;--rc-font-size-base: 16px;--rc-font-size-small: 14px;--rc-line-height: 1.7;--rc-line-height-tight: 1.4;--rc-font-family: "PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-radius-sm: 4px;--rc-radius-md: 8px;--rc-radius-lg: 12px}.km6fx1{--rc-text: #000;--rc-text-secondary: #27272a;--rc-text-tertiary: #71717a;--rc-text-quaternary: #a1a1aa;--rc-bg: #ffffff;--rc-bg-secondary: #fafafa;--rc-bg-tertiary: #f4f4f5;--rc-border: #f4f4f5;--rc-accent: #2563eb;--rc-accent-light: #2563eb20;--rc-link: #2563eb;--rc-code-text: #3f3f46;--rc-code-bg: #f4f4f5;--rc-hr-border: #e4e4e7;--rc-quote-border: #2563eb;--rc-quote-bg: #eff6ff;--rc-alert-info: #006bb7;--rc-alert-warning: #cc5500;--rc-alert-tip: #11cc00;--rc-alert-caution: #cc0011;--rc-alert-important: #5500cc;--rc-max-width: 700px;--rc-space-xs: 4px;--rc-space-sm: 8px;--rc-space-md: 16px;--rc-space-lg: 24px;--rc-space-xl: 32px;--rc-font-family-sans: "PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-font-family-serif: "Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-font-mono: "SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace;--rc-font-size-2xs: .625em;--rc-font-size-xs: .75em;--rc-font-size-sm: .8125em;--rc-font-size-md: .875em;--rc-font-size-lg: 1.25em;--rc-font-size-base: 16px;--rc-font-size-small: 14px;--rc-line-height: 1.8;--rc-line-height-tight: 1.4;--rc-font-family: "Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-radius-sm: 4px;--rc-radius-md: 8px;--rc-radius-lg: 12px}.km6fx2{--rc-text: #000;--rc-text-secondary: #27272a;--rc-text-tertiary: #71717a;--rc-text-quaternary: #a1a1aa;--rc-bg: #ffffff;--rc-bg-secondary: #fafafa;--rc-bg-tertiary: #f4f4f5;--rc-border: #f4f4f5;--rc-accent: #2563eb;--rc-accent-light: #2563eb20;--rc-link: #2563eb;--rc-code-text: #3f3f46;--rc-code-bg: #f4f4f5;--rc-hr-border: #e4e4e7;--rc-quote-border: #a1a1aa;--rc-quote-bg: #fafafa;--rc-alert-info: #006bb7;--rc-alert-warning: #cc5500;--rc-alert-tip: #11cc00;--rc-alert-caution: #cc0011;--rc-alert-important: #5500cc;--rc-max-width: none;--rc-space-xs: 2px;--rc-space-sm: 4px;--rc-space-md: 10px;--rc-space-lg: 16px;--rc-space-xl: 20px;--rc-font-family-sans: "PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-font-family-serif: "Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-font-mono: "SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace;--rc-font-size-2xs: .625em;--rc-font-size-xs: .75em;--rc-font-size-sm: .8125em;--rc-font-size-md: .875em;--rc-font-size-lg: 1.25em;--rc-font-size-base: 14px;--rc-font-size-small: 12px;--rc-line-height: 1.5;--rc-line-height-tight: 1.3;--rc-font-family: "PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-radius-sm: 3px;--rc-radius-md: 6px;--rc-radius-lg: 8px}.dark .km6fx0,[data-theme=dark] .km6fx0,.dark.km6fx0,[data-theme=dark].km6fx0,.dark .km6fx1,[data-theme=dark] .km6fx1,.dark.km6fx1,[data-theme=dark].km6fx1,.dark .km6fx2,[data-theme=dark] .km6fx2,.dark.km6fx2,[data-theme=dark].km6fx2{--rc-text: #fafafa;--rc-text-secondary: #a1a1aa;--rc-text-tertiary: #71717a;--rc-text-quaternary: #52525b;--rc-bg: #09090b;--rc-bg-secondary: #18181b;--rc-bg-tertiary: #27272a;--rc-border: #27272a;--rc-accent: #60a5fa;--rc-accent-light: #60a5fa20;--rc-link: #60a5fa;--rc-code-text: #e4e4e7;--rc-code-bg: #27272a;--rc-hr-border: #27272a;--rc-quote-border: #60a5fa;--rc-quote-bg: #1e3a5f;--rc-alert-info: #7db9e5;--rc-alert-warning: #da864a;--rc-alert-tip: #54da48;--rc-alert-caution: #e16973;--rc-alert-important: #9966e0}._1v9yxw30{overflow-x:auto}._1v9yxw31{width:100%;caption-side:bottom;border-collapse:collapse;font-size:var(--rc-font-size-small)}._1v9yxw32{height:2.5rem;padding:0 var(--rc-space-lg);vertical-align:middle;font-weight:500;white-space:nowrap;color:var(--rc-text-secondary);line-height:1.5}._1v9yxw33{padding:var(--rc-space-lg);vertical-align:middle;line-height:1.5}._1v9yxw31 tr:has(._1v9yxw32){border-bottom:1px solid var(--rc-border)}._1v9yxw31 tr:has(._1v9yxw33):nth-child(2n){background-color:var(--rc-bg-secondary)}._1v9yxw31:has(tr:hover) tr:has(._1v9yxw33):nth-child(2n):not(:hover){background-color:transparent}._1v9yxw31 tr:has(._1v9yxw33){transition:background-color .15s}._1v9yxw31 tr:has(._1v9yxw33):hover{background-color:var(--rc-bg-tertiary)}._1v9yxw31 .rich-paragraph{margin:0;padding:0;line-height:inherit}
@@ -0,0 +1,5 @@
1
+ export declare const tableWrapper: string;
2
+ export declare const table: string;
3
+ export declare const tableHead: string;
4
+ export declare const tableCell: string;
5
+ //# sourceMappingURL=table.css.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.css.d.ts","sourceRoot":"","sources":["../src/table.css.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,YAAY,QAEvB,CAAA;AAEF,eAAO,MAAM,KAAK,QAKhB,CAAA;AAEF,eAAO,MAAM,SAAS,QAQpB,CAAA;AAEF,eAAO,MAAM,SAAS,QAIpB,CAAA"}
@@ -0,0 +1,14 @@
1
+ import { ColorScheme, RendererConfig, RichEditorVariant } from '@haklex/rich-editor/static';
2
+ import { Klass, LexicalNode, SerializedEditorState } from 'lexical';
3
+ import { CSSProperties } from 'react';
4
+ export interface RichRendererProps {
5
+ value: SerializedEditorState;
6
+ variant?: RichEditorVariant;
7
+ theme?: ColorScheme;
8
+ className?: string;
9
+ style?: CSSProperties;
10
+ as?: keyof React.JSX.IntrinsicElements;
11
+ rendererConfig?: RendererConfig;
12
+ extraNodes?: Array<Klass<LexicalNode>>;
13
+ }
14
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,iBAAiB,EAClB,MAAM,4BAA4B,CAAA;AACnC,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,qBAAqB,CAAA;IAC5B,OAAO,CAAC,EAAE,iBAAiB,CAAA;IAC3B,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAA;IACtC,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,UAAU,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAA;CACvC"}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@haklex/rich-static-renderer",
3
+ "version": "0.0.38",
4
+ "description": "Headless SSR engine for Lexical rich content",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.mjs",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "main": "./dist/index.mjs",
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "dependencies": {
18
+ "@lexical/headless": "^0.41.0",
19
+ "@haklex/rich-editor": "0.0.38",
20
+ "@haklex/rich-style-token": "0.0.38"
21
+ },
22
+ "devDependencies": {
23
+ "@lexical/code": "^0.41.0",
24
+ "@lexical/extension": "^0.41.0",
25
+ "@lexical/link": "^0.41.0",
26
+ "@lexical/list": "^0.41.0",
27
+ "@lexical/rich-text": "^0.41.0",
28
+ "@lexical/table": "^0.41.0",
29
+ "@types/react": "^19.2.14",
30
+ "@vanilla-extract/css": "^1.18.0",
31
+ "@vanilla-extract/vite-plugin": "^4.0.20",
32
+ "lexical": "^0.41.0",
33
+ "markdown-to-jsx": "npm:@innei/markdown-to-jsx-yet@7.7.7-11",
34
+ "react": "^19.2.4",
35
+ "react-dom": "^19.2.4",
36
+ "typescript": "^5.9.3",
37
+ "vite": "^7.3.1",
38
+ "vite-plugin-dts": "^4.5.4"
39
+ },
40
+ "peerDependencies": {
41
+ "@lexical/code": "^0.41.0",
42
+ "@lexical/extension": "^0.41.0",
43
+ "@lexical/link": "^0.41.0",
44
+ "@lexical/list": "^0.41.0",
45
+ "@lexical/rich-text": "^0.41.0",
46
+ "@lexical/table": "^0.41.0",
47
+ "lexical": "^0.41.0",
48
+ "react": ">=19",
49
+ "react-dom": ">=19"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "scripts": {
55
+ "bench": "node benchmark/build-and-run.mjs",
56
+ "build": "vite build"
57
+ },
58
+ "types": "./dist/index.d.ts"
59
+ }