@datocms/astro 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@datocms/astro",
3
3
  "description": "A set of components and utilities to work faster with DatoCMS in Astro projects.",
4
4
  "type": "module",
5
- "version": "0.2.1",
5
+ "version": "0.3.0",
6
6
  "sideEffects": false,
7
7
  "repository": {
8
8
  "type": "git",
@@ -44,7 +44,8 @@
44
44
  ],
45
45
  "dependencies": {
46
46
  "@0no-co/graphql.web": "^1.0.7",
47
- "datocms-listen": "^1.0.0",
47
+ "datocms-listen": "^1.0.1",
48
+ "datocms-structured-text-generic-html-renderer": "^4.0.1",
48
49
  "datocms-structured-text-utils": "^4.0.1"
49
50
  },
50
51
  "peerDependencies": {
@@ -1,70 +1,112 @@
1
1
  ---
2
2
  import {
3
3
  type Block,
4
+ type Record as DatocmsRecord,
4
5
  type InlineItem,
5
6
  type ItemLink,
6
- type Node,
7
- type StructuredText,
7
+ type Node as DastNode,
8
8
  hasChildren,
9
9
  isBlock,
10
10
  isInlineItem,
11
11
  isItemLink,
12
+ RenderError,
12
13
  } from 'datocms-structured-text-utils';
13
14
 
14
- import {
15
- defaultComponents,
16
- throwRenderErrorForMissingBlock,
17
- throwRenderErrorForMissingComponent,
18
- throwRenderErrorForMissingLink,
19
- } from './utils';
15
+ import Blockquote from './nodes/Blockquote.astro';
16
+ import Code from './nodes/Code.astro';
17
+ import Heading from './nodes/Heading.astro';
18
+ import Link from './nodes/Link.astro';
19
+ import List from './nodes/List.astro';
20
+ import ListItem from './nodes/ListItem.astro';
21
+ import Paragraph from './nodes/Paragraph.astro';
22
+ import Root from './nodes/Root.astro';
23
+ import Span from './nodes/Span.astro';
24
+ import ThematicBreak from './nodes/ThematicBreak.astro';
25
+ import BlockComponent from './nodes/Block.astro';
26
+ import InlineItemComponent from './nodes/InlineItem.astro';
27
+ import ItemLinkComponent from './nodes/ItemLink.astro';
20
28
 
21
- import type { PredicateComponentTuple } from './types';
29
+ import type { BlockComponents, InlineItemComponents, ItemLinkComponents, Overrides } from './types';
22
30
 
23
31
  interface Props {
24
- node: Node;
25
- blocks: StructuredText['blocks'];
26
- links: StructuredText['links'];
27
- components: PredicateComponentTuple[];
32
+ node: DastNode;
33
+
34
+ blocks?: DatocmsRecord[];
35
+ links?: DatocmsRecord[];
36
+
37
+ blockComponents?: BlockComponents<DatocmsRecord, DatocmsRecord>;
38
+ itemLinkComponents?: ItemLinkComponents<DatocmsRecord, DatocmsRecord>;
39
+ inlineItemComponents?: InlineItemComponents<DatocmsRecord, DatocmsRecord>;
40
+ overrides?: Overrides;
28
41
  }
29
42
 
30
- const { node, blocks, links, components = [] } = Astro.props;
43
+ const { node, ...rest } = Astro.props;
44
+
45
+ const { blocks, links, blockComponents, itemLinkComponents, inlineItemComponents, overrides } =
46
+ rest;
47
+
48
+ function findRecordInBlocks(node: Block) {
49
+ const record = (blocks || []).find(({ id }) => id === node.item);
31
50
 
32
- const findBlock = (node: Block, blocks: StructuredText['blocks']) =>
33
- (blocks || []).find(({ id }) => id === node.item);
51
+ if (!record) {
52
+ throw new RenderError(
53
+ `The Structured Text document contains a 'block' node, but cannot find a record with ID ${node.item} inside data.blocks!`,
54
+ node,
55
+ );
56
+ }
34
57
 
35
- const findLink = (node: ItemLink | InlineItem, links: StructuredText['links']) =>
36
- (links || []).find(({ id }) => id === node.item);
58
+ return record;
59
+ }
37
60
 
38
- const block = isBlock(node) && (findBlock(node, blocks) || throwRenderErrorForMissingBlock(node));
61
+ function findRecordInLinks(node: ItemLink | InlineItem) {
62
+ const record = (links || []).find(({ id }) => id === node.item);
39
63
 
40
- const link =
41
- (isItemLink(node) && (findLink(node, links) || throwRenderErrorForMissingLink(node))) ||
42
- (isInlineItem(node) && (findLink(node, links) || throwRenderErrorForMissingLink(node)));
64
+ if (!record) {
65
+ throw new RenderError(
66
+ `The Structured Text document contains an '${node.type}' node, but cannot find a record with ID ${node.item} inside data.links!`,
67
+ node,
68
+ );
69
+ }
70
+
71
+ return record;
72
+ }
43
73
 
44
- const predicateComponentTuple =
45
- [...components, ...defaultComponents].find(([predicate, component]) => predicate(node)) ||
46
- throwRenderErrorForMissingComponent(node);
74
+ const defaultComponents: Overrides = {
75
+ paragraph: Paragraph,
76
+ root: Root,
77
+ span: Span,
78
+ link: Link,
79
+ list: List,
80
+ heading: Heading,
81
+ blockquote: Blockquote,
82
+ listItem: ListItem,
83
+ thematicBreak: ThematicBreak,
84
+ code: Code,
85
+ itemLink: ItemLinkComponent,
86
+ inlineItem: InlineItemComponent,
87
+ block: BlockComponent,
88
+ };
47
89
 
48
- const Component = (predicateComponentTuple ?? [])[1];
90
+ const otherNodeComponents: Overrides = { ...defaultComponents, ...overrides };
91
+ const Component = otherNodeComponents[node.type];
49
92
  ---
50
93
 
51
94
  <>
52
95
  {
53
- Component &&
54
- (isBlock(node) ? (
55
- <Component {node} {block} />
56
- ) : isInlineItem(node) ? (
57
- <Component {node} {link} />
58
- ) : isItemLink(node) ? (
59
- <Component {node} {link}>
60
- {hasChildren(node) &&
61
- node.children.map((child) => <Astro.self node={child} {blocks} {links} {components} />)}
62
- </Component>
63
- ) : (
64
- <Component {node}>
65
- {hasChildren(node) &&
66
- node.children.map((child) => <Astro.self node={child} {blocks} {links} {components} />)}
67
- </Component>
68
- ))
96
+ isBlock(node) ? (
97
+ <Component {node} block={findRecordInBlocks(node)} {blockComponents} />
98
+ ) : isInlineItem(node) ? (
99
+ <Component {node} record={findRecordInLinks(node)} {inlineItemComponents} />
100
+ ) : isItemLink(node) ? (
101
+ <Component {node} record={findRecordInLinks(node)} {itemLinkComponents}>
102
+ {node.children.map((child) => (
103
+ <Astro.self node={child} {...rest} />
104
+ ))}
105
+ </Component>
106
+ ) : (
107
+ <Component {node}>
108
+ {hasChildren(node) && node.children.map((child) => <Astro.self node={child} {...rest} />)}
109
+ </Component>
110
+ )
69
111
  }
70
112
  </>
@@ -7,10 +7,9 @@
7
7
  <!-- START doctoc generated TOC please keep comment here to allow auto update -->
8
8
  <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
9
9
 
10
- - [Setup](#setup)
11
10
  - [Basic usage](#basic-usage)
12
11
  - [Customization](#customization)
13
- - [Custom components for blocks](#custom-components-for-blocks)
12
+ - [Custom components for blocks, inline records or links to records](#custom-components-for-blocks-inline-records-or-links-to-records)
14
13
  - [Override default rendering of nodes](#override-default-rendering-of-nodes)
15
14
  - [Props](#props)
16
15
 
@@ -57,21 +56,25 @@ The `<StructuredText />` component comes with a set of default components that a
57
56
 
58
57
  You need to use custom components in the following cases:
59
58
 
60
- - you have to render blocks, inline items or item links: there's no conventional way of rendering theses nodes, so you must create and pass custom components;
59
+ - you have to render blocks, inline records or links to records: there's no conventional way of rendering theses nodes, so you must create and pass custom components;
61
60
  - you need to render a conventional node differently (e.g. you may want a custom render for blockquotes)
62
61
 
63
- ### Custom components for blocks
62
+ ### Custom components for blocks, inline records or links to records
64
63
 
65
- Here is an example using custom components for blocks, inline and item links. Take a look at the [test fixtures](https://github.com/datocms/datocms-svelte/tree/main/src/lib/components/StructuredText/__tests__/__fixtures__) to see examples on how to implement these components.
64
+ - Astro components passed in `blockComponents` will be used to render blocks and will receive a `block` prop containing the actual block data.
65
+ - Astro components passed in `inlineItemComponents` will be used to render inline records and will receive a `record` prop containing the actual record.
66
+ - Astro components passed in `itemLinkComponents` will be used to render links to records and will receive the following props: `node` (the actual `'inlineItem'` node), `record` (the record linked to the node), and `attrs` (the custom attributes for the link specified by the node).
66
67
 
67
68
  ```astro
68
69
  ---
69
- import { isBlock, isInlineItem, isItemLink } from 'datocms-structured-text-utils';
70
70
  import { StructuredText } from '@datocms/astro/StructuredText';
71
+ import { executeQuery } from '@datocms/cda-client';
72
+
73
+ import Cta from '~/components/Cta/index.astro';
74
+ import NewsletterSignup from '~/components/NewsletterSignup/index.astro';
71
75
 
72
- import Block from '~/components/Block/index.astro';
73
- import InlineItem from '~/components/InlineItem/index.astro';
74
- import ItemLink from '~/components/ItemLink/index.astro';
76
+ import InlineTeamMember from '~/components/InlineTeamMember/index.astro';
77
+ import LinkToTeamMember from '~/components/LinkToTeamMember/index.astro';
75
78
 
76
79
  const query = gql`
77
80
  query {
@@ -79,32 +82,27 @@ const query = gql`
79
82
  title
80
83
  content {
81
84
  value
82
- links {
83
- __typename
84
- ... on TeamMemberRecord {
85
+ blocks {
86
+ ... on RecordInterface {
85
87
  id
86
- firstName
87
- slug
88
+ __typename
89
+ }
90
+ ... on CtaRecord {
91
+ label
92
+ url
93
+ }
94
+ ... on NewsletterSignupRecord {
95
+ title
88
96
  }
89
97
  }
90
- blocks {
91
- __typename
92
- ... on ImageRecord {
98
+ links {
99
+ ... on RecordInterface {
93
100
  id
94
- image {
95
- responsiveImage(imgixParams: { fit: crop, w: 300, h: 300, auto: format }) {
96
- srcSet
97
- webpSrcSet
98
- sizes
99
- src
100
- width
101
- height
102
- aspectRatio
103
- alt
104
- title
105
- base64
106
- }
107
- }
101
+ __typename
102
+ }
103
+ ... on TeamMemberRecord {
104
+ firstName
105
+ slug
108
106
  }
109
107
  }
110
108
  }
@@ -119,11 +117,16 @@ const { blogPost } = await executeQuery(query, { token: '<YOUR-API-TOKEN>' });
119
117
  <h1>{blogPost.title}</h1>
120
118
  <StructuredText
121
119
  data={blogPost.content}
122
- components={[
123
- [isInlineItem, InlineItem],
124
- [isItemLink, ItemLink],
125
- [isBlock, Block],
126
- ]}
120
+ blockComponents={{
121
+ CtaRecord: Cta,
122
+ NewsletterSignupRecord: NewsletterSignup,
123
+ }}
124
+ inlineItemComponents={{
125
+ TeamMemberRecord: InlineTeamMember,
126
+ }}
127
+ itemLinkComponents={{
128
+ TeamMemberRecord: LinkToTeamMember,
129
+ }}
127
130
  />
128
131
  </article>
129
132
  ```
@@ -135,20 +138,30 @@ const { blogPost } = await executeQuery(query, { token: '<YOUR-API-TOKEN>' });
135
138
  - For `heading` nodes, you might want to add an anchor;
136
139
  - For `code` nodes, you might want to use a custom syntax highlighting component;
137
140
 
138
- In this case, you can easily override default rendering rules with the `components` props.
141
+ In this case, you can easily override default rendering rules with the `overrides` prop.
139
142
 
140
143
  ```astro
141
144
  ---
142
145
  import { isHeading } from 'datocms-structured-text-utils';
143
146
  import HeadingWithAnchorLink from '~/components/HeadingWithAnchorLink/index.astro';
147
+ import Code from '~/components/Code/index.astro';
144
148
  ---
145
149
 
146
- <StructuredText data={blogPost.content} components={[[isHeading, HeadingWithAnchorLink]]} />
150
+ <StructuredText
151
+ data={blogPost.content}
152
+ components={{
153
+ heading: HeadingWithAnchorLink,
154
+ code: Code,
155
+ }}
156
+ />
147
157
  ```
148
158
 
149
159
  ## Props
150
160
 
151
- | prop | type | required | description | default |
152
- | ---------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------- |
153
- | data | `StructuredText \| DastNode` | :white_check_mark: | The actual [field value](https://www.datocms.com/docs/structured-text/dast) you get from DatoCMS | |
154
- | components | [`PredicateComponentTuple[] \| null`](https://github.com/datocms/astro-datocms/blob/main/src/StructuredText/types.ts#L6) | Only required if data contain `block`, `inline_item` or `item_link` nodes | Array of tuples formed by a predicate function and custom component | `[]` |
161
+ | prop | type | required | description |
162
+ | -------------------- | -------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
163
+ | data | `StructuredText \| DastNode` | :white_check_mark: | The actual [field value](https://www.datocms.com/docs/structured-text/dast) you get from DatoCMS |
164
+ | blockComponents | `Record<string, AstroComponent>` | | An object in which the keys are the `__typename` of the blocks to be rendered, and the values are the Astro components |
165
+ | itemLinkComponents | `Record<string, AstroComponent>` | | An object in which the keys are the `__typename` of the records to be rendered, and the values are the Astro components |
166
+ | inlineItemComponents | `Record<string, AstroComponent>` | | An object in which the keys are the `__typename` of the records to be rendered, and the values are the Astro components |
167
+ | overrides | `Record<string, AstroComponent>` | | An object in which the keys are the types of DAST nodes to override, and the values are the Astro components |
@@ -1,25 +1,42 @@
1
1
  ---
2
2
  import {
3
- type StructuredText,
3
+ type Node as DastNode,
4
4
  type Document,
5
+ type Record as DatocmsRecord,
6
+ type StructuredText,
7
+ isDocument,
8
+ isNode,
5
9
  isStructuredText,
6
10
  } from 'datocms-structured-text-utils';
7
11
 
8
12
  import Node from './Node.astro';
9
- import type { PredicateComponentTuple } from './types';
13
+ import type { BlockComponents, InlineItemComponents, ItemLinkComponents, Overrides } from './types';
14
+
15
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
16
+ interface Props<R1 extends DatocmsRecord = any, R2 extends DatocmsRecord = any> {
17
+ data: StructuredText<R1, R2> | Document | DastNode | null | undefined;
10
18
 
11
- interface Props {
12
- data: StructuredText | Document | null;
13
- components?: PredicateComponentTuple[];
19
+ blockComponents?: BlockComponents<R1, R2>;
20
+ itemLinkComponents?: ItemLinkComponents<R1, R2>;
21
+ inlineItemComponents?: InlineItemComponents<R1, R2>;
22
+ overrides?: Overrides;
14
23
  }
15
24
 
16
- const { data, components = [] } = Astro.props;
25
+ const { data, ...rest } = Astro.props;
17
26
 
18
- const node = data != null && (isStructuredText(data) ? (data.value as Document) : data).document;
27
+ const node = !data
28
+ ? null
29
+ : isStructuredText(data) && isDocument(data.value)
30
+ ? data.value.document
31
+ : isDocument(data)
32
+ ? data.document
33
+ : isNode(data)
34
+ ? data
35
+ : undefined;
19
36
 
20
37
  const blocks = isStructuredText(data) ? data?.blocks : undefined;
21
38
 
22
39
  const links = isStructuredText(data) ? data?.links : undefined;
23
40
  ---
24
41
 
25
- {node && <Node {node} {blocks} {links} {components} />}
42
+ {node && <Node {node} {blocks} {links} {...rest} />}
@@ -0,0 +1,27 @@
1
+ ---
2
+ import {
3
+ type Record as DatocmsRecord,
4
+ RenderError,
5
+ type Block,
6
+ } from 'datocms-structured-text-utils';
7
+ import type { BlockComponents } from '../types';
8
+
9
+ interface Props {
10
+ node: Block;
11
+ block: DatocmsRecord;
12
+ blockComponents?: BlockComponents<DatocmsRecord, DatocmsRecord>;
13
+ }
14
+
15
+ const { node, block, blockComponents } = Astro.props;
16
+
17
+ const Component = blockComponents?.[block.__typename];
18
+
19
+ if (!Component) {
20
+ throw new RenderError(
21
+ `The Structured Text document contains a 'block' node, but no component for rendering it is specified in blockComponents prop!`,
22
+ node,
23
+ );
24
+ }
25
+ ---
26
+
27
+ <Component block={block} />
@@ -0,0 +1,27 @@
1
+ ---
2
+ import {
3
+ type Record as DatocmsRecord,
4
+ RenderError,
5
+ type InlineItem,
6
+ } from 'datocms-structured-text-utils';
7
+ import type { InlineItemComponents } from '../types';
8
+
9
+ interface Props {
10
+ node: InlineItem;
11
+ record: DatocmsRecord;
12
+ inlineItemComponents?: InlineItemComponents<DatocmsRecord, DatocmsRecord>;
13
+ }
14
+
15
+ const { node, record, inlineItemComponents } = Astro.props;
16
+
17
+ const Component = inlineItemComponents?.[record.__typename];
18
+
19
+ if (!Component) {
20
+ throw new RenderError(
21
+ `The Structured Text document contains an 'inlineItem' node, but no component for rendering it is specified in the inlineItemComponents prop!`,
22
+ node,
23
+ );
24
+ }
25
+ ---
26
+
27
+ <Component {record} />
@@ -0,0 +1,34 @@
1
+ ---
2
+ import {
3
+ type Record as DatocmsRecord,
4
+ RenderError,
5
+ type ItemLink,
6
+ } from 'datocms-structured-text-utils';
7
+
8
+ import type { ItemLinkComponents } from '../types';
9
+
10
+ import { defaultMetaTransformer } from 'datocms-structured-text-generic-html-renderer';
11
+
12
+ interface Props {
13
+ node: ItemLink;
14
+ record: DatocmsRecord;
15
+ itemLinkComponents?: ItemLinkComponents<DatocmsRecord, DatocmsRecord>;
16
+ }
17
+
18
+ const { node, record, itemLinkComponents } = Astro.props;
19
+
20
+ const Component = itemLinkComponents?.[record.__typename];
21
+
22
+ if (!Component) {
23
+ throw new RenderError(
24
+ `The Structured Text document contains an 'itemLink' node, but no component for rendering it is specified in the itemLinkComponents prop!`,
25
+ node,
26
+ );
27
+ }
28
+
29
+ const { meta } = node;
30
+
31
+ const attrs = meta ? defaultMetaTransformer({ node, meta }) : {};
32
+ ---
33
+
34
+ <Component {node} {record} {attrs} />
@@ -1,6 +1,21 @@
1
- import type { Node } from 'datocms-structured-text-utils';
1
+ import type { Record as DatocmsRecord, NodeType } from 'datocms-structured-text-utils';
2
2
 
3
3
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
4
4
  type AstroComponent = (props: any) => any;
5
5
 
6
- export type PredicateComponentTuple = [(n: Node) => boolean, AstroComponent];
6
+ export type BlockComponents<R1 extends DatocmsRecord, R2 extends DatocmsRecord> = Record<
7
+ R1['__typename'],
8
+ AstroComponent
9
+ >;
10
+
11
+ export type ItemLinkComponents<R1 extends DatocmsRecord, R2 extends DatocmsRecord> = Record<
12
+ R2['__typename'],
13
+ AstroComponent
14
+ >;
15
+
16
+ export type InlineItemComponents<R1 extends DatocmsRecord, R2 extends DatocmsRecord> = Record<
17
+ R2['__typename'],
18
+ AstroComponent
19
+ >;
20
+
21
+ export type Overrides = Record<NodeType, AstroComponent>;