From 6dc8b0364c5d051a4bae5bdcf0aa73727e2baf6d Mon Sep 17 00:00:00 2001 From: Silvano Stralla Date: Tue, 11 Feb 2025 16:17:20 +0100 Subject: [PATCH 1/2] Add support for inline blocks --- package-lock.json | 20 +- package.json | 4 +- .../__snapshots__/index.test.tsx.snap | 15 ++ src/StructuredText/__tests__/index.test.tsx | 179 ++++++++++-------- src/StructuredText/index.tsx | 30 +++ 5 files changed, 159 insertions(+), 89 deletions(-) diff --git a/package-lock.json b/package-lock.json index aeedd6b..4927a0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,8 @@ "dependencies": { "@mux/mux-player-react": "*", "datocms-listen": "^0.1.9", - "datocms-structured-text-generic-html-renderer": "^4.0.1", - "datocms-structured-text-utils": "^4.0.1", + "datocms-structured-text-generic-html-renderer": "next", + "datocms-structured-text-utils": "next", "react-intersection-observer": "^9.4.3", "react-string-replace": "^1.1.0", "use-deep-compare-effect": "^1.6.1" @@ -3105,17 +3105,19 @@ "integrity": "sha512-Ijd5fzQ1Y1EUiSwduvaPRN7a6Edh//c500/2MjDhgaKd+N+zGEy7rpJ/SVXX0TdYDhX0ssoC4FDms6sbR7k+eA==" }, "node_modules/datocms-structured-text-generic-html-renderer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/datocms-structured-text-generic-html-renderer/-/datocms-structured-text-generic-html-renderer-4.0.1.tgz", - "integrity": "sha512-umEWXyBHP3fo1YsOL7AhFxWrmb2LNxiRV26+C8NMkwoBZ/RbF9+dBaGIBv1D/NeasCjq560hBdm7jqChxxIi/A==", + "version": "4.1.0-alpha.0", + "resolved": "https://registry.npmjs.org/datocms-structured-text-generic-html-renderer/-/datocms-structured-text-generic-html-renderer-4.1.0-alpha.0.tgz", + "integrity": "sha512-NN+DwISZaJwCuigYx0EE/jUG+EZ7r52KCxPw8MAYM36LFf/1D7Ufnf+BlUFq4Fd9ys/gjZIfP9ZWtSGnxcBEmg==", + "license": "MIT", "dependencies": { - "datocms-structured-text-utils": "^4.0.1" + "datocms-structured-text-utils": "^4.1.0-alpha.0" } }, "node_modules/datocms-structured-text-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/datocms-structured-text-utils/-/datocms-structured-text-utils-4.0.1.tgz", - "integrity": "sha512-2rK4bZfzKpdKw2AZhnnD043QkGOUaJDOUGPrQHo5w4hvU4jTBGGxoaTg4KIb3Q5Yo+XUFvreb+U0+86xdh7I+Q==", + "version": "4.1.0-alpha.0", + "resolved": "https://registry.npmjs.org/datocms-structured-text-utils/-/datocms-structured-text-utils-4.1.0-alpha.0.tgz", + "integrity": "sha512-jew6+cpIJA210CBIf3lw3iI2nmcNt2OCQWIBnjTZqYhu9kYYjblu3YgtkXSqxXGEWJcEbiDnrjq/dkWrODCxNQ==", + "license": "MIT", "dependencies": { "array-flatten": "^3.0.0" } diff --git a/package.json b/package.json index 6eec531..5be27e9 100644 --- a/package.json +++ b/package.json @@ -135,8 +135,8 @@ "dependencies": { "@mux/mux-player-react": "*", "datocms-listen": "^0.1.9", - "datocms-structured-text-generic-html-renderer": "^4.0.1", - "datocms-structured-text-utils": "^4.0.1", + "datocms-structured-text-generic-html-renderer": "next", + "datocms-structured-text-utils": "next", "react-intersection-observer": "^9.4.3", "react-string-replace": "^1.1.0", "use-deep-compare-effect": "^1.6.1" diff --git a/src/StructuredText/__tests__/__snapshots__/index.test.tsx.snap b/src/StructuredText/__tests__/__snapshots__/index.test.tsx.snap index 828b6ef..aa26d7d 100644 --- a/src/StructuredText/__tests__/__snapshots__/index.test.tsx.snap +++ b/src/StructuredText/__tests__/__snapshots__/index.test.tsx.snap @@ -224,6 +224,11 @@ exports[`StructuredText with links/blocks with default rules renders the documen "id": "456", "quote": "Foo bar.", }, + { + "__typename": "MentionRecord", + "id": "789", + "name": "Jane Doe", + }, ], "links": [ { @@ -269,6 +274,10 @@ exports[`StructuredText with links/blocks with default rules renders the documen ], "type": "itemLink", }, + { + "item": "789", + "type": "inlineBlock", + }, ], "level": 1, "type": "heading", @@ -285,6 +294,7 @@ exports[`StructuredText with links/blocks with default rules renders the documen } } renderBlock={[Function]} + renderInlineBlock={[Function]} renderInlineRecord={[Function]} renderLinkToRecord={[Function]} > @@ -310,6 +320,11 @@ exports[`StructuredText with links/blocks with default rules renders the documen > here! + + Jane Doe +
{ - describe('with no value', () => { - it('renders null', () => { +describe("StructuredText", () => { + describe("with no value", () => { + it("renders null", () => { const wrapper = mount(); expect(wrapper).toMatchSnapshot(); }); }); - describe('simple dast /2', () => { + describe("simple dast /2", () => { const structuredText: StructuredTextDocument = { - schema: 'dast', + schema: "dast", document: { - type: 'root', + type: "root", children: [ { - type: 'heading', + type: "heading", level: 1, children: [ { - type: 'span', - value: 'This\nis a ', + type: "span", + value: "This\nis a ", }, { - type: 'span', - marks: ['strong'], - value: 'title', + type: "span", + marks: ["strong"], + value: "title", }, ], }, @@ -43,49 +43,49 @@ describe('StructuredText', () => { }, }; - describe('with default rules', () => { - it('renders the document', () => { + describe("with default rules", () => { + it("renders the document", () => { const wrapper = mount(); expect(wrapper).toMatchSnapshot(); }); }); - describe('with custom mark rules', () => { - it('renders the document', () => { + describe("with custom mark rules", () => { + it("renders the document", () => { const wrapper = mount( ( + renderMarkRule("strong", ({ children, key }) => ( {children} )), ]} - />, + /> ); expect(wrapper).toMatchSnapshot(); }); }); }); - describe('simple dast with no links/blocks', () => { + describe("simple dast with no links/blocks", () => { const structuredText: StructuredTextGraphQlResponse = { value: { - schema: 'dast', + schema: "dast", document: { - type: 'root', + type: "root", children: [ { - type: 'heading', + type: "heading", level: 1, children: [ { - type: 'span', - value: 'This\nis a ', + type: "span", + value: "This\nis a ", }, { - type: 'span', - marks: ['strong'], - value: 'title', + type: "span", + marks: ["strong"], + value: "title", }, ], }, @@ -94,22 +94,22 @@ describe('StructuredText', () => { }, }; - describe('with default rules', () => { - it('renders the document', () => { + describe("with default rules", () => { + it("renders the document", () => { const wrapper = mount(); expect(wrapper).toMatchSnapshot(); }); }); - describe('with custom rules', () => { - it('renders the document', () => { + describe("with custom rules", () => { + it("renders the document", () => { const wrapper = mount( { return ( - {text.replace(/This/, 'That')} + {text.replace(/This/, "That")} ); }} @@ -118,98 +118,113 @@ describe('StructuredText', () => { isHeading, ({ adapter: { renderNode }, node, children, key }) => { return renderNode(`h${node.level + 1}`, { key }, children); - }, + } ), ]} - />, + /> ); expect(wrapper).toMatchSnapshot(); }); }); }); - describe('with links/blocks', () => { + describe("with links/blocks", () => { type QuoteRecord = { id: string; - __typename: 'QuoteRecord'; + __typename: "QuoteRecord"; quote: string; author: string; }; + type MentionRecord = { + id: string; + __typename: "MentionRecord"; + name: string; + }; + type DocPageRecord = { id: string; - __typename: 'DocPageRecord'; + __typename: "DocPageRecord"; slug: string; title: string; }; const structuredText: StructuredTextGraphQlResponse< - QuoteRecord, + QuoteRecord | MentionRecord, DocPageRecord > = { value: { - schema: 'dast', + schema: "dast", document: { - type: 'root', + type: "root", children: [ { - type: 'heading', + type: "heading", level: 1, children: [ { - type: 'span', - value: 'This is a', + type: "span", + value: "This is a", }, { - type: 'span', - marks: ['highlight'], - value: 'title', + type: "span", + marks: ["highlight"], + value: "title", }, { - type: 'inlineItem', - item: '123', + type: "inlineItem", + item: "123", }, { - type: 'itemLink', - item: '123', - meta: [{ id: 'target', value: '_blank' }], - children: [{ type: 'span', value: 'here!' }], + type: "itemLink", + item: "123", + meta: [{ id: "target", value: "_blank" }], + children: [{ type: "span", value: "here!" }], + }, + { + type: "inlineBlock", + item: "789", }, ], }, { - type: 'block', - item: '456', + type: "block", + item: "456", }, ], }, }, blocks: [ { - id: '456', - __typename: 'QuoteRecord', - quote: 'Foo bar.', - author: 'Mark Smith', + id: "456", + __typename: "QuoteRecord", + quote: "Foo bar.", + author: "Mark Smith", + }, + { + id: "789", + __typename: "MentionRecord", + name: "Jane Doe", }, ], links: [ { - id: '123', - __typename: 'DocPageRecord', - title: 'How to code', - slug: 'how-to-code', + id: "123", + __typename: "DocPageRecord", + title: "How to code", + slug: "how-to-code", }, ], }; - describe('with default rules', () => { - it('renders the document', () => { + describe("with default rules", () => { + it("renders the document", () => { const wrapper = mount( { switch (record.__typename) { - case 'DocPageRecord': + case "DocPageRecord": return {record.title}; default: return null; @@ -217,7 +232,7 @@ describe('StructuredText', () => { }} renderLinkToRecord={({ record, children, transformedMeta }) => { switch (record.__typename) { - case 'DocPageRecord': + case "DocPageRecord": return ( {children} @@ -229,7 +244,7 @@ describe('StructuredText', () => { }} renderBlock={({ record }) => { switch (record.__typename) { - case 'QuoteRecord': + case "QuoteRecord": return (
{record.quote}
@@ -240,22 +255,30 @@ describe('StructuredText', () => { return null; } }} - />, + renderInlineBlock={({ record }) => { + switch (record.__typename) { + case "MentionRecord": + return {record.name}; + default: + return null; + } + }} + /> ); expect(wrapper).toMatchSnapshot(); }); }); - describe('with missing renderInlineRecord prop', () => { - it('raises an error', () => { + describe("with missing renderInlineRecord prop", () => { + it("raises an error", () => { expect(() => { shallow(); }).toThrow(RenderError); }); }); - describe('with missing record', () => { - it('raises an error', () => { + describe("with missing record", () => { + it("raises an error", () => { expect(() => { shallow( { renderInlineRecord={() => { return null; }} - />, + /> ); }).toThrow(RenderError); }); diff --git a/src/StructuredText/index.tsx b/src/StructuredText/index.tsx index 1e25644..139e4a5 100644 --- a/src/StructuredText/index.tsx +++ b/src/StructuredText/index.tsx @@ -17,6 +17,7 @@ import { type Record as StructuredTextGraphQlResponseRecord, type TypesafeStructuredText as TypesafeStructuredTextGraphQlResponse, isBlock, + isInlineBlock, isInlineItem, isItemLink, isStructuredText, @@ -104,6 +105,8 @@ export type StructuredTextPropTypes< ) => ReactElement | null; /** Fuction that converts a 'block' node into React **/ renderBlock?: (context: RenderBlockContext) => ReactElement | null; + /** Fuction that converts an 'inlineBlock' node into React **/ + renderInlineBlock?: (context: RenderBlockContext) => ReactElement | null; /** Function that converts 'link' and 'itemLink' `meta` into HTML props */ metaTransformer?: TransformMetaFn; /** Fuction that converts a simple string text into React **/ @@ -124,6 +127,7 @@ export function StructuredText< renderInlineRecord, renderLinkToRecord, renderBlock, + renderInlineBlock, renderText, renderNode, renderFragment, @@ -234,6 +238,32 @@ export function StructuredText< return appendKeyToValidElement(renderBlock({ record: item }), key); }), + renderNodeRule(isInlineBlock, ({ node, key }) => { + if (!renderInlineBlock) { + throw new RenderError( + `The Structured Text document contains an 'inlineBlock' node, but no 'renderInlineBlock' prop is specified!`, + node, + ); + } + + if (!(isStructuredText(data) && data.blocks)) { + throw new RenderError( + `The document contains an 'inlineBlock' node, but the passed data prop is not a Structured Text GraphQL response, or data.blocks is not present!`, + node, + ); + } + + const item = data.blocks.find((item) => item.id === node.item); + + if (!item) { + throw new RenderError( + `The Structured Text document contains an 'inlineBlock' node, but cannot find a record with ID ${node.item} inside data.blocks!`, + node, + ); + } + + return appendKeyToValidElement(renderInlineBlock({ record: item }), key); + }), ...(customNodeRules || customRules || []), ], }); From 14a38edd0651c238d814abad8a7648f855394236 Mon Sep 17 00:00:00 2001 From: Silvano Stralla Date: Fri, 28 Feb 2025 12:08:04 +0100 Subject: [PATCH 2/2] Use new deps versions --- package-lock.json | 21 +++++++++++---------- package.json | 4 ++-- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4927a0d..7bb87b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,8 @@ "dependencies": { "@mux/mux-player-react": "*", "datocms-listen": "^0.1.9", - "datocms-structured-text-generic-html-renderer": "next", - "datocms-structured-text-utils": "next", + "datocms-structured-text-generic-html-renderer": "^4.1.2", + "datocms-structured-text-utils": "^4.1.2", "react-intersection-observer": "^9.4.3", "react-string-replace": "^1.1.0", "use-deep-compare-effect": "^1.6.1" @@ -1856,7 +1856,8 @@ "node_modules/array-flatten": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", - "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" + "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==", + "license": "MIT" }, "node_modules/array.prototype.filter": { "version": "1.0.2", @@ -3105,18 +3106,18 @@ "integrity": "sha512-Ijd5fzQ1Y1EUiSwduvaPRN7a6Edh//c500/2MjDhgaKd+N+zGEy7rpJ/SVXX0TdYDhX0ssoC4FDms6sbR7k+eA==" }, "node_modules/datocms-structured-text-generic-html-renderer": { - "version": "4.1.0-alpha.0", - "resolved": "https://registry.npmjs.org/datocms-structured-text-generic-html-renderer/-/datocms-structured-text-generic-html-renderer-4.1.0-alpha.0.tgz", - "integrity": "sha512-NN+DwISZaJwCuigYx0EE/jUG+EZ7r52KCxPw8MAYM36LFf/1D7Ufnf+BlUFq4Fd9ys/gjZIfP9ZWtSGnxcBEmg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/datocms-structured-text-generic-html-renderer/-/datocms-structured-text-generic-html-renderer-4.1.2.tgz", + "integrity": "sha512-VsyVnqG/5iio3LHGd09Pk27n3hu2YcAujgTcgtv8CXapYd13r7OkfMkrsbiuJCO+xYBzPNHhI62PL6QOoI6BGA==", "license": "MIT", "dependencies": { - "datocms-structured-text-utils": "^4.1.0-alpha.0" + "datocms-structured-text-utils": "^4.1.2" } }, "node_modules/datocms-structured-text-utils": { - "version": "4.1.0-alpha.0", - "resolved": "https://registry.npmjs.org/datocms-structured-text-utils/-/datocms-structured-text-utils-4.1.0-alpha.0.tgz", - "integrity": "sha512-jew6+cpIJA210CBIf3lw3iI2nmcNt2OCQWIBnjTZqYhu9kYYjblu3YgtkXSqxXGEWJcEbiDnrjq/dkWrODCxNQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/datocms-structured-text-utils/-/datocms-structured-text-utils-4.1.2.tgz", + "integrity": "sha512-xyBG7ZAXedv8wytTR1JqfHx8uuUogg6I9brQmK1uR3y14gQqTRLvAeoY3unp5xcBDm8YXYHdS2W+Bg9AWgdhLQ==", "license": "MIT", "dependencies": { "array-flatten": "^3.0.0" diff --git a/package.json b/package.json index 5be27e9..5d8ff81 100644 --- a/package.json +++ b/package.json @@ -135,8 +135,8 @@ "dependencies": { "@mux/mux-player-react": "*", "datocms-listen": "^0.1.9", - "datocms-structured-text-generic-html-renderer": "next", - "datocms-structured-text-utils": "next", + "datocms-structured-text-generic-html-renderer": "^4.1.2", + "datocms-structured-text-utils": "^4.1.2", "react-intersection-observer": "^9.4.3", "react-string-replace": "^1.1.0", "use-deep-compare-effect": "^1.6.1"