@storyblok/astro 4.0.5 → 4.1.0-alpha.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,3 +1,2 @@
1
- import { default as FallbackComponent } from './FallbackComponent.astro';
2
-
1
+ import FallbackComponent from "./FallbackComponent.astro";
3
2
  export default FallbackComponent;
@@ -1,3 +1,2 @@
1
- import { default as RichTextRenderer } from './RichTextRenderer.astro';
2
-
1
+ import RichTextRenderer from "./RichTextRenderer.astro";
3
2
  export default RichTextRenderer;
@@ -1,3 +1,2 @@
1
- import { default as StoryblokComponent } from './StoryblokComponent.astro';
2
-
1
+ import StoryblokComponent from "./StoryblokComponent.astro";
3
2
  export default StoryblokComponent;
@@ -1,8 +1,16 @@
1
- import { ISbConfig, ISbRichtext, SbRichTextOptions, StoryblokBridgeConfigV2, StoryblokClient } from './types';
2
- import { AstroIntegration } from 'astro';
3
-
4
- export { storyblokEditable, loadStoryblokBridge, RichTextResolver, RichTextSchema, } from '@storyblok/js';
1
+ import type { AstroGlobal, AstroIntegration } from "astro";
2
+ import type { ISbConfig, ISbRichtext, ISbStoriesParams, ISbStoryData, SbRichTextOptions, StoryblokBridgeConfigV2, StoryblokClient } from "./types";
3
+ export { handleStoryblokMessage } from "./live-preview/handleStoryblokMessage";
4
+ export { storyblokEditable, loadStoryblokBridge, RichTextResolver, RichTextSchema, } from "@storyblok/js";
5
5
  export declare function useStoryblokApi(): StoryblokClient;
6
+ export declare function useStoryblok({ slug, apiOptions, bridgeOptions, Astro, }: {
7
+ slug: string;
8
+ apiOptions: ISbStoriesParams;
9
+ bridgeOptions?: StoryblokBridgeConfigV2;
10
+ Astro: AstroGlobal;
11
+ }): Promise<ISbStoryData<import("@storyblok/js").StoryblokComponentType<string> & {
12
+ [index: string]: any;
13
+ }>>;
6
14
  export declare function renderRichText(data?: ISbRichtext, options?: SbRichTextOptions): string;
7
15
  export type IntegrationOptions = {
8
16
  /**
@@ -47,6 +55,10 @@ export type IntegrationOptions = {
47
55
  * Please note: the path takes into account the `componentsDir` option.
48
56
  */
49
57
  customFallbackComponent?: string;
58
+ /**
59
+ * A boolean to enable/disable the Experimental Live Preview feature. Disabled by default.
60
+ */
61
+ experimentalLivePreview?: boolean;
50
62
  };
51
63
  export default function storyblokIntegration(options: IntegrationOptions): AstroIntegration;
52
- export * from './types';
64
+ export * from "./types";
@@ -0,0 +1,11 @@
1
+ import type { ISbStoryData } from "@storyblok/js";
2
+ /**
3
+ * Our current tests indicate that the post request section
4
+ * is the bottleneck in terms of time consumption.
5
+ * We should explore alternative methods to optimize
6
+ * this process and improve efficiency.
7
+ */
8
+ export declare function handleStoryblokMessage(event: {
9
+ action: string;
10
+ story: ISbStoryData;
11
+ }): Promise<void>;
@@ -0,0 +1 @@
1
+ export declare const onRequest: import("astro").MiddlewareHandler;
@@ -9,4 +9,4 @@ ISbAlternateObject, // previously AlternateObject
9
9
  ISbStoriesParams, // previously StoriesParams
10
10
  ISbStoryParams, // previously StoryParams
11
11
  ISbRichtext, // previously Richtext
12
- ISbEventPayload, } from '@storyblok/js';
12
+ ISbEventPayload, } from "@storyblok/js";
@@ -0,0 +1,8 @@
1
+ import type { ISbStoriesParams, StoryblokBridgeConfigV2 } from "@storyblok/js";
2
+ export interface RawCodeItem {
3
+ options?: {
4
+ apiOptions?: ISbStoriesParams;
5
+ bridgeOptions?: StoryblokBridgeConfigV2;
6
+ };
7
+ }
8
+ export declare function generateFinalBridgeObject(rawCode: RawCodeItem[]): StoryblokBridgeConfigV2;
@@ -0,0 +1 @@
1
+ export declare function parseAstRawCode(astCode: any): any;
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from "vite";
2
+ export declare function vitePluginStoryblokBridge(experimentalLivePreview: boolean, output: string): Plugin;
@@ -1,3 +1,2 @@
1
- import { Plugin } from 'vite';
2
-
1
+ import type { Plugin } from "vite";
3
2
  export declare function vitePluginStoryblokComponents(componentsDir: string, components?: object, enableFallbackComponent?: boolean, customFallbackComponent?: string): Plugin;
@@ -1,4 +1,3 @@
1
- import { Plugin } from 'vite';
2
- import { ISbConfig } from '../types';
3
-
1
+ import type { ISbConfig } from "../types";
2
+ import type { Plugin } from "vite";
4
3
  export declare function vitePluginStoryblokInit(accessToken: string, useCustomApi: boolean, apiOptions: ISbConfig): Plugin;
@@ -1,3 +1,2 @@
1
- import { Plugin } from 'vite';
2
-
1
+ import type { Plugin } from "vite";
3
2
  export declare function vitePluginStoryblokOptions(options: object): Plugin;
@@ -0,0 +1,75 @@
1
+ import type { ISbStoryData } from "@storyblok/js";
2
+
3
+ let timeout: NodeJS.Timeout;
4
+ /**
5
+ * Our current tests indicate that the post request section
6
+ * is the bottleneck in terms of time consumption.
7
+ * We should explore alternative methods to optimize
8
+ * this process and improve efficiency.
9
+ */
10
+ export async function handleStoryblokMessage(event: {
11
+ action: string;
12
+ story: ISbStoryData;
13
+ }) {
14
+ const { action, story } = event || {};
15
+
16
+ if (action === "input" && story) {
17
+ // Debounce the getNewHTMLBody function
18
+ const debouncedGetNewHTMLBody = async () => {
19
+ const newBody = await getNewHTMLBody(story);
20
+ const currentBody = document.body as HTMLElement;
21
+ if (newBody.outerHTML === currentBody.outerHTML) return;
22
+ // Get current focused element in Storyblok
23
+ const focusedElem = document.querySelector('[data-blok-focused="true"]');
24
+ updateDOMWithNewBody(currentBody, newBody, focusedElem);
25
+ };
26
+ const debounceDelay = 1000; // Adjust the delay as needed
27
+ clearTimeout(timeout);
28
+ timeout = setTimeout(debouncedGetNewHTMLBody, debounceDelay);
29
+ }
30
+
31
+ if (["published", "change"].includes(event?.action)) {
32
+ location.reload();
33
+ }
34
+ }
35
+
36
+ function updateDOMWithNewBody(
37
+ currentBody: HTMLElement,
38
+ newBody: HTMLElement,
39
+ focusedElem: Element | null
40
+ ) {
41
+ if (focusedElem) {
42
+ //Get the [data-blok-uid] of the focused element in storyblok
43
+ const focusedElementID = focusedElem.getAttribute("data-blok-uid");
44
+ //Now find the same element by above [data-blok-uid] in our new virtual HTML page
45
+ const newDomFocusElem = newBody.querySelector(
46
+ `[data-blok-uid="${focusedElementID}"]`
47
+ );
48
+ if (newDomFocusElem) {
49
+ // Add the [data-blok-focused] attribute to the above element
50
+ newDomFocusElem.setAttribute("data-blok-focused", "true");
51
+ // console.log("Doing partial replace");
52
+ focusedElem.replaceWith(newDomFocusElem);
53
+ }
54
+ } else {
55
+ // console.log("Doing full replace");
56
+ currentBody.replaceWith(newBody);
57
+ }
58
+ }
59
+
60
+ async function getNewHTMLBody(story: ISbStoryData) {
61
+ const result = await fetch(location.href, {
62
+ method: "POST",
63
+ body: JSON.stringify({
64
+ ...story,
65
+ is_storyblok_preview: true,
66
+ }),
67
+ headers: {
68
+ "Content-Type": "application/json",
69
+ },
70
+ });
71
+ const html = await result.text();
72
+ const parser = new DOMParser();
73
+ const doc = parser.parseFromString(html, "text/html");
74
+ return doc.body;
75
+ }
@@ -0,0 +1,11 @@
1
+ import { defineMiddleware } from "astro/middleware";
2
+
3
+ export const onRequest = defineMiddleware(async ({ locals, request }, next) => {
4
+ if (request["method"] === "POST") {
5
+ const requestBody = await request.json();
6
+ if (requestBody && requestBody["is_storyblok_preview"]) {
7
+ locals["_storyblok_preview_data"] = requestBody;
8
+ }
9
+ }
10
+ return next();
11
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storyblok/astro",
3
- "version": "4.0.5",
3
+ "version": "4.1.0-alpha.1",
4
4
  "description": "Official Astro integration for the Storyblok Headless CMS",
5
5
  "main": "./dist/storyblok-astro.js",
6
6
  "module": "./dist/storyblok-astro.mjs",
@@ -8,7 +8,8 @@
8
8
  "dist",
9
9
  "components",
10
10
  "dev-toolbar",
11
- "live-preview"
11
+ "live-preview",
12
+ "utils"
12
13
  ],
13
14
  "exports": {
14
15
  ".": {
@@ -35,6 +36,11 @@
35
36
  "types": "./dev-toolbar/toolbarApp.ts",
36
37
  "import": "./dev-toolbar/toolbarApp.ts",
37
38
  "require": "./dev-toolbar/toolbarApp.ts"
39
+ },
40
+ "./middleware.ts": {
41
+ "types": "./live-preview/middleware.ts",
42
+ "import": "./live-preview/middleware.ts",
43
+ "require": "./live-preview/middleware.ts"
38
44
  }
39
45
  },
40
46
  "types": "./dist/types/index.d.ts",
@@ -51,19 +57,20 @@
51
57
  },
52
58
  "dependencies": {
53
59
  "@storyblok/js": "^3.0.8",
54
- "camelcase": "^8.0.0"
60
+ "camelcase": "^8.0.0",
61
+ "lodash.mergewith": "^4.6.2"
55
62
  },
56
63
  "devDependencies": {
57
64
  "@cypress/vite-dev-server": "^5.0.7",
58
65
  "@rollup/plugin-dynamic-import-vars": "^2.1.2",
59
- "@types/node": "20.12.7",
66
+ "@types/node": "20.12.11",
60
67
  "astro": "^4.6.3",
61
- "cypress": "^13.8.0",
62
- "eslint-plugin-cypress": "^2.15.2",
68
+ "cypress": "^13.8.1",
69
+ "eslint-plugin-cypress": "^3.2.0",
63
70
  "start-server-and-test": "^2.0.3",
64
71
  "typescript": "5.4.5",
65
72
  "vite": "^5.2.10",
66
- "vite-plugin-dts": "^3.8.3"
73
+ "vite-plugin-dts": "^3.7.3"
67
74
  },
68
75
  "peerDependencies": {
69
76
  "astro": "^3.0.0 || ^4.0.0"
@@ -88,6 +95,10 @@
88
95
  {
89
96
  "name": "next",
90
97
  "prerelease": true
98
+ },
99
+ {
100
+ "name": "alpha",
101
+ "prerelease": true
91
102
  }
92
103
  ]
93
104
  },
@@ -0,0 +1,42 @@
1
+ import type { ISbStoriesParams, StoryblokBridgeConfigV2 } from "@storyblok/js";
2
+
3
+ export interface RawCodeItem {
4
+ options?: {
5
+ apiOptions?: ISbStoriesParams;
6
+ bridgeOptions?: StoryblokBridgeConfigV2;
7
+ };
8
+ }
9
+ export function generateFinalBridgeObject(
10
+ rawCode: RawCodeItem[]
11
+ ): StoryblokBridgeConfigV2 {
12
+ let mergedOptions = {
13
+ resolveRelations: [],
14
+ };
15
+
16
+ function addToResolveRelations(
17
+ resolveRelations: string[] | string | undefined
18
+ ) {
19
+ if (resolveRelations) {
20
+ if (Array.isArray(resolveRelations)) {
21
+ mergedOptions.resolveRelations.push(...resolveRelations);
22
+ } else {
23
+ mergedOptions.resolveRelations.push(resolveRelations);
24
+ }
25
+ }
26
+ }
27
+
28
+ for (const item of rawCode) {
29
+ if (item.options) {
30
+ const { apiOptions, bridgeOptions } = item.options;
31
+ addToResolveRelations(apiOptions?.resolve_relations);
32
+ if (bridgeOptions) {
33
+ const { resolveRelations, ...rest } = bridgeOptions;
34
+ addToResolveRelations(resolveRelations);
35
+ Object.assign(mergedOptions, rest);
36
+ }
37
+ }
38
+ }
39
+ mergedOptions.resolveRelations = [...new Set(mergedOptions.resolveRelations)];
40
+
41
+ return mergedOptions;
42
+ }
@@ -0,0 +1,39 @@
1
+ import mergeWith from "lodash.mergewith";
2
+
3
+ export function parseAstRawCode(astCode) {
4
+ let obj;
5
+ function customizer(_, srcValue) {
6
+ if (
7
+ srcValue?.type === "AwaitExpression" &&
8
+ srcValue?.argument?.callee?.name === "useStoryblok"
9
+ ) {
10
+ const props = srcValue?.argument?.arguments;
11
+ props.forEach((argument) => {
12
+ argument?.properties?.forEach((property) => {
13
+ if (["apiOptions", "bridgeOptions"].includes(property.key.name)) {
14
+ obj = {
15
+ ...obj,
16
+ [property.key.name]: getAstPropToObj(property?.value?.properties),
17
+ };
18
+ }
19
+ });
20
+ });
21
+ }
22
+ }
23
+
24
+ mergeWith({}, astCode, customizer);
25
+ return obj;
26
+ }
27
+
28
+ function getAstPropToObj(properties) {
29
+ return properties.reduce((options, { key, value }) => {
30
+ const { type } = value;
31
+ options[key.name] =
32
+ type === "Literal"
33
+ ? value.value
34
+ : type === "ArrayExpression"
35
+ ? value.elements.map((v) => v.value)
36
+ : value.value;
37
+ return options;
38
+ }, {});
39
+ }