From afe6fd870222ee3bbeb972d6723b676ee8c5b02b Mon Sep 17 00:00:00 2001 From: alvarosabu Date: Wed, 25 Sep 2024 14:31:11 +0200 Subject: [PATCH 1/5] feat: added and types --- .github/ISSUE_TEMPLATE/config.yml | 2 +- README.md | 3 + lib/common/SbRichText.tsx | 26 ++ lib/common/client.ts | 6 +- lib/common/index.ts | 15 + lib/common/richtext.ts | 26 ++ lib/cypress/support/component-index.html | 10 +- lib/cypress/support/component.js | 8 +- lib/cypress/support/e2e.js | 2 +- lib/index.ts | 4 +- lib/package.json | 3 +- lib/types.ts | 6 + lib/utils/index.ts | 65 +++ package-lock.json | 86 +++- package.json | 3 + .../.vscode/settings.json | 2 +- playground-next13-live-editing/app/page.tsx | 2 +- playground-next13-live-editing/tsconfig.json | 21 +- playground-next13-rsc/.vscode/settings.json | 2 +- playground/App.tsx | 384 +++++++++++++++++- playground/index.html | 2 +- 21 files changed, 637 insertions(+), 41 deletions(-) create mode 100644 lib/common/SbRichText.tsx create mode 100644 lib/common/richtext.ts create mode 100644 lib/utils/index.ts diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ec4bb386..3ba13e0c 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1 @@ -blank_issues_enabled: false \ No newline at end of file +blank_issues_enabled: false diff --git a/README.md b/README.md index 7ac4616d..852ec955 100755 --- a/README.md +++ b/README.md @@ -31,12 +31,15 @@

## Kickstart a new project + Are you eager to dive into coding? **[Follow these steps to kickstart a new project with Storyblok and React](https://www.storyblok.com/technologies#react?utm_source=github.com&utm_medium=readme&utm_campaign=storyblok-react)**, and get started in just a few minutes! ## 5-minute Tutorial + Are you looking for a hands-on, step-by-step tutorial? The **[React 5-minute Tutorial](https://www.storyblok.com/tp/headless-cms-react?utm_source=github.com&utm_medium=readme&utm_campaign=storyblok-react)** has you covered! It provides comprehensive instructions on how to set up a Storyblok space and connect it to your React project. ## Ultimate Tutorial + Are you looking for a hands-on, step-by-step tutorial? The **[Next.js Ultimate Tutorial](https://www.storyblok.com/tp/nextjs-headless-cms-ultimate-tutorial?utm_source=github.com&utm_medium=readme&utm_campaign=storyblok-react)** has you covered! It provides comprehensive instructions on building a complete, multilingual website using Storyblok and Next.js from start to finish. ## Installation diff --git a/lib/common/SbRichText.tsx b/lib/common/SbRichText.tsx new file mode 100644 index 00000000..28b4cc1f --- /dev/null +++ b/lib/common/SbRichText.tsx @@ -0,0 +1,26 @@ +import React from "react"; + +import { forwardRef } from "react"; +import { convertAttributesInElement } from "../utils"; +import { useSbRichtextResolver } from "./richtext"; +import type { SbRichTextProps } from "../types"; + +// If you're forwarding a ref to SbRichText +const SbRichText = forwardRef( + ({ doc, resolvers }, ref) => { + // Assuming useSbRichtextResolver is a hook you've created + const { render } = useSbRichtextResolver({ + resolvers, + }); + + /* const Root = () => render(doc) */ + const html = render(doc); + const formattedHtml = convertAttributesInElement(html); + + // If you're forwarding a ref, make sure to attach the ref to a DOM element. + // For example, wrapping in a div and attaching the ref to it: + return
{formattedHtml}
; + } +); + +export default SbRichText; diff --git a/lib/common/client.ts b/lib/common/client.ts index 2fbb8328..18b47299 100644 --- a/lib/common/client.ts +++ b/lib/common/client.ts @@ -4,12 +4,13 @@ import { registerStoryblokBridge } from "@storyblok/js"; export const useStoryblokState: TUseStoryblokState = ( initialStory = null, - bridgeOptions = {}, + bridgeOptions = {} ) => { const [story, setStory] = useState(initialStory); const storyId = (initialStory as any)?.internalId ?? initialStory?.id ?? 0; - const isBridgeEnabled = typeof window !== "undefined" && + const isBridgeEnabled = + typeof window !== "undefined" && typeof window.storyblokRegisterEvent !== "undefined"; useEffect(() => { @@ -22,7 +23,6 @@ export const useStoryblokState: TUseStoryblokState = ( (newStory) => setStory(newStory), bridgeOptions ); - }, [initialStory]); return story; diff --git a/lib/common/index.ts b/lib/common/index.ts index 5838207d..d408f9e9 100644 --- a/lib/common/index.ts +++ b/lib/common/index.ts @@ -49,6 +49,8 @@ export const storyblokInit = (pluginOptions: SbReactSDKOptions = {}) => { export { default as StoryblokComponent } from "./storyblok-component"; export { useStoryblokApi as getStoryblokApi }; +export { default as SbRichText } from "./SbRichText"; +export { useSbRichtextResolver } from "./richtext"; export { storyblokEditable, apiPlugin, @@ -60,4 +62,17 @@ export { RichTextSchema, } from "@storyblok/js"; +export { + BlockTypes, + MarkTypes, + richTextResolver, + TextTypes, + type SbRichTextNodeTypes, + type SbRichTextNode, + type SbRichTextOptions, + type SbRichTextResolvers, + type SbRichTextNodeResolver, + type SbRichTextImageOptimizationOptions, +} from "@storyblok/richtext"; + export * from "../types"; diff --git a/lib/common/richtext.ts b/lib/common/richtext.ts new file mode 100644 index 00000000..c09614e6 --- /dev/null +++ b/lib/common/richtext.ts @@ -0,0 +1,26 @@ +import React from "react"; +import { StoryblokComponent } from "@storyblok/react"; +import type { SbRichTextNode, SbRichTextOptions } from "@storyblok/richtext"; +import { BlockTypes, richTextResolver } from "@storyblok/richtext"; + +function componentResolver(node: SbRichTextNode) { + // Convert this to use React.createElement or JSX + // Example with JSX: + return React.createElement(StoryblokComponent, { + blok: node?.attrs?.body[0], + id: node.attrs?.id, + }); +} + +export function useSbRichtextResolver( + options: SbRichTextOptions +) { + const mergedOptions = { + renderFn: React.createElement, + resolvers: { + [BlockTypes.COMPONENT]: componentResolver, + ...options.resolvers, + }, + }; + return richTextResolver(mergedOptions); +} diff --git a/lib/cypress/support/component-index.html b/lib/cypress/support/component-index.html index ac6e79fd..faf3b5f4 100644 --- a/lib/cypress/support/component-index.html +++ b/lib/cypress/support/component-index.html @@ -1,12 +1,12 @@ - + - - - + + + Components App
- \ No newline at end of file + diff --git a/lib/cypress/support/component.js b/lib/cypress/support/component.js index 8f9154b5..e3c23492 100644 --- a/lib/cypress/support/component.js +++ b/lib/cypress/support/component.js @@ -14,14 +14,14 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import "./commands"; // Alternatively you can use CommonJS syntax: // require('./commands') -import { mount } from 'cypress/react18' +import { mount } from "cypress/react18"; -Cypress.Commands.add('mount', mount) +Cypress.Commands.add("mount", mount); // Example use: -// cy.mount() \ No newline at end of file +// cy.mount() diff --git a/lib/cypress/support/e2e.js b/lib/cypress/support/e2e.js index d68db96d..d076cec9 100644 --- a/lib/cypress/support/e2e.js +++ b/lib/cypress/support/e2e.js @@ -14,7 +14,7 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import "./commands"; // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/lib/index.ts b/lib/index.ts index 0198f294..445160df 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -26,8 +26,8 @@ export const useStoryblok = ( if (!storyblokApiInstance) { console.error( "You can't use useStoryblok if you're not loading apiPlugin." - ); - return + ); + return; } async function initStory() { const { data } = await storyblokApiInstance.get( diff --git a/lib/package.json b/lib/package.json index 1ebe0551..19b000f2 100644 --- a/lib/package.json +++ b/lib/package.json @@ -44,7 +44,8 @@ "prepublishOnly": "npm run build && cp ../README.md ./" }, "dependencies": { - "@storyblok/js": "^3.0.8" + "@storyblok/js": "^3.0.8", + "@storyblok/richtext": "^0.3.0" }, "devDependencies": { "@babel/core": "^7.24.7", diff --git a/lib/types.ts b/lib/types.ts index 2d584ff2..1054e225 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,6 +1,7 @@ import React from "react"; import { SbSDKOptions } from "@storyblok/js"; import type { ISbStoryData, StoryblokBridgeConfigV2 } from "@storyblok/js"; +import { SbRichTextNode, SbRichTextResolvers } from "@storyblok/richtext"; export interface SbReactComponentsMap { [key: string]: React.ElementType; @@ -17,6 +18,11 @@ export type TUseStoryblokState = ( bridgeOptions?: StoryblokBridgeConfigV2 ) => ISbStoryData | null; +export interface SbRichTextProps { + doc: SbRichTextNode; + resolvers?: SbRichTextResolvers; +} + export type { ISbConfig, ISbCache, diff --git a/lib/utils/index.ts b/lib/utils/index.ts new file mode 100644 index 00000000..da3c6a78 --- /dev/null +++ b/lib/utils/index.ts @@ -0,0 +1,65 @@ +import React from "react"; + +function camelCase(str: string) { + return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); +} + +function convertStyleStringToObject(styleString: string) { + return styleString + .split(";") + .reduce((styleObject: { [key: string]: string }, styleProperty) => { + let [key, value] = styleProperty.split(":"); + key = key?.trim(); + value = value?.trim(); + if (key && value) { + styleObject[camelCase(key)] = value; + } + return styleObject; + }, {}); +} + +/** + * Recursively converts HTML attributes in a React element tree to their JSX property names. + * + * @param {React.ReactElement} element The React element to process. + * @return {React.ReactElement} A new React element with converted attributes. + */ +export function convertAttributesInElement( + element: React.ReactElement +): React.ReactElement { + // Base case: if the element is not a React element, return it unchanged. + if (!React.isValidElement(element)) { + return element; + } + + // Convert attributes of the current element. + const attributeMap: { [key: string]: string } = { + class: "className", + for: "htmlFor", + targetAttr: "targetattr", + // Add more attribute conversions here as needed + }; + + const newProps: { [key: string]: unknown } = Object.keys( + element.props as Record + ).reduce((acc: { [key: string]: unknown }, key) => { + let value = (element.props as Record)[key]; + + if (key === "style" && typeof value === "string") { + value = convertStyleStringToObject(value); + } + + const mappedKey = attributeMap[key] || key; + acc[mappedKey] = value; + return acc; + }, {}); + + // Process children recursively. + const children = React.Children.map( + (element.props as React.PropsWithChildren).children, + (child) => convertAttributesInElement(child as React.ReactElement) + ); + const newElement = React.createElement(element.type, newProps, children); + // Clone the element with the new properties and updated children. + return newElement; +} diff --git a/package-lock.json b/package-lock.json index 4bf77f79..39d7ffe4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", "@tsconfig/recommended": "^1.0.7", + "babel-eslint": "^10.1.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-react": "^7.34.3", @@ -30,7 +31,8 @@ "name": "@storyblok/react", "version": "0.0.1", "dependencies": { - "@storyblok/js": "^3.0.8" + "@storyblok/js": "^3.0.8", + "@storyblok/richtext": "^0.3.0" }, "devDependencies": { "@babel/core": "^7.24.7", @@ -4583,6 +4585,14 @@ "resolved": "playground", "link": true }, + "node_modules/@storyblok/richtext": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@storyblok/richtext/-/richtext-0.3.0.tgz", + "integrity": "sha512-Kr1CreLMhOPg5QFRq0gJ6ap1J7njyEQb+K3Q+rqBKiaTYJVTEympugOadNkDughO7VJppadR92N7iqV6vOePYw==", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/@swc/helpers": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", @@ -5512,6 +5522,36 @@ "deep-equal": "^2.0.5" } }, + "node_modules/babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "eslint": ">= 4.12.1" + } + }, + "node_modules/babel-eslint/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -6128,6 +6168,14 @@ "dev": true, "license": "MIT" }, + "node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/conventional-changelog-angular": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", @@ -17186,6 +17234,7 @@ "@cypress/react": "^8.0.2", "@cypress/vite-dev-server": "^5.1.1", "@storyblok/js": "^3.0.8", + "@storyblok/richtext": "^0.3.0", "@tsconfig/recommended": "^1.0.7", "@types/react": "18.3.3", "@vitejs/plugin-react": "^4.3.1", @@ -17275,6 +17324,14 @@ } } }, + "@storyblok/richtext": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@storyblok/richtext/-/richtext-0.3.0.tgz", + "integrity": "sha512-Kr1CreLMhOPg5QFRq0gJ6ap1J7njyEQb+K3Q+rqBKiaTYJVTEympugOadNkDughO7VJppadR92N7iqV6vOePYw==", + "requires": { + "consola": "^3.2.3" + } + }, "@swc/helpers": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", @@ -17936,6 +17993,28 @@ "deep-equal": "^2.0.5" } }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, "babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -18337,6 +18416,11 @@ "version": "0.0.1", "dev": true }, + "consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==" + }, "conventional-changelog-angular": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", diff --git a/package.json b/package.json index 1c1ae5ab..b064c2b8 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "demo-next": "npm run demo --workspace=playground-next", "demo-next13-rsc": "npm run demo --workspace=playground-next13-rsc", "demo-next13-live-editing": "npm run demo --workspace=playground-next13-live-editing", + "prettier": "prettier . --write", "build": "npm run build --workspace=lib", "test": "npm run test --workspace=lib", "test:unit": "npm run test:unit --workspace=lib", @@ -25,6 +26,7 @@ "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", "@tsconfig/recommended": "^1.0.7", + "babel-eslint": "^10.1.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-react": "^7.34.3", @@ -56,6 +58,7 @@ "browser": true, "es6": true }, + "parser": "babel-eslint", "ignorePatterns": "dist/", "parserOptions": { "sourceType": "module", diff --git a/playground-next13-live-editing/.vscode/settings.json b/playground-next13-live-editing/.vscode/settings.json index 05ab33d1..37ed20c5 100644 --- a/playground-next13-live-editing/.vscode/settings.json +++ b/playground-next13-live-editing/.vscode/settings.json @@ -1,4 +1,4 @@ { "typescript.tsdk": "../node_modules/typescript/lib", "typescript.enablePromptUseWorkspaceTsdk": true -} \ No newline at end of file +} diff --git a/playground-next13-live-editing/app/page.tsx b/playground-next13-live-editing/app/page.tsx index 804657b2..8d290676 100644 --- a/playground-next13-live-editing/app/page.tsx +++ b/playground-next13-live-editing/app/page.tsx @@ -2,7 +2,7 @@ import { getStoryblokApi, StoryblokClient, ISbStoriesParams, - StoryblokStory + StoryblokStory, } from "@storyblok/react/rsc"; export default async function Home() { diff --git a/playground-next13-live-editing/tsconfig.json b/playground-next13-live-editing/tsconfig.json index 219b9390..ae6c5e80 100644 --- a/playground-next13-live-editing/tsconfig.json +++ b/playground-next13-live-editing/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": false, @@ -24,18 +20,9 @@ } ], "paths": { - "@/*": [ - "./*" - ] + "@/*": ["./*"] } }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts" - ], - "exclude": [ - "node_modules" - ] + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/playground-next13-rsc/.vscode/settings.json b/playground-next13-rsc/.vscode/settings.json index 05ab33d1..37ed20c5 100644 --- a/playground-next13-rsc/.vscode/settings.json +++ b/playground-next13-rsc/.vscode/settings.json @@ -1,4 +1,4 @@ { "typescript.tsdk": "../node_modules/typescript/lib", "typescript.enablePromptUseWorkspaceTsdk": true -} \ No newline at end of file +} diff --git a/playground/App.tsx b/playground/App.tsx index f95076bc..9a458124 100644 --- a/playground/App.tsx +++ b/playground/App.tsx @@ -1,5 +1,10 @@ import React from "react"; -import { useStoryblok, StoryblokComponent } from "@storyblok/react"; +import { + useStoryblok, + StoryblokComponent, + SbRichText, + SbRichTextNode, +} from "@storyblok/react"; function App() { const story = useStoryblok("home", { version: "draft" }); @@ -8,7 +13,382 @@ function App() { return
Loading...
; } - return ; + const doc: SbRichTextNode = { + type: "doc", + content: [ + { + type: "heading", + attrs: { level: 1 }, + content: [{ text: "Headline 1", type: "text" }], + }, + { + type: "paragraph", + content: [ + { + text: "Para", + type: "text", + marks: [ + { + type: "anchor", + attrs: { + id: "verum-audiamus", + }, + }, + ], + }, + { text: "graph", type: "text", marks: [{ type: "bold" }] }, + ], + }, + { + type: "paragraph", + content: [ + { + text: "Bold and italic", + type: "text", + marks: [{ type: "bold" }, { type: "italic" }], + }, + ], + }, + { + type: "bullet_list", + content: [ + { + type: "list_item", + content: [ + { + type: "paragraph", + content: [ + { + text: "Bull", + type: "text", + marks: [{ type: "italic" }], + }, + { + text: "et 1", + type: "text", + marks: [{ type: "bold" }], + }, + ], + }, + ], + }, + { + type: "list_item", + content: [ + { + type: "paragraph", + content: [{ text: "Bullet 2", type: "text" }], + }, + ], + }, + { + type: "list_item", + content: [ + { + type: "paragraph", + content: [ + { + text: "Bullet 3", + type: "text", + marks: [{ type: "styled", attrs: { class: "css-class" } }], + }, + ], + }, + ], + }, + { type: "list_item", content: [{ type: "paragraph" }] }, + ], + }, + { + type: "ordered_list", + attrs: { order: 1 }, + content: [ + { + type: "list_item", + content: [ + { + type: "paragraph", + content: [{ text: "Ordered 1", type: "text" }], + }, + ], + }, + { + type: "list_item", + content: [ + { + type: "paragraph", + content: [{ text: "Ordered 2", type: "text" }], + }, + ], + }, + { + type: "list_item", + content: [ + { + type: "paragraph", + content: [{ text: "Ordered 3", type: "text" }], + }, + ], + }, + ], + }, + { + type: "blockquote", + content: [ + { + type: "paragraph", + content: [{ text: "Quote", type: "text" }], + }, + ], + }, + { type: "horizontal_rule" }, + { + type: "code_block", + attrs: { class: "language-javascript" }, + content: [{ text: "JavaScript Code", type: "text" }], + }, + { + type: "paragraph", + content: [ + { text: "I am a two", type: "text" }, + { type: "hard_break" }, + { + text: "line text!", + type: "text", + marks: [{ type: "code" }], + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "image", + attrs: { + alt: "Alt", + src: "https://a.storyblok.com/f/67536/400x400/166f21bd2c/vue.png", + title: "Caption", + }, + }, + ], + }, + { + type: "paragraph", + content: [ + { + text: "External link", + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://alvarosaburido.dev", + uuid: null, + anchor: null, + target: "_blank", + linktype: "url", + }, + }, + ], + }, + ], + }, + { + type: "paragraph", + content: [ + { + text: "Asset link", + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://a.storyblok.com/f/67536/400x303/ccbe9ca7b3/nuxt-logo.png", + uuid: null, + anchor: null, + target: null, + linktype: "asset", + }, + }, + ], + }, + ], + }, + { + type: "paragraph", + content: [ + { + text: "hola@alvarosaburido.dev", + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "hola@alvarosaburido.dev", + uuid: null, + anchor: null, + target: null, + linktype: "email", + }, + }, + ], + }, + ], + }, + { + type: "paragraph", + content: [ + { + text: "Internal Link", + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "/", + uuid: "2bbf3ee7-acbe-401c-ade5-cf33e6e0babb", + anchor: null, + target: "_blank", + linktype: "story", + }, + }, + ], + }, + ], + }, + { + type: "paragraph", + content: [ + { + text: "This is with ", + type: "text", + }, + { + text: "sub", + type: "text", + marks: [ + { + type: "subscript", + }, + ], + }, + ], + }, + { + type: "paragraph", + content: [ + { + text: "And this with ", + type: "text", + }, + { + text: "sup", + type: "text", + marks: [ + { + type: "superscript", + }, + ], + }, + ], + }, + { + type: "paragraph", + content: [ + { + text: "This is ", + type: "text", + }, + { + text: "highlighted", + type: "text", + marks: [ + { + type: "highlight", + attrs: { + color: "#FFF0B4", + }, + }, + ], + }, + ], + }, + { + type: "paragraph", + content: [ + { + text: "And this has a ", + type: "text", + }, + { + text: "text color", + type: "text", + marks: [ + { + type: "textStyle", + attrs: { + color: "#FC0000", + }, + }, + ], + }, + ], + }, + { + type: "paragraph", + content: [ + { + text: "And this is an emoji ", + type: "text", + marks: [ + { + type: "textStyle", + attrs: { + color: null, + }, + }, + ], + }, + { + type: "emoji", + attrs: { + name: "innocent", + emoji: "😇", + fallbackImage: + "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f607.png", + }, + }, + ], + }, + { + type: "blok", + attrs: { + id: "489f2970-6787-486a-97c3-6f1e8a99b7a9", + body: [ + { + sub: [], + _uid: "i-134324ee-1754-48be-93df-02df1e394733", + title: "Second button!", + component: "button", + }, + { + sub: [], + _uid: "i-437c2948-0be9-442e-949d-a11c79736aa6", + title: "My Button ", + component: "button", + }, + ], + }, + }, + ], + }; + + return ( +
+ + ; +
+ ); } export default App; diff --git a/playground/index.html b/playground/index.html index 74984efc..33feda87 100644 --- a/playground/index.html +++ b/playground/index.html @@ -1,4 +1,4 @@ - + From d7de19a2d9e264b24e4db9f2ef0cf19131b01e6f Mon Sep 17 00:00:00 2001 From: alvarosabu Date: Mon, 30 Sep 2024 11:10:42 +0200 Subject: [PATCH 2/5] chore: fix lint with new config --- lib/common/SbRichText.tsx | 11 ++-- lib/common/client.ts | 24 ++++---- lib/common/index.ts | 62 ++++++++++----------- lib/common/richtext.ts | 10 ++-- lib/package.json | 4 +- lib/types.ts | 22 ++++---- lib/utils/index.ts | 22 ++++---- playground-next13-live-editing/app/page.tsx | 14 ++--- playground/App.tsx | 16 +++--- 9 files changed, 92 insertions(+), 93 deletions(-) diff --git a/lib/common/SbRichText.tsx b/lib/common/SbRichText.tsx index 28b4cc1f..1ce4a33e 100644 --- a/lib/common/SbRichText.tsx +++ b/lib/common/SbRichText.tsx @@ -1,9 +1,8 @@ -import React from "react"; +import React, { forwardRef } from 'react'; -import { forwardRef } from "react"; -import { convertAttributesInElement } from "../utils"; -import { useSbRichtextResolver } from "./richtext"; -import type { SbRichTextProps } from "../types"; +import { convertAttributesInElement } from '../utils'; +import { useSbRichtextResolver } from './richtext'; +import type { SbRichTextProps } from '../types'; // If you're forwarding a ref to SbRichText const SbRichText = forwardRef( @@ -20,7 +19,7 @@ const SbRichText = forwardRef( // If you're forwarding a ref, make sure to attach the ref to a DOM element. // For example, wrapping in a div and attaching the ref to it: return
{formattedHtml}
; - } + }, ); export default SbRichText; diff --git a/lib/common/client.ts b/lib/common/client.ts index 6def2ef0..34532b03 100644 --- a/lib/common/client.ts +++ b/lib/common/client.ts @@ -1,30 +1,30 @@ -import { useEffect, useState } from 'react' -import type { TUseStoryblokState } from '../types' -import { registerStoryblokBridge } from '@storyblok/js' +import { useEffect, useState } from 'react'; +import type { TUseStoryblokState } from '../types'; +import { registerStoryblokBridge } from '@storyblok/js'; export const useStoryblokState: TUseStoryblokState = ( initialStory = null, bridgeOptions = {}, ) => { - const [story, setStory] = useState(initialStory) + const [story, setStory] = useState(initialStory); - const storyId = (initialStory as any)?.internalId ?? initialStory?.id ?? 0 + const storyId = (initialStory as any)?.internalId ?? initialStory?.id ?? 0; const isBridgeEnabled = typeof window !== 'undefined' - && typeof window.storyblokRegisterEvent !== 'undefined' + && typeof window.storyblokRegisterEvent !== 'undefined'; useEffect(() => { - setStory(initialStory) + setStory(initialStory); if (!isBridgeEnabled || !initialStory) { - return + return; } registerStoryblokBridge( storyId, newStory => setStory(newStory), bridgeOptions, - ) - }, [initialStory]) + ); + }, [initialStory]); - return story -} + return story; +}; diff --git a/lib/common/index.ts b/lib/common/index.ts index 334ea48c..83886d30 100644 --- a/lib/common/index.ts +++ b/lib/common/index.ts @@ -1,57 +1,57 @@ -import { storyblokInit as sbInit } from '@storyblok/js' +import { storyblokInit as sbInit } from '@storyblok/js'; import type { SbReactComponentsMap, SbReactSDKOptions, StoryblokClient, -} from '../types' +} from '../types'; -let storyblokApiInstance: StoryblokClient = null -let componentsMap: SbReactComponentsMap = {} -let enableFallbackComponent: boolean = false -let customFallbackComponent: React.ElementType = null +let storyblokApiInstance: StoryblokClient = null; +let componentsMap: SbReactComponentsMap = {}; +let enableFallbackComponent: boolean = false; +let customFallbackComponent: React.ElementType = null; export const useStoryblokApi = (): StoryblokClient => { if (!storyblokApiInstance) { console.error( 'You can\'t use getStoryblokApi if you\'re not loading apiPlugin.', - ) + ); } - return storyblokApiInstance -} + return storyblokApiInstance; +}; export const setComponents = (newComponentsMap: SbReactComponentsMap) => { - componentsMap = newComponentsMap - return componentsMap -} + componentsMap = newComponentsMap; + return componentsMap; +}; export const getComponent = (componentKey: string) => { if (!componentsMap[componentKey]) { - console.error(`Component ${componentKey} doesn't exist.`) - return false + console.error(`Component ${componentKey} doesn't exist.`); + return false; } - return componentsMap[componentKey] -} + return componentsMap[componentKey]; +}; -export const getEnableFallbackComponent = () => enableFallbackComponent -export const getCustomFallbackComponent = () => customFallbackComponent +export const getEnableFallbackComponent = () => enableFallbackComponent; +export const getCustomFallbackComponent = () => customFallbackComponent; export const storyblokInit = (pluginOptions: SbReactSDKOptions = {}) => { - const { storyblokApi } = sbInit(pluginOptions) - storyblokApiInstance = storyblokApi + const { storyblokApi } = sbInit(pluginOptions); + storyblokApiInstance = storyblokApi; - componentsMap = pluginOptions.components - enableFallbackComponent = pluginOptions.enableFallbackComponent - customFallbackComponent = pluginOptions.customFallbackComponent -} + componentsMap = pluginOptions.components; + enableFallbackComponent = pluginOptions.enableFallbackComponent; + customFallbackComponent = pluginOptions.customFallbackComponent; +}; -export * from '../types' -export { useStoryblokApi as getStoryblokApi } -export * from '../types' -export { useSbRichtextResolver } from './richtext' -export { default as SbRichText } from './SbRichText' +export * from '../types'; +export { useStoryblokApi as getStoryblokApi }; +export * from '../types'; +export { useSbRichtextResolver } from './richtext'; +export { default as SbRichText } from './SbRichText'; export { apiPlugin, @@ -60,7 +60,7 @@ export { renderRichText, RichTextResolver, RichTextSchema, -} from '@storyblok/js' +} from '@storyblok/js'; export { BlockTypes, @@ -73,4 +73,4 @@ export { type SbRichTextOptions, type SbRichTextResolvers, TextTypes, -} from '@storyblok/richtext' +} from '@storyblok/richtext'; diff --git a/lib/common/richtext.ts b/lib/common/richtext.ts index c09614e6..f204ef53 100644 --- a/lib/common/richtext.ts +++ b/lib/common/richtext.ts @@ -1,7 +1,7 @@ -import React from "react"; -import { StoryblokComponent } from "@storyblok/react"; -import type { SbRichTextNode, SbRichTextOptions } from "@storyblok/richtext"; -import { BlockTypes, richTextResolver } from "@storyblok/richtext"; +import React from 'react'; +import { StoryblokComponent } from '@storyblok/react'; +import type { SbRichTextNode, SbRichTextOptions } from '@storyblok/richtext'; +import { BlockTypes, richTextResolver } from '@storyblok/richtext'; function componentResolver(node: SbRichTextNode) { // Convert this to use React.createElement or JSX @@ -13,7 +13,7 @@ function componentResolver(node: SbRichTextNode) { } export function useSbRichtextResolver( - options: SbRichTextOptions + options: SbRichTextOptions, ) { const mergedOptions = { renderFn: React.createElement, diff --git a/lib/package.json b/lib/package.json index 0b489ebc..f1b17ccc 100644 --- a/lib/package.json +++ b/lib/package.json @@ -57,8 +57,8 @@ "react-dom": "^17.0.0 || ^18.0.0" }, "dependencies": { - "@storyblok/richtext": "^0.3.0", - "@storyblok/js": "^3.1.1" + "@storyblok/js": "^3.1.1", + "@storyblok/richtext": "^0.3.0" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/lib/types.ts b/lib/types.ts index e1df9874..0a135255 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,25 +1,25 @@ -import type React from 'react' -import type { ISbStoryData, SbSDKOptions, StoryblokBridgeConfigV2 } from '@storyblok/js' -import type { SbRichTextNode, SbRichTextResolvers } from '@storyblok/richtext' +import type React from 'react'; +import type { ISbStoryData, SbSDKOptions, StoryblokBridgeConfigV2 } from '@storyblok/js'; +import type { SbRichTextNode, SbRichTextResolvers } from '@storyblok/richtext'; export interface SbReactComponentsMap { - [key: string]: React.ElementType + [key: string]: React.ElementType; } export interface SbReactSDKOptions extends SbSDKOptions { - components?: SbReactComponentsMap - enableFallbackComponent?: boolean - customFallbackComponent?: React.ElementType + components?: SbReactComponentsMap; + enableFallbackComponent?: boolean; + customFallbackComponent?: React.ElementType; } export type TUseStoryblokState = ( initialStory: ISbStoryData | null, bridgeOptions?: StoryblokBridgeConfigV2 -) => ISbStoryData | null +) => ISbStoryData | null; export interface SbRichTextProps { - doc: SbRichTextNode - resolvers?: SbRichTextResolvers + doc: SbRichTextNode; + resolvers?: SbRichTextResolvers; } export type { @@ -52,4 +52,4 @@ export type { StoryblokComponentType, ThrottleFn, useStoryblokBridge, -} from '@storyblok/js' +} from '@storyblok/js'; diff --git a/lib/utils/index.ts b/lib/utils/index.ts index da3c6a78..eb659dd1 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -1,14 +1,14 @@ -import React from "react"; +import React from 'react'; function camelCase(str: string) { - return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); + return str.replace(/-([a-z])/g, g => g[1].toUpperCase()); } function convertStyleStringToObject(styleString: string) { return styleString - .split(";") + .split(';') .reduce((styleObject: { [key: string]: string }, styleProperty) => { - let [key, value] = styleProperty.split(":"); + let [key, value] = styleProperty.split(':'); key = key?.trim(); value = value?.trim(); if (key && value) { @@ -25,7 +25,7 @@ function convertStyleStringToObject(styleString: string) { * @return {React.ReactElement} A new React element with converted attributes. */ export function convertAttributesInElement( - element: React.ReactElement + element: React.ReactElement, ): React.ReactElement { // Base case: if the element is not a React element, return it unchanged. if (!React.isValidElement(element)) { @@ -34,18 +34,18 @@ export function convertAttributesInElement( // Convert attributes of the current element. const attributeMap: { [key: string]: string } = { - class: "className", - for: "htmlFor", - targetAttr: "targetattr", + class: 'className', + for: 'htmlFor', + targetAttr: 'targetattr', // Add more attribute conversions here as needed }; const newProps: { [key: string]: unknown } = Object.keys( - element.props as Record + element.props as Record, ).reduce((acc: { [key: string]: unknown }, key) => { let value = (element.props as Record)[key]; - if (key === "style" && typeof value === "string") { + if (key === 'style' && typeof value === 'string') { value = convertStyleStringToObject(value); } @@ -57,7 +57,7 @@ export function convertAttributesInElement( // Process children recursively. const children = React.Children.map( (element.props as React.PropsWithChildren).children, - (child) => convertAttributesInElement(child as React.ReactElement) + child => convertAttributesInElement(child as React.ReactElement), ); const newElement = React.createElement(element.type, newProps, children); // Clone the element with the new properties and updated children. diff --git a/playground-next13-live-editing/app/page.tsx b/playground-next13-live-editing/app/page.tsx index 64d6a30a..05f9bca5 100644 --- a/playground-next13-live-editing/app/page.tsx +++ b/playground-next13-live-editing/app/page.tsx @@ -1,14 +1,14 @@ import type { ISbStoriesParams, StoryblokClient, -} from '@storyblok/react/rsc' +} from '@storyblok/react/rsc'; import { getStoryblokApi, StoryblokStory, -} from '@storyblok/react/rsc' +} from '@storyblok/react/rsc'; export default async function Home() { - const { data } = await fetchData() + const { data } = await fetchData(); return (
@@ -18,12 +18,12 @@ export default async function Home() {
- ) + ); } export async function fetchData() { - const sbParams: ISbStoriesParams = { version: 'draft' } + const sbParams: ISbStoriesParams = { version: 'draft' }; - const storyblokApi: StoryblokClient = getStoryblokApi() - return storyblokApi.get(`cdn/stories/home`, sbParams) + const storyblokApi: StoryblokClient = getStoryblokApi(); + return storyblokApi.get(`cdn/stories/home`, sbParams); } diff --git a/playground/App.tsx b/playground/App.tsx index 5e51f98b..c121603c 100644 --- a/playground/App.tsx +++ b/playground/App.tsx @@ -1,18 +1,18 @@ -import React from 'react' +import React from 'react'; import type { SbRichTextNode, -} from '@storyblok/react' +} from '@storyblok/react'; import { SbRichText, StoryblokComponent, useStoryblok, -} from '@storyblok/react' +} from '@storyblok/react'; function App() { - const story = useStoryblok('home', { version: 'draft' }) + const story = useStoryblok('home', { version: 'draft' }); if (!story?.content) { - return
Loading...
+ return
Loading...
; } const doc: SbRichTextNode = { @@ -383,7 +383,7 @@ function App() { }, }, ], - } + }; return (
@@ -391,7 +391,7 @@ function App() { ;
- ) + ); } -export default App +export default App; From 3329d09d6774584e631e9a9ac1fd41d28bcd2e47 Mon Sep 17 00:00:00 2001 From: alvarosabu Date: Mon, 30 Sep 2024 11:29:02 +0200 Subject: [PATCH 3/5] chore: update deps --- lib/package.json | 4 ++-- package-lock.json | 35 +++++++++-------------------------- 2 files changed, 11 insertions(+), 28 deletions(-) diff --git a/lib/package.json b/lib/package.json index f1b17ccc..ce96717a 100644 --- a/lib/package.json +++ b/lib/package.json @@ -57,8 +57,8 @@ "react-dom": "^17.0.0 || ^18.0.0" }, "dependencies": { - "@storyblok/js": "^3.1.1", - "@storyblok/richtext": "^0.3.0" + "@storyblok/js": "^3.1.4", + "@storyblok/richtext": "^2.0.0" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/package-lock.json b/package-lock.json index 16808a27..cfa0add1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,8 +26,8 @@ "name": "@storyblok/react", "version": "0.0.1", "dependencies": { - "@storyblok/js": "^3.1.1", - "@storyblok/richtext": "^0.3.0" + "@storyblok/js": "^3.1.4", + "@storyblok/richtext": "^2.0.0" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -53,14 +53,6 @@ "react-dom": "^17.0.0 || ^18.0.0" } }, - "lib/node_modules/@storyblok/richtext": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@storyblok/richtext/-/richtext-0.3.0.tgz", - "integrity": "sha512-Kr1CreLMhOPg5QFRq0gJ6ap1J7njyEQb+K3Q+rqBKiaTYJVTEympugOadNkDughO7VJppadR92N7iqV6vOePYw==", - "dependencies": { - "consola": "^3.2.3" - } - }, "lib/node_modules/react": { "version": "18.3.1", "dev": true, @@ -1940,18 +1932,6 @@ "sisteransi": "^1.0.5" } }, - "node_modules/@clack/prompts/node_modules/is-unicode-supported": { - "version": "1.3.0", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@colors/colors": { "version": "1.5.0", "dev": true, @@ -3937,10 +3917,12 @@ } }, "node_modules/@storyblok/js": { - "version": "3.1.1", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@storyblok/js/-/js-3.1.4.tgz", + "integrity": "sha512-Uh+ESR4xF+8C7veSiVA3IeYNqPdwRDOxJdqyGHAVBZ7OuQ2SzZkW3eAKax2TiX8W4DPSljIKvI6MKpvEG3KwWA==", "dependencies": { "@storyblok/richtext": "^2.0.0", - "storyblok-js-client": "^6.9.1" + "storyblok-js-client": "^6.9.2" } }, "node_modules/@storyblok/react": { @@ -13119,8 +13101,9 @@ } }, "node_modules/storyblok-js-client": { - "version": "6.9.1", - "license": "MIT" + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/storyblok-js-client/-/storyblok-js-client-6.9.2.tgz", + "integrity": "sha512-31GM5X/SIP4eJsSMCpAnaPDRmmUotSSWD3Umnuzf3CGqjyakot2Gv5QmuV23fRM7TCDUQlg5wurROmAzkKMKKg==" }, "node_modules/stream-combiner": { "version": "0.0.4", From 1c98fdc643bc3426d6a0be81c3201bc843ad5572 Mon Sep 17 00:00:00 2001 From: alvarosabu Date: Thu, 10 Oct 2024 16:58:51 +0200 Subject: [PATCH 4/5] feat: add richtext --- lib/common/index.ts | 30 +++++++++---------- lib/common/richtext.ts | 12 ++++---- ...SbRichText.tsx => storyblok-rich-text.tsx} | 13 ++++---- lib/package.json | 3 +- lib/types.ts | 8 ++--- lib/utils/index.ts | 5 +++- package-lock.json | 3 +- playground/App.tsx | 8 ++--- playground/package.json | 2 +- 9 files changed, 43 insertions(+), 41 deletions(-) rename lib/common/{SbRichText.tsx => storyblok-rich-text.tsx} (57%) diff --git a/lib/common/index.ts b/lib/common/index.ts index 83886d30..ca0a894d 100644 --- a/lib/common/index.ts +++ b/lib/common/index.ts @@ -50,27 +50,27 @@ export const storyblokInit = (pluginOptions: SbReactSDKOptions = {}) => { export * from '../types'; export { useStoryblokApi as getStoryblokApi }; export * from '../types'; -export { useSbRichtextResolver } from './richtext'; -export { default as SbRichText } from './SbRichText'; +export { useStoryblokRichtextResolver } from './richtext'; +export { default as StoryblokComponent } from './storyblok-component'; +export { default as StoryblokRichText } from './storyblok-rich-text'; export { apiPlugin, + BlockTypes, loadStoryblokBridge, + MarkTypes, registerStoryblokBridge, renderRichText, RichTextResolver, - RichTextSchema, -} from '@storyblok/js'; - -export { - BlockTypes, - MarkTypes, richTextResolver, - type SbRichTextImageOptimizationOptions, - type SbRichTextNode, - type SbRichTextNodeResolver, - type SbRichTextNodeTypes, - type SbRichTextOptions, - type SbRichTextResolvers, + RichTextSchema, + storyblokEditable, + type StoryblokRichTextImageOptimizationOptions, + type StoryblokRichTextNode, + type StoryblokRichTextNodeResolver, + type StoryblokRichTextNodeTypes, + type StoryblokRichTextOptions, + type StoryblokRichTextResolvers, TextTypes, -} from '@storyblok/richtext'; + useStoryblokBridge, +} from '@storyblok/js'; diff --git a/lib/common/richtext.ts b/lib/common/richtext.ts index f204ef53..e414c917 100644 --- a/lib/common/richtext.ts +++ b/lib/common/richtext.ts @@ -1,9 +1,9 @@ import React from 'react'; -import { StoryblokComponent } from '@storyblok/react'; -import type { SbRichTextNode, SbRichTextOptions } from '@storyblok/richtext'; -import { BlockTypes, richTextResolver } from '@storyblok/richtext'; +import StoryblokComponent from './storyblok-component'; +import type { StoryblokRichTextNode, StoryblokRichTextOptions } from '@storyblok/js'; +import { BlockTypes, richTextResolver } from '@storyblok/js'; -function componentResolver(node: SbRichTextNode) { +function componentResolver(node: StoryblokRichTextNode) { // Convert this to use React.createElement or JSX // Example with JSX: return React.createElement(StoryblokComponent, { @@ -12,8 +12,8 @@ function componentResolver(node: SbRichTextNode) { }); } -export function useSbRichtextResolver( - options: SbRichTextOptions, +export function useStoryblokRichtextResolver( + options: StoryblokRichTextOptions, ) { const mergedOptions = { renderFn: React.createElement, diff --git a/lib/common/SbRichText.tsx b/lib/common/storyblok-rich-text.tsx similarity index 57% rename from lib/common/SbRichText.tsx rename to lib/common/storyblok-rich-text.tsx index 1ce4a33e..059c2a74 100644 --- a/lib/common/SbRichText.tsx +++ b/lib/common/storyblok-rich-text.tsx @@ -1,24 +1,25 @@ import React, { forwardRef } from 'react'; import { convertAttributesInElement } from '../utils'; -import { useSbRichtextResolver } from './richtext'; -import type { SbRichTextProps } from '../types'; +import { useStoryblokRichtextResolver } from './richtext'; +import type { StoryblokRichTextProps } from '../types'; // If you're forwarding a ref to SbRichText -const SbRichText = forwardRef( +const SbRichText = forwardRef( ({ doc, resolvers }, ref) => { // Assuming useSbRichtextResolver is a hook you've created - const { render } = useSbRichtextResolver({ + const { render } = useStoryblokRichtextResolver({ resolvers, }); /* const Root = () => render(doc) */ const html = render(doc); - const formattedHtml = convertAttributesInElement(html); + console.log('this is the html', html); + /* const formattedHtml = convertAttributesInElement(html); */ // If you're forwarding a ref, make sure to attach the ref to a DOM element. // For example, wrapping in a div and attaching the ref to it: - return
{formattedHtml}
; + return
{}
; }, ); diff --git a/lib/package.json b/lib/package.json index ce96717a..100943bd 100644 --- a/lib/package.json +++ b/lib/package.json @@ -57,8 +57,7 @@ "react-dom": "^17.0.0 || ^18.0.0" }, "dependencies": { - "@storyblok/js": "^3.1.4", - "@storyblok/richtext": "^2.0.0" + "@storyblok/js": "^3.1.4" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/lib/types.ts b/lib/types.ts index 0a135255..6ee99761 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,6 +1,6 @@ import type React from 'react'; import type { ISbStoryData, SbSDKOptions, StoryblokBridgeConfigV2 } from '@storyblok/js'; -import type { SbRichTextNode, SbRichTextResolvers } from '@storyblok/richtext'; +import type { StoryblokRichTextNode, StoryblokRichTextResolvers } from '@storyblok/richtext'; export interface SbReactComponentsMap { [key: string]: React.ElementType; @@ -17,9 +17,9 @@ export type TUseStoryblokState = ( bridgeOptions?: StoryblokBridgeConfigV2 ) => ISbStoryData | null; -export interface SbRichTextProps { - doc: SbRichTextNode; - resolvers?: SbRichTextResolvers; +export interface StoryblokRichTextProps { + doc: StoryblokRichTextNode; + resolvers?: StoryblokRichTextResolvers; } export type { diff --git a/lib/utils/index.ts b/lib/utils/index.ts index eb659dd1..eec98f73 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -52,7 +52,9 @@ export function convertAttributesInElement( const mappedKey = attributeMap[key] || key; acc[mappedKey] = value; return acc; - }, {}); + }, { + key: `${element.type}-${Math.random().toString(36).substring(7)}`, + }); // Process children recursively. const children = React.Children.map( @@ -61,5 +63,6 @@ export function convertAttributesInElement( ); const newElement = React.createElement(element.type, newProps, children); // Clone the element with the new properties and updated children. + console.log(newElement); return newElement; } diff --git a/package-lock.json b/package-lock.json index cfa0add1..577fabb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,8 +26,7 @@ "name": "@storyblok/react", "version": "0.0.1", "dependencies": { - "@storyblok/js": "^3.1.4", - "@storyblok/richtext": "^2.0.0" + "@storyblok/js": "^3.1.4" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/playground/App.tsx b/playground/App.tsx index c121603c..8e84bf3d 100644 --- a/playground/App.tsx +++ b/playground/App.tsx @@ -1,10 +1,10 @@ import React from 'react'; import type { - SbRichTextNode, + StoryblokRichTextNode, } from '@storyblok/react'; import { - SbRichText, StoryblokComponent, + StoryblokRichText, useStoryblok, } from '@storyblok/react'; @@ -15,7 +15,7 @@ function App() { return
Loading...
; } - const doc: SbRichTextNode = { + const doc: StoryblokRichTextNode = { type: 'doc', content: [ { @@ -387,7 +387,7 @@ function App() { return (
- + ;
diff --git a/playground/package.json b/playground/package.json index 1b059dc0..54c97cdd 100644 --- a/playground/package.json +++ b/playground/package.json @@ -2,7 +2,7 @@ "name": "@storyblok/react-playground", "version": "0.0.1", "scripts": { - "demo": "vite", + "dev": "vite", "build": "vite build" }, "devDependencies": { From 12fde21d69085647a85c79c161ba8e1468d8d1d0 Mon Sep 17 00:00:00 2001 From: alvarosabu Date: Wed, 30 Oct 2024 11:36:21 +0100 Subject: [PATCH 5/5] feat: keyedResolvers enable --- lib/common/richtext.ts | 2 + lib/common/storyblok-rich-text.tsx | 10 ++-- lib/package.json | 2 +- lib/utils/index.ts | 1 - package-lock.json | 33 +++++------- playground/App.tsx | 70 ++++++++++++++++++++++---- playground/components/iframe-embed.tsx | 19 +++++++ playground/index.tsx | 12 +++-- 8 files changed, 108 insertions(+), 41 deletions(-) create mode 100644 playground/components/iframe-embed.tsx diff --git a/lib/common/richtext.ts b/lib/common/richtext.ts index e414c917..81ac5e79 100644 --- a/lib/common/richtext.ts +++ b/lib/common/richtext.ts @@ -9,6 +9,7 @@ function componentResolver(node: StoryblokRichTextNode) { return React.createElement(StoryblokComponent, { blok: node?.attrs?.body[0], id: node.attrs?.id, + key: node.attrs?.id, }); } @@ -21,6 +22,7 @@ export function useStoryblokRichtextResolver( [BlockTypes.COMPONENT]: componentResolver, ...options.resolvers, }, + keyedResolvers: true, }; return richTextResolver(mergedOptions); } diff --git a/lib/common/storyblok-rich-text.tsx b/lib/common/storyblok-rich-text.tsx index 059c2a74..517611a0 100644 --- a/lib/common/storyblok-rich-text.tsx +++ b/lib/common/storyblok-rich-text.tsx @@ -14,12 +14,16 @@ const SbRichText = forwardRef( /* const Root = () => render(doc) */ const html = render(doc); - console.log('this is the html', html); - /* const formattedHtml = convertAttributesInElement(html); */ + const formattedHtml = convertAttributesInElement(html as React.ReactElement); // If you're forwarding a ref, make sure to attach the ref to a DOM element. // For example, wrapping in a div and attaching the ref to it: - return
{}
; + // return
{formattedHtml}
; + return ( +
+ {formattedHtml} +
+ ); }, ); diff --git a/lib/package.json b/lib/package.json index 100943bd..0df2a6d7 100644 --- a/lib/package.json +++ b/lib/package.json @@ -57,7 +57,7 @@ "react-dom": "^17.0.0 || ^18.0.0" }, "dependencies": { - "@storyblok/js": "^3.1.4" + "@storyblok/js": "^3.1.7" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/lib/utils/index.ts b/lib/utils/index.ts index eec98f73..e29ebc8f 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -63,6 +63,5 @@ export function convertAttributesInElement( ); const newElement = React.createElement(element.type, newProps, children); // Clone the element with the new properties and updated children. - console.log(newElement); return newElement; } diff --git a/package-lock.json b/package-lock.json index 577fabb3..27ee6a66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "name": "@storyblok/react", "version": "0.0.1", "dependencies": { - "@storyblok/js": "^3.1.4" + "@storyblok/js": "^3.1.7" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -3916,12 +3916,12 @@ } }, "node_modules/@storyblok/js": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@storyblok/js/-/js-3.1.4.tgz", - "integrity": "sha512-Uh+ESR4xF+8C7veSiVA3IeYNqPdwRDOxJdqyGHAVBZ7OuQ2SzZkW3eAKax2TiX8W4DPSljIKvI6MKpvEG3KwWA==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@storyblok/js/-/js-3.1.7.tgz", + "integrity": "sha512-kH59EJoHB1OgZ/LrofKBAfeREabPbQz6CW2LaQq7w7GjEu4p9zcJF8PZIF3i2/b1yp3V3YkSOCuqtg716z70Rg==", "dependencies": { - "@storyblok/richtext": "^2.0.0", - "storyblok-js-client": "^6.9.2" + "@storyblok/richtext": "^3.0.0", + "storyblok-js-client": "^6.10.0" } }, "node_modules/@storyblok/react": { @@ -3933,11 +3933,9 @@ "link": true }, "node_modules/@storyblok/richtext": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "consola": "^3.2.3" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@storyblok/richtext/-/richtext-3.0.0.tgz", + "integrity": "sha512-EpqnASTkn0nLv3buxlUGNhfWqhTgUFBJx6QUdE4dW8l0Dg0c3B97ZGithUC5uxXy3dqmsm5x6pCF97CVLNC6JQ==" }, "node_modules/@stylistic/eslint-plugin": { "version": "2.8.0", @@ -5916,13 +5914,6 @@ "dev": true, "license": "MIT" }, - "node_modules/consola": { - "version": "3.2.3", - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, "node_modules/conventional-changelog-angular": { "version": "7.0.0", "dev": true, @@ -13100,9 +13091,9 @@ } }, "node_modules/storyblok-js-client": { - "version": "6.9.2", - "resolved": "https://registry.npmjs.org/storyblok-js-client/-/storyblok-js-client-6.9.2.tgz", - "integrity": "sha512-31GM5X/SIP4eJsSMCpAnaPDRmmUotSSWD3Umnuzf3CGqjyakot2Gv5QmuV23fRM7TCDUQlg5wurROmAzkKMKKg==" + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/storyblok-js-client/-/storyblok-js-client-6.10.1.tgz", + "integrity": "sha512-vfV6BRuE/elcpPDfhValfC2TkxPZzdAlWGhwo2piN8REaxbzeMmsKpQ7yENBrkQTGXFYxZWi39SCxgRrCGGj+w==" }, "node_modules/stream-combiner": { "version": "0.0.4", diff --git a/playground/App.tsx b/playground/App.tsx index 8e84bf3d..5d2fa2c7 100644 --- a/playground/App.tsx +++ b/playground/App.tsx @@ -1,21 +1,22 @@ import React from 'react'; -import type { +/* import type { StoryblokRichTextNode, -} from '@storyblok/react'; +} from '@storyblok/react'; */ import { - StoryblokComponent, + /* StoryblokComponent, */ StoryblokRichText, useStoryblok, } from '@storyblok/react'; function App() { - const story = useStoryblok('home', { version: 'draft' }); + // const story = useStoryblok('home', { version: 'draft' }); + const story = useStoryblok('react/test-richtext', { version: 'draft' }); if (!story?.content) { return
Loading...
; } - const doc: StoryblokRichTextNode = { + /* const doc: StoryblokRichTextNode = { type: 'doc', content: [ { @@ -98,7 +99,6 @@ function App() { }, ], }, - { type: 'list_item', content: [{ type: 'paragraph' }] }, ], }, { @@ -383,13 +383,63 @@ function App() { }, }, ], - }; + }; */ + /* const list = { + type: 'bullet_list', + content: [ + { + type: 'list_item', + content: [ + { + type: 'paragraph', + content: [ + { + text: 'Bull', + type: 'text', + marks: [{ type: 'italic' }], + }, + { + text: 'et 1', + type: 'text', + marks: [{ type: 'bold' }], + }, + ], + }, + ], + }, + { + type: 'list_item', + content: [ + { + type: 'paragraph', + content: [{ text: 'Bullet 2', type: 'text' }], + }, + ], + }, + { + type: 'list_item', + content: [ + { + type: 'paragraph', + content: [ + { + text: 'Bullet 3', + type: 'text', + marks: [{ type: 'styled', attrs: { class: 'css-class' } }], + }, + ], + }, + ], + }, + { type: 'list_item', content: [{ type: 'paragraph' }] }, + ], + }; */ return (
- - - ; + + {/* + ; */}
); } diff --git a/playground/components/iframe-embed.tsx b/playground/components/iframe-embed.tsx new file mode 100644 index 00000000..efbd6806 --- /dev/null +++ b/playground/components/iframe-embed.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import type { SbBlokData } from '@storyblok/react'; +import { storyblokEditable } from '@storyblok/react'; + +interface IframeEmbedProps { + blok: SbBlokData; +} + +const IFrameEmbed = ({ blok }: IframeEmbedProps) => { + return ( +
+
+