@storyblok/astro 9.0.0 → 9.0.1

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.
@@ -1 +1,2 @@
1
+ export { richTextToHTML } from './richTextToHTML';
1
2
  export { storyblokApiInstance as storyblokApi } from 'virtual:storyblok-init';
@@ -1 +1,4 @@
1
+ import '../public.d.ts';
2
+
3
+ export { richTextToHTML } from './richTextToHTML';
1
4
  export { storyblokApiInstance as storyblokApi } from 'virtual:storyblok-init';
@@ -0,0 +1,94 @@
1
+ import {
2
+ ComponentBlok,
3
+ richTextResolver,
4
+ type StoryblokRichTextNode,
5
+ type StoryblokRichTextOptions,
6
+ } from '@storyblok/js';
7
+ import { experimental_AstroContainer } from 'astro/container';
8
+ import StoryblokComponent from '@storyblok/astro/StoryblokComponent.astro';
9
+
10
+ // Lazily initialized Astro container (for rendering blok components)
11
+ let container: null | experimental_AstroContainer = null;
12
+
13
+ /**
14
+ * @experimental Converts a Storyblok RichText field into an HTML string.
15
+ *
16
+ * This API is still under development and may change in future releases.
17
+ * It also relies on Astro's experimental
18
+ * [experimental_AstroContainer](https://docs.astro.build/en/reference/container-reference/) feature.
19
+ *
20
+ * @async
21
+ * @param {StoryblokRichTextNode} richTextField - The root RichText node to convert.
22
+ * @param {StoryblokRichTextOptions['tiptapExtensions']} [tiptapExtensions] - Optional custom
23
+ * tiptap extensions for customizing how specific nodes or marks are rendered.
24
+ * @returns {Promise<string>} A promise that resolves to the HTML string representation
25
+ * of the provided RichText content.
26
+ *
27
+ * @example
28
+ * ```astro
29
+ * ---
30
+ * import { richTextToHTML } from '@storyblok/astro/client';
31
+ * const { blok } = Astro.props;
32
+ * const renderedRichText = await richTextToHTML(blok.text);
33
+ * ---
34
+ *
35
+ * <div set:html={renderedRichText} />
36
+ * ```
37
+ */
38
+ export const richTextToHTML = async (
39
+ richTextField: StoryblokRichTextNode,
40
+ tiptapExtensions?: StoryblokRichTextOptions['tiptapExtensions'],
41
+ ): Promise<string> => {
42
+ // Create Astro container only once
43
+ if (!container) {
44
+ container = await experimental_AstroContainer.create();
45
+ }
46
+
47
+ // Collect async render results keyed by placeholder ID
48
+ const asyncReplacements: Promise<{ id: string; result: string }>[] = [];
49
+
50
+ const resolver = richTextResolver<string>({
51
+ tiptapExtensions: {
52
+ blok: ComponentBlok.configure({
53
+ renderComponent: (blok: Record<string, unknown>, _id?: string) => {
54
+ if (!blok || typeof blok !== 'object' || !container) {
55
+ return '';
56
+ }
57
+
58
+ // Generate unique placeholder ID
59
+ const id = crypto.randomUUID();
60
+ const placeholder = `<!--ASYNC-${id}-->`;
61
+
62
+ // Queue async render
63
+ const promise = container
64
+ .renderToString(StoryblokComponent, {
65
+ props: { blok },
66
+ })
67
+ .then(result => ({ id, result }))
68
+ .catch((err) => {
69
+ console.error('Component rendering failed:', err);
70
+ return { id, result: '<!-- Component render error -->' };
71
+ });
72
+
73
+ asyncReplacements.push(promise);
74
+ return placeholder;
75
+ },
76
+ }),
77
+ ...tiptapExtensions,
78
+ },
79
+ });
80
+
81
+ let html = resolver.render(richTextField);
82
+ // Wait for all async renders
83
+ const results = await Promise.all(asyncReplacements);
84
+ const replacements = new Map(
85
+ results.map(({ id, result }) => [id, result ?? '']),
86
+ );
87
+
88
+ // Single-pass replacement using regex
89
+ html = html.replace(/<!--ASYNC-([\w-]+)-->/g, (_, id: string) => {
90
+ return replacements.get(id) ?? '';
91
+ });
92
+
93
+ return html;
94
+ };
package/dist/public.d.ts CHANGED
@@ -3,6 +3,12 @@
3
3
  * Provides IntelliSense, JSDoc, and type safety for SDK consumers.
4
4
  */
5
5
 
6
+ declare module 'virtual:storyblok-init' {
7
+ import type { StoryblokClient } from '@storyblok/astro';
8
+
9
+ export const storyblokApiInstance: StoryblokClient;
10
+ }
11
+
6
12
  declare module '@storyblok/astro/StoryblokComponent.astro' {
7
13
  import type { SbBlokData } from '@storyblok/astro';
8
14
 
@@ -28,7 +34,7 @@ declare module '@storyblok/astro/client' {
28
34
  import type {
29
35
  StoryblokClient,
30
36
  StoryblokRichTextNode,
31
- StoryblokRichTextResolvers,
37
+ StoryblokRichTextOptions,
32
38
  } from '@storyblok/astro';
33
39
  /**
34
40
  * @experimental Converts a Storyblok RichText field into an HTML string.
@@ -39,7 +45,7 @@ declare module '@storyblok/astro/client' {
39
45
  *
40
46
  * @async
41
47
  * @param {StoryblokRichTextNode} richTextField - The root RichText node to convert.
42
- * @param {StoryblokRichTextResolvers} [customResolvers] - Optional custom resolvers
48
+ * @param {StoryblokRichTextOptions['tiptapExtensions']} [tiptapExtensions] - Optional custom resolvers
43
49
  * for customizing how specific nodes or marks are transformed into HTML.
44
50
  * @returns {Promise<string>} A promise that resolves to the HTML string representation
45
51
  * of the provided RichText content.
@@ -57,7 +63,7 @@ declare module '@storyblok/astro/client' {
57
63
  */
58
64
  export function richTextToHTML(
59
65
  richTextField: StoryblokRichTextNode,
60
- customResolvers?: StoryblokRichTextResolvers
66
+ tiptapExtensions?: StoryblokRichTextOptions['tiptapExtensions'],
61
67
  ): Promise<string>;
62
68
 
63
69
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@storyblok/astro",
3
3
  "type": "module",
4
- "version": "9.0.0",
4
+ "version": "9.0.1",
5
5
  "private": false,
6
6
  "description": "Official Astro integration for the Storyblok Headless CMS",
7
7
  "author": "Storyblok",