nuxt-content-assets 0.7.0 → 0.9.0-beta

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.
@@ -1,39 +1,92 @@
1
- import * as Fs from "fs";
2
1
  import * as Path from "path";
3
- import glob from "glob";
4
2
  import { createStorage } from "unstorage";
5
3
  import githubDriver from "unstorage/drivers/github";
6
- export async function getGithubAssets(key, source, tempPath, extensions) {
4
+ import fsDriver from "unstorage/drivers/fs";
5
+ import { warn, isAsset, toPath, removeFile, copyFile, writeBlob, writeFile, deKey } from "../utils/index.mjs";
6
+ export function makeStorage(source, key = "") {
7
7
  const storage = createStorage();
8
- storage.mount(key, githubDriver({
9
- repo: source.repo,
10
- branch: source.branch || "main",
11
- dir: source.dir || "/",
12
- ttl: source.ttl || 600
13
- }));
14
- const rx = new RegExp(`.${extensions.join("|")}$`);
15
- const keys = await storage.getKeys();
16
- const assetKeys = keys.filter((key2) => rx.test(key2));
17
- const assetItems = await Promise.all(assetKeys.map(async (id) => {
18
- const data = await storage.getItem(id);
19
- return { id, data };
20
- }));
21
- const prefix = source.prefix || "";
22
- const paths = [];
23
- for (const { id, data } of assetItems) {
24
- if (data) {
25
- const path = id.replaceAll(":", "/");
26
- const absPath = Path.join(tempPath, path.replace(key, `${key}/${prefix}`));
27
- const absFolder = Path.dirname(absPath);
28
- const buffer = data.constructor.name === "Blob" ? Buffer.from(await data.arrayBuffer()) : typeof data === "object" ? JSON.stringify(data, null, " ") : String(data);
29
- Fs.mkdirSync(absFolder, { recursive: true });
30
- Fs.writeFileSync(absPath, buffer);
31
- paths.push(absPath);
32
- }
8
+ const options = typeof source === "string" ? { driver: "fs", base: source } : source;
9
+ switch (options.driver) {
10
+ case "fs":
11
+ storage.mount(key, fsDriver({
12
+ ...options,
13
+ ignore: ["[^:]+?\\.md"]
14
+ }));
15
+ break;
16
+ case "github":
17
+ storage.mount(key, githubDriver({
18
+ branch: "main",
19
+ dir: "/",
20
+ ...options
21
+ }));
22
+ break;
33
23
  }
34
- return paths;
24
+ return storage;
35
25
  }
36
- export function getFsAssets(path, extensions) {
37
- const pattern = `${path}/**/*.{${extensions.join(",")}}`;
38
- return glob.globSync(pattern) || [];
26
+ export function makeSourceManager(key, source, publicPath, callback) {
27
+ async function onWatch(event, key2) {
28
+ if (isAsset(key2)) {
29
+ const path = event === "update" ? await copyItem(key2) : removeItem(key2);
30
+ if (callback) {
31
+ callback(event, path);
32
+ }
33
+ }
34
+ }
35
+ function getRelSrc(key2) {
36
+ return toPath(key2).replace(/\w+/, "").replace(source.prefix || "", "");
37
+ }
38
+ function getAbsSrc(key2) {
39
+ return Path.join(source.base, getRelSrc(key2));
40
+ }
41
+ function getRelTrg(key2) {
42
+ return Path.join(source.prefix || "", toPath(deKey(key2)));
43
+ }
44
+ function getAbsTrg(key2) {
45
+ return Path.join(publicPath, getRelTrg(key2));
46
+ }
47
+ function removeItem(key2) {
48
+ const absTrg = getAbsTrg(key2);
49
+ removeFile(absTrg);
50
+ return absTrg;
51
+ }
52
+ async function copyItem(key2) {
53
+ const absTrg = getAbsTrg(key2);
54
+ const driver = source.driver;
55
+ if (driver === "fs") {
56
+ const absSrc = getAbsSrc(key2);
57
+ copyFile(absSrc, absTrg);
58
+ } else if (driver === "github") {
59
+ try {
60
+ const data = await storage.getItem(key2);
61
+ if (data) {
62
+ data?.constructor.name === "Blob" ? await writeBlob(absTrg, data) : writeFile(absTrg, data);
63
+ } else {
64
+ warn(`No data for key "${key2}"`);
65
+ }
66
+ } catch (err) {
67
+ warn(err.message);
68
+ }
69
+ }
70
+ return absTrg;
71
+ }
72
+ async function getKeys() {
73
+ const keys = await storage.getKeys();
74
+ return keys.filter(isAsset);
75
+ }
76
+ async function init() {
77
+ const keys = await getKeys();
78
+ const paths = [];
79
+ for (const key2 of keys) {
80
+ const path = await copyItem(key2);
81
+ paths.push(path);
82
+ }
83
+ return paths;
84
+ }
85
+ const storage = makeStorage(source, key);
86
+ storage.watch(onWatch);
87
+ return {
88
+ storage,
89
+ init,
90
+ keys: getKeys
91
+ };
39
92
  }
@@ -0,0 +1,2 @@
1
+ import { Callback, SocketInstance } from '../../types';
2
+ export declare function useSockets(channel: string, callback?: Callback): Promise<SocketInstance | null>;
@@ -0,0 +1,12 @@
1
+ import { useRuntimeConfig } from "#imports";
2
+ export function useSockets(channel, callback) {
3
+ const url = useRuntimeConfig().public.sockets?.wsUrl;
4
+ return new Promise(function(resolve) {
5
+ if (process.client && url) {
6
+ return import("./setup").then(({ setupSocketClient }) => {
7
+ return resolve(setupSocketClient(channel, callback));
8
+ });
9
+ }
10
+ resolve(null);
11
+ });
12
+ }
@@ -0,0 +1,5 @@
1
+ import { Callback } from '../../types';
2
+ export declare function createWebSocket(): {
3
+ send: (data: any) => void;
4
+ addHandler(callback: Callback): void;
5
+ } | null;
@@ -0,0 +1,86 @@
1
+ import { useRuntimeConfig } from "#app";
2
+ const plugin = "[Content Assets]";
3
+ const logger = {
4
+ // eslint-disable-next-line no-console
5
+ log: (...args) => console.log(plugin, ...args),
6
+ // eslint-disable-next-line no-console
7
+ warn: (...args) => console.warn(plugin, ...args)
8
+ };
9
+ let ws;
10
+ export function createWebSocket() {
11
+ if (!window.WebSocket) {
12
+ logger.warn("Your browser does not support WebSocket");
13
+ return null;
14
+ }
15
+ const onOpen = () => logger.log("WS connected!");
16
+ const onError = (e) => {
17
+ switch (e.code) {
18
+ case "ECONNREFUSED":
19
+ connect(true);
20
+ break;
21
+ default:
22
+ logger.warn("Socket error:", e);
23
+ break;
24
+ }
25
+ };
26
+ const onClose = (e) => {
27
+ if (e.code === 1e3 || e.code === 1005) {
28
+ logger.log("Socket closed");
29
+ } else {
30
+ connect(true);
31
+ }
32
+ };
33
+ const handlers = [];
34
+ const onMessage = (message) => {
35
+ let data;
36
+ try {
37
+ data = JSON.parse(message.data);
38
+ } catch (err) {
39
+ logger.warn("Error parsing message:", message.data);
40
+ return;
41
+ }
42
+ handlers.forEach((handler) => {
43
+ if (typeof handler === "function") {
44
+ handler(data);
45
+ }
46
+ });
47
+ };
48
+ const send = (data) => {
49
+ if (ws) {
50
+ ws.send(JSON.stringify(data));
51
+ }
52
+ };
53
+ const connect = (retry = false) => {
54
+ if (retry) {
55
+ logger.log("WS reconnecting..");
56
+ setTimeout(connect, 1e3);
57
+ return;
58
+ }
59
+ if (ws) {
60
+ try {
61
+ ws.close();
62
+ } catch (err) {
63
+ }
64
+ ws = void 0;
65
+ }
66
+ const url = useRuntimeConfig().public.sockets?.wsUrl;
67
+ if (url) {
68
+ const wsUrl = `${url}ws`;
69
+ logger.log(`Running on ${wsUrl}`);
70
+ ws = new WebSocket(wsUrl);
71
+ ws.onopen = onOpen;
72
+ ws.onmessage = onMessage;
73
+ ws.onerror = onError;
74
+ ws.onclose = onClose;
75
+ }
76
+ };
77
+ if (!ws) {
78
+ connect();
79
+ }
80
+ return {
81
+ send,
82
+ addHandler(callback) {
83
+ handlers.push(callback);
84
+ }
85
+ };
86
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,22 @@
1
+ import { defineNuxtPlugin } from "#imports";
2
+ import { useSockets } from "./composable.mjs";
3
+ export default defineNuxtPlugin(async () => {
4
+ if (process.client) {
5
+ const sockets = await useSockets("content-assets");
6
+ if (sockets) {
7
+ sockets.addHandler(({ data }) => {
8
+ const { event, src } = data;
9
+ if (src) {
10
+ const isUpdate = event === "update";
11
+ document.querySelectorAll(`img[src^="${src}"]`).forEach((el) => {
12
+ const img = el;
13
+ img.style.opacity = isUpdate ? "1" : "0.2";
14
+ if (isUpdate) {
15
+ img.setAttribute("src", `${src}?${(/* @__PURE__ */ new Date()).getTime()}`);
16
+ }
17
+ });
18
+ }
19
+ });
20
+ }
21
+ }
22
+ });
@@ -0,0 +1,2 @@
1
+ import { Callback, SocketInstance } from '../../types';
2
+ export declare function setupSocketClient(channel: string, callback?: Callback): SocketInstance | null;
@@ -0,0 +1,26 @@
1
+ import { createWebSocket } from "./factory.mjs";
2
+ const client = createWebSocket();
3
+ export function setupSocketClient(channel, callback) {
4
+ const instance = {
5
+ addHandler(callback2) {
6
+ if (client && typeof callback2 === "function") {
7
+ client.addHandler((data) => {
8
+ if (data.channel === channel) {
9
+ return callback2(data);
10
+ }
11
+ });
12
+ }
13
+ return this;
14
+ },
15
+ send(data) {
16
+ if (client) {
17
+ client.send({ channel, data });
18
+ }
19
+ return this;
20
+ }
21
+ };
22
+ if (callback) {
23
+ instance.addHandler(callback);
24
+ }
25
+ return instance;
26
+ }
@@ -3,14 +3,18 @@
3
3
  */
4
4
  export declare function isRelative(path: string): boolean;
5
5
  /**
6
- * Test path for image extension
6
+ * Test path or id for image extension
7
7
  */
8
8
  export declare function isImage(path: string): boolean;
9
9
  /**
10
- * Test path for asset extension
10
+ * Test path or id is markdown
11
+ */
12
+ export declare function isArticle(path: string): boolean;
13
+ /**
14
+ * Test path or id is asset
11
15
  */
12
16
  export declare function isAsset(path: string): boolean;
13
17
  /**
14
- * Test if value is a valid asset
18
+ * Test if value is a relative asset
15
19
  */
16
20
  export declare function isValidAsset(value?: string): boolean;
@@ -1,15 +1,18 @@
1
1
  import Path from "path";
2
- import { extensions, imageExtensions } from "../options.mjs";
2
+ import { extensions } from "../options.mjs";
3
3
  export function isRelative(path) {
4
4
  return !(path.startsWith("http") || Path.isAbsolute(path));
5
5
  }
6
6
  export function isImage(path) {
7
7
  const ext = Path.extname(path).substring(1);
8
- return imageExtensions.includes(ext);
8
+ return extensions.image.includes(ext);
9
+ }
10
+ export function isArticle(path) {
11
+ return /\.mdx?$/.test(path);
9
12
  }
10
13
  export function isAsset(path) {
11
- const ext = Path.extname(path).substring(1);
12
- return extensions.includes(ext);
14
+ const ext = Path.extname(path);
15
+ return !!ext && ext !== ".DS_Store" && !isArticle(path);
13
16
  }
14
17
  export function isValidAsset(value) {
15
18
  return typeof value === "string" && isAsset(value) && isRelative(value);
@@ -1,4 +1,7 @@
1
+ export declare function readFile(path: string, asJson?: boolean): any;
1
2
  export declare function writeFile(path: string, data: null | string | number | boolean | object): void;
3
+ export declare function writeBlob(path: string, data: object): Promise<void>;
2
4
  export declare function copyFile(src: string, trg: string): void;
5
+ export declare function removeFile(src: string): void;
3
6
  export declare function createFolder(path: string): void;
4
7
  export declare function removeFolder(path: string): void;
@@ -1,14 +1,26 @@
1
1
  import * as Path from "path";
2
2
  import * as Fs from "fs";
3
+ export function readFile(path, asJson = false) {
4
+ const text = Fs.readFileSync(path, { encoding: "utf8" });
5
+ return asJson ? JSON.parse(text) : text;
6
+ }
3
7
  export function writeFile(path, data) {
4
8
  const text = typeof data === "object" ? JSON.stringify(data, null, " ") : String(data);
5
9
  createFolder(Path.dirname(path));
6
10
  Fs.writeFileSync(path, text, { encoding: "utf8" });
7
11
  }
12
+ export async function writeBlob(path, data) {
13
+ const buffer = Buffer.from(await data.arrayBuffer());
14
+ createFolder(Path.dirname(path));
15
+ Fs.writeFileSync(path, buffer);
16
+ }
8
17
  export function copyFile(src, trg) {
9
18
  createFolder(Path.dirname(trg));
10
19
  Fs.copyFileSync(src, trg);
11
20
  }
21
+ export function removeFile(src) {
22
+ Fs.rmSync(src);
23
+ }
12
24
  export function createFolder(path) {
13
25
  Fs.mkdirSync(path, { recursive: true });
14
26
  }
@@ -2,3 +2,6 @@
2
2
  * Get matched words from a string
3
3
  */
4
4
  export declare function matchWords(value?: string): string[];
5
+ export declare function toPath(key: string): string;
6
+ export declare function toKey(path: string): any;
7
+ export declare function deKey(path: string): string;
@@ -1,3 +1,12 @@
1
1
  export function matchWords(value) {
2
2
  return typeof value === "string" ? value.match(/\w+/g) || [] : [];
3
3
  }
4
+ export function toPath(key) {
5
+ return key.replaceAll(":", "/");
6
+ }
7
+ export function toKey(path) {
8
+ return path.replaceAll("/", ":");
9
+ }
10
+ export function deKey(path) {
11
+ return path.replace(/^[^:]+:/, "");
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-content-assets",
3
- "version": "0.7.0",
3
+ "version": "0.9.0-beta",
4
4
  "description": "Enable locally-located assets in Nuxt Content",
5
5
  "repository": "davestewart/nuxt-content-assets",
6
6
  "license": "MIT",
@@ -32,11 +32,14 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@nuxt/kit": "^3.3.2",
35
+ "debounce": "^1.2.1",
35
36
  "glob": "^9.3.2",
36
37
  "image-size": "^1.0.2",
38
+ "listhen": "^1.0.4",
37
39
  "ohash": "^1.0.0",
38
40
  "unist-util-visit": "^4.1.2",
39
- "unstorage": "^1.4.1"
41
+ "unstorage": "^1.4.1",
42
+ "ws": "^8.13.0"
40
43
  },
41
44
  "peerDependencies": {
42
45
  "@nuxt/content": "latest"
@@ -47,6 +50,8 @@
47
50
  "@nuxt/module-builder": "^0.2.1",
48
51
  "@nuxt/schema": "^3.3.2",
49
52
  "@nuxt/test-utils": "^3.3.2",
53
+ "@types/debounce": "^1.2.1",
54
+ "@types/ws": "^8.5.4",
50
55
  "changelogen": "^0.5.1",
51
56
  "eslint": "^8.36.0",
52
57
  "nuxt": "^3.3.2",