@haklex/rich-static-renderer 0.0.45 → 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 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
@@ -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":"AAuBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAyMhD,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,OAAmB,EACnB,KAAe,EACf,SAAS,EACT,KAAK,EACL,EAAE,EAAE,SAAiB,EACrB,cAAc,EACd,UAAU,GACX,EAAE,iBAAiB,2CAwCnB"}
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>): ReactNode;
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":"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,GAChC,SAAS,CAmMX"}
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
@@ -1,3 +1,3 @@
1
1
  export { RichRenderer } from './RichRenderer';
2
- export type { RichRendererProps } from './types';
2
+ export type { BuiltinNodeRenderer, RichRendererProps } from './types';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -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
@@ -3,20 +3,20 @@ import { RendererWrapper, RubyRenderer, LinkFavicon, getVariantClass, allNodes,
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\u3001-\u9fff\uac00-\ud7af\uff00-\uffef-]/g, "").replaceAll(/[\s_]+/g, "-").replaceAll(/^-+|-+$/g, "");
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,14 +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
- tabIndex: 0,
47
- href: `#${slug}`
48
- }
49
- ),
42
+ slug && /* @__PURE__ */ jsx("a", { className: "rich-heading-anchor", tabIndex: 0, href: `#${slug}` }),
50
43
  children
51
44
  ] }, key);
52
45
  }
@@ -300,7 +293,7 @@ function applyBlockId(element, blockId, nodeKey) {
300
293
  `${nodeKey}-block-anchor`
301
294
  );
302
295
  }
303
- function renderTree(node, editor, editorConfig, headingSlugs, key, blockId) {
296
+ function renderTree(node, editor, editorConfig, headingSlugs, key, blockId, builtinNodeOverrides) {
304
297
  const nodeKey = node.getKey ? node.getKey() : key;
305
298
  if (typeof node.decorate === "function") {
306
299
  try {
@@ -330,18 +323,36 @@ function renderTree(node, editor, editorConfig, headingSlugs, key, blockId) {
330
323
  editor,
331
324
  editorConfig,
332
325
  headingSlugs,
333
- `${nodeKey}-${i}`
326
+ `${nodeKey}-${i}`,
327
+ void 0,
328
+ builtinNodeOverrides
334
329
  )
335
330
  );
336
331
  }
337
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
+ }
338
349
  return applyBlockId(
339
- renderBuiltinNode(serialized, nodeKey, children, headingSlugs),
350
+ renderBuiltinNode(serialized, nodeKey, children, headingSlugs, textContent),
340
351
  blockId,
341
352
  nodeKey
342
353
  );
343
354
  }
344
- function renderEditorToReact(value, nodes) {
355
+ function renderEditorToReact(value, nodes, builtinNodeOverrides) {
345
356
  const editor = createHeadlessEditor({
346
357
  nodes,
347
358
  theme: editorTheme,
@@ -366,7 +377,8 @@ function renderEditorToReact(value, nodes) {
366
377
  editorConfig,
367
378
  headingSlugs,
368
379
  `ssr-${i}`,
369
- rawRootChildren?.[i]?.$?.blockId
380
+ rawRootChildren?.[i]?.$?.blockId,
381
+ builtinNodeOverrides
370
382
  )
371
383
  );
372
384
  content = /* @__PURE__ */ jsx(Fragment, { children });
@@ -398,7 +410,8 @@ function renderEditorToReact(value, nodes) {
398
410
  nestedEditorConfig,
399
411
  headingSlugs,
400
412
  `nested-${i}`,
401
- nestedRawChildren?.[i]?.$?.blockId
413
+ nestedRawChildren?.[i]?.$?.blockId,
414
+ builtinNodeOverrides
402
415
  )
403
416
  );
404
417
  nested = /* @__PURE__ */ jsx(Fragment, { children: ch });
@@ -415,14 +428,14 @@ function RichRenderer({
415
428
  style,
416
429
  as: Component = "div",
417
430
  rendererConfig,
418
- extraNodes
431
+ extraNodes,
432
+ builtinNodeOverrides
419
433
  }) {
420
434
  const variantClass = getVariantClass(variant);
421
- const nodes = extraNodes ? [...allNodes, ...extraNodes] : allNodes;
422
- const { content, footnoteData, renderNestedContent } = renderEditorToReact(
423
- value,
424
- nodes
425
- );
435
+ const { content, footnoteData, renderNestedContent } = useMemo(() => {
436
+ const nodes = extraNodes ? [...allNodes, ...extraNodes] : allNodes;
437
+ return renderEditorToReact(value, nodes, builtinNodeOverrides);
438
+ }, [builtinNodeOverrides, extraNodes, value]);
426
439
  const classes = ["rich-content", variantClass, className].filter(Boolean).join(" ");
427
440
  return /* @__PURE__ */ jsx(PortalThemeProvider, { className: variantClass, theme, children: /* @__PURE__ */ jsx(ColorSchemeProvider, { colorScheme: theme, children: /* @__PURE__ */ jsx(
428
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
@@ -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;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"}
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.45",
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-style-token": "0.0.45",
20
- "@haklex/rich-editor": "0.0.45"
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",