@druxt-contrib/node-preview 0.1.0-dev.20261030024907 → 0.1.0-dev.20261109195239

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 CHANGED
@@ -1,7 +1,13 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.0-dev.20261030024907
3
+ ## 0.1.0-dev.20261109195239
4
4
 
5
5
  ### Minor Changes
6
6
 
7
7
  - 6b39690: Add the DruxtNodePreview module. It renders an unsaved Drupal node in a Druxt site, in Consumer Node Preview's Frontend tab, from the JSON:API Node Preview document.
8
+ - 0ac6a76: Read a preview with Consumer Node Preview's `[preview_token]` from the URL fragment, so a frontend on another domain or a tunnel previews without the editor's session. The new `register.token` option registers that form of the target URL, and `register.backend` registers on a Drupal site other than `druxt.baseUrl`, with its own secret.
9
+ - 2c2f5a4: Add automatic registration. With the new `register` option, the frontend adds its own preview target to its Drupal consumer when Nuxt starts, over Consumer Node Preview's targets API with a Simple OAuth client credentials token. The requests go through a `DruxtClient`, so the site's `druxt.axios` options apply, and they never follow a redirect. The target sends the preview's address as a path, `[jsonapi_node_preview_path]`, so it works for an editor on Drupal's host or on the frontend's. It sends a PATCH, so the consumer's other targets stay and Drupal records the time, and it sends it again every 6 hours while Nuxt runs, so a running frontend's target stays active. The secret comes only from the `DRUXT_NODE_PREVIEW_CLIENT_SECRET` environment variable.
10
+
11
+ ### Patch Changes
12
+
13
+ - adeeb35: The `origins` option now ignores an origin whose host has characters other than letters, digits, dots, hyphens and `_`, unless it is an IPv6 address in brackets. The page fetches from a listed origin without the Druxt client, whose headers and auth are for `druxt.baseUrl`, unless that origin is the page's own or `druxt.baseUrl`'s. A preview read with a token takes the token out of the page's address as soon as it starts. Where the browser allows session storage, the tab keeps the token there for a reload, until it expires.
package/README.md CHANGED
@@ -8,10 +8,7 @@
8
8
  # @druxt-contrib/node-preview
9
9
 
10
10
  [![npm](https://img.shields.io/npm/v/@druxt-contrib/node-preview)](https://www.npmjs.com/package/@druxt-contrib/node-preview)
11
- [![npm@dev](https://img.shields.io/npm/v/@druxt-contrib/node-preview/dev)](https://www.npmjs.com/package/@druxt-contrib/node-preview/v/dev)
12
11
  [![CI](https://github.com/druxt-contrib/druxt-node-preview/actions/workflows/ci.yml/badge.svg?branch=0.x)](https://github.com/druxt-contrib/druxt-node-preview/actions/workflows/ci.yml)
13
- [![codecov](https://codecov.io/gh/druxt-contrib/druxt-node-preview/branch/0.x/graph/badge.svg)](https://codecov.io/gh/druxt-contrib/druxt-node-preview)
14
- [![Known Vulnerabilities](https://snyk.io/test/github/druxt-contrib/druxt-node-preview/badge.svg?targetFile=package.json)](https://snyk.io/test/github/druxt-contrib/druxt-node-preview?targetFile=package.json)
15
12
  [![Discord](https://img.shields.io/discord/753866667590680587?label=discord&logo=discord&logoColor=white)](https://discord.druxtjs.org)
16
13
 
17
14
  > Node Preview module for Druxt
@@ -22,40 +19,54 @@ show it, paragraphs included.
22
19
 
23
20
  ![The Frontend tab of a Drupal preview, showing an unsaved node rendered by the Druxt frontend](https://raw.githubusercontent.com/druxt-contrib/druxt-node-preview/0.x/.github/preview.png)
24
21
 
25
- It works with two Drupal modules:
22
+ ## Contents
26
23
 
27
- - [JSON:API Node Preview](https://www.drupal.org/project/jsonapi_node_preview)
28
- serves the unsaved node over JSON:API.
29
- - [Consumer Node Preview](https://www.drupal.org/project/consumer_node_preview)
30
- adds Frontend, Drupal and JSON:API tabs to Drupal's preview. The Frontend tab
31
- shows this module's page.
24
+ - [Features](#features)
25
+ - [How it works](#how-it-works)
26
+ - [Requirements](#requirements)
27
+ - [Quick start](#quick-start)
28
+ - [Options](#options)
29
+ - [Troubleshooting](#troubleshooting)
30
+ - [More documentation](#more-documentation)
32
31
 
33
- ## Links
32
+ ## Features
34
33
 
35
- - [DruxtJS](https://druxtjs.org)
36
- - [Community Discord server](https://discord.druxtjs.org)
34
+ - Renders the unsaved node with the site's own Druxt components.
35
+ - Includes unsaved paragraphs, and the media they reference.
36
+ - Lets the editor pick a Nuxt layout, such as a bare one for embedded content.
37
+ - Wraps the preview in a component of your own, for a title or a container.
38
+ - Reads the preview with a Simple OAuth token on a frontend on another domain.
39
+ - Can add its own preview target to Drupal each time Nuxt starts up.
37
40
 
38
- ## Requirements
39
-
40
- The frontend needs:
41
+ ## How it works
41
42
 
42
- - Druxt 0.25 and Druxt Entity 0.29 or later, through `druxt-site` 0.15.
43
- - Node 16 for the build. Druxt's schema build fails on newer Node.
43
+ 1. The editor presses Preview in Drupal. The Frontend tab of
44
+ [Consumer Node Preview](https://www.drupal.org/project/consumer_node_preview)
45
+ opens this module's page in a frame.
46
+ 2. The page reads the preview's JSON:API address from the part of its URL
47
+ after the `#`. Browsers do not send that part to the frontend's server.
48
+ 3. The page fetches the preview, from
49
+ [JSON:API Node Preview](https://www.drupal.org/project/jsonapi_node_preview),
50
+ with the editor's Drupal session, or with a token from the URL for a
51
+ frontend on another domain. It puts the document in the Druxt store.
52
+ 4. `DruxtEntity` renders the node from the store. The unsaved content goes
53
+ through the same components as the live site.
44
54
 
45
- Drupal needs:
55
+ ## Requirements
46
56
 
47
- - Consumer Node Preview and JSON:API Node Preview.
48
- - The fixes from <https://www.drupal.org/i/3367724> for JSON:API Node Preview.
49
- Version 1.0.0-beta5 and earlier show stale or missing paragraphs without
50
- them. The [example's
51
- `composer.json`](https://github.com/druxt-contrib/druxt-node-preview/blob/0.x/example/drupal/composer.json)
52
- applies them with composer-patches.
57
+ - Nuxt 2, with Druxt 0.25 and Druxt Entity 0.29 or later, through `druxt-site`
58
+ 0.15.
59
+ - Node 16 for the build. Druxt's schema build fails on newer Node.
60
+ - Drupal 10.3 or 11, with Consumer Node Preview and JSON:API Node Preview. See
61
+ [Consumer Node Preview's requirements](https://git.drupalcode.org/project/consumer_node_preview/-/blob/1.0.x/README.md#requirements)
62
+ for the JSON:API Node Preview fixes it needs.
63
+ - The frontend and Drupal on one parent domain, such as `www.example.com` and
64
+ `cms.example.com`. Otherwise, see [Previews with a
65
+ token](docs/preview-request.md#previews-with-a-token).
53
66
 
54
- Both need one parent domain, such as `www.example.com` for the frontend and
55
- `cms.example.com` for Drupal. The editor's Drupal session cookie does not
56
- reach a frontend on another domain.
67
+ ## Quick start
57
68
 
58
- ## Install
69
+ On the frontend:
59
70
 
60
71
  ```sh
61
72
  npm install druxt-site @druxt-contrib/node-preview
@@ -67,245 +78,109 @@ export default {
67
78
  modules: ['@druxt-contrib/node-preview', 'druxt-site'],
68
79
  druxt: {
69
80
  baseUrl: 'https://cms.example.com',
70
- nodePreview: {
71
- include: { 'node--page': ['field_content.field_media'] },
72
- origins: ['https://cms.example.com'],
73
- layouts: ['default', 'preview'],
74
- },
75
81
  },
76
82
  }
77
83
  ```
78
84
 
79
85
  A site without `druxt-site` lists `druxt-entity` in `modules` instead.
80
86
 
81
- ## Options
82
-
83
- All options are optional.
84
-
85
- | Option | Type | Default | Description |
86
- | --------------------------- | ------------------- | ----------------------- | -------------------------------------------------------------------------- |
87
- | `druxt.nodePreview.path` | `string` | `'/druxt/node/preview'` | The preview page's path. |
88
- | `druxt.nodePreview.include` | `array` or `object` | `[]` | Relationships to add to the preview request. |
89
- | `druxt.nodePreview.origins` | `array` | `[]` | More origins the preview may come from. Each is a full URL. |
90
- | `druxt.nodePreview.layouts` | `array` | `[]` | Nuxt layouts the preview may use. See [Preview layouts](#preview-layouts). |
91
-
92
- **include.** Consumer Node Preview already includes a node's paragraphs. Add
93
- what the paragraphs reference, such as `field_content.field_media`. JSON:API
94
- refuses a path that the previewed type does not have. Key a path by type when
95
- only some bundles have it, as in the example above.
96
-
97
- **origins.** Any link can put an endpoint in the page's URL. So the page only
98
- fetches from this site, from `druxt.baseUrl`, and from `origins`. The editor's
99
- session cookie goes with each request to those origins.
87
+ On Drupal:
100
88
 
101
- A value the module cannot use shows a warning in the build output, and the
102
- module ignores it. The page's route name is `druxt-node-preview`.
89
+ 1. Install the modules:
103
90
 
104
- ## Drupal setup
91
+ ```sh
92
+ composer require drupal/jsonapi_node_preview:^1.0@beta drupal/consumer_node_preview
93
+ drush en consumer_node_preview
94
+ ```
105
95
 
106
- 1. Install Consumer Node Preview and JSON:API Node Preview, with the fixes
107
- from [Requirements](#requirements).
108
- 2. Go to **Administration > Configuration > Web services > Consumers** and
109
- edit the frontend's consumer. Under **Preview targets**, add a label and
110
- this URL template, with your frontend's host:
96
+ 2. Go to **Administration > Configuration > Web services > Consumers**, and
97
+ add or edit the frontend's consumer. Under **Preview targets**, add a label
98
+ and this URL, with your frontend's host:
111
99
 
112
100
  ```text
113
- https://www.example.com/druxt/node/preview?vm=[view_mode]&layout=[layout]#[jsonapi_node_preview]
101
+ https://www.example.com/druxt/node/preview?vm=[view_mode]#[jsonapi_node_preview_path]
114
102
  ```
115
103
 
116
104
  Drupal fills in the parts in square brackets when the editor presses
117
- Preview. Keep them as they are.
118
-
119
- 3. Optional: under **Preview layouts**, add one layout name in each box. Put
120
- `default` first, because the preview opens in the first one. See
121
- [Preview layouts](#preview-layouts).
122
- 4. In the site's `services.yml`, such as `sites/default/services.yml`, allow
123
- the frontend's origin with credentials. Name the origin. Do not use `'*'`.
124
-
125
- ```yaml
126
- parameters:
127
- cors.config:
128
- enabled: true
129
- allowedHeaders: ['*']
130
- allowedMethods: ['GET']
131
- allowedOrigins: ['https://www.example.com']
132
- supportsCredentials: true
133
- ```
105
+ Preview. Keep them as they are. The frontend can also add this target
106
+ itself. See [Automatic registration](docs/registration.md).
134
107
 
135
- Then rebuild the caches with `drush cr`.
136
-
137
- 5. Let the frontend show in Drupal's frame. Remove any `X-Frame-Options`
138
- header from the frontend's responses. If the frontend sends a
139
- `frame-ancestors` rule, add Drupal's origin to it. Name only Drupal's
140
- origin, so other sites cannot frame the preview.
108
+ 3. Allow the frontend's origin, with credentials, in the CORS settings in
109
+ `services.yml`. [Consumer Node Preview's
110
+ configuration](https://git.drupalcode.org/project/consumer_node_preview/-/blob/1.0.x/README.md#configuration)
111
+ has the YAML.
112
+ 4. Let Drupal show the preview page in a frame. On the preview page's route
113
+ only, remove any `X-Frame-Options` header, and add Drupal's origin to its
114
+ `frame-ancestors` rule. Keep the frontend's other pages as they are.
141
115
 
142
116
  To check it, edit a node, change something and press Preview. The Frontend
143
117
  tab shows the change.
144
118
 
145
- [Consumer Node Preview's
146
- README](https://www.drupal.org/project/consumer_node_preview) lists the other
147
- URL tokens, and how to set targets in `settings.php`.
148
-
149
- ## How it works
150
-
151
- 1. The editor presses Preview in Drupal. The Frontend tab opens this module's
152
- page in a frame.
153
- 2. The page reads the preview's JSON:API address from the part of its URL
154
- after the `#`. Browsers do not send that part to the frontend's server.
155
- 3. The page fetches the preview with the editor's Drupal session. It puts the
156
- document in the Druxt store.
157
- 4. `DruxtEntity` renders the node from the store, inside a `DruxtNodePreview`
158
- wrapper. The unsaved content goes through the same components as the live
159
- site.
160
-
161
- ## Preview layouts
162
-
163
- The page uses the layout that `?layout=` names, but only if `layouts` lists
164
- it. If not, the page uses the default layout. A preview URL cannot pick any
165
- other layout.
166
-
167
- Each listed layout must be a file in the site's `layouts/` folder that renders
168
- `<Nuxt />`. A bare layout suits content that sits inside other pages. It has
169
- the site's type and spacing, but no header or footer.
170
-
171
- A site that picks layouts somewhere else can call
172
- `$druxtNodePreview.layout(name)`. It makes the same check.
173
-
174
- ## Theming the preview
175
-
176
- A live page often adds a title or a container around the node. The preview
177
- gets the same from a wrapper component, picked like any Druxt wrapper. For a
178
- `node--doc_page` previewed in `full`, the page uses the first of these that
179
- exists:
180
-
181
- 1. `DruxtNodePreviewDocPageFull`
182
- 2. `DruxtNodePreviewDocPage`
183
- 3. `DruxtNodePreviewDefault`
184
-
185
- If none of them exists, the node renders in Druxt's plain wrapper.
186
-
187
- A wrapper gets any of these props that it declares:
188
-
189
- - `document`: the whole JSON:API preview document.
190
- - `entity`: the previewed resource, with `attributes` such as the title.
191
- - `mode`, `type` and `uuid`.
192
-
193
- Its default slot renders the node with `DruxtEntity`:
194
-
195
- ```vue
196
- <!-- components/druxt/node-preview/DocPageFull.vue -->
197
- <template>
198
- <article class="prose">
199
- <h1>{{ entity.attributes.title }}</h1>
200
- <slot />
201
- </article>
202
- </template>
203
-
204
- <script>
205
- export default {
206
- // The props it does not declare would otherwise land on <article>.
207
- inheritAttrs: false,
208
- props: {
209
- entity: { type: Object, required: true },
210
- },
211
- }
212
- </script>
213
- ```
119
+ ## Options
214
120
 
215
- A wrapper named by bundle alone, such as `DruxtNodePreviewDocPage`, applies to
216
- every view mode. Put page parts such as a title in the `<Bundle>Full` wrapper,
217
- as above, or check the `mode` prop.
121
+ All options are optional, and go under `druxt.nodePreview`.
218
122
 
219
- ## Components that fetch their own data
123
+ | Option | Default | Description |
124
+ | ---------- | ----------------------- | ------------------------------------------------------------------------------------------------------------- |
125
+ | `path` | `'/druxt/node/preview'` | The preview page's path. |
126
+ | `include` | `[]` | Relationships to add to the preview request. Key them by type when only some bundles have them. |
127
+ | `origins` | `[]` | More origins the preview may come from, as full URLs. |
128
+ | `layouts` | `[]` | Nuxt layouts the preview may use. See [Layouts and theming](docs/layouts-and-theming.md). |
129
+ | `register` | `false` | Adds the frontend's preview target to Drupal on start up. See [Automatic registration](docs/registration.md). |
220
130
 
221
- A component that reads the Druxt store shows the unsaved data. A component
222
- that fetches its data again, such as paragraphs at a revision, shows the saved
223
- content instead. Skip that fetch while a preview is active:
131
+ **include.** Consumer Node Preview already includes a node's paragraphs. Add
132
+ what the paragraphs reference, such as `field_content.field_media`. JSON:API
133
+ refuses a path that the previewed type does not have, so key it by type when
134
+ only some bundles have it:
224
135
 
225
136
  ```js
226
- if ((this.$store.state.druxtNodePreview || {}).active) return
137
+ include: { 'node--page': ['field_content.field_media'] }
227
138
  ```
228
139
 
229
- When the editor leaves the preview, the module removes the preview data from
230
- the store. The next page loads the saved content.
231
-
232
- The module stores the data under the route's `langcode` meta. `DruxtEntity`
233
- reads it there when it has no `langcode` prop.
234
-
235
- ## The preview request
236
-
237
- The page fetches through the Druxt client, `$druxt.axios`, with credentials.
238
- The site's request interceptors apply, such as one that adds a workspace
239
- header. Without the Druxt client, the page uses `window.fetch`.
240
-
241
- A preview address that is a path stays a path. The Druxt client resolves it
242
- against its own base URL, and interceptors that match by path still match it.
243
- This suits a frontend that reaches Drupal through a proxy on its own origin.
244
- The editor must then sign in through that proxy too, because the session
245
- cookie belongs to the host the editor signed in on. Druxt's
246
- `proxy: { api: true }` forwards the API but not Drupal's sign-in pages, so it
247
- is not enough on its own.
248
-
249
- Without a proxy, the request goes to Drupal's origin. Drupal then needs the
250
- CORS settings from [Drupal setup](#drupal-setup). The session cookie is
251
- `SameSite=Lax`, so the frontend and Drupal need one parent domain.
140
+ **origins.** Any link can put an address in the page's URL. So the page only
141
+ fetches from this site, from `druxt.baseUrl`, and from `origins`. A request to
142
+ an origin in `origins` goes without the Druxt client, whose headers and auth
143
+ are for `druxt.baseUrl`, unless that origin is the frontend's own or
144
+ `druxt.baseUrl`'s. An origin whose host has characters other than
145
+ letters, digits, dots, hyphens and `_` is ignored, unless it is an IPv6 address
146
+ in brackets, such as `http://[::1]:3000`.
252
147
 
253
- The page only fetches a node preview, from an address that ends in
254
- `/{uuid}/preview`. The response must be `application/vnd.api+json`, and each
255
- resource in it must have a JSON:API type and a UUID. If a check fails, the
256
- page shows an error and puts nothing in the store.
257
-
258
- ## Server rendering and static sites
259
-
260
- The page only works in the browser. A server render shows "Loading
261
- preview..." and the browser fills it in. A statically generated page has the
262
- default layout, so the page switches to the listed layout in the browser.
148
+ A value the module cannot use shows a warning in the build output, and the
149
+ module ignores it.
263
150
 
264
151
  ## Troubleshooting
265
152
 
266
153
  The page shows "Preview unavailable" and a message.
267
154
 
268
- - **HTTP 404.** Drupal has no preview of this node for the editor. Check, in
269
- this order:
270
- 1. The editor pressed Preview, and can view the node.
271
- 2. The frontend and Drupal share a parent domain. On your own computer,
272
- use one host name for both: `localhost` and `127.0.0.1` count as
273
- different.
274
- 3. The CORS settings in `services.yml` name the frontend's origin.
155
+ - **HTTP 404.** Drupal has no preview of this node for the editor. Check that
156
+ the editor pressed Preview, and that the frontend and Drupal share a parent
157
+ domain. On your own computer, use one host name for both: `localhost` and
158
+ `127.0.0.1` count as different. Then check the CORS settings.
275
159
  - **"The preview endpoint is not on this site or its Drupal site".** Add the
276
160
  preview's origin to the `origins` option.
277
- - **"The URL fragment has no preview endpoint".** The preview target's URL
278
- template has no `#[jsonapi_node_preview]` at the end.
161
+ - **"The URL fragment has no preview endpoint".** Make the target URL end in
162
+ `#[jsonapi_node_preview_path]` or `#[jsonapi_node_preview]`.
279
163
  - **HTTP 400.** An `include` path that the previewed type does not have. Key
280
164
  that path by type.
281
165
 
282
- If `DruxtEntity` reports a missing schema:
283
-
284
- 1. Check that the content type has a display for the previewed view mode, on
285
- its **Manage display** tab.
286
- 2. Make sure Drupal is up, then restart the frontend. Nuxt builds the schema
287
- when it starts, and `.nuxt` can hold an old one.
288
-
289
- The [end to end troubleshooting
290
- notes](https://github.com/druxt-contrib/druxt-node-preview/blob/0.x/test/e2e/TROUBLESHOOTING.md)
291
- cover the test stack.
292
-
293
- ## Writing another frontend
166
+ If the Frontend tab stays blank, check the frame headers from step 4 of the
167
+ [Quick start](#quick-start), because the frontend refused the frame.
294
168
 
295
- [Consumer Node Preview's
296
- README](https://www.drupal.org/project/consumer_node_preview) shows how a
297
- frontend reads the preview address from the URL, including the `https:/`
298
- repair that some routers need. Before it renders anything, a frontend makes
299
- the same checks as this module. See [The preview request](#the-preview-request).
169
+ For automatic registration and schema errors, see
170
+ [Troubleshooting](docs/troubleshooting.md).
300
171
 
301
- This module reads `[view_mode]` and `[layout]` from the `vm` and `layout`
302
- query parameters. They change only how the preview looks. The JSON:API
303
- document is the same for each.
172
+ ## More documentation
304
173
 
305
- ## Example
174
+ - [Automatic registration](docs/registration.md), and its [Drupal
175
+ setup](docs/registration-drupal-setup.md)
176
+ - [Layouts and theming](docs/layouts-and-theming.md)
177
+ - [The preview request](docs/preview-request.md), including previews with a
178
+ token
179
+ - [Troubleshooting](docs/troubleshooting.md)
180
+ - [Writing another frontend](docs/other-frontends.md)
306
181
 
307
- See the [example](https://github.com/druxt-contrib/druxt-node-preview/tree/0.x/example)
308
- for a paired Drupal and Nuxt setup.
182
+ The [example](https://github.com/druxt-contrib/druxt-node-preview/tree/0.x/example)
183
+ pairs a Drupal site and a Nuxt site.
309
184
 
310
185
  ## Licence
311
186