@haklex/rich-static-renderer 0.0.44 → 0.0.46
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 +35 -0
- package/dist/RichRenderer.d.ts +1 -1
- package/dist/RichRenderer.d.ts.map +1 -1
- package/dist/engine/renderBuiltinNode.d.ts +1 -1
- package/dist/engine/renderBuiltinNode.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +51 -33
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -43,7 +43,15 @@ interface RichRendererProps {
|
|
|
43
43
|
as?: keyof React.JSX.IntrinsicElements
|
|
44
44
|
rendererConfig?: RendererConfig
|
|
45
45
|
extraNodes?: Array<Klass<LexicalNode>>
|
|
46
|
+
builtinNodeOverrides?: Record<string, BuiltinNodeRenderer>
|
|
46
47
|
}
|
|
48
|
+
|
|
49
|
+
type BuiltinNodeRenderer = (
|
|
50
|
+
node: any,
|
|
51
|
+
key: string,
|
|
52
|
+
children: ReactNode[] | null,
|
|
53
|
+
defaultRenderer: () => ReactNode,
|
|
54
|
+
) => ReactNode
|
|
47
55
|
```
|
|
48
56
|
|
|
49
57
|
字段说明:
|
|
@@ -52,6 +60,7 @@ interface RichRendererProps {
|
|
|
52
60
|
- `as`:渲染容器标签,默认 `div`
|
|
53
61
|
- `rendererConfig`:覆写某些节点的渲染组件(Image/CodeBlock/...)
|
|
54
62
|
- `extraNodes`:注册扩展节点(如 Tldraw、Embed、Gallery、CodeSnippet)
|
|
63
|
+
- `builtinNodeOverrides`:覆写内置节点的渲染(paragraph/heading/link/...),按 `node.type` 匹配
|
|
55
64
|
|
|
56
65
|
## 与增强渲染器一起用
|
|
57
66
|
|
|
@@ -86,6 +95,32 @@ const extraNodes = [
|
|
|
86
95
|
/>
|
|
87
96
|
```
|
|
88
97
|
|
|
98
|
+
## 覆写内置节点渲染
|
|
99
|
+
|
|
100
|
+
通过 `builtinNodeOverrides` 可按 `node.type` 覆写 paragraph、heading、link 等内置节点的渲染逻辑。回调提供 `defaultRenderer` 用于回退默认渲染。
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
<RichRenderer
|
|
104
|
+
value={value}
|
|
105
|
+
builtinNodeOverrides={{
|
|
106
|
+
link: (node, key, children) => (
|
|
107
|
+
<a key={key} href={node.url} className="custom-link" target="_blank">
|
|
108
|
+
{children}
|
|
109
|
+
</a>
|
|
110
|
+
),
|
|
111
|
+
heading: (node, key, children, defaultRenderer) => {
|
|
112
|
+
// 仅覆写 h1,其余回退默认
|
|
113
|
+
if (node.tag === 'h1') {
|
|
114
|
+
return <h1 key={key} className="custom-h1">{children}</h1>
|
|
115
|
+
}
|
|
116
|
+
return defaultRenderer()
|
|
117
|
+
},
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
支持的 type:`root`、`paragraph`、`heading`、`quote`、`list`、`listitem`、`link`、`autolink`、`horizontalrule`、`table`、`tablerow`、`tablecell`、`details`、`spoiler`、`ruby`、`code`、`code-highlight`、`linebreak`、`tab`。
|
|
123
|
+
|
|
89
124
|
## 渲染行为说明
|
|
90
125
|
|
|
91
126
|
- 内部使用 `@lexical/headless` 构建只读 editor,再将节点树转为 React
|
package/dist/RichRenderer.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
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;
|
|
2
|
+
export declare function RichRenderer({ value, variant, theme, className, style, as: Component, rendererConfig, extraNodes, builtinNodeOverrides, }: RichRendererProps): import("react/jsx-runtime").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=RichRenderer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RichRenderer.d.ts","sourceRoot":"","sources":["../src/RichRenderer.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RichRenderer.d.ts","sourceRoot":"","sources":["../src/RichRenderer.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAuB,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAkOrE,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,OAAmB,EACnB,KAAe,EACf,SAAS,EACT,KAAK,EACL,EAAE,EAAE,SAAiB,EACrB,cAAc,EACd,UAAU,EACV,oBAAoB,GACrB,EAAE,iBAAiB,2CAuCnB"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
export declare function renderBuiltinNode(node: any, key: string, children: ReactNode[] | null, headingSlugs: Map<string, number
|
|
2
|
+
export declare function renderBuiltinNode(node: any, key: string, children: ReactNode[] | null, headingSlugs: Map<string, number>, textContent?: string): ReactNode;
|
|
3
3
|
//# sourceMappingURL=renderBuiltinNode.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderBuiltinNode.d.ts","sourceRoot":"","sources":["../../src/engine/renderBuiltinNode.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"renderBuiltinNode.d.ts","sourceRoot":"","sources":["../../src/engine/renderBuiltinNode.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAmBrD,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,EACjC,WAAW,CAAC,EAAE,MAAM,GACnB,SAAS,CA+LX"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +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"}
|
|
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,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { RendererWrapper, RubyRenderer, getVariantClass, allNodes, ColorSchemeProvider, RendererConfigProvider, FootnoteDefinitionsProvider, NestedContentRendererProvider, editorTheme } from "@haklex/rich-editor/static";
|
|
2
|
+
import { RendererWrapper, RubyRenderer, LinkFavicon, getVariantClass, allNodes, ColorSchemeProvider, RendererConfigProvider, FootnoteDefinitionsProvider, NestedContentRendererProvider, editorTheme } from "@haklex/rich-editor/static";
|
|
3
3
|
import { PortalThemeProvider } from "@haklex/rich-style-token";
|
|
4
4
|
import { createHeadlessEditor } from "@lexical/headless";
|
|
5
5
|
import { $getRoot } from "lexical";
|
|
6
|
-
import { createElement, isValidElement, cloneElement } from "react";
|
|
6
|
+
import { createElement, useMemo, isValidElement, cloneElement } from "react";
|
|
7
7
|
var tableWrapper = "_1v9yxw30";
|
|
8
8
|
var table = "_1v9yxw31";
|
|
9
9
|
var tableHead = "_1v9yxw32";
|
|
10
10
|
var tableCell = "_1v9yxw33";
|
|
11
11
|
function textToSlug(text) {
|
|
12
|
-
return text.toLowerCase().trim().replaceAll(/[^\w\s\
|
|
12
|
+
return text.toLowerCase().trim().replaceAll(/[^\w\s\u3000-\u9fff\uac00-\ud7af\uff00-\uffef-]/g, "").replaceAll(/[\s_]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
13
13
|
}
|
|
14
14
|
function extractText(node) {
|
|
15
15
|
if (node.text) return node.text;
|
|
16
16
|
if (node.children) return node.children.map(extractText).join("");
|
|
17
17
|
return "";
|
|
18
18
|
}
|
|
19
|
-
function renderBuiltinNode(node, key, children, headingSlugs) {
|
|
19
|
+
function renderBuiltinNode(node, key, children, headingSlugs, textContent) {
|
|
20
20
|
switch (node.type) {
|
|
21
21
|
case "root":
|
|
22
22
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
@@ -26,7 +26,7 @@ function renderBuiltinNode(node, key, children, headingSlugs) {
|
|
|
26
26
|
}
|
|
27
27
|
case "heading": {
|
|
28
28
|
const Tag = node.tag;
|
|
29
|
-
const text = extractText(node);
|
|
29
|
+
const text = textContent || extractText(node);
|
|
30
30
|
const baseSlug = textToSlug(text);
|
|
31
31
|
let slug = baseSlug;
|
|
32
32
|
if (baseSlug) {
|
|
@@ -39,15 +39,7 @@ function renderBuiltinNode(node, key, children, headingSlugs) {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
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
|
-
),
|
|
42
|
+
slug && /* @__PURE__ */ jsx("a", { className: "rich-heading-anchor", tabIndex: 0, href: `#${slug}` }),
|
|
51
43
|
children
|
|
52
44
|
] }, key);
|
|
53
45
|
}
|
|
@@ -80,26 +72,32 @@ function renderBuiltinNode(node, key, children, headingSlugs) {
|
|
|
80
72
|
return /* @__PURE__ */ jsx("li", { className: cls, value: node.value, children }, key);
|
|
81
73
|
}
|
|
82
74
|
case "link":
|
|
83
|
-
return /* @__PURE__ */
|
|
75
|
+
return /* @__PURE__ */ jsxs(
|
|
84
76
|
"a",
|
|
85
77
|
{
|
|
86
78
|
className: "rich-link",
|
|
87
79
|
href: node.url,
|
|
88
|
-
target: node.target,
|
|
89
|
-
rel: node.rel,
|
|
90
|
-
children
|
|
80
|
+
target: node.target || "_blank",
|
|
81
|
+
rel: node.rel || "noopener",
|
|
82
|
+
children: [
|
|
83
|
+
/* @__PURE__ */ jsx(LinkFavicon, { href: node.url }),
|
|
84
|
+
children
|
|
85
|
+
]
|
|
91
86
|
},
|
|
92
87
|
key
|
|
93
88
|
);
|
|
94
89
|
case "autolink":
|
|
95
|
-
return /* @__PURE__ */
|
|
90
|
+
return /* @__PURE__ */ jsxs(
|
|
96
91
|
"a",
|
|
97
92
|
{
|
|
98
93
|
className: "rich-link",
|
|
99
94
|
href: node.url,
|
|
100
95
|
target: "_blank",
|
|
101
|
-
rel: "noopener
|
|
102
|
-
children
|
|
96
|
+
rel: "noopener",
|
|
97
|
+
children: [
|
|
98
|
+
/* @__PURE__ */ jsx(LinkFavicon, { href: node.url }),
|
|
99
|
+
children
|
|
100
|
+
]
|
|
103
101
|
},
|
|
104
102
|
key
|
|
105
103
|
);
|
|
@@ -295,7 +293,7 @@ function applyBlockId(element, blockId, nodeKey) {
|
|
|
295
293
|
`${nodeKey}-block-anchor`
|
|
296
294
|
);
|
|
297
295
|
}
|
|
298
|
-
function renderTree(node, editor, editorConfig, headingSlugs, key, blockId) {
|
|
296
|
+
function renderTree(node, editor, editorConfig, headingSlugs, key, blockId, builtinNodeOverrides) {
|
|
299
297
|
const nodeKey = node.getKey ? node.getKey() : key;
|
|
300
298
|
if (typeof node.decorate === "function") {
|
|
301
299
|
try {
|
|
@@ -325,18 +323,36 @@ function renderTree(node, editor, editorConfig, headingSlugs, key, blockId) {
|
|
|
325
323
|
editor,
|
|
326
324
|
editorConfig,
|
|
327
325
|
headingSlugs,
|
|
328
|
-
`${nodeKey}-${i}
|
|
326
|
+
`${nodeKey}-${i}`,
|
|
327
|
+
void 0,
|
|
328
|
+
builtinNodeOverrides
|
|
329
329
|
)
|
|
330
330
|
);
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
|
+
const textContent = node.getTextContent ? node.getTextContent() : void 0;
|
|
334
|
+
const override = builtinNodeOverrides?.[serialized.type];
|
|
335
|
+
if (override) {
|
|
336
|
+
const defaultRenderer = () => renderBuiltinNode(
|
|
337
|
+
serialized,
|
|
338
|
+
nodeKey,
|
|
339
|
+
children,
|
|
340
|
+
headingSlugs,
|
|
341
|
+
textContent
|
|
342
|
+
);
|
|
343
|
+
return applyBlockId(
|
|
344
|
+
override(serialized, nodeKey, children, defaultRenderer),
|
|
345
|
+
blockId,
|
|
346
|
+
nodeKey
|
|
347
|
+
);
|
|
348
|
+
}
|
|
333
349
|
return applyBlockId(
|
|
334
|
-
renderBuiltinNode(serialized, nodeKey, children, headingSlugs),
|
|
350
|
+
renderBuiltinNode(serialized, nodeKey, children, headingSlugs, textContent),
|
|
335
351
|
blockId,
|
|
336
352
|
nodeKey
|
|
337
353
|
);
|
|
338
354
|
}
|
|
339
|
-
function renderEditorToReact(value, nodes) {
|
|
355
|
+
function renderEditorToReact(value, nodes, builtinNodeOverrides) {
|
|
340
356
|
const editor = createHeadlessEditor({
|
|
341
357
|
nodes,
|
|
342
358
|
theme: editorTheme,
|
|
@@ -361,7 +377,8 @@ function renderEditorToReact(value, nodes) {
|
|
|
361
377
|
editorConfig,
|
|
362
378
|
headingSlugs,
|
|
363
379
|
`ssr-${i}`,
|
|
364
|
-
rawRootChildren?.[i]?.$?.blockId
|
|
380
|
+
rawRootChildren?.[i]?.$?.blockId,
|
|
381
|
+
builtinNodeOverrides
|
|
365
382
|
)
|
|
366
383
|
);
|
|
367
384
|
content = /* @__PURE__ */ jsx(Fragment, { children });
|
|
@@ -393,7 +410,8 @@ function renderEditorToReact(value, nodes) {
|
|
|
393
410
|
nestedEditorConfig,
|
|
394
411
|
headingSlugs,
|
|
395
412
|
`nested-${i}`,
|
|
396
|
-
nestedRawChildren?.[i]?.$?.blockId
|
|
413
|
+
nestedRawChildren?.[i]?.$?.blockId,
|
|
414
|
+
builtinNodeOverrides
|
|
397
415
|
)
|
|
398
416
|
);
|
|
399
417
|
nested = /* @__PURE__ */ jsx(Fragment, { children: ch });
|
|
@@ -410,14 +428,14 @@ function RichRenderer({
|
|
|
410
428
|
style,
|
|
411
429
|
as: Component = "div",
|
|
412
430
|
rendererConfig,
|
|
413
|
-
extraNodes
|
|
431
|
+
extraNodes,
|
|
432
|
+
builtinNodeOverrides
|
|
414
433
|
}) {
|
|
415
434
|
const variantClass = getVariantClass(variant);
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
value,
|
|
419
|
-
|
|
420
|
-
);
|
|
435
|
+
const { content, footnoteData, renderNestedContent } = useMemo(() => {
|
|
436
|
+
const nodes = extraNodes ? [...allNodes, ...extraNodes] : allNodes;
|
|
437
|
+
return renderEditorToReact(value, nodes, builtinNodeOverrides);
|
|
438
|
+
}, [builtinNodeOverrides, extraNodes, value]);
|
|
421
439
|
const classes = ["rich-content", variantClass, className].filter(Boolean).join(" ");
|
|
422
440
|
return /* @__PURE__ */ jsx(PortalThemeProvider, { className: variantClass, theme, children: /* @__PURE__ */ jsx(ColorSchemeProvider, { colorScheme: theme, children: /* @__PURE__ */ jsx(
|
|
423
441
|
RendererConfigProvider,
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ColorScheme, RendererConfig, RichEditorVariant } from '@haklex/rich-editor/static';
|
|
2
2
|
import { Klass, LexicalNode, SerializedEditorState } from 'lexical';
|
|
3
|
-
import { CSSProperties } from 'react';
|
|
3
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
4
|
+
export type BuiltinNodeRenderer = (node: any, key: string, children: ReactNode[] | null, defaultRenderer: () => ReactNode) => ReactNode;
|
|
4
5
|
export interface RichRendererProps {
|
|
5
6
|
value: SerializedEditorState;
|
|
6
7
|
variant?: RichEditorVariant;
|
|
@@ -10,5 +11,6 @@ export interface RichRendererProps {
|
|
|
10
11
|
as?: keyof React.JSX.IntrinsicElements;
|
|
11
12
|
rendererConfig?: RendererConfig;
|
|
12
13
|
extraNodes?: Array<Klass<LexicalNode>>;
|
|
14
|
+
builtinNodeOverrides?: Record<string, BuiltinNodeRenderer>;
|
|
13
15
|
}
|
|
14
16
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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,SAAS,EAAE,MAAM,OAAO,CAAA;AAErD,MAAM,MAAM,mBAAmB,GAAG,CAChC,IAAI,EAAE,GAAG,EACT,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,EAC5B,eAAe,EAAE,MAAM,SAAS,KAC7B,SAAS,CAAA;AAEd,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;IACtC,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;CAC3D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haklex/rich-static-renderer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.46",
|
|
4
4
|
"description": "Headless SSR engine for Lexical rich content",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@lexical/headless": "^0.41.0",
|
|
19
|
-
"@haklex/rich-editor": "0.0.
|
|
20
|
-
"@haklex/rich-style-token": "0.0.
|
|
19
|
+
"@haklex/rich-editor": "0.0.46",
|
|
20
|
+
"@haklex/rich-style-token": "0.0.46"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@lexical/code": "^0.41.0",
|