@portabletext/astro 0.0.0 → 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # @portabletext/astro
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#4](https://github.com/portabletext/astro-portabletext/pull/4) [`0fd7cd3`](https://github.com/portabletext/astro-portabletext/commit/0fd7cd372df10919c6ff88d7533f4f7a0293a42e) Thanks [@msfragala](https://github.com/msfragala)! - Allow `PortableText` slots to be scoped to a specific type with `nodeType:type`, e.g. `block:h1`, `mark:link`, `list:bullet`, `listItem:number` and `type:myCustomType`. A scoped slot takes precedence over the node type it belongs to.
8
+
9
+ Slots now also take precedence over `render` from `usePortableText`. Previously a component reached through the `components` prop could displace a slot by rendering its children with `render`, so a slot for that node type never applied.
10
+
11
+ `Component` is now resolved lazily, so a slot that renders its own markup no longer reports a missing component. This makes a scoped slot enough on its own to render a custom type, which has no default component.
12
+
13
+ ## 0.1.0
14
+
15
+ ### Minor Changes
16
+
17
+ - [#1](https://github.com/portabletext/astro-portabletext/pull/1) [`e3997e2`](https://github.com/portabletext/astro-portabletext/commit/e3997e216c13cd9da693e924c674cfed52a2d002) Thanks [@stipsan](https://github.com/stipsan)! - Initial release of `@portabletext/astro`, forked from [`astro-portabletext`](https://github.com/theisel/astro-portabletext) by Tom Theisel
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License (ISC)
2
+
3
+ Copyright 2022 - CURRENT Tom Theisel <tom.theisel@gmail.com>
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
11
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
13
+ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
14
+ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
15
+ THIS SOFTWARE.
package/README.md CHANGED
@@ -1,45 +1,356 @@
1
1
  # @portabletext/astro
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ [![npm version](https://img.shields.io/npm/v/@portabletext/astro.svg?style=flat-square)](https://www.npmjs.com/package/@portabletext/astro)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@portabletext/astro.svg?style=flat-square)](https://www.npmjs.com/package/@portabletext/astro)
5
+ ![license](https://img.shields.io/npm/l/@portabletext/astro?style=flat-square)
4
6
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
7
+ Render [Portable Text](https://portabletext.org) with [Astro](https://astro.build).
6
8
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
9
+ > [!NOTE]
10
+ > This package is a fork of [`astro-portabletext`](https://github.com/theisel/astro-portabletext) by [Tom Theisel](https://github.com/theisel), maintained under the [`@portabletext`](https://github.com/portabletext) organization. All credit for the original design and implementation goes to Tom. It remains distributed under the original [ISC License](./LICENSE). See [Migrating from `astro-portabletext`](#migrating-from-astro-portabletext).
8
11
 
9
- ## Purpose
12
+ ## Table of contents
10
13
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@portabletext/astro`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
14
+ - [Installation](#installation)
15
+ - [Basic usage](#basic-usage)
16
+ - [Sanity integration](#sanity-integration)
17
+ - [Customizing components](#customizing-components)
18
+ - [Default components](#default-components)
19
+ - [Custom components](#custom-components)
20
+ - [Slots](#slots)
21
+ - [`PortableText` component properties](#portabletext-component-properties)
22
+ - [Utility functions](#utility-functions)
23
+ - [`usePortableText`](#useportabletext)
24
+ - [`mergeComponents`](#mergecomponents)
25
+ - [`toPlainText`](#toplaintext)
26
+ - [Migrating from `astro-portabletext`](#migrating-from-astro-portabletext)
27
+ - [License](#license)
15
28
 
16
- ## What is OIDC Trusted Publishing?
29
+ ## Installation
17
30
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
31
+ ```bash
32
+ npm install @portabletext/astro
33
+ ```
19
34
 
20
- ## Setup Instructions
35
+ > **Prerequisites:** Astro v4.6 or newer.
21
36
 
22
- To properly configure OIDC trusted publishing for this package:
37
+ ## Basic usage
23
38
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
39
+ Import the `PortableText` component and pass it a Portable Text value. The library provides sensible defaults for rendering all standard Portable Text elements, which you can override.
28
40
 
29
- ## DO NOT USE THIS PACKAGE
41
+ ```astro
42
+ ---
43
+ import {PortableText} from '@portabletext/astro'
44
+
45
+ const portableText = [
46
+ {
47
+ _type: 'block',
48
+ style: 'normal',
49
+ markDefs: [],
50
+ children: [
51
+ {_type: 'span', marks: [], text: 'This is a '},
52
+ {_type: 'span', marks: ['strong'], text: 'bold'},
53
+ {_type: 'span', marks: [], text: ' text example!'},
54
+ ],
55
+ },
56
+ ]
57
+ ---
58
+
59
+ <PortableText value={portableText} />
60
+ ```
61
+
62
+ ## Sanity integration
63
+
64
+ This library's predecessor is [officially recommended](https://www.sanity.io/plugins/sanity-astro#rendering-rich-text-and-block-content-with-portable-text) by [Sanity](https://sanity.io) for rendering Portable Text in Astro projects. Helpful resources:
65
+
66
+ - [Sanity integration for Astro](https://www.sanity.io/plugins/sanity-astro)
67
+ - [Guide: building a blog with Sanity and Astro](https://www.sanity.io/guides/sanity-astro-blog)
68
+
69
+ ## Customizing components
70
+
71
+ ### Default components
72
+
73
+ Default components are provided for all standard features of the Portable Text spec, with logical HTML defaults. Provided components are merged with the defaults, so you only need to provide the things you want to override.
74
+
75
+ <details>
76
+ <summary>View the default structure and output</summary>
77
+
78
+ ```js
79
+ {
80
+ type: {
81
+ /* Custom types go here */
82
+ },
83
+ block: {
84
+ h1: /* <h1 {...attrs}><slot /></h1> */,
85
+ h2: /* <h2 {...attrs}><slot /></h2> */,
86
+ h3: /* <h3 {...attrs}><slot /></h3> */,
87
+ h4: /* <h4 {...attrs}><slot /></h4> */,
88
+ h5: /* <h5 {...attrs}><slot /></h5> */,
89
+ h6: /* <h6 {...attrs}><slot /></h6> */,
90
+ blockquote: /* <blockquote {...attrs}><slot /></blockquote> */,
91
+ normal: /* <p {...attrs}><slot /></p> */
92
+ },
93
+ list: {
94
+ bullet: /* <ul {...attrs}><slot /></ul> */,
95
+ number: /* <ol {...attrs}><slot /></ol> */,
96
+ menu: /* <menu {...attrs}><slot /></menu> */,
97
+ },
98
+ listItem: {
99
+ bullet: /* <li {...attrs}><slot /></li> */,
100
+ number: /* <li {...attrs}><slot /></li> */,
101
+ menu: /* <li {...attrs}><slot /></li> */,
102
+ },
103
+ mark: {
104
+ code: /* <code {...attrs}><slot /></code> */,
105
+ em: /* <em {...attrs}><slot /></em> */,
106
+ link: /* <a {...attrs} href="..."><slot /></a> */,
107
+ 'strike-through': /* <del {...attrs}><slot /></del> */,
108
+ strong: /* <strong {...attrs}><slot /></strong> */,
109
+ underline: /* <span {...attrs} style="text-decoration: underline;"><slot /></span> */
110
+ },
111
+ text: /* Renders plain text */,
112
+ hardBreak: /* <br /> */,
113
+ }
114
+ ```
115
+
116
+ </details>
117
+
118
+ ### Custom components
119
+
120
+ Custom components give you control over how each node is rendered. Map a component to a whole node type, or to a specific property (style, mark type, list item type, etc.) of that node type.
121
+
122
+ ```astro
123
+ ---
124
+ import {PortableText} from '@portabletext/astro'
125
+ import Code from '../components/Code.astro'
126
+ import Link from '../components/Link.astro'
127
+
128
+ const portableText = [
129
+ // ... your Portable Text content
130
+ ]
131
+
132
+ const components = {
133
+ // Custom object types, keyed by `_type` (or a single component for all types)
134
+ type: {code: Code},
135
+ // Block styles, keyed by `style`
136
+ block: {/* h1, h2, normal, ... */},
137
+ // Lists, keyed by `listItem`
138
+ list: {/* bullet, number, ... */},
139
+ // List items, keyed by `listItem`
140
+ listItem: {/* bullet, number, ... */},
141
+ // Marks (decorators and annotations), keyed by mark type
142
+ mark: {link: Link},
143
+ // Fallbacks for unknown nodes
144
+ unknownType: undefined,
145
+ unknownBlock: undefined,
146
+ unknownList: undefined,
147
+ unknownListItem: undefined,
148
+ unknownMark: undefined,
149
+ // Plain text spans and hard breaks
150
+ text: undefined,
151
+ hardBreak: undefined,
152
+ }
153
+ ---
154
+
155
+ <PortableText value={portableText} components={components} />
156
+ ```
157
+
158
+ Each custom component receives `node`, `index` and `isInline` props, and renders any children through a `<slot />`. For example, a custom `link` mark:
159
+
160
+ ```astro
161
+ ---
162
+ import type {MarkProps} from '@portabletext/astro/types'
163
+
164
+ export type Props = MarkProps<{href?: string}>
165
+
166
+ const {node} = Astro.props
167
+ const href = node.markDef?.href
168
+ ---
169
+
170
+ <a href={href}><slot /></a>
171
+ ```
172
+
173
+ ### Slots
174
+
175
+ Slots provide a flexible way to enhance rendering by passing additional props to the resolved component - for example applying custom classes or wrapping elements - without replacing the default component entirely.
176
+
177
+ ```astro
178
+ ---
179
+ import {PortableText} from '@portabletext/astro'
180
+
181
+ const portableText = [
182
+ // ... your Portable Text content
183
+ ]
184
+ ---
185
+
186
+ <PortableText value={portableText}>
187
+ <fragment slot="mark">
188
+ {({Component, props, children}) => (
189
+ <Component {...props} class="mark">{children}</Component>
190
+ )}
191
+ </fragment>
192
+ </PortableText>
193
+
194
+ <style>
195
+ .mark:where(strong) {
196
+ /* some styles */
197
+ }
198
+ </style>
199
+ ```
30
200
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
201
+ A slot named after a node type applies to every node of that type: `type`, `block`, `list`, `listItem`, `mark`, `text` and `hardBreak`. To target a single block style, list type, mark type or custom type, scope the slot name with `nodeType:type`.
36
202
 
37
- ## More Information
203
+ ```astro
204
+ <PortableText value={portableText}>
205
+ <fragment slot="block:h1">
206
+ {({Component, props, children}) => (
207
+ <Component {...props} class="heading">{children}</Component>
208
+ )}
209
+ </fragment>
210
+ <fragment slot="mark:link">
211
+ {({Component, props, children}) => (
212
+ <Component {...props} rel="noopener">{children}</Component>
213
+ )}
214
+ </fragment>
215
+ <fragment slot="type:callout">
216
+ {({props}) => <aside class="callout">{props.node.text}</aside>}
217
+ </fragment>
218
+ </PortableText>
219
+ ```
38
220
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
221
+ A slot only has a `Component` to render when the node type has one. Standard block styles, list types and mark types all ship defaults, but `components.type` starts out empty - a custom type has no default component. So a slot for a custom type either renders its own markup, as `type:callout` does above, or registers a component to receive:
42
222
 
223
+ ```astro
224
+ <PortableText value={portableText} components={{type: {callout: Callout}}}>
225
+ <fragment slot="type:callout">
226
+ {({Component, props}) => <Component {...props} class="callout" />}
227
+ </fragment>
228
+ </PortableText>
229
+ ```
230
+
231
+ Asking for `Component` when the node type has none gives you the unknown-type component and reports the missing component through [`onMissingComponent`](#portabletext-component-properties). A slot that never touches `Component` renders cleanly.
232
+
233
+ A scoped slot takes precedence over the node type it belongs to, so `block` can handle every block while `block:h1` handles headings.
234
+
235
+ ```astro
236
+ <PortableText value={portableText}>
237
+ <!-- Every block except `h1` -->
238
+ <fragment slot="block">
239
+ {({Component, props, children}) => (
240
+ <Component {...props} class="block">{children}</Component>
241
+ )}
242
+ </fragment>
243
+ <fragment slot="block:h1">
244
+ {({Component, props, children}) => (
245
+ <Component {...props} class="heading">{children}</Component>
246
+ )}
247
+ </fragment>
248
+ </PortableText>
249
+ ```
250
+
251
+ The `text` and `hardBreak` node types have no types of their own, so they cannot be scoped.
252
+
253
+ #### Slots and the `components` prop
254
+
255
+ A slot does not replace the [`components`](#customizing-components) prop, it wraps it. The `Component` handed to a slot is whatever the `components` prop resolved to for that node, so the two compose - the slot decides how the resolved component is rendered.
256
+
257
+ ```astro
258
+ <PortableText value={portableText} components={{block: {h1: MyHeading}}}>
259
+ <fragment slot="block:h1">
260
+ <!-- `Component` is `MyHeading` -->
261
+ {({Component, props, children}) => (
262
+ <Component {...props} class="heading">{children}</Component>
263
+ )}
264
+ </fragment>
265
+ </PortableText>
266
+ ```
267
+
268
+ Slots belong to the author of the `PortableText` element, so they always win. A component reached through the `components` prop can customize its own children with [`render`](#useportabletext), but a slot for that node type takes precedence over it.
269
+
270
+ ```astro
271
+ <PortableText value={portableText} components={{block: MyBlock}}>
272
+ <!-- Applies even if `MyBlock` renders its children with `render({mark: ...})` -->
273
+ <fragment slot="mark">
274
+ {({Component, props, children}) => (
275
+ <Component {...props} class="mark">{children}</Component>
276
+ )}
277
+ </fragment>
278
+ </PortableText>
279
+ ```
280
+
281
+ ## `PortableText` component properties
282
+
283
+ | Property | Type | Description |
284
+ | ------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
285
+ | `value` | `array` or `object` | Portable Text payload |
286
+ | `components` (optional) | `object` | Mapping of components to node types or their properties. |
287
+ | `onMissingComponent` (optional) | `function` or `boolean` | Disable warning messages or handle unknown types. **Default** prints to console. |
288
+ | `listNestingMode` (optional) | `"html"` or `"direct"` | List nesting mode. **Default** is `html`. See [ToolkitListNestMode](https://portabletext.github.io/toolkit/types/ToolkitListNestMode.html). |
289
+
290
+ ## Utility functions
291
+
292
+ ```js
293
+ import {usePortableText, mergeComponents, toPlainText, spanToPlainText} from '@portabletext/astro'
294
+ ```
295
+
296
+ ### `usePortableText`
297
+
298
+ Within a component passed into the `components` prop, `usePortableText(node)` returns rendering utilities scoped to that node: `getDefaultComponent()`, `getUnknownComponent()` and `render()`.
299
+
300
+ ```astro
301
+ ---
302
+ import type {BlockProps} from '@portabletext/astro/types'
303
+ import {usePortableText} from '@portabletext/astro'
304
+
305
+ export type Props = BlockProps
306
+
307
+ const {node} = Astro.props
308
+ const {getDefaultComponent} = usePortableText(node)
309
+ const Default = getDefaultComponent()
310
+ ---
311
+
312
+ <Default {...Astro.props}><slot /></Default>
313
+ ```
314
+
315
+ ### `mergeComponents`
316
+
317
+ Merges two component maps, giving priority to the overrides. Useful for extending a shared base set of components.
318
+
319
+ ```js
320
+ import {mergeComponents} from '@portabletext/astro'
321
+
322
+ const components = mergeComponents(baseComponents, {
323
+ block: {h1: MyHeading},
324
+ })
325
+ ```
326
+
327
+ ### `toPlainText`
328
+
329
+ Renders one or more Portable Text blocks as a plain string - handy for meta descriptions or generating slugs. `spanToPlainText` does the same for a single span's children.
330
+
331
+ ```astro
332
+ ---
333
+ import {toPlainText} from '@portabletext/astro'
334
+
335
+ const {node} = Astro.props
336
+ const text = toPlainText(node)
43
337
  ---
338
+ ```
339
+
340
+ ## Migrating from `astro-portabletext`
341
+
342
+ `@portabletext/astro` is a drop-in fork of [`astro-portabletext`](https://github.com/theisel/astro-portabletext). To migrate, swap the dependency and update your imports:
343
+
344
+ ```diff
345
+ - import {PortableText} from 'astro-portabletext'
346
+ + import {PortableText} from '@portabletext/astro'
347
+
348
+ - import type {BlockProps} from 'astro-portabletext/types'
349
+ + import type {BlockProps} from '@portabletext/astro/types'
350
+ ```
351
+
352
+ The component API, props and utility functions are unchanged.
353
+
354
+ ## License
44
355
 
45
- **Maintained for OIDC setup purposes only**
356
+ [ISC](./LICENSE) © [Tom Theisel](https://github.com/theisel) (original author) and the Portable Text authors.
@@ -0,0 +1,36 @@
1
+ ---
2
+ import type { Block, Props as $ } from "../lib/types";
3
+ import { usePortableText } from "../lib/utils";
4
+
5
+ export type Props = $<Block>;
6
+
7
+ const props = Astro.props;
8
+ const { node, index, isInline, ...attrs } = props;
9
+ const styleIs = (style: string) => style === node.style;
10
+
11
+ const { getUnknownComponent } = usePortableText(node);
12
+
13
+ const UnknownStyle = getUnknownComponent();
14
+ ---
15
+
16
+ {
17
+ styleIs("h1") ? (
18
+ <h1 {...attrs}><slot /></h1>
19
+ ) : styleIs("h2") ? (
20
+ <h2 {...attrs}><slot /></h2>
21
+ ) : styleIs("h3") ? (
22
+ <h3 {...attrs}><slot /></h3>
23
+ ) : styleIs("h4") ? (
24
+ <h4 {...attrs}><slot /></h4>
25
+ ) : styleIs("h5") ? (
26
+ <h5 {...attrs}><slot /></h5>
27
+ ) : styleIs("h6") ? (
28
+ <h6 {...attrs}><slot /></h6>
29
+ ) : styleIs("blockquote") ? (
30
+ <blockquote {...attrs}><slot /></blockquote>
31
+ ) : styleIs("normal") ? (
32
+ <p {...attrs}><slot /></p>
33
+ ) : (
34
+ <UnknownStyle {...props}><slot /></UnknownStyle>
35
+ )
36
+ }
@@ -0,0 +1,7 @@
1
+ ---
2
+ import type { TextNode, Props as $ } from "../lib/types";
3
+
4
+ export type Props = $<TextNode>;
5
+ ---
6
+
7
+ <br />
@@ -0,0 +1,24 @@
1
+ ---
2
+ import type { List, Props as $ } from "../lib/types";
3
+
4
+ export type Props = $<List>;
5
+
6
+ const { node, index, isInline, ...attrs } = Astro.props;
7
+ const listItemIs = (listItem: string) => listItem === node.listItem;
8
+ ---
9
+
10
+ {
11
+ listItemIs("menu") ? (
12
+ <menu {...attrs}>
13
+ <slot />
14
+ </menu>
15
+ ) : listItemIs("number") ? (
16
+ <ol {...attrs}>
17
+ <slot />
18
+ </ol>
19
+ ) : (
20
+ <ul {...attrs}>
21
+ <slot />
22
+ </ul>
23
+ )
24
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ import type { ListItem, Props as $ } from "../lib/types";
3
+
4
+ export type Props = $<ListItem>;
5
+
6
+ const { node, index, isInline, ...attrs } = Astro.props;
7
+ ---
8
+
9
+ <li {...attrs}><slot /></li>
@@ -0,0 +1,32 @@
1
+ ---
2
+ import type { Mark, Props as $ } from "../lib/types";
3
+ import { usePortableText } from "../lib/utils";
4
+
5
+ export type Props = $<Mark>;
6
+
7
+ const props = Astro.props;
8
+ const { node, index, isInline, ...attrs } = props;
9
+ const markTypeIs = (markType: string) => markType === node.markType;
10
+
11
+ const { getUnknownComponent } = usePortableText(node);
12
+
13
+ const UnknownMarkType = getUnknownComponent();
14
+ ---
15
+
16
+ {
17
+ markTypeIs("code") ? (
18
+ <code {...attrs}><slot /></code>
19
+ ) : markTypeIs("em") ? (
20
+ <em {...attrs}><slot /></em>
21
+ ) : markTypeIs("link") ? (
22
+ <a href={(node as unknown as Mark<{ href: string }>).markDef.href} {...attrs}><slot /></a>
23
+ ) : markTypeIs("strike-through") ? (
24
+ <del {...attrs}><slot /></del>
25
+ ) : markTypeIs("strong") ? (
26
+ <strong {...attrs}><slot /></strong>
27
+ ) : markTypeIs("underline") ? (
28
+ <span style="text-decoration: underline;" {...attrs}><slot /></span>
29
+ ) : (
30
+ <UnknownMarkType {...props}><slot /></UnknownMarkType>
31
+ )
32
+ }