@datocms/astro 0.1.7 → 0.1.9

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.1.7",
5
+ "version": "0.1.9",
6
6
  "sideEffects": false,
7
7
  "repository": {
8
8
  "type": "git",
@@ -21,9 +21,10 @@
21
21
  "ui"
22
22
  ],
23
23
  "exports": {
24
- ".": "./src/index.ts",
24
+ "./useVideoPlayer": "./src/useVideoPlayer/index.ts",
25
25
  "./VideoPlayer": "./src/VideoPlayer/index.ts",
26
- "./Image": "./src/Image/index.ts"
26
+ "./Image": "./src/Image/index.ts",
27
+ "./StructuredText": "./src/StructuredText/index.ts"
27
28
  },
28
29
  "engines": {
29
30
  "node": ">=18.0.0"
@@ -31,6 +32,7 @@
31
32
  "files": ["src"],
32
33
  "dependencies": {
33
34
  "@mux/playback-core": "^0.25.1",
35
+ "datocms-structured-text-utils": "^4.0.1",
34
36
  "typescript": "^5.5.4"
35
37
  },
36
38
  "peerDependencies": {
@@ -1,2 +1,2 @@
1
- import Component from "./Image.astro";
2
- export default Component;
1
+ import Image from "./Image.astro";
2
+ export { Image };
@@ -0,0 +1,18 @@
1
+ ---
2
+ interface Props {
3
+ lines: string[];
4
+ }
5
+
6
+ const { lines = [] } = Astro.props;
7
+
8
+ const [first, ...rest] = lines;
9
+ ---
10
+
11
+ {first}{
12
+ rest.length > 0 && (
13
+ <>
14
+ <br />
15
+ <Astro.self lines={rest} />
16
+ </>
17
+ )
18
+ }
@@ -0,0 +1,81 @@
1
+ ---
2
+ import {
3
+ type Block,
4
+ type InlineItem,
5
+ type ItemLink,
6
+ type Node,
7
+ type StructuredText,
8
+ hasChildren,
9
+ isBlock,
10
+ isInlineItem,
11
+ isItemLink,
12
+ } from "datocms-structured-text-utils";
13
+
14
+ import {
15
+ defaultComponents,
16
+ throwRenderErrorForMissingBlock,
17
+ throwRenderErrorForMissingComponent,
18
+ throwRenderErrorForMissingLink,
19
+ } from "./utils";
20
+
21
+ import type { PredicateComponentTuple } from "./types";
22
+
23
+ interface Props {
24
+ node: Node;
25
+ blocks: StructuredText["blocks"];
26
+ links: StructuredText["links"];
27
+ components: PredicateComponentTuple[];
28
+ }
29
+
30
+ const { node, blocks, links, components = [] } = Astro.props;
31
+
32
+ const findBlock = (node: Block, blocks: StructuredText["blocks"]) =>
33
+ (blocks || []).find(({ id }) => id === node.item);
34
+
35
+ const findLink = (
36
+ node: ItemLink | InlineItem,
37
+ links: StructuredText["links"]
38
+ ) => (links || []).find(({ id }) => id === node.item);
39
+
40
+ const block =
41
+ isBlock(node) &&
42
+ (findBlock(node, blocks) || throwRenderErrorForMissingBlock(node));
43
+
44
+ const link =
45
+ (isItemLink(node) &&
46
+ (findLink(node, links) || throwRenderErrorForMissingLink(node))) ||
47
+ (isInlineItem(node) &&
48
+ (findLink(node, links) || throwRenderErrorForMissingLink(node)));
49
+
50
+ const predicateComponentTuple =
51
+ [...components, ...defaultComponents].find(([predicate, component]) =>
52
+ predicate(node)
53
+ ) || throwRenderErrorForMissingComponent(node);
54
+
55
+ const Component = (predicateComponentTuple ?? [])[1];
56
+ ---
57
+
58
+ <>
59
+ {
60
+ Component &&
61
+ (isBlock(node) ? (
62
+ <Component {node} {block} />
63
+ ) : isInlineItem(node) ? (
64
+ <Component {node} {link} />
65
+ ) : isItemLink(node) ? (
66
+ <Component {node} {link}>
67
+ {hasChildren(node) &&
68
+ node.children.map((child) => (
69
+ <svelte:self node={child} {blocks} {links} {components} />
70
+ ))}
71
+ </Component>
72
+ ) : (
73
+ <Component {node}>
74
+ {hasChildren(node) &&
75
+ node.children.map((child) => (
76
+ <svelte:self node={child} {blocks} {links} {components} />
77
+ ))}
78
+ </Component>
79
+ ))
80
+ }
81
+ </>
@@ -0,0 +1,208 @@
1
+ # Structured text
2
+
3
+ `StructuredText />` is a Svelte component that you can use to render the value contained inside a DatoCMS [Structured Text field type](https://www.datocms.com/docs/structured-text/dast).
4
+
5
+ ### Table of contents
6
+
7
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
8
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
9
+
10
+ - [Setup](#setup)
11
+ - [Basic usage](#basic-usage)
12
+ - [Customization](#customization)
13
+ - [Custom components for blocks](#custom-components-for-blocks)
14
+ - [Override default rendering of nodes](#override-default-rendering-of-nodes)
15
+ - [Props](#props)
16
+
17
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
18
+
19
+ ### Setup
20
+
21
+ Import the component like this:
22
+
23
+ ```js
24
+ import { StructuredText } from '@datocms/svelte';
25
+ ```
26
+
27
+ ## Basic usage
28
+
29
+ ```svelte
30
+ <script>
31
+
32
+ import { onMount } from 'svelte';
33
+
34
+ import { StructuredText } from '@datocms/svelte';
35
+
36
+ const query = `
37
+ query {
38
+ blogPost {
39
+ title
40
+ content {
41
+ value
42
+ }
43
+ }
44
+ }
45
+ `;
46
+
47
+ export let data = null;
48
+
49
+ onMount(async () => {
50
+ const response = await fetch('https://graphql.datocms.com/', {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/json',
54
+ Authorization: "Bearer AN_API_TOKEN",
55
+ },
56
+ body: JSON.stringify({ query })
57
+ })
58
+
59
+ const json = await response.json()
60
+
61
+ data = json.data;
62
+ });
63
+
64
+ </script>
65
+
66
+ <article>
67
+ {#if data}
68
+ <h1>{{ data.blogPost.title }}</h1>
69
+ <StructuredText data={data.blogPost.content} />
70
+ {/if}
71
+ </article>
72
+ ```
73
+
74
+ ## Customization
75
+
76
+ The `<StructuredText />` component comes with a set of default components that are use to render all the nodes present in [DatoCMS Dast trees](https://www.datocms.com/docs/structured-text/dast). These default components are enough to cover most of the simple cases.
77
+
78
+ You need to use custom components in the following cases:
79
+
80
+ - 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;
81
+ - you need to render a conventional node differently (e.g. you may want a custom render for blockquotes)
82
+
83
+ ### Custom components for blocks
84
+
85
+ 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.
86
+
87
+ ```svelte
88
+ <script>
89
+
90
+ import { onMount } from 'svelte';
91
+
92
+ import { isBlock, isInlineItem, isItemLink } from 'datocms-structured-text-utils';
93
+
94
+ import { StructuredText } from '@datocms/svelte';
95
+
96
+ import Block from './Block.svelte';
97
+ import InlineItem from './InlineItem.svelte';
98
+ import ItemLink from './ItemLink.svelte';
99
+
100
+ const query = `
101
+ query {
102
+ blogPost {
103
+ title
104
+ content {
105
+ value
106
+ links {
107
+ __typename
108
+ ... on TeamMemberRecord {
109
+ id
110
+ firstName
111
+ slug
112
+ }
113
+ }
114
+ blocks {
115
+ __typename
116
+ ... on ImageRecord {
117
+ id
118
+ image {
119
+ responsiveImage(
120
+ imgixParams: { fit: crop, w: 300, h: 300, auto: format }
121
+ ) {
122
+ srcSet
123
+ webpSrcSet
124
+ sizes
125
+ src
126
+ width
127
+ height
128
+ aspectRatio
129
+ alt
130
+ title
131
+ base64
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ `;
140
+
141
+ export let data = null;
142
+
143
+ onMount(async () => {
144
+ const response = await fetch('https://graphql.datocms.com/', {
145
+ method: 'POST',
146
+ headers: {
147
+ 'Content-Type': 'application/json',
148
+ Authorization: "Bearer AN_API_TOKEN",
149
+ },
150
+ body: JSON.stringify({ query })
151
+ })
152
+
153
+ const json = await response.json()
154
+
155
+ data = json.data;
156
+ });
157
+
158
+ </script>
159
+
160
+ <article>
161
+ {#if data}
162
+ <h1>{{ data.blogPost.title }}</h1>
163
+ <datocms-structured-text
164
+ data={data.blogPost.content}
165
+ components={[
166
+ [isInlineItem, InlineItem],
167
+ [isItemLink, ItemLink],
168
+ [isBlock, Block]
169
+ ]}
170
+ />
171
+ {/if}
172
+ </article>
173
+ ```
174
+
175
+ ### Override default rendering of nodes
176
+
177
+ `<StructuredText />` automatically renders all nodes (except for `inline_item`, `item_link` and `block`) using a set of default components, that you might want to customize. For example:
178
+
179
+ - For `heading` nodes, you might want to add an anchor;
180
+ - For `code` nodes, you might want to use a custom syntax highlighting component;
181
+
182
+ In this case, you can easily override default rendering rules with the `components` props. See test fixtures for example implementations of custom components (e.g. [this special heading component](https://github.com/datocms/datocms-svelte/blob/main/src/lib/components/StructuredText/__tests__/__fixtures__/IncreasedLevelHeading.svelte)).
183
+
184
+ ```svelte
185
+ <script>
186
+ import { isHeading, isCode } from 'datocms-structured-text-utils';
187
+
188
+ import Heading from './Heading.svelte';
189
+ import Code from './Code.svelte';
190
+
191
+ export let data;
192
+ </script>
193
+
194
+ <StructuredText
195
+ data={data.blogPost.content}
196
+ components={[
197
+ [isHeading, Heading],
198
+ [isCode, Code]
199
+ ]}
200
+ />
201
+ ```
202
+
203
+ ## Props
204
+
205
+ | prop | type | required | description | default |
206
+ | ---------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------- |
207
+ | data | `StructuredText \| DastNode` | :white_check_mark: | The actual [field value](https://www.datocms.com/docs/structured-text/dast) you get from DatoCMS | |
208
+ | components | [`PredicateComponentTuple[] \| null`](https://github.com/datocms/datocms-svelte/blob/main/src/lib/index.ts) | Only required if data contain `block`, `inline_item` or `item_link` nodes | Array of tuples formed by a predicate function and custom component | `[]` |
@@ -0,0 +1,27 @@
1
+ ---
2
+ import {
3
+ type StructuredText,
4
+ type Document,
5
+ isStructuredText,
6
+ } from "datocms-structured-text-utils";
7
+
8
+ import Node from "./Node.astro";
9
+ import type { PredicateComponentTuple } from "./types";
10
+
11
+ interface Props {
12
+ data: StructuredText | Document | null;
13
+ components: PredicateComponentTuple[];
14
+ }
15
+
16
+ const { data, components } = Astro.props;
17
+
18
+ const node =
19
+ data != null &&
20
+ (isStructuredText(data) ? (data.value as Document) : data).document;
21
+
22
+ const blocks = isStructuredText(data) ? data?.blocks : undefined;
23
+
24
+ const links = isStructuredText(data) ? data?.links : undefined;
25
+ ---
26
+
27
+ {node && <Node {node} {blocks} {links} {components} />}
@@ -0,0 +1,5 @@
1
+ import StructuredText from "./StructuredText.astro";
2
+
3
+ export { defaultComponents } from "./utils";
4
+
5
+ export { StructuredText };
@@ -0,0 +1,14 @@
1
+ ---
2
+ import type { Blockquote } from "datocms-structured-text-utils";
3
+
4
+ interface Props {
5
+ node: Blockquote;
6
+ }
7
+
8
+ const { node } = Astro.props;
9
+ ---
10
+
11
+ <blockquote>
12
+ <slot />
13
+ <footer>{node.attribution}</footer>
14
+ </blockquote>
@@ -0,0 +1,13 @@
1
+ ---
2
+ import type { Code } from "datocms-structured-text-utils";
3
+
4
+ interface Props {
5
+ node: Code;
6
+ }
7
+
8
+ const { node } = Astro.props;
9
+
10
+ const { code, language } = node;
11
+ ---
12
+
13
+ <pre class={language}>{code}</pre>
@@ -0,0 +1,14 @@
1
+ ---
2
+ import type { Heading } from "datocms-structured-text-utils";
3
+
4
+ interface Props {
5
+ node: Heading;
6
+ }
7
+
8
+ const { node } = Astro.props;
9
+
10
+ const { level = 1 } = node;
11
+ const Element = `h${level}`;
12
+ ---
13
+
14
+ <Element><slot /></Element>
@@ -0,0 +1,29 @@
1
+ ---
2
+ import type { Link } from "datocms-structured-text-utils";
3
+
4
+ interface Props {
5
+ node: Link;
6
+ }
7
+
8
+ const { node } = Astro.props;
9
+
10
+ const { url, meta } = node;
11
+
12
+ let target: string | undefined = undefined;
13
+
14
+ const targetMetaEntry = meta?.find((metaEntry) => metaEntry.id === "target");
15
+
16
+ if (targetMetaEntry?.value) {
17
+ target = targetMetaEntry.value;
18
+ }
19
+
20
+ let rel: string | undefined = undefined;
21
+
22
+ const relMetaEntry = meta?.find((metaEntry) => metaEntry.id === "rel");
23
+
24
+ if (relMetaEntry?.value) {
25
+ rel = relMetaEntry.value;
26
+ }
27
+ ---
28
+
29
+ <a href={url} {target} {rel}><slot /></a>
@@ -0,0 +1,21 @@
1
+ ---
2
+ import type { List } from "datocms-structured-text-utils";
3
+
4
+ interface Props {
5
+ node: List;
6
+ }
7
+
8
+ const { node } = Astro.props;
9
+ ---
10
+
11
+ {
12
+ node.style === "numbered" ? (
13
+ <ol>
14
+ <slot />
15
+ </ol>
16
+ ) : (
17
+ <ul>
18
+ <slot />
19
+ </ul>
20
+ )
21
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ import type { ListItem } from "datocms-structured-text-utils";
3
+
4
+ interface Props {
5
+ node: ListItem;
6
+ }
7
+ ---
8
+
9
+ <li><slot /></li>
@@ -0,0 +1,9 @@
1
+ ---
2
+ import type { Paragraph } from "datocms-structured-text-utils";
3
+
4
+ interface Props {
5
+ node: Paragraph;
6
+ }
7
+ ---
8
+
9
+ <p><slot /></p>
@@ -0,0 +1,9 @@
1
+ ---
2
+ import type { Root } from "datocms-structured-text-utils";
3
+
4
+ interface Props {
5
+ node: Root;
6
+ }
7
+ ---
8
+
9
+ <slot />
@@ -0,0 +1,56 @@
1
+ ---
2
+ import type { Span } from "datocms-structured-text-utils";
3
+ import Lines from "../utils/Lines.astro";
4
+
5
+ interface Props {
6
+ node: Span;
7
+ }
8
+
9
+ const { node } = Astro.props;
10
+
11
+ const { type, value, marks } = node;
12
+
13
+ const [mark, ...otherMarks] = marks ?? [];
14
+ ---
15
+
16
+ {
17
+ !mark ? (
18
+ <Lines lines={node.value.split(/\n/)} />
19
+ ) : mark === "emphasis" ? (
20
+ <em>
21
+ <Astro.self node={{ type, value, marks: otherMarks }}>
22
+ <slot />
23
+ </Astro.self>
24
+ </em>
25
+ ) : mark === "highlight" ? (
26
+ <mark>
27
+ <Astro.self node={{ type, value, marks: otherMarks }}>
28
+ <slot />
29
+ </Astro.self>
30
+ </mark>
31
+ ) : mark === "strikethrough" ? (
32
+ <del>
33
+ <Astro.self node={{ type, value, marks: otherMarks }}>
34
+ <slot />
35
+ </Astro.self>
36
+ </del>
37
+ ) : mark === "strong" ? (
38
+ <strong>
39
+ <Astro.self node={{ type, value, marks: otherMarks }}>
40
+ <slot />
41
+ </Astro.self>
42
+ </strong>
43
+ ) : mark === "underline" ? (
44
+ <u>
45
+ <Astro.self node={{ type, value, marks: otherMarks }}>
46
+ <slot />
47
+ </Astro.self>
48
+ </u>
49
+ ) : mark === "code" ? (
50
+ <code>
51
+ <Astro.self node={{ type, value, marks: otherMarks }}>
52
+ <slot />
53
+ </Astro.self>
54
+ </code>
55
+ ) : undefined
56
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ import type { ThematicBreak } from "datocms-structured-text-utils";
3
+
4
+ interface Props {
5
+ node: ThematicBreak;
6
+ }
7
+ ---
8
+
9
+ <hr />
@@ -0,0 +1,6 @@
1
+ import type { Node } from "datocms-structured-text-utils";
2
+
3
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
4
+ type AstroComponent = (props: any) => any;
5
+
6
+ export type PredicateComponentTuple = [(n: Node) => boolean, AstroComponent];
@@ -0,0 +1,92 @@
1
+ import {
2
+ type Block,
3
+ type InlineItem,
4
+ type ItemLink,
5
+ type Node,
6
+ RenderError,
7
+ type StructuredText,
8
+ isBlock,
9
+ isBlockquote,
10
+ isCode,
11
+ isHeading,
12
+ isInlineItem,
13
+ isItemLink,
14
+ isLink,
15
+ isList,
16
+ isListItem,
17
+ isParagraph,
18
+ isRoot,
19
+ isSpan,
20
+ isThematicBreak,
21
+ } from "datocms-structured-text-utils";
22
+
23
+ import Blockquote from "./nodes/Blockquote.astro";
24
+ import Code from "./nodes/Code.astro";
25
+ import Heading from "./nodes/Heading.astro";
26
+ import Link from "./nodes/Link.astro";
27
+ import List from "./nodes/List.astro";
28
+ import ListItem from "./nodes/ListItem.astro";
29
+ import Paragraph from "./nodes/Paragraph.astro";
30
+ import Root from "./nodes/Root.astro";
31
+ import Span from "./nodes/Span.astro";
32
+ import ThematicBreak from "./nodes/ThematicBreak.astro";
33
+
34
+ import type { PredicateComponentTuple } from "./types";
35
+
36
+ export const defaultComponents: PredicateComponentTuple[] = [
37
+ [isParagraph, Paragraph],
38
+ [isRoot, Root],
39
+ [isSpan, Span],
40
+ [isLink, Link],
41
+ [isList, List],
42
+ [isHeading, Heading],
43
+ [isBlockquote, Blockquote],
44
+ [isListItem, ListItem],
45
+ [isThematicBreak, ThematicBreak],
46
+ [isCode, Code],
47
+ ];
48
+
49
+ export const throwRenderErrorForMissingComponent = (node: Node) => {
50
+ if (isInlineItem(node)) {
51
+ throw new RenderError(
52
+ `The Structured Text document contains an 'inlineItem' node, but no component for rendering is specified!`,
53
+ node,
54
+ );
55
+ }
56
+
57
+ if (isItemLink(node)) {
58
+ throw new RenderError(
59
+ `The Structured Text document contains an 'itemLink' node, but no component for rendering is specified!`,
60
+ node,
61
+ );
62
+ }
63
+
64
+ if (isBlock(node)) {
65
+ throw new RenderError(
66
+ `The Structured Text document contains a 'block' node, but no component for rendering is specified!`,
67
+ node,
68
+ );
69
+ }
70
+ };
71
+
72
+ export const throwRenderErrorForMissingBlock = (node: Block) => {
73
+ throw new RenderError(
74
+ `The Structured Text document contains a 'block' node, but cannot find a record with ID ${node.item} inside data.blocks!`,
75
+ node,
76
+ );
77
+ };
78
+
79
+ export const throwRenderErrorForMissingLink = (node: ItemLink | InlineItem) => {
80
+ throw new RenderError(
81
+ `The Structured Text document contains an 'itemLink' node, but cannot find a record with ID ${node.item} inside data.links!`,
82
+ node,
83
+ );
84
+ };
85
+
86
+ export const findBlock = (node: Block, blocks: StructuredText["blocks"]) =>
87
+ (blocks || []).find(({ id }) => id === node.item);
88
+
89
+ export const findLink = (
90
+ node: ItemLink | InlineItem,
91
+ links: StructuredText["links"],
92
+ ) => (links || []).find(({ id }) => id === node.item);
@@ -1,2 +1,2 @@
1
- import Component from "./VideoPlayer.astro";
2
- export default Component;
1
+ import VideoPlayer from "./VideoPlayer.astro";
2
+ export { VideoPlayer };
package/src/index.ts DELETED
@@ -1,4 +0,0 @@
1
- export { default as Image } from "./Image";
2
- export { useVideoPlayer } from './useVideoPlayer';
3
- export { default as VideoPlayer } from "./VideoPlayer";
4
-