nuxt-content-assets 0.10.3 → 1.1.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
@@ -45,6 +45,24 @@ Almost as much as being in the sea!
45
45
 
46
46
  At build time the module [collates and serves](#how-it-works) assets and content together.
47
47
 
48
+ ### Features
49
+
50
+ Built on top of [Nuxt Content](https://github.com/nuxt/content/) and compatible with any Nuxt Content project or theme, including [Docus](https://github.com/nuxt-themes/docus).
51
+
52
+ User experience:
53
+
54
+ - co-locate assets with content files
55
+ - reference assets using relative paths
56
+ - supports any format (image, video, doc)
57
+
58
+ Developer experience:
59
+
60
+ - works with tags and custom components
61
+ - works in markdown and frontmatter
62
+ - file watching and asset live-reload
63
+ - image size injection
64
+ - zero config
65
+
48
66
  ## Demo
49
67
 
50
68
  To view the demo locally, run:
@@ -88,7 +106,7 @@ Run the dev server or build and local assets should now be served alongside mark
88
106
 
89
107
  Use relative paths anywhere within your documents:
90
108
 
91
- ```mdx
109
+ ```md
92
110
  Images
93
111
  ![image](image.jpg)
94
112
 
@@ -104,7 +122,7 @@ HTML
104
122
 
105
123
  Relative paths can be defined in frontmatter – as long as they are the only value:
106
124
 
107
- ```mdx
125
+ ```md
108
126
  ---
109
127
  title: Portfolio
110
128
  images:
@@ -132,17 +150,23 @@ If you edit an image, video, embed or iframe source, the content will update imm
132
150
 
133
151
  ### Image sizing
134
152
 
135
- You can [configure](#image-size) the module to add image size attributes to generated `<img>` tags:
153
+ #### HTML
154
+
155
+ The module is [preconfigured](#image-size) to pass image size hints (by default `style`) to generated `<img>` tags:
136
156
 
137
157
  ```html
138
- <img src="/image.jpg"
139
- style="aspect-ratio:640/480"
140
- width="640"
141
- height="480"
142
- >
158
+ <!-- imageSize: 'style' -->
159
+ <img src="/image.jpg" style="aspect-ratio:640/480">
160
+
161
+ <!-- imageSize: 'attrs' -->
162
+ <img src="/image.jpg" width="640" height="480">
143
163
  ```
144
164
 
145
- If you use [ProseImg](https://content.nuxtjs.org/api/components/prose) components, you can [hook into these values](demo/components/temp/ProseImg.vue) via the `$attrs` property:
165
+ Passing image sizes prevents content jumps on page load.
166
+
167
+ #### Prose components
168
+
169
+ If you use [ProseImg](https://content.nuxtjs.org/api/components/prose) components, you can use `imageSize: 'attrs'` to [hook into these values](demo/components/temp/ProseImg.vue) via Vue's `$attrs` property:
146
170
 
147
171
  ```vue
148
172
  <template>
@@ -158,12 +182,22 @@ export default {
158
182
  </script>
159
183
  ```
160
184
 
161
- If you pass [frontmatter](demo/content/advanced/gallery.md) to [custom components](demo/components/content/ContentImage.vue) set the `'url'` configuration option to encode size in the URL:
185
+ #### Frontmatter
186
+
187
+ If you pass [frontmatter](demo/content/advanced/gallery.md) to [custom components](demo/components/content/ContentImage.vue) configure `imageSize` as `'src'` to encode the size in `src`:
162
188
 
163
189
  ```
164
- :image-gallery={:data="images"}
190
+ :image-content{:src="image"}
191
+ ```
192
+
193
+ The component will receive the updated path which you can parse and implement as you see fit.
194
+
195
+ ```html
196
+ <img class="image-content" src="/image.jpg?width=640&height=480">
165
197
  ```
166
198
 
199
+ See demo component [here](demo/components/content/ContentImage.vue).
200
+
167
201
  ## Configuration
168
202
 
169
203
  The module can be configured in your Nuxt configuration file:
@@ -176,10 +210,10 @@ export default defineNuxtConfig({
176
210
  imageSize: 'style',
177
211
 
178
212
  // treat these extensions as content
179
- contentExtensions: 'mdx? csv ya?ml json',
213
+ contentExtensions: 'md csv ya?ml json',
180
214
 
181
215
  // output debug messages
182
- debug: true,
216
+ debug: false,
183
217
  }
184
218
  })
185
219
  ```
@@ -190,7 +224,7 @@ You can add one or more image size hints to the generated images:
190
224
 
191
225
  ```ts
192
226
  {
193
- imageSize: 'attrs url'
227
+ imageSize: 'style attrs src'
194
228
  }
195
229
  ```
196
230
 
@@ -200,9 +234,9 @@ Pick from the following switches:
200
234
  | ------- | ------------------------------------------------------------ |
201
235
  | `style` | Adds `style="aspect-ratio:..."` to any `<img>` tag |
202
236
  | `attrs` | Adds `width` and `height` attributes to any `<img>` tag |
203
- | `url` | Adds a `?width=...&height=...` query string to image paths in frontmatter |
237
+ | `src` | Adds `?width=...&height=...` to `src` attribute (frontmatter only) |
204
238
 
205
- Note: if you add `attrs` only, include the following CSS in your app:
239
+ Note: if you add *only* `attrs` include the following CSS in your app:
206
240
 
207
241
  ```css
208
242
  img {
@@ -211,12 +245,14 @@ img {
211
245
  }
212
246
  ```
213
247
 
248
+ To disable, pass `false`.
249
+
214
250
  ### Content extensions
215
251
 
216
252
  This setting tells Nuxt Content to ignore anything that is **not** one of these file extensions:
217
253
 
218
254
  ```
219
- mdx? csv ya?ml json
255
+ md csv ya?ml json
220
256
  ```
221
257
 
222
258
  This way, you can use any **other** file type as an asset, without needing to explicitly configure extensions.
package/dist/module.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
 
3
3
  interface ModuleOptions {
4
- imageSize?: string | string[];
4
+ imageSize?: string | string[] | false;
5
5
  contentExtensions: string | string[];
6
6
  debug?: boolean;
7
7
  }
package/dist/module.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0"
6
6
  },
7
- "version": "0.10.3"
7
+ "version": "1.1.0"
8
8
  }
package/dist/module.mjs CHANGED
@@ -35,7 +35,7 @@ function deKey(path) {
35
35
 
36
36
  const defaults = {
37
37
  // inject image size into the rendered html
38
- imageSize: "attrs",
38
+ imageSize: "style",
39
39
  // treat these extensions as content
40
40
  contentExtensions: "md csv ya?ml json",
41
41
  // output debug messages
@@ -51,6 +51,9 @@ function getIgnores(extensions2) {
51
51
  return `^((?!(${matchTokens(extensions2).join("|")})).)*$`;
52
52
  }
53
53
 
54
+ function removeQuery(path) {
55
+ return path.replace(/\?.*$/, "");
56
+ }
54
57
  function isExcluded(path) {
55
58
  return path.split("/").some((segment) => segment.startsWith(".") || segment.startsWith("_"));
56
59
  }
@@ -59,7 +62,7 @@ function isImage(path) {
59
62
  return extensions.image.includes(ext);
60
63
  }
61
64
  function isArticle(path) {
62
- return path.endsWith(".md");
65
+ return removeQuery(path).endsWith(".md");
63
66
  }
64
67
  function isAsset(path) {
65
68
  return !isArticle(path);
@@ -124,15 +127,15 @@ function getAssetSizes(srcAbs, hints) {
124
127
  if (hints.length && isImage(srcAbs)) {
125
128
  try {
126
129
  const size = getImageSize(srcAbs);
127
- if (hints.includes("style")) {
128
- ratio = `${size.width}/${size.height}`;
129
- }
130
130
  if (hints.includes("attrs")) {
131
131
  width = size.width;
132
132
  height = size.height;
133
133
  }
134
- if (hints.includes("url")) {
135
- query = `?width=${width}&height=${height}`;
134
+ if (hints.includes("style")) {
135
+ ratio = `${size.width}/${size.height}`;
136
+ }
137
+ if (hints.includes("src") || hints.includes("url")) {
138
+ query = `width=${size.width}&height=${size.height}`;
136
139
  }
137
140
  } catch (err) {
138
141
  warn(`could not read image "${srcAbs}`);
@@ -319,8 +322,10 @@ async function setupSocketServer(channel, handler) {
319
322
  });
320
323
  nuxt._socketServer = server;
321
324
  server.on("upgrade", ws.serve);
325
+ const wsUrl = url.replace("http", "ws");
326
+ log(`Websocket listening on "${wsUrl}"`);
322
327
  nitro.options.runtimeConfig.public.sockets = {
323
- wsUrl: url.replace("http", "ws")
328
+ wsUrl
324
329
  };
325
330
  nitro.hooks.hook("close", async () => {
326
331
  await ws.close();
@@ -1,7 +1,7 @@
1
1
  import { matchTokens } from "./utils/string.mjs";
2
2
  export const defaults = {
3
3
  // inject image size into the rendered html
4
- imageSize: "attrs",
4
+ imageSize: "style",
5
5
  // treat these extensions as content
6
6
  contentExtensions: "md csv ya?ml json",
7
7
  // output debug messages
@@ -1,6 +1,17 @@
1
1
  import Path from "path";
2
2
  import { visit, SKIP, CONTINUE } from "unist-util-visit";
3
- import { deKey, isValidAsset, list, matchTokens, toPath, walk } from "./utils/index.mjs";
3
+ import {
4
+ buildStyle,
5
+ deKey,
6
+ isValidAsset,
7
+ list,
8
+ matchTokens,
9
+ toPath,
10
+ walk,
11
+ removeQuery,
12
+ buildQuery,
13
+ parseQuery
14
+ } from "./utils/index.mjs";
4
15
  import { debug, cachePath } from "#nuxt-content-assets";
5
16
  import { makeStorage } from "./services/index.mjs";
6
17
  async function updateAssets() {
@@ -51,10 +62,11 @@ const plugin = async (nitro) => {
51
62
  const filter = (value, key) => !(String(key).startsWith("_") || key === "body");
52
63
  walk(file, (value, parent, key) => {
53
64
  if (isValidAsset(value)) {
54
- const { srcAttr, query } = getAsset(value);
65
+ const { srcAttr, query } = getAsset(removeQuery(value));
55
66
  if (srcAttr) {
56
- parent[key] = srcAttr + (query || "");
57
- updated.push(`meta: ${key} to "${srcAttr}"`);
67
+ const srcUrl = query ? buildQuery(srcAttr, parseQuery(value), query) : srcAttr;
68
+ parent[key] = srcUrl;
69
+ updated.push(`meta: ${key} to "${srcUrl}"`);
58
70
  }
59
71
  }
60
72
  }, filter);
@@ -82,7 +94,7 @@ const plugin = async (nitro) => {
82
94
  }
83
95
  if (ratio) {
84
96
  if (typeof node.props.style === "string") {
85
- node.props.style += `; aspect-ratio: ${ratio};`;
97
+ node.props.style = buildStyle(node.props.style, `aspect-ratio: ${ratio}`);
86
98
  } else {
87
99
  node.props.style ||= {};
88
100
  node.props.style.aspectRatio = ratio;
@@ -22,7 +22,7 @@ export declare function getAssetPaths(srcDir: string, srcAbs: string): {
22
22
  * Get asset image sizes
23
23
  *
24
24
  * @param srcAbs The absolute path to the asset itself
25
- * @param hints A list of named image size hints, i.e. 'style', 'attrs', etc
25
+ * @param hints A list of named image size hints, one of 'attrs', 'style', or 'src'
26
26
  */
27
27
  export declare function getAssetSizes(srcAbs: string, hints: string[]): {
28
28
  width: number | undefined;
@@ -19,15 +19,15 @@ export function getAssetSizes(srcAbs, hints) {
19
19
  if (hints.length && isImage(srcAbs)) {
20
20
  try {
21
21
  const size = getImageSize(srcAbs);
22
- if (hints.includes("style")) {
23
- ratio = `${size.width}/${size.height}`;
24
- }
25
22
  if (hints.includes("attrs")) {
26
23
  width = size.width;
27
24
  height = size.height;
28
25
  }
29
- if (hints.includes("url")) {
30
- query = `?width=${width}&height=${height}`;
26
+ if (hints.includes("style")) {
27
+ ratio = `${size.width}/${size.height}`;
28
+ }
29
+ if (hints.includes("src") || hints.includes("url")) {
30
+ query = `width=${size.width}&height=${size.height}`;
31
31
  }
32
32
  } catch (err) {
33
33
  warn(`could not read image "${srcAbs}`);
@@ -1,5 +1,6 @@
1
1
  import { Callback } from '../../types';
2
2
  export declare function createWebSocket(): {
3
+ connect: (retry?: boolean) => void;
3
4
  send: (data: any) => void;
4
5
  addHandler(callback: Callback): void;
5
6
  } | null;
@@ -46,10 +46,17 @@ export function createWebSocket() {
46
46
  ws.send(JSON.stringify(data));
47
47
  }
48
48
  };
49
+ let retries = 0;
50
+ const url = useRuntimeConfig().public.sockets?.wsUrl;
49
51
  const connect = (retry = false) => {
50
52
  if (retry) {
51
- logger.log("WS reconnecting..");
52
- setTimeout(connect, 1e3);
53
+ retries++;
54
+ if (retries < 5) {
55
+ logger.log("Reconnecting...");
56
+ setTimeout(connect, 1e3);
57
+ } else {
58
+ logger.warn("Giving up!");
59
+ }
53
60
  return;
54
61
  }
55
62
  if (ws) {
@@ -59,10 +66,9 @@ export function createWebSocket() {
59
66
  }
60
67
  ws = void 0;
61
68
  }
62
- const url = useRuntimeConfig().public.sockets?.wsUrl;
63
69
  if (url) {
64
70
  const wsUrl = `${url}ws`;
65
- logger.log(`Running on ${wsUrl}`);
71
+ logger.log(`WS connect to ${wsUrl}`);
66
72
  ws = new WebSocket(wsUrl);
67
73
  ws.onopen = onOpen;
68
74
  ws.onmessage = onMessage;
@@ -74,6 +80,7 @@ export function createWebSocket() {
74
80
  connect();
75
81
  }
76
82
  return {
83
+ connect,
77
84
  send,
78
85
  addHandler(callback) {
79
86
  if (typeof callback === "function") {
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Build a style string by passing multiple independent expressions
3
+ */
4
+ export declare function buildStyle(...expr: string[]): string;
5
+ /**
6
+ * Build a query string by passing multiple independent expressions
7
+ */
8
+ export declare function buildQuery(...expr: string[]): string;
@@ -0,0 +1,12 @@
1
+ export function buildStyle(...expr) {
2
+ return expr.map((expr2) => expr2.replace(/^[; ]+|[; ]+$/g, "")).filter((s) => s).join(";").replace(/\s*;\s*/g, "; ") + ";";
3
+ }
4
+ export function buildQuery(...expr) {
5
+ const output = expr.map((expr2) => expr2.replace(/^[?&]+|&+$/g, "")).filter((s) => s);
6
+ if (output.length) {
7
+ const [first, ...rest] = output;
8
+ const isParam = (expr2) => /^[^?]+=[^=]+$/.test(expr2);
9
+ return !isParam(first) ? rest.length > 0 ? first + (first.includes("?") ? "&" : "?") + rest.join("&") : first : "?" + output.join("&");
10
+ }
11
+ return "";
12
+ }
@@ -1,5 +1,6 @@
1
- export * from './assert';
1
+ export * from './path';
2
2
  export * from './debug';
3
+ export * from './build';
3
4
  export * from './fs';
4
5
  export * from './string';
5
6
  export * from './object';
@@ -1,5 +1,6 @@
1
- export * from "./assert.mjs";
1
+ export * from "./path.mjs";
2
2
  export * from "./debug.mjs";
3
+ export * from "./build.mjs";
3
4
  export * from "./fs.mjs";
4
5
  export * from "./string.mjs";
5
6
  export * from "./object.mjs";
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Parses the query string from a path
3
+ */
4
+ export declare function parseQuery(path: string): string;
5
+ /**
6
+ * Removes the query string from a path
7
+ */
8
+ export declare function removeQuery(path: string): string;
1
9
  /**
2
10
  * Test path to be relative
3
11
  */
@@ -1,5 +1,12 @@
1
1
  import Path from "path";
2
2
  import { extensions } from "../options.mjs";
3
+ export function parseQuery(path) {
4
+ const matches = path.match(/\?.+$/);
5
+ return matches ? matches[0] : "";
6
+ }
7
+ export function removeQuery(path) {
8
+ return path.replace(/\?.*$/, "");
9
+ }
3
10
  export function isRelative(path) {
4
11
  return !(path.startsWith("http") || Path.isAbsolute(path));
5
12
  }
@@ -11,7 +18,7 @@ export function isImage(path) {
11
18
  return extensions.image.includes(ext);
12
19
  }
13
20
  export function isArticle(path) {
14
- return path.endsWith(".md");
21
+ return removeQuery(path).endsWith(".md");
15
22
  }
16
23
  export function isAsset(path) {
17
24
  return !isArticle(path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-content-assets",
3
- "version": "0.10.3",
3
+ "version": "1.1.0",
4
4
  "description": "Enable locally-located assets in Nuxt Content",
5
5
  "repository": "davestewart/nuxt-content-assets",
6
6
  "license": "MIT",
@@ -33,7 +33,6 @@
33
33
  "dependencies": {
34
34
  "@nuxt/kit": "^3.3.2",
35
35
  "debounce": "^1.2.1",
36
- "glob": "^9.3.2",
37
36
  "image-size": "^1.0.2",
38
37
  "listhen": "^1.0.4",
39
38
  "ohash": "^1.0.0",