Skip to content

Commit

Permalink
feat: add textResolver
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Lozovsky <[email protected]>
  • Loading branch information
Alexander Lozovsky committed Mar 27, 2024
1 parent cd1f889 commit 056fb86
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 7 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,7 @@ const { text } = blok;
## Advanced usage

Sensible default resolvers for marks and nodes are provided out-of-the-box. You only have to provide custom ones if you want to
override the default behavior.

Use `resolver` to enable and control the rendering of embedded components, and `schema` to control how you want the nodes and marks be rendered:
override the default behavior:

```js
<RichTextRenderer
Expand Down Expand Up @@ -144,10 +142,20 @@ Use `resolver` to enable and control the rendering of embedded components, and `
props: { blok },
};
}}
textResolver={(text) => {
const currentYear = new Date().getFullYear().toString();
return {
content: text.replaceAll("{currentYear}", currentYear),
};
}}
{...storyblokEditable(blok)}
/>
```

- `schema` - controls how you want the nodes and marks be rendered
- `resolver` - enables and controls the rendering of embedded components
- `textResolver` - controls the rendering of the plain text. Useful if you need some text preprocessing (translation, sanitization, etc.)

### Content via prop

By default, content in `nodes` is handled automatically and passed via slots keeping configuration as follows:
Expand Down
9 changes: 9 additions & 0 deletions demo/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,15 @@ const richTextFromStoryblok: RichTextType = {
},
],
},
{
type: "paragraph",
content: [
{
text: "© {currentYear} Company X. All Rights Reserved",
type: "text",
},
],
},
],
};
Expand Down
6 changes: 6 additions & 0 deletions demo/src/storyblok/RichText.astro
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,11 @@ const { text } = blok;
props: { blok },
};
}}
textResolver={(text) => {
const currentYear = new Date().getFullYear().toString();
return {
content: text.replaceAll("{currentYear}", currentYear),
};
}}
{...storyblokEditable(blok)}
/>
21 changes: 19 additions & 2 deletions lib/RichTextRenderer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,28 @@ export type Props = {
```
*/
resolver: Options["resolver"];
/**
Function to control the rendering of the plain text. Useful for text preprocessing, f.e.
```
// replaces {currentYear} substring with the actual value in all texts
textResolver={(text) => {
const currentYear = new Date().getFullYear().toString();
return {
content: text.replaceAll("{currentYear}", currentYear),
};
}}
```
*/
textResolver: Options["textResolver"];
};
const { content, schema, resolver, ...props } = Astro.props;
const { content, schema, resolver, textResolver, ...props } = Astro.props;
const nodes = resolveRichTextToNodes(content, { schema, resolver });
const nodes = resolveRichTextToNodes(content, {
schema,
resolver,
textResolver,
});
---

<div {...props}>
Expand Down
1 change: 1 addition & 0 deletions lib/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type Schema = {
export type Options = {
schema?: Schema;
resolver?: (blok: SbBlok) => ComponentNode;
textResolver?: (str: string) => ComponentNode;
};

export type Anchor = {
Expand Down
77 changes: 77 additions & 0 deletions lib/src/utils/resolveRichTextToNodes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,83 @@ describe("resolveNode", () => {
});
});

it("text with textResolver", () => {
const node: SchemaNode = {
text: "Hello {name}",
type: "text",
};

const textResolver = (text: string) => {
return {
content: text.replace("{name}", "World"),
};
};

// default
expect(
resolveNode(node, {
textResolver,
})
).toStrictEqual({
content: "Hello World",
});

// with marks
expect(
resolveNode(
{
...node,
marks: [{ type: "bold" }],
},
{
textResolver,
}
)
).toStrictEqual({
content: [
{
component: "b",
content: [
{
content: "Hello World",
},
],
},
],
});

// with schema override
expect(
resolveNode(node, {
schema: {
nodes: {
text: () => ({
component: "p",
props: { class: "class-1" },
}),
},
},
textResolver: (text) => ({
content: text.replace("{name}", "World"),
component: "span",
props: { class: "class-2" },
}),
})
).toStrictEqual({
component: "p",
props: {
class: "class-1",
},
content: [
{
component: "span",
props: { class: "class-2" },
content: "Hello World",
},
],
});
});

it("paragraph", () => {
const node: SchemaNode = {
type: "paragraph",
Expand Down
23 changes: 21 additions & 2 deletions lib/src/utils/resolveRichTextToNodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export const resolveNode = (
node: SchemaNode,
options: Options = {}
): ComponentNode => {
const { schema } = options;
const { schema, textResolver } = options;

if (node.type === "heading") {
const resolverFn = schema?.nodes?.[node.type];
Expand Down Expand Up @@ -286,7 +286,13 @@ export const resolveNode = (
const { text, marks } = node;

if (marks) {
let marked: ComponentNode[] = [{ content: text }];
let marked: ComponentNode[] = [
{
content: text,
...textResolver?.(text),
},
];

[...marks].reverse().forEach((mark) => {
marked = [resolveMark(marked, mark, schema)];
});
Expand All @@ -297,9 +303,22 @@ export const resolveNode = (
};
}

const resolverResult = resolverFn?.(node);
const textResolverResult = textResolver?.(text);

if (resolverResult && textResolverResult) {
return {
component: resolverResult.component,
props: resolverResult.props,
content: [textResolverResult],
};
}

return {
content: text,
...resolverFn?.(node),
...textResolverResult,
...resolverResult,
};
}

Expand Down

0 comments on commit 056fb86

Please sign in to comment.