nuxt-content-assets 0.9.0-beta → 0.10.1
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 +63 -48
- package/dist/module.d.ts +2 -2
- package/dist/module.json +2 -2
- package/dist/module.mjs +78 -90
- package/dist/runtime/config.d.ts +1 -1
- package/dist/runtime/config.mjs +1 -1
- package/dist/runtime/options.d.ts +4 -3
- package/dist/runtime/options.mjs +14 -6
- package/dist/runtime/plugin.mjs +8 -11
- package/dist/runtime/services/assets.d.ts +17 -3
- package/dist/runtime/services/assets.mjs +17 -5
- package/dist/runtime/services/sources.mjs +6 -3
- package/dist/runtime/sockets/factory.mjs +4 -6
- package/dist/runtime/sockets/plugin.mjs +3 -4
- package/dist/runtime/utils/assert.d.ts +8 -3
- package/dist/runtime/utils/assert.mjs +5 -3
- package/dist/runtime/utils/string.d.ts +4 -2
- package/dist/runtime/utils/string.mjs +5 -2
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,10 +40,10 @@ I loved being in the mountains.
|
|
|
40
40
|
|
|
41
41
|
Almost as much as being in the sea!
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
:video{src="media/seaside.mp4"}
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
At build time the module [collates and serves](#how-it-works) assets and content together.
|
|
47
47
|
|
|
48
48
|
## Demo
|
|
49
49
|
|
|
@@ -82,8 +82,6 @@ export default defineNuxtConfig({
|
|
|
82
82
|
|
|
83
83
|
Run the dev server or build and local assets should now be served alongside markdown content.
|
|
84
84
|
|
|
85
|
-
See the [How it works](#how-it-works) section for more information.
|
|
86
|
-
|
|
87
85
|
## Usage
|
|
88
86
|
|
|
89
87
|
### Overview
|
|
@@ -91,15 +89,24 @@ See the [How it works](#how-it-works) section for more information.
|
|
|
91
89
|
Use relative paths anywhere within your documents:
|
|
92
90
|
|
|
93
91
|
```mdx
|
|
92
|
+
Images
|
|
94
93
|

|
|
95
|
-
|
|
94
|
+
|
|
95
|
+
Links
|
|
96
|
+
[link](docs/article.txt)
|
|
97
|
+
|
|
98
|
+
Elements / components
|
|
99
|
+
:video{src="media/video.mp4"}
|
|
100
|
+
|
|
101
|
+
HTML
|
|
102
|
+
<iframe src="media/example.html" />
|
|
96
103
|
```
|
|
97
104
|
|
|
98
105
|
Relative paths can be defined in frontmatter – as long as they are the only value:
|
|
99
106
|
|
|
100
107
|
```mdx
|
|
101
108
|
---
|
|
102
|
-
title: Portfolio
|
|
109
|
+
title: Portfolio
|
|
103
110
|
images:
|
|
104
111
|
- assets/image-1.jpg
|
|
105
112
|
- assets/image-2.jpg
|
|
@@ -110,27 +117,32 @@ images:
|
|
|
110
117
|
These values can then be passed to components:
|
|
111
118
|
|
|
112
119
|
```markdown
|
|
113
|
-
|
|
114
|
-
::
|
|
120
|
+
:image-gallery{:data="images"}
|
|
115
121
|
```
|
|
116
122
|
|
|
117
|
-
See the Demo for [markup](demo/content/
|
|
123
|
+
See the Demo for [markup](demo/content/advanced/gallery.md) and [component](demo/components/content/ContentGallery.vue) examples.
|
|
118
124
|
|
|
119
125
|
### Live reload
|
|
120
126
|
|
|
121
|
-
|
|
127
|
+
In development, the module watches for asset additions, moves and deletes, and will update the browser live.
|
|
122
128
|
|
|
123
|
-
|
|
129
|
+
If you delete an asset, it will be greyed out in the browser until you replace the file or modify the path to it.
|
|
130
|
+
|
|
131
|
+
If you edit an image, video, embed or iframe source, the content will update immediately, which is useful if you're looking to get that design just right!
|
|
124
132
|
|
|
125
133
|
### Image sizing
|
|
126
134
|
|
|
127
|
-
|
|
135
|
+
You can [configure](#image-size) the module to add image size attributes to generated `<img>` tags:
|
|
128
136
|
|
|
129
137
|
```html
|
|
130
|
-
<img src="/image.jpg
|
|
138
|
+
<img src="/image.jpg"
|
|
139
|
+
style="aspect-ratio:640/480"
|
|
140
|
+
width="640"
|
|
141
|
+
height="480"
|
|
142
|
+
>
|
|
131
143
|
```
|
|
132
144
|
|
|
133
|
-
If you use [ProseImg](https://content.nuxtjs.org/api/components/prose) components, you can hook into these values via the `$attrs` property:
|
|
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:
|
|
134
146
|
|
|
135
147
|
```vue
|
|
136
148
|
<template>
|
|
@@ -146,51 +158,32 @@ export default {
|
|
|
146
158
|
</script>
|
|
147
159
|
```
|
|
148
160
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
## How it works
|
|
152
|
-
|
|
153
|
-
Nuxt Content Assets works by serving a _copy_ of your assets using [Nitro](https://nitro.unjs.io/guide/assets#custom-server-assets).
|
|
154
|
-
|
|
155
|
-
When Nuxt builds, the following happens:
|
|
156
|
-
|
|
157
|
-
- content sources are scanned for valid assets
|
|
158
|
-
- found assets are copied to a temporary build folder
|
|
159
|
-
- any relative asset paths are rewritten as absolute
|
|
160
|
-
- metadata such as image size is written to a lookup file
|
|
161
|
-
- finally, Nitro serves the folder for public access
|
|
162
|
-
|
|
163
|
-
Note that in the rewriting phase, only specific tags and attributes are targeted :
|
|
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:
|
|
164
162
|
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
<img src="...">
|
|
168
|
-
<video src="...">
|
|
169
|
-
<audio src="...">
|
|
170
|
-
<source src="...">
|
|
171
|
-
<embed src="...">
|
|
172
|
-
<iframe src="...">
|
|
163
|
+
```
|
|
164
|
+
:image-gallery={:data="images"}
|
|
173
165
|
```
|
|
174
166
|
|
|
175
167
|
## Configuration
|
|
176
168
|
|
|
177
|
-
|
|
169
|
+
The module can be configured in your Nuxt configuration file:
|
|
178
170
|
|
|
179
171
|
```ts
|
|
180
172
|
// nuxt.config.ts
|
|
181
173
|
export default defineNuxtConfig({
|
|
182
|
-
|
|
183
|
-
//
|
|
174
|
+
contentAssets: {
|
|
175
|
+
// inject image sizes into the rendered html
|
|
184
176
|
imageSize: 'style',
|
|
185
177
|
|
|
186
|
-
//
|
|
178
|
+
// treat these extensions as content
|
|
179
|
+
contentExtensions: 'mdx? csv ya?ml json',
|
|
180
|
+
|
|
181
|
+
// output debug messages
|
|
187
182
|
debug: true,
|
|
188
183
|
}
|
|
189
184
|
})
|
|
190
185
|
```
|
|
191
186
|
|
|
192
|
-
Note that from version `0.9.0-alpha` the `output` location is no longer configurable; images are copied relative to their original locations.
|
|
193
|
-
|
|
194
187
|
### Image size
|
|
195
188
|
|
|
196
189
|
You can add one or more image size hints to the generated images:
|
|
@@ -203,11 +196,11 @@ You can add one or more image size hints to the generated images:
|
|
|
203
196
|
|
|
204
197
|
Pick from the following switches:
|
|
205
198
|
|
|
206
|
-
| Switch | What it does
|
|
207
|
-
|
|
208
|
-
| `style` | Adds `style="aspect-ratio:..."` to any `<img>` tag
|
|
209
|
-
| `attrs` | Adds `width` and `height` attributes to any `<img>` tag
|
|
210
|
-
| `url` | Adds
|
|
199
|
+
| Switch | What it does |
|
|
200
|
+
| ------- | ------------------------------------------------------------ |
|
|
201
|
+
| `style` | Adds `style="aspect-ratio:..."` to any `<img>` tag |
|
|
202
|
+
| `attrs` | Adds `width` and `height` attributes to any `<img>` tag |
|
|
203
|
+
| `url` | Adds a `?width=...&height=...` query string to image paths in frontmatter |
|
|
211
204
|
|
|
212
205
|
Note: if you add `attrs` only, include the following CSS in your app:
|
|
213
206
|
|
|
@@ -218,6 +211,18 @@ img {
|
|
|
218
211
|
}
|
|
219
212
|
```
|
|
220
213
|
|
|
214
|
+
### Content extensions
|
|
215
|
+
|
|
216
|
+
This setting tells Nuxt Content to ignore anything that is **not** one of these file extensions:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
mdx? csv ya?ml json
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
This way, you can use any **other** file type as an asset, without needing to explicitly configure extensions.
|
|
223
|
+
|
|
224
|
+
Generally, you shouldn't need to touch this setting.
|
|
225
|
+
|
|
221
226
|
### Debug
|
|
222
227
|
|
|
223
228
|
If you want to see what the module does as it runs, set `debug` to true:
|
|
@@ -228,6 +233,16 @@ If you want to see what the module does as it runs, set `debug` to true:
|
|
|
228
233
|
}
|
|
229
234
|
```
|
|
230
235
|
|
|
236
|
+
## How it works
|
|
237
|
+
|
|
238
|
+
When Nuxt builds, the module scans all content sources for assets, copies them to an accessible public assets folder, and indexes path and image metadata.
|
|
239
|
+
|
|
240
|
+
After Nuxt Content has run the parsed content is traversed, and both element attributes and frontmatter properties are checked to see if they resolve to the indexed asset paths.
|
|
241
|
+
|
|
242
|
+
If they do, then the attribute or property is rewritten with the absolute path. If the asset is an image, then the element or path is optionally updated with size attributes or size query string.
|
|
243
|
+
|
|
244
|
+
Finally, Nitro serves the site, and any requests made to the transformed asset paths should be picked up and the *copied* asset served by the browser.
|
|
245
|
+
|
|
231
246
|
## Development
|
|
232
247
|
|
|
233
248
|
Should you wish to develop the project, the scripts are:
|
package/dist/module.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
|
|
3
3
|
interface ModuleOptions {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
imageSize?: string | string[];
|
|
5
|
+
contentExtensions: string | string[];
|
|
6
6
|
debug?: boolean;
|
|
7
7
|
}
|
|
8
8
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -7,12 +7,15 @@ import getImageSize from 'image-size';
|
|
|
7
7
|
import { createStorage } from 'unstorage';
|
|
8
8
|
import githubDriver from 'unstorage/drivers/github';
|
|
9
9
|
import fsDriver from 'unstorage/drivers/fs';
|
|
10
|
-
import
|
|
10
|
+
import 'ohash';
|
|
11
11
|
import { listen } from 'listhen';
|
|
12
12
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
13
13
|
|
|
14
|
-
function
|
|
15
|
-
|
|
14
|
+
function matchTokens(value) {
|
|
15
|
+
const tokens = typeof value === "string" ? value.match(/[^\s,|]+/g) || [] : Array.isArray(value) ? value.filter((value2) => typeof value2 === "string").reduce((output, input) => {
|
|
16
|
+
return [...output, ...matchTokens(input)];
|
|
17
|
+
}, []) : [];
|
|
18
|
+
return Array.from(new Set(tokens));
|
|
16
19
|
}
|
|
17
20
|
function toPath(key) {
|
|
18
21
|
return key.replaceAll(":", "/");
|
|
@@ -21,29 +24,40 @@ function deKey(path) {
|
|
|
21
24
|
return path.replace(/^[^:]+:/, "");
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
const defaults
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
const defaults = {
|
|
28
|
+
// inject image size into the rendered html
|
|
29
|
+
imageSize: "attrs",
|
|
30
|
+
// treat these extensions as content
|
|
31
|
+
contentExtensions: "md csv ya?ml json",
|
|
32
|
+
// output debug messages
|
|
33
|
+
debug: false
|
|
27
34
|
};
|
|
28
35
|
const extensions = {
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
// used to get image size
|
|
37
|
+
image: matchTokens("png jpg jpeg gif svg webp ico"),
|
|
38
|
+
// unused for now
|
|
39
|
+
media: matchTokens("mp3 m4a wav mp4 mov webm ogg avi flv avchd")
|
|
31
40
|
};
|
|
41
|
+
function getIgnores(extensions2) {
|
|
42
|
+
return `^((?!(${matchTokens(extensions2).join("|")})).)*$`;
|
|
43
|
+
}
|
|
32
44
|
|
|
45
|
+
function isExcluded(path) {
|
|
46
|
+
return path.split("/").some((segment) => segment.startsWith(".") || segment.startsWith("_"));
|
|
47
|
+
}
|
|
33
48
|
function isImage(path) {
|
|
34
49
|
const ext = Path__default.extname(path).substring(1);
|
|
35
50
|
return extensions.image.includes(ext);
|
|
36
51
|
}
|
|
37
52
|
function isArticle(path) {
|
|
38
|
-
return
|
|
53
|
+
return path.endsWith(".md");
|
|
39
54
|
}
|
|
40
55
|
function isAsset(path) {
|
|
41
|
-
|
|
42
|
-
return !!ext && ext !== ".DS_Store" && !isArticle(path);
|
|
56
|
+
return !isExcluded(path) && !isArticle(path);
|
|
43
57
|
}
|
|
44
58
|
|
|
45
59
|
const moduleName = "nuxt-content-assets";
|
|
46
|
-
const moduleKey = "
|
|
60
|
+
const moduleKey = "contentAssets";
|
|
47
61
|
|
|
48
62
|
function log(...data) {
|
|
49
63
|
console.info(`[${moduleKey}]`, ...data);
|
|
@@ -85,7 +99,17 @@ function removeFolder(path) {
|
|
|
85
99
|
}
|
|
86
100
|
}
|
|
87
101
|
|
|
88
|
-
function
|
|
102
|
+
function getAssetPaths(srcDir, srcAbs) {
|
|
103
|
+
const srcRel = Path.relative(srcDir, srcAbs);
|
|
104
|
+
const srcAttr = "/" + srcRel;
|
|
105
|
+
const id = srcRel.replaceAll("/", ":");
|
|
106
|
+
return {
|
|
107
|
+
id,
|
|
108
|
+
srcRel,
|
|
109
|
+
srcAttr
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function getAssetSizes(srcAbs, hints) {
|
|
89
113
|
let width = void 0;
|
|
90
114
|
let height = void 0;
|
|
91
115
|
let ratio = void 0;
|
|
@@ -107,10 +131,12 @@ function getAssetConfig(srcDir, srcAbs, pattern, hints) {
|
|
|
107
131
|
warn(`could not read image "${srcAbs}`);
|
|
108
132
|
}
|
|
109
133
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
134
|
+
return {
|
|
135
|
+
width,
|
|
136
|
+
height,
|
|
137
|
+
ratio,
|
|
138
|
+
query
|
|
139
|
+
};
|
|
114
140
|
}
|
|
115
141
|
|
|
116
142
|
function makeStorage(source, key = "") {
|
|
@@ -120,7 +146,10 @@ function makeStorage(source, key = "") {
|
|
|
120
146
|
case "fs":
|
|
121
147
|
storage.mount(key, fsDriver({
|
|
122
148
|
...options,
|
|
123
|
-
ignore: [
|
|
149
|
+
ignore: [
|
|
150
|
+
"[^:]+?\\.md",
|
|
151
|
+
"_dir\\.yml"
|
|
152
|
+
]
|
|
124
153
|
}));
|
|
125
154
|
break;
|
|
126
155
|
case "github":
|
|
@@ -135,7 +164,7 @@ function makeStorage(source, key = "") {
|
|
|
135
164
|
}
|
|
136
165
|
function makeSourceManager(key, source, publicPath, callback) {
|
|
137
166
|
async function onWatch(event, key2) {
|
|
138
|
-
if (isAsset(key2)) {
|
|
167
|
+
if (isAsset(toPath(key2))) {
|
|
139
168
|
const path = event === "update" ? await copyItem(key2) : removeItem(key2);
|
|
140
169
|
if (callback) {
|
|
141
170
|
callback(event, path);
|
|
@@ -181,7 +210,7 @@ function makeSourceManager(key, source, publicPath, callback) {
|
|
|
181
210
|
}
|
|
182
211
|
async function getKeys() {
|
|
183
212
|
const keys = await storage.getKeys();
|
|
184
|
-
return keys.filter(isAsset);
|
|
213
|
+
return keys.map(toPath).filter(isAsset);
|
|
185
214
|
}
|
|
186
215
|
async function init() {
|
|
187
216
|
const keys = await getKeys();
|
|
@@ -201,30 +230,6 @@ function makeSourceManager(key, source, publicPath, callback) {
|
|
|
201
230
|
};
|
|
202
231
|
}
|
|
203
232
|
|
|
204
|
-
const replacers = {
|
|
205
|
-
key: (src) => Path__default.dirname(src).split("/").filter((e) => e).shift() || "",
|
|
206
|
-
path: (src) => Path__default.dirname(src),
|
|
207
|
-
folder: (src) => Path__default.dirname(src).replace(/[^/]+\//, ""),
|
|
208
|
-
file: (src) => Path__default.basename(src),
|
|
209
|
-
name: (src) => Path__default.basename(src, Path__default.extname(src)),
|
|
210
|
-
extname: (src) => Path__default.extname(src),
|
|
211
|
-
ext: (src) => Path__default.extname(src).substring(1),
|
|
212
|
-
hash: (src) => hash({ src })
|
|
213
|
-
};
|
|
214
|
-
function interpolatePattern(pattern, src, warn = false) {
|
|
215
|
-
return Path__default.join(pattern.replace(/\[\w+]/g, (match) => {
|
|
216
|
-
const name = match.substring(1, match.length - 1);
|
|
217
|
-
const fn = replacers[name];
|
|
218
|
-
if (fn) {
|
|
219
|
-
return fn(src);
|
|
220
|
-
}
|
|
221
|
-
if (warn) {
|
|
222
|
-
log(`Unknown output token ${match}`, true);
|
|
223
|
-
}
|
|
224
|
-
return match;
|
|
225
|
-
}));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
233
|
function createWebSocket() {
|
|
229
234
|
const wss = new WebSocketServer({ noServer: true });
|
|
230
235
|
const serve = (req, socket = req.socket, head = "") => wss.handleUpgrade(req, socket, head, (client) => wss.emit("connection", client, req));
|
|
@@ -288,19 +293,16 @@ function makeChannelBroker(ws2) {
|
|
|
288
293
|
}
|
|
289
294
|
const ws = createWebSocket();
|
|
290
295
|
const broker = makeChannelBroker(ws);
|
|
291
|
-
const defaults = {
|
|
292
|
-
port: {
|
|
293
|
-
port: 4001,
|
|
294
|
-
portRange: [4001, 4040]
|
|
295
|
-
},
|
|
296
|
-
hostname: "localhost",
|
|
297
|
-
showURL: false
|
|
298
|
-
};
|
|
299
296
|
async function setupSocketServer(channel, handler) {
|
|
300
297
|
const nuxt = useNuxt();
|
|
301
298
|
nuxt.hook("nitro:init", async (nitro) => {
|
|
302
299
|
if (!nuxt._socketServer) {
|
|
303
|
-
const
|
|
300
|
+
const defaults = nuxt.options.runtimeConfig.content.watch.ws;
|
|
301
|
+
const { server, url } = await listen(() => "Nuxt Content Assets", {
|
|
302
|
+
port: defaults.port.port + 1,
|
|
303
|
+
hostname: defaults.hostname,
|
|
304
|
+
showURL: false
|
|
305
|
+
});
|
|
304
306
|
nuxt._socketServer = server;
|
|
305
307
|
server.on("upgrade", ws.serve);
|
|
306
308
|
nitro.options.runtimeConfig.public.sockets = {
|
|
@@ -337,11 +339,7 @@ const module = defineNuxtModule({
|
|
|
337
339
|
nuxt: "^3.0.0"
|
|
338
340
|
}
|
|
339
341
|
},
|
|
340
|
-
defaults
|
|
341
|
-
output: `${defaults$1.assetsDir}/${defaults$1.assetsPattern}`,
|
|
342
|
-
imageSize: "",
|
|
343
|
-
debug: false
|
|
344
|
-
},
|
|
342
|
+
defaults,
|
|
345
343
|
async setup(options, nuxt) {
|
|
346
344
|
var _a, _b;
|
|
347
345
|
const pluginPath = resolve("./runtime/plugin");
|
|
@@ -358,17 +356,14 @@ const module = defineNuxtModule({
|
|
|
358
356
|
if (nuxt.options.content) {
|
|
359
357
|
(_b = nuxt.options.content).ignores || (_b.ignores = []);
|
|
360
358
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
const
|
|
365
|
-
interpolatePattern(assetsPattern, "", true);
|
|
366
|
-
const imageFlags = matchWords(options.imageSize);
|
|
367
|
-
const sources = nuxt.options._layers.map((layer) => layer.config?.content?.sources).reduce((output2, sources2) => {
|
|
359
|
+
const ignores = getIgnores(options.contentExtensions);
|
|
360
|
+
nuxt.options.content?.ignores.push(ignores);
|
|
361
|
+
const imageFlags = matchTokens(options.imageSize);
|
|
362
|
+
const sources = nuxt.options._layers.map((layer) => layer.config?.content?.sources).reduce((output, sources2) => {
|
|
368
363
|
if (sources2) {
|
|
369
|
-
Object.assign(
|
|
364
|
+
Object.assign(output, sources2);
|
|
370
365
|
}
|
|
371
|
-
return
|
|
366
|
+
return output;
|
|
372
367
|
}, {});
|
|
373
368
|
if (Object.keys(sources).length === 0 || !sources.content) {
|
|
374
369
|
const content = nuxt.options.srcDir + "/content";
|
|
@@ -379,23 +374,9 @@ const module = defineNuxtModule({
|
|
|
379
374
|
};
|
|
380
375
|
}
|
|
381
376
|
}
|
|
382
|
-
addPlugin(resolve("./runtime/sockets/plugin"));
|
|
383
|
-
const socket = nuxt.options.dev ? await setupSocketServer("content-assets") : null;
|
|
384
|
-
function removeAsset(src) {
|
|
385
|
-
const srcRel = Path.relative(publicPath, src);
|
|
386
|
-
delete assets[srcRel];
|
|
387
|
-
saveAssets();
|
|
388
|
-
return "/" + srcRel;
|
|
389
|
-
}
|
|
390
377
|
function updateAsset(src) {
|
|
391
|
-
const {
|
|
392
|
-
|
|
393
|
-
srcAttr,
|
|
394
|
-
width,
|
|
395
|
-
height,
|
|
396
|
-
ratio,
|
|
397
|
-
query
|
|
398
|
-
} = getAssetConfig(publicPath, src, assetsPattern, imageFlags);
|
|
378
|
+
const { srcRel, srcAttr } = getAssetPaths(publicPath, src);
|
|
379
|
+
const { width, height, ratio, query } = getAssetSizes(src, imageFlags);
|
|
399
380
|
assets[srcRel] = {
|
|
400
381
|
srcRel,
|
|
401
382
|
srcAttr,
|
|
@@ -407,22 +388,30 @@ const module = defineNuxtModule({
|
|
|
407
388
|
saveAssets();
|
|
408
389
|
return srcAttr;
|
|
409
390
|
}
|
|
410
|
-
function
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
391
|
+
function removeAsset(src) {
|
|
392
|
+
const { srcRel, srcAttr } = getAssetPaths(publicPath, src);
|
|
393
|
+
delete assets[srcRel];
|
|
394
|
+
saveAssets();
|
|
395
|
+
return srcAttr;
|
|
415
396
|
}
|
|
416
397
|
const saveAssets = debounce(() => {
|
|
417
398
|
writeFile(indexPath, assets);
|
|
418
399
|
}, 50);
|
|
419
400
|
const assets = {};
|
|
401
|
+
function onAssetChange(event, absTrg) {
|
|
402
|
+
const src = event === "update" ? updateAsset(absTrg) : removeAsset(absTrg);
|
|
403
|
+
if (socket) {
|
|
404
|
+
socket.send({ event, src });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
addPlugin(resolve("./runtime/sockets/plugin"));
|
|
408
|
+
const socket = nuxt.options.dev ? await setupSocketServer("content-assets") : null;
|
|
420
409
|
const managers = {};
|
|
421
410
|
for (const [key, source] of Object.entries(sources)) {
|
|
422
411
|
if (options.debug) {
|
|
423
412
|
log(`Creating source "${key}"`);
|
|
424
413
|
}
|
|
425
|
-
managers[key] = makeSourceManager(key, source, publicPath,
|
|
414
|
+
managers[key] = makeSourceManager(key, source, publicPath, onAssetChange);
|
|
426
415
|
}
|
|
427
416
|
nuxt.hook("build:before", async function() {
|
|
428
417
|
for (const [key, manager] of Object.entries(managers)) {
|
|
@@ -434,7 +423,6 @@ const module = defineNuxtModule({
|
|
|
434
423
|
}
|
|
435
424
|
});
|
|
436
425
|
const virtualConfig = [
|
|
437
|
-
// `export const assets = ${JSON.stringify(assets, null, ' ')}`,
|
|
438
426
|
`export const cachePath = '${cachePath}'`
|
|
439
427
|
].join("\n");
|
|
440
428
|
nuxt.hook("nitro:config", async (config) => {
|
package/dist/runtime/config.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const moduleName = "nuxt-content-assets";
|
|
2
|
-
export declare const moduleKey = "
|
|
2
|
+
export declare const moduleKey = "contentAssets";
|
package/dist/runtime/config.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const moduleName = "nuxt-content-assets";
|
|
2
|
-
export const moduleKey = "
|
|
2
|
+
export const moduleKey = "contentAssets";
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export declare const defaults: {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
imageSize: string;
|
|
3
|
+
contentExtensions: string;
|
|
4
|
+
debug: boolean;
|
|
4
5
|
};
|
|
5
|
-
export declare const tags: string[];
|
|
6
6
|
export declare const extensions: {
|
|
7
7
|
image: string[];
|
|
8
8
|
media: string[];
|
|
9
9
|
};
|
|
10
|
+
export declare function getIgnores(extensions: string | string[]): string;
|
package/dist/runtime/options.mjs
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { matchTokens } from "./utils/string.mjs";
|
|
2
2
|
export const defaults = {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
// inject image size into the rendered html
|
|
4
|
+
imageSize: "attrs",
|
|
5
|
+
// treat these extensions as content
|
|
6
|
+
contentExtensions: "md csv ya?ml json",
|
|
7
|
+
// output debug messages
|
|
8
|
+
debug: false
|
|
5
9
|
};
|
|
6
|
-
export const tags = ["img", "video", "audio", "source", "embed", "iframe", "a"];
|
|
7
10
|
export const extensions = {
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
// used to get image size
|
|
12
|
+
image: matchTokens("png jpg jpeg gif svg webp ico"),
|
|
13
|
+
// unused for now
|
|
14
|
+
media: matchTokens("mp3 m4a wav mp4 mov webm ogg avi flv avchd")
|
|
10
15
|
};
|
|
16
|
+
export function getIgnores(extensions2) {
|
|
17
|
+
return `^((?!(${matchTokens(extensions2).join("|")})).)*$`;
|
|
18
|
+
}
|
package/dist/runtime/plugin.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import Path from "path";
|
|
2
2
|
import { visit } from "unist-util-visit";
|
|
3
3
|
import { deKey, isValidAsset, toPath, walk } from "./utils/index.mjs";
|
|
4
|
-
import { tags } from "./options.mjs";
|
|
5
4
|
import { cachePath } from "#nuxt-content-assets";
|
|
6
5
|
import { makeStorage } from "./services/index.mjs";
|
|
7
6
|
async function updateAssets() {
|
|
@@ -32,11 +31,14 @@ const plugin = async (nitro) => {
|
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
}, filter);
|
|
35
|
-
visit(file.body, (
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
visit(file.body, (node) => node.type === "element", (node) => {
|
|
35
|
+
for (const [prop, value] of Object.entries(node.props)) {
|
|
36
|
+
if (typeof value !== "string") {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const { srcAttr, width, height, ratio } = getAsset(srcDoc, value);
|
|
38
40
|
if (srcAttr) {
|
|
39
|
-
node.props
|
|
41
|
+
node.props[prop] = srcAttr;
|
|
40
42
|
if (width && height) {
|
|
41
43
|
node.props.width = width;
|
|
42
44
|
node.props.height = height;
|
|
@@ -44,12 +46,7 @@ const plugin = async (nitro) => {
|
|
|
44
46
|
if (ratio) {
|
|
45
47
|
node.props.style = `aspect-ratio:${ratio}`;
|
|
46
48
|
}
|
|
47
|
-
|
|
48
|
-
} else if (node.tag === "a") {
|
|
49
|
-
if (node.props.href) {
|
|
50
|
-
const { srcAttr } = getAsset(srcDoc, node.props.href);
|
|
51
|
-
if (srcAttr) {
|
|
52
|
-
node.props.href = srcAttr;
|
|
49
|
+
if (node.tag === "a" && !node.props.target) {
|
|
53
50
|
node.props.target = "_blank";
|
|
54
51
|
}
|
|
55
52
|
}
|
|
@@ -8,11 +8,25 @@ export type AssetConfig = {
|
|
|
8
8
|
query?: string;
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Parse asset paths from absolute path
|
|
12
12
|
*
|
|
13
13
|
* @param srcDir The absolute path to the asset's source folder
|
|
14
14
|
* @param srcAbs The absolute path to the asset itself
|
|
15
|
-
|
|
15
|
+
*/
|
|
16
|
+
export declare function getAssetPaths(srcDir: string, srcAbs: string): {
|
|
17
|
+
id: any;
|
|
18
|
+
srcRel: string;
|
|
19
|
+
srcAttr: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Get asset image sizes
|
|
23
|
+
*
|
|
24
|
+
* @param srcAbs The absolute path to the asset itself
|
|
16
25
|
* @param hints A list of named image size hints, i.e. 'style', 'attrs', etc
|
|
17
26
|
*/
|
|
18
|
-
export declare function
|
|
27
|
+
export declare function getAssetSizes(srcAbs: string, hints: string[]): {
|
|
28
|
+
width: number | undefined;
|
|
29
|
+
height: number | undefined;
|
|
30
|
+
ratio: string | undefined;
|
|
31
|
+
query: string | undefined;
|
|
32
|
+
};
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import * as Path from "path";
|
|
2
2
|
import getImageSize from "image-size";
|
|
3
3
|
import { isImage, warn } from "../utils/index.mjs";
|
|
4
|
-
export function
|
|
4
|
+
export function getAssetPaths(srcDir, srcAbs) {
|
|
5
|
+
const srcRel = Path.relative(srcDir, srcAbs);
|
|
6
|
+
const srcAttr = "/" + srcRel;
|
|
7
|
+
const id = srcRel.replaceAll("/", ":");
|
|
8
|
+
return {
|
|
9
|
+
id,
|
|
10
|
+
srcRel,
|
|
11
|
+
srcAttr
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function getAssetSizes(srcAbs, hints) {
|
|
5
15
|
let width = void 0;
|
|
6
16
|
let height = void 0;
|
|
7
17
|
let ratio = void 0;
|
|
@@ -23,8 +33,10 @@ export function getAssetConfig(srcDir, srcAbs, pattern, hints) {
|
|
|
23
33
|
warn(`could not read image "${srcAbs}`);
|
|
24
34
|
}
|
|
25
35
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
return {
|
|
37
|
+
width,
|
|
38
|
+
height,
|
|
39
|
+
ratio,
|
|
40
|
+
query
|
|
41
|
+
};
|
|
30
42
|
}
|
|
@@ -10,7 +10,10 @@ export function makeStorage(source, key = "") {
|
|
|
10
10
|
case "fs":
|
|
11
11
|
storage.mount(key, fsDriver({
|
|
12
12
|
...options,
|
|
13
|
-
ignore: [
|
|
13
|
+
ignore: [
|
|
14
|
+
"[^:]+?\\.md",
|
|
15
|
+
"_dir\\.yml"
|
|
16
|
+
]
|
|
14
17
|
}));
|
|
15
18
|
break;
|
|
16
19
|
case "github":
|
|
@@ -25,7 +28,7 @@ export function makeStorage(source, key = "") {
|
|
|
25
28
|
}
|
|
26
29
|
export function makeSourceManager(key, source, publicPath, callback) {
|
|
27
30
|
async function onWatch(event, key2) {
|
|
28
|
-
if (isAsset(key2)) {
|
|
31
|
+
if (isAsset(toPath(key2))) {
|
|
29
32
|
const path = event === "update" ? await copyItem(key2) : removeItem(key2);
|
|
30
33
|
if (callback) {
|
|
31
34
|
callback(event, path);
|
|
@@ -71,7 +74,7 @@ export function makeSourceManager(key, source, publicPath, callback) {
|
|
|
71
74
|
}
|
|
72
75
|
async function getKeys() {
|
|
73
76
|
const keys = await storage.getKeys();
|
|
74
|
-
return keys.filter(isAsset);
|
|
77
|
+
return keys.map(toPath).filter(isAsset);
|
|
75
78
|
}
|
|
76
79
|
async function init() {
|
|
77
80
|
const keys = await getKeys();
|
|
@@ -39,11 +39,7 @@ export function createWebSocket() {
|
|
|
39
39
|
logger.warn("Error parsing message:", message.data);
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
|
-
handlers.forEach((handler) =>
|
|
43
|
-
if (typeof handler === "function") {
|
|
44
|
-
handler(data);
|
|
45
|
-
}
|
|
46
|
-
});
|
|
42
|
+
handlers.forEach((handler) => handler(data));
|
|
47
43
|
};
|
|
48
44
|
const send = (data) => {
|
|
49
45
|
if (ws) {
|
|
@@ -80,7 +76,9 @@ export function createWebSocket() {
|
|
|
80
76
|
return {
|
|
81
77
|
send,
|
|
82
78
|
addHandler(callback) {
|
|
83
|
-
|
|
79
|
+
if (typeof callback === "function") {
|
|
80
|
+
handlers.push(callback);
|
|
81
|
+
}
|
|
84
82
|
}
|
|
85
83
|
};
|
|
86
84
|
}
|
|
@@ -8,11 +8,10 @@ export default defineNuxtPlugin(async () => {
|
|
|
8
8
|
const { event, src } = data;
|
|
9
9
|
if (src) {
|
|
10
10
|
const isUpdate = event === "update";
|
|
11
|
-
document.querySelectorAll(
|
|
12
|
-
|
|
13
|
-
img.style.opacity = isUpdate ? "1" : "0.2";
|
|
11
|
+
document.querySelectorAll(`:is(img, video, source, embed, iframe):where([src^="${src}"])`).forEach((el) => {
|
|
12
|
+
el.style.opacity = isUpdate ? "1" : "0.2";
|
|
14
13
|
if (isUpdate) {
|
|
15
|
-
|
|
14
|
+
el.setAttribute("src", `${src}?${(/* @__PURE__ */ new Date()).getTime()}`);
|
|
16
15
|
}
|
|
17
16
|
});
|
|
18
17
|
}
|
|
@@ -3,15 +3,20 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export declare function isRelative(path: string): boolean;
|
|
5
5
|
/**
|
|
6
|
-
* Test path
|
|
6
|
+
* Test if path is excluded (_partial or .ignored)
|
|
7
|
+
* @param path
|
|
8
|
+
*/
|
|
9
|
+
export declare function isExcluded(path: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Test path for image extension
|
|
7
12
|
*/
|
|
8
13
|
export declare function isImage(path: string): boolean;
|
|
9
14
|
/**
|
|
10
|
-
* Test path
|
|
15
|
+
* Test path is markdown
|
|
11
16
|
*/
|
|
12
17
|
export declare function isArticle(path: string): boolean;
|
|
13
18
|
/**
|
|
14
|
-
* Test path
|
|
19
|
+
* Test path is asset
|
|
15
20
|
*/
|
|
16
21
|
export declare function isAsset(path: string): boolean;
|
|
17
22
|
/**
|
|
@@ -3,16 +3,18 @@ import { extensions } from "../options.mjs";
|
|
|
3
3
|
export function isRelative(path) {
|
|
4
4
|
return !(path.startsWith("http") || Path.isAbsolute(path));
|
|
5
5
|
}
|
|
6
|
+
export function isExcluded(path) {
|
|
7
|
+
return path.split("/").some((segment) => segment.startsWith(".") || segment.startsWith("_"));
|
|
8
|
+
}
|
|
6
9
|
export function isImage(path) {
|
|
7
10
|
const ext = Path.extname(path).substring(1);
|
|
8
11
|
return extensions.image.includes(ext);
|
|
9
12
|
}
|
|
10
13
|
export function isArticle(path) {
|
|
11
|
-
return
|
|
14
|
+
return path.endsWith(".md");
|
|
12
15
|
}
|
|
13
16
|
export function isAsset(path) {
|
|
14
|
-
|
|
15
|
-
return !!ext && ext !== ".DS_Store" && !isArticle(path);
|
|
17
|
+
return !isExcluded(path) && !isArticle(path);
|
|
16
18
|
}
|
|
17
19
|
export function isValidAsset(value) {
|
|
18
20
|
return typeof value === "string" && isAsset(value) && isRelative(value);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Get matched words from a string
|
|
2
|
+
* Get matched tokens (words, expressions) from a string or an array of possible strings
|
|
3
|
+
*
|
|
4
|
+
* Tokens may be separated by space, comma or pipe
|
|
3
5
|
*/
|
|
4
|
-
export declare function
|
|
6
|
+
export declare function matchTokens(value?: string | unknown[]): string[];
|
|
5
7
|
export declare function toPath(key: string): string;
|
|
6
8
|
export declare function toKey(path: string): any;
|
|
7
9
|
export declare function deKey(path: string): string;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export function
|
|
2
|
-
|
|
1
|
+
export function matchTokens(value) {
|
|
2
|
+
const tokens = typeof value === "string" ? value.match(/[^\s,|]+/g) || [] : Array.isArray(value) ? value.filter((value2) => typeof value2 === "string").reduce((output, input) => {
|
|
3
|
+
return [...output, ...matchTokens(input)];
|
|
4
|
+
}, []) : [];
|
|
5
|
+
return Array.from(new Set(tokens));
|
|
3
6
|
}
|
|
4
7
|
export function toPath(key) {
|
|
5
8
|
return key.replaceAll(":", "/");
|
package/dist/types.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { ModuleOptions } from './module'
|
|
3
3
|
|
|
4
4
|
declare module '@nuxt/schema' {
|
|
5
|
-
interface NuxtConfig { ['
|
|
6
|
-
interface NuxtOptions { ['
|
|
5
|
+
interface NuxtConfig { ['contentAssets']?: Partial<ModuleOptions> }
|
|
6
|
+
interface NuxtOptions { ['contentAssets']?: ModuleOptions }
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
|