@buildinams/react-storyblok
Advanced tools
Comparing version 0.4.2 to 0.5.0
{ | ||
"extends": "./node_modules/@buildinams/lint/react-typescript" | ||
"extends": "./node_modules/@buildinams/lint/eslint/react-typescript" | ||
} |
@@ -1,5 +0,4 @@ | ||
/// <reference types="react" /> | ||
import { BlocksRendererComponents, BlocksRendererProps } from "../../types"; | ||
/** | ||
* Generates a component that uses key / value lookup to render components and provides them with the `containerSpread` for Storyblok editable. | ||
* Generates a component that uses key / value lookup to render components and provides them with the `previewIndicatorSpread` for Storyblok editable. | ||
* | ||
@@ -10,3 +9,3 @@ * @param components - Key / component map. | ||
* | ||
* @note Don't forget to spread the `containerSpread` props for preview mode to fully work. | ||
* @note Don't forget to spread the `previewIndicatorSpread` props to get Storyblok block highlighting to work. | ||
* @note It is possible to pass any other props other than `blocks`. But beware that all components receive these props. | ||
@@ -18,7 +17,7 @@ * | ||
* ```jsx | ||
* const ChapterBlock = ({ containerSpread, person }) => { | ||
* return <div {...containerSpread}>Hello, { person }!</div> | ||
* const ChapterBlock = ({ previewIndicatorSpread, person }) => { | ||
* return <div {...previewIndicatorSpread}>Hello, { person }!</div> | ||
* } | ||
* | ||
* const BlocksRenderer = getBlocksRenderer({ chapter_block: ChapterBlock }) | ||
* const BlocksRenderer = getBlocksRenderer({ chapterBlock: ChapterBlock }) | ||
* | ||
@@ -37,4 +36,4 @@ * const Page = ({ blocks }) => { | ||
* const BlocksRenderer = getBlocksRenderer({ | ||
* foo_block: FooBlock, | ||
* bar_block: BarBlock | ||
* fooBlock: FooBlock, | ||
* barBlock: BarBlock | ||
* }) | ||
@@ -47,3 +46,3 @@ * | ||
* propsPerBlock={(item) => { | ||
* if (item.component === 'foo_block') { | ||
* if (item.component === 'fooBlock') { | ||
* return { isFoo: true }; | ||
@@ -57,2 +56,2 @@ * } | ||
*/ | ||
export declare const getBlocksRenderer: <T>(components: BlocksRendererComponents) => (props: BlocksRendererProps<T>) => JSX.Element; | ||
export declare const getBlocksRenderer: <T, K>(components: BlocksRendererComponents) => (props: BlocksRendererProps<T, K>) => (import("react/jsx-runtime").JSX.Element | null)[]; |
@@ -16,5 +16,5 @@ "use strict"; | ||
const jsx_runtime_1 = require("react/jsx-runtime"); | ||
const useStoryblokPreviewSpread_1 = require("../useStoryblokPreviewSpread"); | ||
const useStoryblokPreviewIndicatorSpread_1 = require("../useStoryblokPreviewIndicatorSpread"); | ||
/** | ||
* Generates a component that uses key / value lookup to render components and provides them with the `containerSpread` for Storyblok editable. | ||
* Generates a component that uses key / value lookup to render components and provides them with the `previewIndicatorSpread` for Storyblok editable. | ||
* | ||
@@ -25,3 +25,3 @@ * @param components - Key / component map. | ||
* | ||
* @note Don't forget to spread the `containerSpread` props for preview mode to fully work. | ||
* @note Don't forget to spread the `previewIndicatorSpread` props to get Storyblok block highlighting to work. | ||
* @note It is possible to pass any other props other than `blocks`. But beware that all components receive these props. | ||
@@ -33,7 +33,7 @@ * | ||
* ```jsx | ||
* const ChapterBlock = ({ containerSpread, person }) => { | ||
* return <div {...containerSpread}>Hello, { person }!</div> | ||
* const ChapterBlock = ({ previewIndicatorSpread, person }) => { | ||
* return <div {...previewIndicatorSpread}>Hello, { person }!</div> | ||
* } | ||
* | ||
* const BlocksRenderer = getBlocksRenderer({ chapter_block: ChapterBlock }) | ||
* const BlocksRenderer = getBlocksRenderer({ chapterBlock: ChapterBlock }) | ||
* | ||
@@ -52,4 +52,4 @@ * const Page = ({ blocks }) => { | ||
* const BlocksRenderer = getBlocksRenderer({ | ||
* foo_block: FooBlock, | ||
* bar_block: BarBlock | ||
* fooBlock: FooBlock, | ||
* barBlock: BarBlock | ||
* }) | ||
@@ -62,3 +62,3 @@ * | ||
* propsPerBlock={(item) => { | ||
* if (item.component === 'foo_block') { | ||
* if (item.component === 'fooBlock') { | ||
* return { isFoo: true }; | ||
@@ -75,14 +75,15 @@ * } | ||
const { blocks = [], propsPerBlock } = props, propsRest = __rest(props, ["blocks", "propsPerBlock"]); | ||
// Fragment added to inform TS inference that we return JSX | ||
return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: blocks.map((item, index) => { | ||
const Component = components === null || components === void 0 ? void 0 : components[item.component]; | ||
if (!Component) | ||
return null; | ||
const { _uid, _editable } = item, itemRest = __rest(item, ["_uid", "_editable"]); | ||
const containerSpread = (0, useStoryblokPreviewSpread_1.useStoryblokPreviewSpread)(_editable); | ||
const additionalItemProps = propsPerBlock === null || propsPerBlock === void 0 ? void 0 : propsPerBlock(item, index); | ||
return ((0, jsx_runtime_1.jsx)(Component, Object.assign({ index: index }, propsRest, itemRest, additionalItemProps, { containerSpread: containerSpread }), _uid || `${item.component}-${index}`)); | ||
}) })); | ||
return blocks.map((item, index) => { | ||
if (!item.component) | ||
return null; | ||
const Component = components === null || components === void 0 ? void 0 : components[item.component]; | ||
if (!Component) | ||
return null; | ||
const { _uid, _editable } = item, itemRest = __rest(item, ["_uid", "_editable"]); | ||
const previewIndicatorSpread = (0, useStoryblokPreviewIndicatorSpread_1.useStoryblokPreviewIndicatorSpread)(_editable); | ||
const itemBlockProps = propsPerBlock === null || propsPerBlock === void 0 ? void 0 : propsPerBlock(item, index); | ||
return ((0, jsx_runtime_1.jsx)(Component, Object.assign({ index: index }, propsRest, itemRest, itemBlockProps, { previewIndicatorSpread: previewIndicatorSpread }), _uid || `${item.component}-${index}`)); | ||
}); | ||
}; | ||
}; | ||
exports.getBlocksRenderer = getBlocksRenderer; |
export { getBlocksRenderer } from "./getBlocksRenderer"; | ||
export { useStoryblokPreviewSpread } from "./useStoryblokPreviewSpread"; | ||
export { useStoryblokPreviewIndicatorSpread } from "./useStoryblokPreviewIndicatorSpread"; | ||
export { withStoryblokPreviewHOC } from "./withStoryblokPreviewHOC"; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.withStoryblokPreviewHOC = exports.useStoryblokPreviewSpread = exports.getBlocksRenderer = void 0; | ||
exports.withStoryblokPreviewHOC = exports.useStoryblokPreviewIndicatorSpread = exports.getBlocksRenderer = void 0; | ||
var getBlocksRenderer_1 = require("./getBlocksRenderer"); | ||
Object.defineProperty(exports, "getBlocksRenderer", { enumerable: true, get: function () { return getBlocksRenderer_1.getBlocksRenderer; } }); | ||
var useStoryblokPreviewSpread_1 = require("./useStoryblokPreviewSpread"); | ||
Object.defineProperty(exports, "useStoryblokPreviewSpread", { enumerable: true, get: function () { return useStoryblokPreviewSpread_1.useStoryblokPreviewSpread; } }); | ||
var useStoryblokPreviewIndicatorSpread_1 = require("./useStoryblokPreviewIndicatorSpread"); | ||
Object.defineProperty(exports, "useStoryblokPreviewIndicatorSpread", { enumerable: true, get: function () { return useStoryblokPreviewIndicatorSpread_1.useStoryblokPreviewIndicatorSpread; } }); | ||
var withStoryblokPreviewHOC_1 = require("./withStoryblokPreviewHOC"); | ||
Object.defineProperty(exports, "withStoryblokPreviewHOC", { enumerable: true, get: function () { return withStoryblokPreviewHOC_1.withStoryblokPreviewHOC; } }); |
@@ -1,9 +0,8 @@ | ||
import { ContentTypeData, StoryData } from "../../types"; | ||
import { ISbEventPayload } from "@storyblok/js"; | ||
/** | ||
* Bridge logic that connects to the Storyblok admin UI. This JS should only be loaded when the site is loaded in the admin. | ||
* | ||
* @param storyID - Story ID to connect to. | ||
* @param fetchArgs - Fetch args from the original request. Used to mimic in from the client. | ||
* @param resolveRelations - Array of relations to resolve. This should match the resolved relations of story this is being used on. | ||
* @param callback - Function to call when the data changed in Storyblok preview. | ||
*/ | ||
export declare const storyblokBridge: <T extends ContentTypeData>(storyID: number, resolveRelations: string[] | undefined, callback: (newData: StoryData<T>) => void) => void; | ||
export declare const listenForStoryblokBridgeChanges: (resolveRelations: string[] | undefined, callback: (event: ISbEventPayload) => void) => Promise<void>; |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.storyblokBridge = void 0; | ||
exports.listenForStoryblokBridgeChanges = void 0; | ||
const js_1 = require("@storyblok/js"); | ||
(0, js_1.loadStoryblokBridge)(); | ||
/** | ||
* Bridge logic that connects to the Storyblok admin UI. This JS should only be loaded when the site is loaded in the admin. | ||
* | ||
* @param storyID - Story ID to connect to. | ||
* @param fetchArgs - Fetch args from the original request. Used to mimic in from the client. | ||
* @param resolveRelations - Array of relations to resolve. This should match the resolved relations of story this is being used on. | ||
* @param callback - Function to call when the data changed in Storyblok preview. | ||
*/ | ||
const storyblokBridge = (storyID, resolveRelations, callback) => { | ||
// eslint-disable-next-line react-hooks/rules-of-hooks | ||
(0, js_1.useStoryblokBridge)(storyID, callback, { resolveRelations }); | ||
}; | ||
exports.storyblokBridge = storyblokBridge; | ||
const listenForStoryblokBridgeChanges = (resolveRelations, callback) => __awaiter(void 0, void 0, void 0, function* () { | ||
// Load the bridge to Window | ||
yield (0, js_1.loadStoryblokBridge)(); | ||
// Then, connect to the bridge | ||
const storyblokBridge = new window.StoryblokBridge({ resolveRelations }); | ||
// And then, listen for input change events | ||
storyblokBridge.on(["input"], (event) => { | ||
if (event) | ||
callback(event); | ||
}); | ||
}); | ||
exports.listenForStoryblokBridgeChanges = listenForStoryblokBridgeChanges; |
@@ -1,2 +0,2 @@ | ||
/// <reference types="react" /> | ||
import { ElementType } from "react"; | ||
import { ContentTypeData, FetchStoryblokAdaptor, StoryAdaptorProps } from "../../types"; | ||
@@ -6,2 +6,2 @@ /** | ||
*/ | ||
export declare const withStoryblokPreviewHOC: <T extends ContentTypeData>(Component: any, fetchStoryblokAdaptor: FetchStoryblokAdaptor) => ({ data, resolveRelations, preview, ...rest }: StoryAdaptorProps<T>) => JSX.Element; | ||
export declare const withStoryblokPreviewHOC: <T extends ContentTypeData>(Component: ElementType, fetchStoryblokAdaptor: FetchStoryblokAdaptor) => ({ storyData, resolveRelations, preview, ...rest }: StoryAdaptorProps<T>) => import("react/jsx-runtime").JSX.Element; |
@@ -52,21 +52,23 @@ "use strict"; | ||
*/ | ||
const useAdaptedStory = (data, fetchStoryblokAdaptor, resolveRelations, isPreview) => { | ||
const [adaptedData, setAdaptedData] = (0, react_1.useState)(data); | ||
const useAdaptedStoryData = (storyData, fetchStoryblokAdaptor, resolveRelations, isPreview) => { | ||
const [adaptedStoryData, setAdaptedStoryData] = (0, react_1.useState)(storyData); | ||
(0, react_1.useEffect)(() => { | ||
if (!isPreview) | ||
return; | ||
const connectToStoryblok = () => __awaiter(void 0, void 0, void 0, function* () { | ||
const [{ storyblokBridge }, { storyblokAdaptor }] = yield Promise.all([ | ||
Promise.resolve().then(() => __importStar(require("./bridge"))), | ||
fetchStoryblokAdaptor(), | ||
]); | ||
if (!storyblokAdaptor) | ||
throw new Error(`"storyblokAdaptor" couldn't be found in the module fetched from the provided; fetchStoryblokAdaptor`); | ||
storyblokBridge(data.id, resolveRelations, (data) => { | ||
setAdaptedData(storyblokAdaptor.adaptData(data)); | ||
const listenForChanges = () => __awaiter(void 0, void 0, void 0, function* () { | ||
const [{ listenForStoryblokBridgeChanges }, { storyblokAdaptor }] = yield Promise.all([Promise.resolve().then(() => __importStar(require("./bridge"))), fetchStoryblokAdaptor()]); | ||
// Ensure that the adaptor is present | ||
if (!storyblokAdaptor) { | ||
throw new Error("'storyblokAdaptor' couldn't be found in the module fetched from the provided; 'fetchStoryblokAdaptor'."); | ||
} | ||
listenForStoryblokBridgeChanges(resolveRelations, (event) => { | ||
// If event doesn't include story, ignore it | ||
if (!event.story) | ||
return; | ||
setAdaptedStoryData(storyblokAdaptor.adaptData(event.story)); | ||
}); | ||
}); | ||
connectToStoryblok(); | ||
}, [data.id, isPreview, resolveRelations, fetchStoryblokAdaptor]); | ||
return adaptedData; | ||
listenForChanges(); | ||
}, [isPreview, resolveRelations, fetchStoryblokAdaptor]); | ||
return adaptedStoryData; | ||
}; | ||
@@ -76,12 +78,9 @@ /** | ||
*/ | ||
// TODO: Type 'Component' properly | ||
const withStoryblokPreviewHOC = ( | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
Component, fetchStoryblokAdaptor) => { | ||
const withStoryblokPreviewHOC = (Component, fetchStoryblokAdaptor) => { | ||
return (_a) => { | ||
var { data, resolveRelations, preview } = _a, rest = __rest(_a, ["data", "resolveRelations", "preview"]); | ||
const adaptedData = useAdaptedStory(data, fetchStoryblokAdaptor, resolveRelations, preview); | ||
return (0, jsx_runtime_1.jsx)(Component, Object.assign({}, rest, adaptedData)); | ||
var { storyData, resolveRelations, preview } = _a, rest = __rest(_a, ["storyData", "resolveRelations", "preview"]); | ||
const adaptedStoryData = useAdaptedStoryData(storyData, fetchStoryblokAdaptor, resolveRelations, preview); | ||
return (0, jsx_runtime_1.jsx)(Component, Object.assign({ storyData: adaptedStoryData }, rest)); | ||
}; | ||
}; | ||
exports.withStoryblokPreviewHOC = withStoryblokPreviewHOC; |
@@ -1,1 +0,1 @@ | ||
export { getBlocksRenderer, useStoryblokPreviewSpread, withStoryblokPreviewHOC, } from "./client"; | ||
export { getBlocksRenderer, useStoryblokPreviewIndicatorSpread, withStoryblokPreviewHOC, } from "./client"; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.withStoryblokPreviewHOC = exports.useStoryblokPreviewSpread = exports.getBlocksRenderer = void 0; | ||
exports.withStoryblokPreviewHOC = exports.useStoryblokPreviewIndicatorSpread = exports.getBlocksRenderer = void 0; | ||
var client_1 = require("./client"); | ||
Object.defineProperty(exports, "getBlocksRenderer", { enumerable: true, get: function () { return client_1.getBlocksRenderer; } }); | ||
Object.defineProperty(exports, "useStoryblokPreviewSpread", { enumerable: true, get: function () { return client_1.useStoryblokPreviewSpread; } }); | ||
Object.defineProperty(exports, "useStoryblokPreviewIndicatorSpread", { enumerable: true, get: function () { return client_1.useStoryblokPreviewIndicatorSpread; } }); | ||
Object.defineProperty(exports, "withStoryblokPreviewHOC", { enumerable: true, get: function () { return client_1.withStoryblokPreviewHOC; } }); |
@@ -33,3 +33,3 @@ import { DatasourceData, DatasourceResponse, GetDatasourceProps, GetStoriesProps, GetStoryProps, StoriesResponse, StoryblokFetcherProps, StoryData, StoryResponse, TagsData, TagsResponse } from "../../types"; | ||
*/ | ||
getStory: <T = StoryData<any>>({ slug, params, isPreview, }: GetStoryProps) => Promise<StoryResponse<T>>; | ||
getStory: <T = StoryData>({ slug, params, isPreview, }: GetStoryProps) => Promise<StoryResponse<T>>; | ||
/** | ||
@@ -63,3 +63,3 @@ * Fetch a list of [stories](https://www.storyblok.com/docs/api/content-delivery/v2#core-resources/stories/stories) from Storyblok with adapted response. | ||
*/ | ||
getStories: <T = StoryData<any>>({ params, isPreview, }?: GetStoriesProps) => Promise<StoriesResponse<T>>; | ||
getStories: <T = StoryData>({ params, isPreview, }?: GetStoriesProps) => Promise<StoriesResponse<T>>; | ||
/** Get the total number of pages to fetch for a given `headers` object. */ | ||
@@ -73,3 +73,3 @@ totalPages: (headers: Headers, perPage?: number) => number; | ||
*/ | ||
getPagedStories: <T = StoryData<any>>({ params, isPreview, }?: GetStoriesProps) => Promise<StoriesResponse<T>>; | ||
getPagedStories: <T = StoryData>({ params, isPreview, }?: GetStoriesProps) => Promise<StoriesResponse<T>>; | ||
/** | ||
@@ -76,0 +76,0 @@ * Fetch a list of [datasource_entries](https://www.storyblok.com/docs/api/content-delivery/v2#core-resources/datasource-entries/retrieve-multiple-datasource-entries) based on a given `slug` with adapted response. |
@@ -0,8 +1,9 @@ | ||
import { ElementType } from "react"; | ||
import { ISbCache, ISbComponentType, ISbStoriesParams, ISbStoryData, ISbStoryParams } from "storyblok-js-client"; | ||
import { StoryblokAdaptor } from "./server"; | ||
export interface StoryblokAdaptorProps extends Partial<AllAdaptors> { | ||
export type StoryblokAdaptorProps = { | ||
handlers?: Handler; | ||
} | ||
} & Partial<AllAdaptors>; | ||
export type Cache = ISbCache; | ||
export interface StoryblokClientConfig { | ||
export type StoryblokClientConfig = { | ||
/** Number of retries to make when a request fails. Defaults to `5`. */ | ||
@@ -16,4 +17,4 @@ maxRetries?: number; | ||
cache?: Cache; | ||
} | ||
export interface StoryblokFetcherProps { | ||
}; | ||
export type StoryblokFetcherProps = { | ||
accessToken: string; | ||
@@ -23,46 +24,46 @@ config?: StoryblokClientConfig; | ||
isPreview?: boolean; | ||
} | ||
export interface PluginData { | ||
}; | ||
export type PluginData = { | ||
_uid: string; | ||
plugin: string; | ||
} | ||
}; | ||
export type PluginAdaptor<T extends PluginData = any, K = any> = Adaptor<T, K>; | ||
export type StoryParams = ISbStoryParams; | ||
export interface GetStoryProps { | ||
export type GetStoryProps = { | ||
slug: string; | ||
params?: StoryParams; | ||
isPreview?: boolean; | ||
} | ||
}; | ||
export type StoryData<T extends ContentTypeData = any> = ISbStoryData<T>; | ||
export interface StoryResponse<T = StoryData> { | ||
export type StoryResponse<T = StoryData> = { | ||
data: T; | ||
headers: Headers; | ||
} | ||
}; | ||
export type StoryAdaptor<T extends ContentTypeData = any, K = any> = Adaptor<StoryData<T>, K>; | ||
export type StoriesParams = ISbStoriesParams; | ||
export interface GetStoriesProps { | ||
export type GetStoriesProps = { | ||
params?: StoriesParams; | ||
isPreview?: boolean; | ||
} | ||
export interface StoriesResponse<T = StoryData> { | ||
}; | ||
export type StoriesResponse<T = StoryData> = { | ||
data: T[]; | ||
headers: Headers; | ||
} | ||
}; | ||
export type ContentTypeData = ISbComponentType<string>; | ||
export type ContentTypeAdaptor<T extends ContentTypeData = any, K = any> = Adaptor<T, K>; | ||
export interface FieldTypeData { | ||
export type FieldTypeData = { | ||
fieldtype: string; | ||
} | ||
}; | ||
export type FieldTypeAdaptor<T extends FieldTypeData = any, K = any> = Adaptor<T, K>; | ||
export type DatasourceParams = Pick<StoriesParams, "dimension" | "page" | "per_page" | "cv">; | ||
export interface GetDatasourceProps { | ||
export type GetDatasourceProps = { | ||
slug: string; | ||
params?: DatasourceParams; | ||
} | ||
}; | ||
export type DatasourceData = Record<string, string>; | ||
export interface DatasourceResponse<T = DatasourceData> { | ||
export type DatasourceResponse<T = DatasourceData> = { | ||
data: T; | ||
headers: Headers; | ||
} | ||
export interface DatasourceEntry { | ||
}; | ||
export type DatasourceEntry = { | ||
id: number; | ||
@@ -72,15 +73,15 @@ name: string; | ||
dimension_value: string | null; | ||
} | ||
}; | ||
export type DatasourceAdaptor<T = any> = Adaptor<DatasourceEntry[], T>; | ||
export type TagsData = TagEntry[]; | ||
export interface TagEntry { | ||
export type TagEntry = { | ||
name: string; | ||
taggings_count: number; | ||
} | ||
export interface TagsResponse<T = TagsData> { | ||
}; | ||
export type TagsResponse<T = TagsData> = { | ||
data: T; | ||
headers: Headers; | ||
} | ||
}; | ||
export type TagsAdaptor<T = any> = Adaptor<TagEntry[], T>; | ||
export interface AllAdaptors { | ||
export type AllAdaptors = { | ||
content_types: Adaptors<ContentTypeAdaptor>; | ||
@@ -93,3 +94,3 @@ field_types: Adaptors<FieldTypeAdaptor>; | ||
tags: TagsAdaptor; | ||
} | ||
}; | ||
export type Adaptors<Adaptor> = Record<string, Adaptor>; | ||
@@ -99,31 +100,31 @@ export type Adaptor<T, K> = (data: T) => K; | ||
export type StoryPathHandler = (data: StoryData) => string; | ||
export interface Handler { | ||
export type Handler = { | ||
formatStoryPath?: StoryPathHandler; | ||
} | ||
export interface DefaultPageFetcherProps<T extends ContentTypeData> { | ||
}; | ||
export type DefaultPageFetcherProps<T extends ContentTypeData> = { | ||
data: StoryData<T>; | ||
fetchProps: GetStoryProps; | ||
preview?: boolean; | ||
} | ||
export interface StoryAdaptorProps<T extends ContentTypeData> { | ||
data: StoryData<T>; | ||
}; | ||
export type StoryAdaptorProps<T extends ContentTypeData> = { | ||
storyData: StoryData<T>; | ||
resolveRelations?: string[]; | ||
preview?: boolean; | ||
} | ||
export interface StoryblokPreviewSpreadAttributes { | ||
}; | ||
export type StoryblokPreviewIndicatorSpreadAttributes = { | ||
"data-blok-c"?: string; | ||
"data-blok-uid"?: string; | ||
} | ||
export type BlocksRendererComponents = Record<string, any>; | ||
export interface BlocksRendererProps<T> { | ||
}; | ||
export type BlocksRendererComponents = Record<string, ElementType>; | ||
export type BlocksRendererProps<T, K> = T & { | ||
blocks: Block[]; | ||
propsPerBlock?: (item: Block, index: number) => T; | ||
} | ||
export interface Block { | ||
_editable?: string; | ||
_uid?: string; | ||
component: string; | ||
} | ||
propsPerBlock?: (item: Block, index: number) => K; | ||
}; | ||
export type Block = { | ||
_editable?: string | null; | ||
_uid?: string | null; | ||
component?: string | null; | ||
}; | ||
export type FetchStoryblokAdaptor = () => Promise<{ | ||
storyblokAdaptor: StoryblokAdaptor; | ||
}>; |
{ | ||
"name": "@buildinams/react-storyblok", | ||
"description": "Opinionated Reactjs Storyblok wrapper", | ||
"version": "0.4.2", | ||
"version": "0.5.0", | ||
"license": "MIT", | ||
@@ -36,7 +36,7 @@ "author": "Build in Amsterdam <development@buildinamsterdam.com> (https://www.buildinamsterdam.com/)", | ||
"lint": "NODE_ENV=test npm-run-all --parallel lint:*", | ||
"lint:script": "eslint \"src/**/*.{ts,tsx}\"", | ||
"lint:script": "eslint \"{src,tests}/**/*.{ts,tsx}\"", | ||
"lint:format": "prettier \"**/*.{md,yml}\" --check", | ||
"lint:type-check": "tsc --noEmit", | ||
"fix": "npm-run-all --sequential fix:*", | ||
"fix:js": "eslint \"src/**/*.{ts,tsx}\" --fix", | ||
"fix:js": "eslint \"{src,tests}/**/*.{ts,tsx}\" --fix", | ||
"fix:format": "prettier \"**/*.{md,yml}\" --write", | ||
@@ -49,25 +49,24 @@ "depcheck": "npx npm-check --update" | ||
"dependencies": { | ||
"@storyblok/js": "^2.0.15", | ||
"storyblok-js-client": "^5.4.1" | ||
"@storyblok/js": "^2.3.0", | ||
"storyblok-js-client": "^6.2.0" | ||
}, | ||
"devDependencies": { | ||
"@babel/preset-env": "^7.20.2", | ||
"@babel/preset-typescript": "^7.18.6", | ||
"@buildinams/lint": "^0.0.4", | ||
"@testing-library/react": "^13.4.0", | ||
"@types/jest": "^29.4.0", | ||
"@types/node": "^18.13.0", | ||
"@types/react": "^18.0.27", | ||
"@types/react-dom": "^18.0.10", | ||
"cross-fetch": "^3.1.5", | ||
"expect-type": "^0.15.0", | ||
"jest": "^29.4.2", | ||
"jest-environment-jsdom": "^29.4.2", | ||
"@babel/preset-env": "^7.23.2", | ||
"@babel/preset-typescript": "^7.23.2", | ||
"@buildinams/lint": "^0.3.0", | ||
"@testing-library/react": "^14.0.0", | ||
"@types/jest": "^29.5.5", | ||
"@types/node": "^20.8.6", | ||
"@types/react": "^18.2.28", | ||
"@types/react-dom": "^18.2.13", | ||
"cross-fetch": "^4.0.0", | ||
"expect-type": "^0.17.3", | ||
"jest": "^29.7.0", | ||
"jest-environment-jsdom": "^29.7.0", | ||
"npm-run-all": "^4.1.5", | ||
"prettier": "^2.8.4", | ||
"react-dom": "^18.0.0", | ||
"ts-jest": "^29.0.3", | ||
"ts-jest": "^29.1.1", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^4.9.5" | ||
"typescript": "^5.2.2" | ||
} | ||
} |
@@ -237,10 +237,10 @@ # react-storyblok | ||
Then you should use the `withStoryblokPreview` helper on every page for preview support, for example in [Next.js](https://nextjs.org/): | ||
Then you should use the `withStoryblokPreview` helper on every page with stories for preview support, for example in [Next.js](https://nextjs.org/): | ||
```jsx | ||
import HomePage from "~/scopes/HomePage"; | ||
import { HomePage } from "~/scopes/HomePage"; | ||
import { storyblokFetcher } from "~/server/fetcher"; | ||
export const getStaticProps = async ({ preview = null }) => { | ||
const { data } = await storyblokFetcher.getStory({ | ||
const story = await storyblokFetcher.getStory({ | ||
slug: "home", | ||
@@ -251,3 +251,3 @@ params: { resolve_relations: ["home.foo"] }, | ||
if (!data) { | ||
if (!story) { | ||
return { | ||
@@ -260,3 +260,3 @@ notFound: true, | ||
props: { | ||
data, | ||
storyData: story, | ||
resolveRelations: ["home.foo"], | ||
@@ -271,5 +271,5 @@ preview, | ||
Using this setup, `withStoryblokPreview` will automatically adapt the `data` prop on CMS changes. It will also add the `_editable` identifier to the data so the CMS knows which blocks to highlight. | ||
Using this setup, `withStoryblokPreview` will automatically adapt the `storyData` prop on CMS changes. | ||
**Note**: If you're resolving relations in your fetchers (such as `storyblokFetcher.getStory()`), you need to pass the `resolveRelations` prop to the `withStoryblokPreview` function. This is to make sure the bridge knows which relations to adapt when receiving new data. | ||
**Note**: If you're resolving relations in your fetchers you need to pass the `resolveRelations` prop to the `withStoryblokPreview` function. This is to make sure the bridge knows which relations to adapt when receiving new data. | ||
@@ -286,8 +286,8 @@ ### getBlocksRenderer | ||
const Renderer = getBlocksRenderer({ | ||
foo_block: FooBlock, | ||
fooBlock: FooBlock, | ||
}); | ||
const PageComponent = ({ blocks }) => ( | ||
export const HomePage = ({ storyData }) => ( | ||
<main> | ||
<Renderer blocks={blocks} title="Hello World 👋" /> | ||
<Renderer blocks={storyData.blocks} title="Hello World 👋" /> | ||
</main> | ||
@@ -303,15 +303,15 @@ ); | ||
#### Using `containerSpread` | ||
#### Using `previewIndicatorSpread` | ||
On initialising the Renderer we pass it an object containing a key / value lookup. Then when rendering the component we use this map to render the correct component. Whenever the list of blocks gets a value that isn't available in the lookup it will not render anything. | ||
Under the hood the renderer uses the `useStoryblokPreviewSpread` hook to provide all the props to make an HTML element show up as the editable element. We still need to attach this to whichever element you want to use in your block. This can be done like so: | ||
Under the hood the renderer uses the `useStoryblokPreviewIndicatorSpread` hook to provide all the props to make an HTML element show up as the editable element. We still need to attach this to whichever element you want to use in your block. This can be done like so: | ||
```js | ||
const FooBlock = ({ containerSpread, title }) => ( | ||
<h1 {...containerSpread}>{title}</h1> | ||
const FooBlock = ({ previewIndicatorSpread, title }) => ( | ||
<h1 {...previewIndicatorSpread}>{title}</h1> | ||
); | ||
``` | ||
**Note**: Make sure every component rendered by the `getBlocksRenderer` is provided the `containerSpread` prop. Without this you won't get real time updates in Storyblok. | ||
**Note**: Make sure every component rendered by the `getBlocksRenderer` is provided the `previewIndicatorSpread` prop. Without this you won't see Storyblok block highlighting. | ||
@@ -326,12 +326,12 @@ #### Using `propsPerBlock` | ||
const BlocksRenderer = getBlocksRenderer({ | ||
foo_block: FooBlock, | ||
bar_block: BarBlock, | ||
fooBlock: FooBlock, | ||
barBlock: BarBlock, | ||
}); | ||
const Page = ({ blocks }) => { | ||
export const HomePage = ({ storyData }) => { | ||
return ( | ||
<BlocksRenderer | ||
blocks={blocks} | ||
blocks={storyData.blocks} | ||
propsPerBlock={(item, index) => { | ||
if (item.component === "foo_block") { | ||
if (item.component === "fooBlock") { | ||
return { isFoo: true }; | ||
@@ -354,17 +354,17 @@ } | ||
### Using The `useStoryblokPreviewSpread` Hook | ||
### Using The `useStoryblokPreviewIndicatorSpread` Hook | ||
If you want to use the `useStoryblokPreviewSpread` hook directly you can do so. This is useful if you want to use the spread on a custom component. For example: | ||
If you want to use the `useStoryblokPreviewIndicatorSpread` hook directly you can do so! This is useful if you want to use the spread on a custom component. For example: | ||
```jsx | ||
import { useStoryblokPreviewSpread } from "@buildinams/react-storyblok"; | ||
import { useStoryblokPreviewIndicatorSpread } from "@buildinams/react-storyblok"; | ||
const FooBlock = ({ title, _editable }) => { | ||
const previewSpread = useStoryblokPreviewSpread(_editable); | ||
const previewIndicatorSpread = useStoryblokPreviewIndicatorSpread(_editable); | ||
return <h1 {...previewSpread}>{title}</h1>; | ||
return <h1 {...previewIndicatorSpread}>{title}</h1>; | ||
}; | ||
``` | ||
Now in the CMS you'll see the component highlighted when you hover over it. | ||
Now in the CMS you'll see the component highlighted when you hover over it + you can click it to open the content editor for that content type. | ||
@@ -371,0 +371,0 @@ ## Requirements |
export { getBlocksRenderer } from "./getBlocksRenderer"; | ||
export { useStoryblokPreviewSpread } from "./useStoryblokPreviewSpread"; | ||
export { useStoryblokPreviewIndicatorSpread } from "./useStoryblokPreviewIndicatorSpread"; | ||
export { withStoryblokPreviewHOC } from "./withStoryblokPreviewHOC"; |
@@ -1,21 +0,23 @@ | ||
import { loadStoryblokBridge, useStoryblokBridge } from "@storyblok/js"; | ||
import { ISbEventPayload, loadStoryblokBridge } from "@storyblok/js"; | ||
import { ContentTypeData, StoryData } from "../../types"; | ||
loadStoryblokBridge(); | ||
/** | ||
* Bridge logic that connects to the Storyblok admin UI. This JS should only be loaded when the site is loaded in the admin. | ||
* | ||
* @param storyID - Story ID to connect to. | ||
* @param fetchArgs - Fetch args from the original request. Used to mimic in from the client. | ||
* @param resolveRelations - Array of relations to resolve. This should match the resolved relations of story this is being used on. | ||
* @param callback - Function to call when the data changed in Storyblok preview. | ||
*/ | ||
export const storyblokBridge = <T extends ContentTypeData>( | ||
storyID: number, | ||
export const listenForStoryblokBridgeChanges = async ( | ||
resolveRelations: string[] | undefined, | ||
callback: (newData: StoryData<T>) => void | ||
callback: (event: ISbEventPayload) => void | ||
) => { | ||
// eslint-disable-next-line react-hooks/rules-of-hooks | ||
useStoryblokBridge(storyID, callback, { resolveRelations }); | ||
// Load the bridge to Window | ||
await loadStoryblokBridge(); | ||
// Then, connect to the bridge | ||
const storyblokBridge = new window.StoryblokBridge({ resolveRelations }); | ||
// And then, listen for input change events | ||
storyblokBridge.on(["input"], (event) => { | ||
if (event) callback(event); | ||
}); | ||
}; |
export { | ||
getBlocksRenderer, | ||
useStoryblokPreviewSpread, | ||
useStoryblokPreviewIndicatorSpread, | ||
withStoryblokPreviewHOC, | ||
} from "./client"; |
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import { ElementType } from "react"; | ||
import { | ||
@@ -12,5 +13,5 @@ ISbCache, | ||
export interface StoryblokAdaptorProps extends Partial<AllAdaptors> { | ||
export type StoryblokAdaptorProps = { | ||
handlers?: Handler; | ||
} | ||
} & Partial<AllAdaptors>; | ||
@@ -20,3 +21,3 @@ export type Cache = ISbCache; | ||
// Config adapted from 'ISbConfig' in storyblok-js-client | ||
export interface StoryblokClientConfig { | ||
export type StoryblokClientConfig = { | ||
/** Number of retries to make when a request fails. Defaults to `5`. */ | ||
@@ -33,5 +34,5 @@ maxRetries?: number; | ||
cache?: Cache; | ||
} | ||
}; | ||
export interface StoryblokFetcherProps { | ||
export type StoryblokFetcherProps = { | ||
accessToken: string; | ||
@@ -41,10 +42,10 @@ config?: StoryblokClientConfig; | ||
isPreview?: boolean; | ||
} | ||
}; | ||
// PLUGINS | ||
export interface PluginData { | ||
export type PluginData = { | ||
_uid: string; | ||
plugin: string; | ||
} | ||
}; | ||
@@ -57,14 +58,14 @@ export type PluginAdaptor<T extends PluginData = any, K = any> = Adaptor<T, K>; | ||
export interface GetStoryProps { | ||
export type GetStoryProps = { | ||
slug: string; | ||
params?: StoryParams; | ||
isPreview?: boolean; | ||
} | ||
}; | ||
export type StoryData<T extends ContentTypeData = any> = ISbStoryData<T>; | ||
export interface StoryResponse<T = StoryData> { | ||
export type StoryResponse<T = StoryData> = { | ||
data: T; | ||
headers: Headers; | ||
} | ||
}; | ||
@@ -80,11 +81,11 @@ export type StoryAdaptor<T extends ContentTypeData = any, K = any> = Adaptor< | ||
export interface GetStoriesProps { | ||
export type GetStoriesProps = { | ||
params?: StoriesParams; | ||
isPreview?: boolean; | ||
} | ||
}; | ||
export interface StoriesResponse<T = StoryData> { | ||
export type StoriesResponse<T = StoryData> = { | ||
data: T[]; | ||
headers: Headers; | ||
} | ||
}; | ||
@@ -102,5 +103,5 @@ // CONTENT TYPES | ||
export interface FieldTypeData { | ||
export type FieldTypeData = { | ||
fieldtype: string; | ||
} | ||
}; | ||
@@ -119,15 +120,15 @@ export type FieldTypeAdaptor<T extends FieldTypeData = any, K = any> = Adaptor< | ||
export interface GetDatasourceProps { | ||
export type GetDatasourceProps = { | ||
slug: string; | ||
params?: DatasourceParams; | ||
} | ||
}; | ||
export type DatasourceData = Record<string, string>; | ||
export interface DatasourceResponse<T = DatasourceData> { | ||
export type DatasourceResponse<T = DatasourceData> = { | ||
data: T; | ||
headers: Headers; | ||
} | ||
}; | ||
export interface DatasourceEntry { | ||
export type DatasourceEntry = { | ||
id: number; | ||
@@ -137,3 +138,3 @@ name: string; | ||
dimension_value: string | null; | ||
} | ||
}; | ||
@@ -146,11 +147,11 @@ export type DatasourceAdaptor<T = any> = Adaptor<DatasourceEntry[], T>; | ||
export interface TagEntry { | ||
export type TagEntry = { | ||
name: string; | ||
taggings_count: number; | ||
} | ||
}; | ||
export interface TagsResponse<T = TagsData> { | ||
export type TagsResponse<T = TagsData> = { | ||
data: T; | ||
headers: Headers; | ||
} | ||
}; | ||
@@ -161,3 +162,3 @@ export type TagsAdaptor<T = any> = Adaptor<TagEntry[], T>; | ||
export interface AllAdaptors { | ||
export type AllAdaptors = { | ||
content_types: Adaptors<ContentTypeAdaptor>; | ||
@@ -171,3 +172,3 @@ field_types: Adaptors<FieldTypeAdaptor>; | ||
tags: TagsAdaptor; | ||
} | ||
}; | ||
@@ -184,38 +185,37 @@ export type Adaptors<Adaptor> = Record<string, Adaptor>; | ||
export interface Handler { | ||
export type Handler = { | ||
formatStoryPath?: StoryPathHandler; | ||
} | ||
}; | ||
// CLIENT | ||
export interface DefaultPageFetcherProps<T extends ContentTypeData> { | ||
export type DefaultPageFetcherProps<T extends ContentTypeData> = { | ||
data: StoryData<T>; | ||
fetchProps: GetStoryProps; | ||
preview?: boolean; | ||
} | ||
}; | ||
export interface StoryAdaptorProps<T extends ContentTypeData> { | ||
data: StoryData<T>; | ||
export type StoryAdaptorProps<T extends ContentTypeData> = { | ||
storyData: StoryData<T>; | ||
resolveRelations?: string[]; | ||
preview?: boolean; | ||
} | ||
}; | ||
export interface StoryblokPreviewSpreadAttributes { | ||
export type StoryblokPreviewIndicatorSpreadAttributes = { | ||
"data-blok-c"?: string; | ||
"data-blok-uid"?: string; | ||
} | ||
}; | ||
// TODO: Type this properly | ||
export type BlocksRendererComponents = Record<string, any>; | ||
export type BlocksRendererComponents = Record<string, ElementType>; | ||
export interface BlocksRendererProps<T> { | ||
export type BlocksRendererProps<T, K> = T & { | ||
blocks: Block[]; | ||
propsPerBlock?: (item: Block, index: number) => T; | ||
} | ||
propsPerBlock?: (item: Block, index: number) => K; | ||
}; | ||
export interface Block { | ||
_editable?: string; | ||
_uid?: string; | ||
component: string; | ||
} | ||
export type Block = { | ||
_editable?: string | null; | ||
_uid?: string | null; | ||
component?: string | null; | ||
}; | ||
@@ -222,0 +222,0 @@ export type FetchStoryblokAdaptor = () => Promise<{ |
@@ -8,5 +8,5 @@ import { expectTypeOf } from "expect-type"; | ||
interface AdaptedFeature { | ||
type AdaptedFeature = { | ||
title: string; | ||
} | ||
}; | ||
@@ -20,13 +20,13 @@ /** Rename `name` to `title`. */ | ||
interface AdaptedHomeData { | ||
type AdaptedHomeData = { | ||
content: { | ||
blocks: (AdaptedFeature | Teaser)[]; | ||
}; | ||
} | ||
}; | ||
interface AdaptedHomeDataWithNull { | ||
type AdaptedHomeDataWithNull = { | ||
content: { | ||
blocks: (null | Teaser)[]; | ||
}; | ||
} | ||
}; | ||
@@ -33,0 +33,0 @@ describe("With 'content_types' adaptor on", () => { |
@@ -7,6 +7,6 @@ import { expectTypeOf } from "expect-type"; | ||
interface AdaptedDatasourceEntry { | ||
type AdaptedDatasourceEntry = { | ||
id: number; | ||
value: string; | ||
} | ||
}; | ||
@@ -13,0 +13,0 @@ type AdaptedDatasourcesData = AdaptedDatasourceEntry[]; |
@@ -18,13 +18,13 @@ import { expectTypeOf } from "expect-type"; | ||
interface AdaptedHomeData { | ||
type AdaptedHomeData = { | ||
content: { | ||
asset: AdaptedAsset; | ||
}; | ||
} | ||
}; | ||
interface AdaptedHomeDataWithNull { | ||
type AdaptedHomeDataWithNull = { | ||
content: { | ||
asset: null; | ||
}; | ||
} | ||
}; | ||
@@ -31,0 +31,0 @@ describe("With 'field_types' adaptor on", () => { |
@@ -18,13 +18,13 @@ import { expectTypeOf } from "expect-type"; | ||
interface AdaptedHomeData { | ||
type AdaptedHomeData = { | ||
content: { | ||
colorPicker: AdaptedColorPicker; | ||
}; | ||
} | ||
}; | ||
interface AdaptedHomeDataWithNull { | ||
type AdaptedHomeDataWithNull = { | ||
content: { | ||
colorPicker: null; | ||
}; | ||
} | ||
}; | ||
@@ -31,0 +31,0 @@ describe("With 'plugins' adaptor on", () => { |
@@ -15,7 +15,7 @@ import { expectTypeOf } from "expect-type"; | ||
interface AdaptedHomeData { | ||
type AdaptedHomeData = { | ||
content: { | ||
title: string; | ||
}; | ||
} | ||
}; | ||
@@ -34,5 +34,5 @@ /** Exclude all but nested 'title' from the response. */ | ||
interface AdaptedHomeDataAfterWildcard { | ||
type AdaptedHomeDataAfterWildcard = { | ||
title: string; | ||
} | ||
}; | ||
@@ -39,0 +39,0 @@ /** Exclude all but 'title' from the response. */ |
import { ContentTypeData, FieldTypeData, PluginData } from "../../dist/types"; | ||
export interface Feature extends ContentTypeData { | ||
export type Feature = { | ||
name: string; | ||
} | ||
} & ContentTypeData; | ||
export interface Teaser extends ContentTypeData { | ||
export type Teaser = { | ||
headline: string; | ||
} | ||
} & ContentTypeData; | ||
export interface Asset extends FieldTypeData { | ||
export type Asset = { | ||
id: number; | ||
@@ -20,5 +20,5 @@ alt: string; | ||
is_external_url: boolean; | ||
} | ||
} & FieldTypeData; | ||
export interface Link extends FieldTypeData { | ||
export type Link = { | ||
id: string; | ||
@@ -29,9 +29,9 @@ url: string; | ||
cached_url: string; | ||
} | ||
} & FieldTypeData; | ||
export interface ColorPicker extends PluginData { | ||
export type ColorPicker = { | ||
color: string; | ||
} | ||
} & PluginData; | ||
export interface HomeContent extends ContentTypeData { | ||
export type HomeContent = { | ||
title: string; | ||
@@ -42,2 +42,2 @@ blocks: (Feature | Teaser)[]; | ||
colorPicker: ColorPicker; | ||
} | ||
} & ContentTypeData; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
112100
17
2130
+ Addedstoryblok-js-client@6.10.7(transitive)
Updated@storyblok/js@^2.3.0
Updatedstoryblok-js-client@^6.2.0