druxt-router 0.23.0 → 0.25.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 CHANGED
@@ -1,5 +1,56 @@
1
1
  # druxt-router
2
2
 
3
+ ## 0.25.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7b749bd: Added improved error handling.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [7b749bd]
12
+ - druxt@0.16.0
13
+
14
+ ## 0.24.2
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies [2ae1d6d]
19
+ - druxt@0.15.0
20
+
21
+ ## 0.24.1
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies [45bc0b9]
26
+ - druxt@0.14.0
27
+
28
+ ## 0.24.0
29
+
30
+ ### Minor Changes
31
+
32
+ - 9161b38: Added Storybook integration for the DruxtRouter component.
33
+ - 9161b38: Added ability to render a specific route using the Path property.
34
+
35
+ ```jsx
36
+ <DruxtRouter path="/node/1" />
37
+ ```
38
+
39
+ - 9161b38: Added fetch hook to DruxtRouter component.
40
+ - 9161b38: Added option to disable the DruxtRouter page middleware.
41
+
42
+ ```js
43
+ export default {
44
+ druxt: {
45
+ router: {
46
+ middleware: false
47
+ }
48
+ }
49
+ };
50
+ ```
51
+
52
+ Note: This is experimental and your results may vary.
53
+
3
54
  ## 0.23.0
4
55
 
5
56
  ### Minor Changes
@@ -0,0 +1,16 @@
1
+ import DruxtRouter from './DruxtRouter.vue'
2
+
3
+ export default {
4
+ title: 'Druxt/Router/DruxtRouter',
5
+ component: DruxtRouter
6
+ }
7
+
8
+ const Template = (args, { argTypes }) => ({
9
+ components: { DruxtRouter },
10
+ props: Object.keys(argTypes),
11
+ template: `<DruxtRouter v-bind="$props" />`
12
+ })
13
+
14
+ export const Default = Template.bind({})
15
+ Default.storyName = 'DruxtRouter'
16
+ Default.args = { path: '/' }
@@ -1,21 +1,61 @@
1
1
  <script>
2
2
 
3
3
  import DruxtModule from 'druxt/dist/components/DruxtModule.vue'
4
- import { mapState } from 'vuex'
5
4
 
6
5
  /**
7
- * Renders a Druxt module router component based on the resolved route provided
8
- * by the Drupal Decoupled Router module.
6
+ * The DruxtRouter component renders a Drupal decoupled route, or path, using
7
+ * the appropriate Druxt component.
9
8
  *
10
- * @example @lang vue
9
+ * `<DruxtRouter path="/" />`
10
+ *
11
+ * If no Path is specified, the Vue router path will be used by default.
12
+ *
13
+ * For instance, using the `/node/1` route would result in a DruxtRouter
14
+ * component for the content in Drupal with the Node ID of 1.
15
+ *
16
+ * @example <caption>Render using the Vue router path</caption> @lang vue
11
17
  * <DruxtRouter />
18
+ *
19
+ * @example <caption>Render a specified path</caption> @lang vue
20
+ * <DruxtRouter path="/node/1" />
21
+ *
22
+ * @example <caption>Render the result of the model, bypasses Drupal backend</caption> @lang vue
23
+ * <DruxtRouter v-model="route" />
24
+ *
25
+ * @example <caption>Render via a template</caption> @lang vue
26
+ * <DruxtRouter>
27
+ * <template #default="{ route }">
28
+ * <DruxtRouter v-bind="route.props" />
29
+ * </template>
30
+ * </DruxtRouter>
12
31
  */
13
32
  export default {
14
33
  name: 'DruxtRouter',
15
34
 
16
35
  extends: DruxtModule,
17
36
 
18
- async middleware ({ store, redirect, route }) {
37
+ /**
38
+ * Nuxt middleware; gets and sets the current route, and processes redirects.
39
+ *
40
+ * This can be disabled by setting the `druxt.router.middleware` option to
41
+ * `false` in `nuxt.config.js`
42
+ *
43
+ * @example @lang js
44
+ * export default {
45
+ * druxt: {
46
+ * router: {
47
+ * middleware: false
48
+ * }
49
+ * }
50
+ * }
51
+ */
52
+ async middleware ({ $druxt, redirect, route, store }) {
53
+ // Ensure Router middleware is enabled.
54
+ if (typeof ($druxt.settings.router || {}).middleware !== 'undefined' && !$druxt.settings.router.middleware) {
55
+ return
56
+ }
57
+
58
+ // Get and set the current route and redirect information.
19
59
  const result = await store.dispatch('druxtRouter/get', route.fullPath)
20
60
 
21
61
  // Process redirect.
@@ -24,6 +64,44 @@ export default {
24
64
  }
25
65
  },
26
66
 
67
+ /** */
68
+ props: {
69
+ /**
70
+ * The Decoupled router path.
71
+ *
72
+ * If not set, the Vue router value will be used instead.
73
+ *
74
+ * @type {string}
75
+ *
76
+ * @example @lang vue
77
+ * <DruxtRouter path="/node/1" />
78
+ */
79
+ path: {
80
+ type: String,
81
+ default: undefined,
82
+ },
83
+
84
+ /**
85
+ * The Router object, used to determine the resolved route component.
86
+ *
87
+ * Setting this value will bypass the JSON:API.
88
+ *
89
+ * @type {object}
90
+ * @model
91
+ */
92
+ value: {
93
+ type: Object,
94
+ default: () => undefined
95
+ }
96
+ },
97
+
98
+ /**
99
+ * @property {object} model - The model object.
100
+ */
101
+ data: ({ value }) => ({
102
+ model: value,
103
+ }),
104
+
27
105
  /**
28
106
  * Nuxt head method.
29
107
  *
@@ -39,7 +117,7 @@ export default {
39
117
  {
40
118
  hid: 'canonical',
41
119
  rel: 'canonical',
42
- href: this.canonical || this.route.canonical
120
+ href: this.canonical || (this.route || {}).canonical
43
121
  }
44
122
  ]
45
123
  }
@@ -51,44 +129,142 @@ export default {
51
129
  return head
52
130
  },
53
131
 
54
- /**
55
- * Vue.js Computed properties.
56
- *
57
- * @vue-computed {object} redirect The current Redirect, if applicable.
58
- * @vue-computed {object} route The current Route.
59
- */
132
+ /** */
60
133
  computed: {
134
+ /**
135
+ * The route module.
136
+ *
137
+ * @type {string}
138
+ */
61
139
  module: ({ route }) =>
62
140
  (route || {}).component && route.component.startsWith('druxt-') ? route.component.substring(6) : false,
63
141
 
142
+ /**
143
+ * The route object.
144
+ *
145
+ * @type {object}
146
+ */
147
+ route: ({ model }) => model,
148
+
64
149
  /**
65
150
  * Route title.
66
151
  * @type {boolean|string}
67
152
  * @default false
68
153
  */
69
- title: ({ route }) => route.label || false,
154
+ title: ({ route }) => (route || {}).label || false,
70
155
 
71
156
  /**
72
157
  * Route component property data.
73
158
  * @type {object|string}
74
159
  * @default false
75
160
  */
76
- props: ({ route }) => route.props || false,
161
+ props: ({ route }) => (route || {}).props || false,
162
+ },
77
163
 
78
- ...mapState({
79
- redirect: state => state.druxtRouter.redirect,
80
- route: state => state.druxtRouter.route
81
- })
164
+ /** */
165
+ watch: {
166
+ /**
167
+ * Re-fetch on update to Path prop.
168
+ */
169
+ async path() {
170
+ await this.$fetch()
171
+ }
82
172
  },
83
173
 
174
+ /** DruxtModule settings */
84
175
  druxt: {
176
+ /**
177
+ * Provides the available component naming options for the Druxt Wrapper.
178
+ *
179
+ * @param {object} context - The module component ViewModel.
180
+ * @returns {ComponentOptions}
181
+ */
85
182
  componentOptions: ({ module, route }) => [
86
183
  // @TODO - Add Path options.
87
184
  [module || 'error', route.isHomePath ? 'front' : 'not-front'],
88
185
  ['default']
89
186
  ],
90
187
 
91
- propsData: ({ route }) => ({ route })
188
+ /**
189
+ * Fetch the decoupled route.
190
+ */
191
+ async fetchConfig() {
192
+ // Use the v-model value if provided.
193
+ if (this.value) {
194
+ return
195
+ }
196
+
197
+ // Get the route from the Drupal decoupled router module via the
198
+ // druxtRouter store.
199
+ // Use the Path prop or the Vue Router as the route to lookup.
200
+ const path = this.path || this.$route.fullPath
201
+ const route = await this.$store.dispatch('druxtRouter/getRoute', path)
202
+ this.model = route
203
+
204
+ // If this the path is the active Vue route, set the active route in the
205
+ // druxtRouter store for other modules to use.
206
+ // This is also done when via the middleware if in use.
207
+ const setActiveRoute = path === this.$route.fullPath
208
+ if (setActiveRoute) {
209
+ this.$store.commit('druxtRouter/setRoute', path)
210
+ }
211
+ },
212
+
213
+ /**
214
+ * Provides propsData for the DruxtWrapper.
215
+ *
216
+ * @param {object} context - The module component ViewModel.
217
+ * @returns {PropsData}
218
+ */
219
+ propsData: ({ $route, path, model }) => ({
220
+ path: path || $route.fullPath,
221
+ route: model
222
+ })
92
223
  }
93
224
  }
225
+
226
+ /**
227
+ * Provides the available naming options for the Wrapper component.
228
+ *
229
+ * @typedef {array[]} ComponentOptions
230
+ *
231
+ * @example @lang js
232
+ * [
233
+ * 'DruxtRouter[Module][IsFront?]',
234
+ * 'DruxtRouterDefault',
235
+ * ]
236
+ *
237
+ * @example <caption>Entity route</caption> @lang js
238
+ * [
239
+ * 'DruxtRouterEntityFront',
240
+ * 'DruxtRouterEntity',
241
+ * 'DruxtRouterDefault',
242
+ * '',
243
+ * ]
244
+ */
245
+
246
+ /**
247
+ * Provides property data for use in the Wrapper component.
248
+ *
249
+ * @typedef {object} PropsData
250
+ * @param {string} path - The route path.
251
+ * @param {object} route - The Decoupled Router object.
252
+ *
253
+ * @example @lang js
254
+ * {
255
+ * path: '/',
256
+ * route: {
257
+ * canonical: 'https://demo-api.druxtjs.org/en/node',
258
+ * component: 'druxt-view',
259
+ * error: false,
260
+ * isHomePath: true,
261
+ * jsonapi: {},
262
+ * label: 'Home',
263
+ * props: {},
264
+ * redirect: false,
265
+ * resolvedPath: '/en/node',
266
+ * type: 'views',
267
+ * }
268
+ * }
269
+ */
94
270
  </script>
@@ -104,7 +104,7 @@ class DruxtRouter {
104
104
  }
105
105
  async getRoute(path) {
106
106
  const url = `/router/translate-path?path=${path}`;
107
- const response = await this.druxt.axios.get(url, {
107
+ const response = await this.druxt.get(url, {
108
108
  validateStatus: (status) => status < 500
109
109
  });
110
110
  const data = {
@@ -147,9 +147,16 @@ class DruxtRouter {
147
147
  break;
148
148
  }
149
149
  if (!(response.status >= 200 && response.status < 300)) {
150
- const error = new Error();
151
- error.response = response;
152
- throw error;
150
+ if (response.status === 404) {
151
+ if (typeof response.data !== "object") {
152
+ response.data = { errors: [{
153
+ detail: "Please ensure the Decoupled Router module is installed and configured correctly."
154
+ }] };
155
+ } else if (response.data.message || response.data.details) {
156
+ response.data.errors = [{ detail: [response.data.message, response.data.details].filter((s) => s).join("\n") }];
157
+ }
158
+ }
159
+ this.druxt.error({ response }, { url });
153
160
  }
154
161
  return route;
155
162
  }
@@ -282,6 +289,10 @@ const DruxtRouterEntityMixin = {
282
289
 
283
290
  const DruxtRouterMixin = {
284
291
  props: {
292
+ path: {
293
+ type: String,
294
+ default: void 0
295
+ },
285
296
  route: {
286
297
  type: Object,
287
298
  required: true
@@ -112,7 +112,7 @@ class DruxtRouter {
112
112
  }
113
113
  async getRoute(path) {
114
114
  const url = `/router/translate-path?path=${path}`;
115
- const response = await this.druxt.axios.get(url, {
115
+ const response = await this.druxt.get(url, {
116
116
  validateStatus: (status) => status < 500
117
117
  });
118
118
  const data = {
@@ -155,9 +155,16 @@ class DruxtRouter {
155
155
  break;
156
156
  }
157
157
  if (!(response.status >= 200 && response.status < 300)) {
158
- const error = new Error();
159
- error.response = response;
160
- throw error;
158
+ if (response.status === 404) {
159
+ if (typeof response.data !== "object") {
160
+ response.data = { errors: [{
161
+ detail: "Please ensure the Decoupled Router module is installed and configured correctly."
162
+ }] };
163
+ } else if (response.data.message || response.data.details) {
164
+ response.data.errors = [{ detail: [response.data.message, response.data.details].filter((s) => s).join("\n") }];
165
+ }
166
+ }
167
+ this.druxt.error({ response }, { url });
161
168
  }
162
169
  return route;
163
170
  }
@@ -290,6 +297,10 @@ const DruxtRouterEntityMixin = {
290
297
 
291
298
  const DruxtRouterMixin = {
292
299
  props: {
300
+ path: {
301
+ type: String,
302
+ default: void 0
303
+ },
293
304
  route: {
294
305
  type: Object,
295
306
  required: true
package/nuxt/index.js CHANGED
@@ -48,6 +48,9 @@ const DruxtRouterNuxtModule = async function(moduleOptions = {}) {
48
48
  fileName: "store/druxt-router.js",
49
49
  options
50
50
  });
51
+ this.nuxt.hook("storybook:config", async ({ stories }) => {
52
+ stories.push("druxt-router/dist/components/*.stories.mjs");
53
+ });
51
54
  };
52
55
  DruxtRouterNuxtModule.meta = require("../package.json");
53
56
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "druxt-router",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "description": "Drupal router for Nuxt, powered by the Drupal Decoupled Router module.",
5
5
  "keywords": [
6
6
  "cms",
@@ -42,7 +42,7 @@
42
42
  "templates"
43
43
  ],
44
44
  "dependencies": {
45
- "druxt": "^0.13.0",
45
+ "druxt": "^0.16.0",
46
46
  "url-parse": "^1.4.7"
47
47
  },
48
48
  "optionalDependencies": {