@datocms/astro 0.6.4 → 0.6.5-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/README.md CHANGED
@@ -27,6 +27,7 @@ A set of TypeScript-ready components and utilities to work faster with [DatoCMS]
27
27
 
28
28
  `@datocms/astro` contains ready-to-use Astro components and helpers:
29
29
 
30
+ - [`<ContentLink />`](src/ContentLink) for Visual Editing with click-to-edit overlays
30
31
  - [`<Image />`](src/Image)
31
32
  - [`<Seo />`](src/Seo)
32
33
  - [`<StructuredText />`](src/StructuredText)
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.6.4",
5
+ "version": "0.6.5-0",
6
6
  "sideEffects": false,
7
7
  "repository": {
8
8
  "type": "git",
@@ -34,7 +34,8 @@
34
34
  "./Image": "./src/Image/index.ts",
35
35
  "./StructuredText": "./src/StructuredText/index.ts",
36
36
  "./Seo": "./src/Seo/index.ts",
37
- "./QueryListener": "./src/QueryListener/index.ts"
37
+ "./QueryListener": "./src/QueryListener/index.ts",
38
+ "./ContentLink": "./src/ContentLink/index.ts"
38
39
  },
39
40
  "engines": {
40
41
  "node": ">=18.0.0"
@@ -44,6 +45,7 @@
44
45
  ],
45
46
  "dependencies": {
46
47
  "@0no-co/graphql.web": "^1.0.7",
48
+ "@datocms/content-link": "^0.3.7",
47
49
  "datocms-listen": "^1.0.1",
48
50
  "datocms-structured-text-generic-html-renderer": "^5.0.0",
49
51
  "datocms-structured-text-utils": "^5.1.6"
@@ -0,0 +1,159 @@
1
+ ---
2
+ /**
3
+ * ContentLink component enables Visual Editing for DatoCMS content by providing click-to-edit overlays.
4
+ *
5
+ * This component provides two powerful editing experiences:
6
+ *
7
+ * 1. **Standalone website editing**: When viewing your draft content on the website,
8
+ * editors can click on any content element to open the DatoCMS editor and modify
9
+ * that specific field. This works by detecting stega-encoded metadata in the content
10
+ * and creating interactive overlays.
11
+ *
12
+ * 2. **Web Previews plugin integration**: When your preview runs inside the Visual Editing
13
+ * mode of the DatoCMS Web Previews plugin, this component automatically establishes
14
+ * bidirectional communication with the plugin. This enables:
15
+ * - Clicking content to instantly open the correct field in the side panel
16
+ * - In-plugin navigation: users can navigate to different URLs within Visual mode
17
+ * (like a browser navigation bar), and the preview updates accordingly
18
+ * - Synchronized state between the preview and the DatoCMS interface
19
+ *
20
+ * The component handles client-side routing by:
21
+ * - Listening to navigation requests from the Web Previews plugin via `onNavigateTo`
22
+ * - Notifying the plugin when the URL changes via `setCurrentPath`
23
+ *
24
+ * This integration is completely automatic when running inside the plugin's iframe,
25
+ * with graceful fallback to opening edit URLs in a new tab when running standalone.
26
+ *
27
+ * @see https://www.npmjs.com/package/@datocms/content-link
28
+ * @see https://www.datocms.com/marketplace/plugins/i/datocms-plugin-web-previews
29
+ */
30
+
31
+ export interface Props {
32
+ /**
33
+ * Whether to enable click-to-edit overlays on mount, or options to configure them.
34
+ *
35
+ * - `true`: Enable click-to-edit mode immediately
36
+ * - `{ scrollToNearestTarget: true }`: Enable and scroll to nearest editable if none visible
37
+ * - `undefined`: Don't enable automatically (use Alt/Option key to toggle)
38
+ *
39
+ * @example
40
+ * ```astro
41
+ * <ContentLink enableClickToEdit={true} />
42
+ * <ContentLink enableClickToEdit={{ scrollToNearestTarget: true }} />
43
+ * ```
44
+ */
45
+ enableClickToEdit?: boolean | { scrollToNearestTarget?: boolean };
46
+
47
+ /**
48
+ * Whether to strip stega-encoded invisible characters from text content after stamping.
49
+ *
50
+ * - `false` (default): Stega encoding remains in the DOM (allows controller to be disposed/recreated)
51
+ * - `true`: Stega encoding is permanently removed (provides clean textContent)
52
+ *
53
+ * @default false
54
+ */
55
+ stripStega?: boolean;
56
+ }
57
+
58
+ const { enableClickToEdit, stripStega = false } = Astro.props;
59
+
60
+ // Serialize props for client-side script
61
+ const enableClickToEditValue =
62
+ enableClickToEdit === undefined
63
+ ? undefined
64
+ : typeof enableClickToEdit === 'boolean'
65
+ ? enableClickToEdit
66
+ : enableClickToEdit;
67
+ ---
68
+
69
+ <div
70
+ id="datocms-content-link-root"
71
+ data-enable-click-to-edit={enableClickToEditValue !== undefined
72
+ ? JSON.stringify(enableClickToEditValue)
73
+ : undefined}
74
+ data-strip-stega={stripStega ? 'true' : undefined}
75
+ style="display: none;"
76
+ >
77
+ </div>
78
+
79
+ <script>
80
+ import { createController } from '@datocms/content-link';
81
+ import { navigate } from 'astro:transitions/client';
82
+
83
+ let controller: ReturnType<typeof createController> | null = null;
84
+
85
+ // Read configuration from data attributes
86
+ const rootElement = document.getElementById('datocms-content-link-root');
87
+ const enableClickToEditAttr = rootElement?.getAttribute('data-enable-click-to-edit');
88
+ const stripStegaAttr = rootElement?.getAttribute('data-strip-stega');
89
+
90
+ const enableClickToEdit = enableClickToEditAttr ? JSON.parse(enableClickToEditAttr) : undefined;
91
+ const stripStega = stripStegaAttr === 'true';
92
+
93
+ // Initialize the content-link controller
94
+ function initContentLink() {
95
+ controller = createController({
96
+ // Handle navigation requests from the Web Previews plugin
97
+ // Inside Visual mode, users can navigate to different URLs (like a browser bar)
98
+ // and the plugin will request the preview to navigate accordingly
99
+ onNavigateTo: (path: string) => {
100
+ navigate(path);
101
+ },
102
+ // Strip stega encoding if requested
103
+ stripStega: stripStega,
104
+ });
105
+
106
+ // Notify the controller of the current path
107
+ controller.setCurrentPath(document.location.toString());
108
+
109
+ // Listen for Astro page-load events (fires after client-side navigation)
110
+ //
111
+ // IMPORTANT: Why this works even without <ClientRouter /> (View Transitions):
112
+ //
113
+ // The 'astro:page-load' event fires in two scenarios:
114
+ //
115
+ // 1. WITH View Transitions (<ClientRouter />):
116
+ // - Event fires on initial page load and after every client-side navigation
117
+ // - The <ClientRouter /> component enables full SPA-like routing
118
+ //
119
+ // 2. WITHOUT View Transitions:
120
+ // - Event STILL fires on initial page load if any module imports from 'astro:transitions/client'
121
+ // - The import above ('import { navigate } from astro:transitions/client') triggers
122
+ // the module's initialization code, which registers a 'load' event listener that
123
+ // dispatches 'astro:page-load' on page load
124
+ // - The navigate() function will work but falls back to full page reload
125
+ // (it detects no view transitions are enabled and uses location.href instead)
126
+ // - This means the component works correctly for both scenarios without requiring
127
+ // the user to enable View Transitions
128
+ //
129
+ // In summary: By importing from 'astro:transitions/client', we ensure this component
130
+ // works seamlessly regardless of whether the site uses View Transitions or not.
131
+ document.addEventListener('astro:page-load', () => {
132
+ if (controller) {
133
+ controller.setCurrentPath(document.location.toString());
134
+ }
135
+ });
136
+
137
+ // Enable click-to-edit mode if requested via prop
138
+ if (enableClickToEdit !== undefined) {
139
+ if (typeof enableClickToEdit === 'boolean') {
140
+ controller.enableClickToEdit();
141
+ } else {
142
+ controller.enableClickToEdit(enableClickToEdit);
143
+ }
144
+ }
145
+ // Otherwise, click-to-edit overlays are not enabled by default.
146
+ // Users can press and hold Alt/Option key to temporarily enable click-to-edit mode
147
+ }
148
+
149
+ // Initialize on page load
150
+ initContentLink();
151
+
152
+ // Cleanup on page unload
153
+ window.addEventListener('beforeunload', () => {
154
+ if (controller) {
155
+ controller.dispose();
156
+ controller = null;
157
+ }
158
+ });
159
+ </script>
@@ -0,0 +1,328 @@
1
+ # Visual Editing with click-to-edit overlays
2
+
3
+ `<ContentLink />` enables Visual Editing for your DatoCMS content by providing click-to-edit overlays. It's built on top of the framework-agnostic [`@datocms/content-link`](https://www.npmjs.com/package/@datocms/content-link) library.
4
+
5
+ ## What is Visual Editing?
6
+
7
+ Visual Editing transforms how editors interact with your content by letting them see and edit it directly in the context of your website. Instead of switching between the CMS and the live site, editors can:
8
+
9
+ - **See content in context**: View draft content exactly as it appears on the website
10
+ - **Click to edit**: Click any content element to instantly open the editor for that specific field
11
+ - **Navigate seamlessly**: Browse between pages while staying in editing mode
12
+ - **Get instant feedback**: See changes immediately without page refreshes
13
+
14
+ ## Out-of-the-box features
15
+
16
+ - **Click-to-edit overlays**: Visual indicators showing which content is editable
17
+ - **Stega decoding**: Automatically detects stega-encoded metadata from DatoCMS GraphQL responses
18
+ - **Keyboard shortcuts**: Hold Alt/Option key to temporarily toggle click-to-edit mode
19
+ - **Flash-all highlighting**: Animated effect to show all editable elements at once
20
+ - **Bidirectional navigation**: Sync URL changes between your preview and the DatoCMS interface
21
+ - **Framework-agnostic**: Works with any Astro setup (with or without View Transitions)
22
+ - **StructuredText integration**: Special handling for complex structured content fields
23
+ - **Web Previews plugin integration**: Automatic bidirectional communication when running inside the DatoCMS Web Previews plugin
24
+
25
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
26
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
27
+
28
+ - [Installation](#installation)
29
+ - [Basic Setup](#basic-setup)
30
+ - [Step 1: Fetch content with Content Link metadata](#step-1-fetch-content-with-content-link-metadata)
31
+ - [Step 2: Add the ContentLink component](#step-2-add-the-contentlink-component)
32
+ - [Usage](#usage)
33
+ - [Props](#props)
34
+ - [StructuredText integration](#structuredtext-integration)
35
+ - [Edit groups with `data-datocms-content-link-group`](#edit-groups-with-data-datocms-content-link-group)
36
+ - [Edit boundaries with `data-datocms-content-link-boundary`](#edit-boundaries-with-data-datocms-content-link-boundary)
37
+ - [Manual overlays with `data-datocms-content-link-url`](#manual-overlays-with-data-datocms-content-link-url)
38
+ - [Low-level utilities](#low-level-utilities)
39
+ - [Troubleshooting](#troubleshooting)
40
+ - [Click-to-edit overlays not appearing](#click-to-edit-overlays-not-appearing)
41
+ - [Navigation not syncing in Web Previews plugin](#navigation-not-syncing-in-web-previews-plugin)
42
+ - [Content inside StructuredText not clickable](#content-inside-structuredtext-not-clickable)
43
+
44
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ npm install --save @datocms/astro
50
+ ```
51
+
52
+ Note that `@datocms/content-link` is included as a dependency and will be installed automatically.
53
+
54
+ ## Basic Setup
55
+
56
+ ### Step 1: Fetch content with Content Link metadata
57
+
58
+ Make sure you pass the `contentLink` and `baseEditingUrl` options when fetching content from DatoCMS:
59
+
60
+ ```astro
61
+ ---
62
+ import { executeQuery } from '@datocms/cda-client';
63
+
64
+ const query = `
65
+ query {
66
+ blogPost {
67
+ title
68
+ content
69
+ }
70
+ }
71
+ `;
72
+
73
+ const result = await executeQuery(query, {
74
+ token: import.meta.env.DATOCMS_API_TOKEN,
75
+ contentLink: 'v1',
76
+ baseEditingUrl: 'https://your-project.admin.datocms.com',
77
+ });
78
+ ---
79
+ ```
80
+
81
+ The `contentLink: 'v1'` option enables stega encoding, which embeds invisible metadata into text fields. The `baseEditingUrl` tells DatoCMS where your project is located so edit URLs can be generated correctly.
82
+
83
+ ### Step 2: Add the ContentLink component
84
+
85
+ Add the `<ContentLink />` component to your page or layout. This component renders nothing visible but activates all the Visual Editing features:
86
+
87
+ ```astro
88
+ ---
89
+ import { ContentLink } from '@datocms/astro/ContentLink';
90
+ ---
91
+
92
+ <html>
93
+ <head>
94
+ <!-- your head content -->
95
+ </head>
96
+ <body>
97
+ <!-- your page content -->
98
+ <ContentLink />
99
+ </body>
100
+ </html>
101
+ ```
102
+
103
+ That's it! The component will automatically:
104
+
105
+ - Scan the page for stega-encoded content
106
+ - Enable Alt/Option key toggling for click-to-edit mode
107
+ - Connect to the Web Previews plugin if running inside its iframe
108
+ - Handle navigation synchronization
109
+
110
+ ## Usage
111
+
112
+ The ContentLink component works seamlessly whether or not your Astro site uses [View Transitions](https://docs.astro.build/en/guides/view-transitions/). Simply add it to your layout:
113
+
114
+ ```astro
115
+ ---
116
+ // src/layouts/Layout.astro
117
+ import { ContentLink } from '@datocms/astro/ContentLink';
118
+ ---
119
+
120
+ <html>
121
+ <head>
122
+ <!-- ViewTransitions are optional -->
123
+ </head>
124
+ <body>
125
+ <slot />
126
+ <ContentLink />
127
+ </body>
128
+ </html>
129
+ ```
130
+
131
+ The component automatically handles both scenarios:
132
+
133
+ - **With View Transitions**: Listens to `astro:page-load` events and syncs the URL with the Web Previews plugin during client-side navigation
134
+ - **Without View Transitions**: Still initializes correctly and handles navigation via standard page reloads
135
+
136
+ You get the full Visual Editing experience regardless of your routing setup.
137
+
138
+ ## Props
139
+
140
+ | Prop | Type | Default | Description |
141
+ | ------------------- | ------------------------------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
142
+ | `enableClickToEdit` | `boolean \| { scrollToNearestTarget?: boolean }` | - | Enable click-to-edit overlays on mount. Use `true` for immediate activation, or pass options like `{ scrollToNearestTarget: true }` |
143
+ | `stripStega` | `boolean` | `false` | Strip stega-encoded invisible characters from text content. When `true`, encoding is permanently removed (prevents controller recreation) |
144
+
145
+ ## StructuredText integration
146
+
147
+ When working with DatoCMS's [Structured Text fields](https://www.datocms.com/docs/structured-text/dast), you may want more control over which areas are clickable.
148
+
149
+ ### Edit groups with `data-datocms-content-link-group`
150
+
151
+ By default, only the specific element containing stega-encoded data is clickable. For Structured Text fields, this might be a single `<span>` inside a paragraph, which creates a poor editing experience.
152
+
153
+ Use the `data-datocms-content-link-group` attribute to make a larger area clickable:
154
+
155
+ ```astro
156
+ ---
157
+ import { StructuredText } from '@datocms/astro/StructuredText';
158
+ ---
159
+
160
+ <!-- Make the entire structured text area clickable -->
161
+ <div data-datocms-content-link-group>
162
+ <StructuredText data={content.structuredTextField} />
163
+ </div>
164
+ ```
165
+
166
+ Now editors can click anywhere within the structured text content to open the field editor.
167
+
168
+ ### Edit boundaries with `data-datocms-content-link-boundary`
169
+
170
+ When Structured Text contains embedded blocks or inline records, you typically want:
171
+
172
+ - Main text content (paragraphs, headings, lists) to open the Structured Text field editor
173
+ - Embedded blocks to open their own specific record editor
174
+
175
+ Use `data-datocms-content-link-boundary` to prevent click events from bubbling up past a certain point:
176
+
177
+ ```astro
178
+ ---
179
+ import { StructuredText } from '@datocms/astro/StructuredText';
180
+ import BlogPost from './BlogPost.astro';
181
+ ---
182
+
183
+ <div data-datocms-content-link-group>
184
+ <StructuredText
185
+ data={content.structuredTextField}
186
+ components={{
187
+ renderBlock: ({ record }) => {
188
+ // This boundary prevents the block from using the parent group
189
+ return (
190
+ <div data-datocms-content-link-boundary>
191
+ <BlogPost data={record} />
192
+ </div>
193
+ );
194
+ },
195
+ }}
196
+ />
197
+ </div>
198
+ ```
199
+
200
+ This ensures:
201
+
202
+ - Clicking the main text opens the Structured Text field editor
203
+ - Clicking an embedded block opens that specific block's editor
204
+
205
+ ## Manual overlays with `data-datocms-content-link-url`
206
+
207
+ Text-based fields automatically include stega-encoded metadata. However, non-text fields (booleans, numbers, dates, JSON) cannot contain stega encoding.
208
+
209
+ For these fields, manually specify the edit URL using the `data-datocms-content-link-url` attribute:
210
+
211
+ **1. Request the `_editingUrl` field in your GraphQL query:**
212
+
213
+ ```graphql
214
+ query {
215
+ product {
216
+ id
217
+ price
218
+ isActive
219
+ inStock
220
+ _editingUrl
221
+ }
222
+ }
223
+ ```
224
+
225
+ **2. Add the attribute to your HTML element:**
226
+
227
+ ```astro
228
+ ---
229
+ const { product } = await executeQuery(query, {
230
+ token: import.meta.env.DATOCMS_API_TOKEN,
231
+ contentLink: 'v1',
232
+ baseEditingUrl: 'https://your-project.admin.datocms.com',
233
+ });
234
+ ---
235
+
236
+ <div>
237
+ <span data-datocms-content-link-url={product._editingUrl}>
238
+ ${product.price}
239
+ </span>
240
+
241
+ <span data-datocms-content-link-url={product._editingUrl}>
242
+ {product.inStock ? 'In Stock' : 'Out of Stock'}
243
+ </span>
244
+ </div>
245
+ ```
246
+
247
+ The `_editingUrl` field ensures the URL format is always correct and adapts to future changes automatically.
248
+
249
+ ## Low-level utilities
250
+
251
+ The `@datocms/content-link` package provides low-level utilities for working with stega-encoded content:
252
+
253
+ ```astro
254
+ ---
255
+ import { decodeStega, stripStega } from '@datocms/astro/ContentLink';
256
+
257
+ const text = 'Some content with invisible stega encoding';
258
+
259
+ // Extract editing metadata from stega-encoded text
260
+ const metadata = decodeStega(text);
261
+ // Returns: { origin: string, href: string } | null
262
+
263
+ // Remove stega encoding to get clean text
264
+ const cleanText = stripStega(text);
265
+ // Returns: 'Some content with invisible stega encoding' (without zero-width characters)
266
+ ---
267
+ ```
268
+
269
+ **Use cases:**
270
+
271
+ - **Meta tags and social sharing**: Use `stripStega()` to clean text before adding to `<meta>` tags
272
+ - **Programmatic text processing**: Remove invisible characters before string operations
273
+ - **Debugging**: Use `decodeStega()` to inspect what editing URLs are embedded in content
274
+
275
+ ## Troubleshooting
276
+
277
+ ### Click-to-edit overlays not appearing
278
+
279
+ If you don't see any overlays when holding Alt/Option:
280
+
281
+ 1. **Check that Content Link is enabled in your query:**
282
+
283
+ ```ts
284
+ const result = await executeQuery(query, {
285
+ contentLink: 'v1', // Must be present
286
+ baseEditingUrl: 'https://your-project.admin.datocms.com', // Must be present
287
+ });
288
+ ```
289
+
290
+ 2. **Verify the component is loaded:**
291
+ Check your browser console for any errors. The component should initialize silently.
292
+
293
+ 3. **Ensure you're viewing draft content:**
294
+ Content Link metadata is only included for draft content. Make sure you're using an API token with draft access and have `includeDrafts: true` in your query options if needed.
295
+
296
+ 4. **Check for conflicting CSS:**
297
+ The overlays use z-index and absolute positioning. Make sure your site's CSS isn't hiding them.
298
+
299
+ ### Navigation not syncing in Web Previews plugin
300
+
301
+ If navigation isn't syncing between your preview and the DatoCMS interface:
302
+
303
+ 1. **Verify you're running inside the plugin:**
304
+ The bidirectional connection only works when your preview is loaded inside the Web Previews plugin's iframe.
305
+
306
+ 2. **Check browser console:**
307
+ Look for any errors related to the iframe communication. The component uses `postMessage` for communication.
308
+
309
+ 3. **Ensure the component is in your layout:**
310
+ The `<ContentLink />` component should be in a layout that persists across page navigations.
311
+
312
+ ### Content inside StructuredText not clickable
313
+
314
+ If structured text content isn't opening the editor:
315
+
316
+ 1. **Wrap with `data-datocms-content-link-group`:**
317
+
318
+ ```astro
319
+ <div data-datocms-content-link-group>
320
+ <StructuredText data={content.body} />
321
+ </div>
322
+ ```
323
+
324
+ 2. **Check for `data-datocms-content-link-boundary` blocking clicks:**
325
+ Make sure you haven't accidentally added a boundary attribute that's preventing the click from reaching the group.
326
+
327
+ 3. **Verify stega encoding is present:**
328
+ Use the browser inspector to check if the structured text HTML contains zero-width characters (stega encoding). If not, check your query options.
@@ -0,0 +1,7 @@
1
+ import ContentLink from './ContentLink.astro';
2
+
3
+ // Re-export types and utilities from @datocms/content-link
4
+ export type { Controller } from '@datocms/content-link';
5
+ export { decodeStega, stripStega } from '@datocms/content-link';
6
+
7
+ export { ContentLink };
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './ContentLink';
1
2
  export * from './Image';
2
3
  export * from './QueryListener';
3
4
  export * from './Seo';