@wizzlethorpe/vaults 0.13.2 → 0.13.5
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/dist/foundry-importer.bundle.js +79 -12
- package/package.json +1 -1
|
@@ -131,13 +131,19 @@ async function syncImages(host, vault, manifestFiles) {
|
|
|
131
131
|
}
|
|
132
132
|
const lastImageManifest = host.getVaultState(vault.id).lastImageManifest;
|
|
133
133
|
const last = new Map(Object.entries(lastImageManifest || {}));
|
|
134
|
+
const baseDir = vaultCacheDir(vault.id);
|
|
135
|
+
const present = await listCached(baseDir, last.keys());
|
|
136
|
+
const vanished = [...last.keys()].filter((p) => !present.has(p));
|
|
137
|
+
for (const path of vanished) last.delete(path);
|
|
138
|
+
if (vanished.length > 0) {
|
|
139
|
+
console.info(`Vaults | ${vanished.length} cached file(s) are gone from ${baseDir}; re-fetching.`);
|
|
140
|
+
}
|
|
134
141
|
const toDownload = [];
|
|
135
142
|
for (const [path, hash] of remoteImages) {
|
|
136
143
|
if (last.get(path) !== hash) toDownload.push(path);
|
|
137
144
|
}
|
|
138
|
-
|
|
145
|
+
let toDelete = [...last.keys()].filter((p) => !remoteImages.has(p));
|
|
139
146
|
if (toDownload.length === 0 && toDelete.length === 0) return { downloaded: 0, removed: 0, errors: 0 };
|
|
140
|
-
const baseDir = vaultCacheDir(vault.id);
|
|
141
147
|
const worldRoot = `worlds/${game.world.id}`;
|
|
142
148
|
const dirsNeeded = /* @__PURE__ */ new Set();
|
|
143
149
|
const addChain = (fullPath) => {
|
|
@@ -155,6 +161,7 @@ async function syncImages(host, vault, manifestFiles) {
|
|
|
155
161
|
if (segs.length > 0) addChain(`${baseDir}/${segs.join("/")}`);
|
|
156
162
|
}
|
|
157
163
|
await ensureDirs([...dirsNeeded]);
|
|
164
|
+
await writeCacheMarker(baseDir, vault);
|
|
158
165
|
const sizeOf = /* @__PURE__ */ new Map();
|
|
159
166
|
for (const f of manifestFiles) sizeOf.set(f.path, f.size ?? 0);
|
|
160
167
|
const chunks = [];
|
|
@@ -203,6 +210,10 @@ async function syncImages(host, vault, manifestFiles) {
|
|
|
203
210
|
console.warn(`Vaults | ${errors.length} image(s) failed to download:`, errors);
|
|
204
211
|
}
|
|
205
212
|
let removed = 0;
|
|
213
|
+
if (toDelete.length > 0 && !canDelete()) {
|
|
214
|
+
console.info(`Vaults | ${toDelete.length} cached image(s) are no longer in the vault. Foundry provides no API to delete files, so they stay in ${baseDir} until removed by hand.`);
|
|
215
|
+
toDelete = [];
|
|
216
|
+
}
|
|
206
217
|
for (const path of toDelete) {
|
|
207
218
|
try {
|
|
208
219
|
await deleteFromWorld(baseDir, path);
|
|
@@ -302,6 +313,47 @@ async function ensureDirs(paths) {
|
|
|
302
313
|
}
|
|
303
314
|
}
|
|
304
315
|
}
|
|
316
|
+
async function writeCacheMarker(baseDir, vault) {
|
|
317
|
+
const body = JSON.stringify({
|
|
318
|
+
label: vault.label ?? null,
|
|
319
|
+
url: vault.url ?? null,
|
|
320
|
+
vaultId: vault.id,
|
|
321
|
+
note: "Cache for one vault. Safe to delete entirely; the next sync re-downloads it."
|
|
322
|
+
}, null, 2);
|
|
323
|
+
try {
|
|
324
|
+
await uploadToWorld(baseDir, "vault-info.json", new Blob([body], { type: "application/json" }));
|
|
325
|
+
} catch (err) {
|
|
326
|
+
console.debug(`Vaults | could not write cache marker in ${baseDir}:`, err?.message || err);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async function listCached(baseDir, paths) {
|
|
330
|
+
const byDir = /* @__PURE__ */ new Map();
|
|
331
|
+
for (const p of paths) {
|
|
332
|
+
const segs = p.split("/");
|
|
333
|
+
segs.pop();
|
|
334
|
+
const dir = segs.join("/");
|
|
335
|
+
if (!byDir.has(dir)) byDir.set(dir, []);
|
|
336
|
+
byDir.get(dir).push(p);
|
|
337
|
+
}
|
|
338
|
+
const present = /* @__PURE__ */ new Set();
|
|
339
|
+
for (const dir of byDir.keys()) {
|
|
340
|
+
const full = dir ? `${baseDir}/${dir}` : baseDir;
|
|
341
|
+
let listing;
|
|
342
|
+
try {
|
|
343
|
+
listing = await fp().browse("data", full);
|
|
344
|
+
} catch {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
for (const file of listing?.files ?? []) {
|
|
348
|
+
const name = decodeURIComponent(String(file).split("/").pop());
|
|
349
|
+
present.add(dir ? `${dir}/${name}` : name);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return present;
|
|
353
|
+
}
|
|
354
|
+
function canDelete() {
|
|
355
|
+
return typeof fp().deleteFile === "function";
|
|
356
|
+
}
|
|
305
357
|
async function deleteFromWorld(baseDir, path) {
|
|
306
358
|
const full = `${baseDir}/${path}`;
|
|
307
359
|
const impl = fp();
|
|
@@ -364,7 +416,9 @@ function buildPathIndex(manifestFiles) {
|
|
|
364
416
|
docTargets.set(mdPath, docName);
|
|
365
417
|
}
|
|
366
418
|
}
|
|
367
|
-
|
|
419
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
420
|
+
for (const f of manifestFiles) if (f.hash) hashes.set(f.path, f.hash);
|
|
421
|
+
return { paths, idOverrides, docTargets, hashes };
|
|
368
422
|
}
|
|
369
423
|
function docNameFromBase(base) {
|
|
370
424
|
if (typeof base !== "string" || !base) return null;
|
|
@@ -398,9 +452,9 @@ function decodeHtmlEntities(s) {
|
|
|
398
452
|
ta.innerHTML = s;
|
|
399
453
|
return ta.value;
|
|
400
454
|
}
|
|
401
|
-
async function transformHtmlForFoundry(vault, html, index) {
|
|
455
|
+
async function transformHtmlForFoundry(vault, html, index, mediaRefs) {
|
|
402
456
|
html = await rewriteWikilinks(vault.id, html, index);
|
|
403
|
-
html = rewriteMediaSrcs(vault.id, html);
|
|
457
|
+
html = rewriteMediaSrcs(vault.id, html, index?.hashes, mediaRefs);
|
|
404
458
|
html = rewritePassthroughLinks(vault.id, html);
|
|
405
459
|
html = await applyDomTransforms(html, vault, index);
|
|
406
460
|
return html;
|
|
@@ -537,14 +591,20 @@ async function rewriteWikilinks(vaultId, html, index) {
|
|
|
537
591
|
}
|
|
538
592
|
return html;
|
|
539
593
|
}
|
|
540
|
-
function rewriteMediaSrcs(vaultId, html) {
|
|
594
|
+
function rewriteMediaSrcs(vaultId, html, hashes, mediaRefs) {
|
|
541
595
|
return html.replace(MEDIA_SRC_RE, (full, tag, before, src, after) => {
|
|
542
596
|
if (!src.startsWith("/")) return full;
|
|
543
597
|
const path = decodeURIComponent(src.replace(/^\//, ""));
|
|
544
598
|
if (!CACHED_EXT_RE.test(path)) return full;
|
|
545
|
-
|
|
599
|
+
mediaRefs?.add(path);
|
|
600
|
+
const url2 = localFileUrl(vaultId, path) + mediaVersion(hashes, path);
|
|
601
|
+
return `<${tag}${before}src="${escapeAttr(url2)}"${after}>`;
|
|
546
602
|
});
|
|
547
603
|
}
|
|
604
|
+
function mediaVersion(hashes, path) {
|
|
605
|
+
const hash = hashes?.get(path);
|
|
606
|
+
return hash ? `?v=${encodeURIComponent(String(hash).slice(0, 12))}` : "";
|
|
607
|
+
}
|
|
548
608
|
function rewritePassthroughLinks(vaultId, html) {
|
|
549
609
|
return html.replace(ANCHOR_RE, (full, attrs, inner) => {
|
|
550
610
|
const cls = ATTR_CLASS_RE.exec(attrs)?.[1] || "";
|
|
@@ -629,8 +689,8 @@ function buildFolderInfo(mdPaths) {
|
|
|
629
689
|
function isIndexFile(filename) {
|
|
630
690
|
return filename.replace(/\.md$/i, "") === INDEX_BASENAME;
|
|
631
691
|
}
|
|
632
|
-
async function upsertFile(vault, path, body, index, meta, folderInfo) {
|
|
633
|
-
let html = await transformHtmlForFoundry(vault, body, index);
|
|
692
|
+
async function upsertFile(vault, path, body, index, meta, folderInfo, mediaRefs) {
|
|
693
|
+
let html = await transformHtmlForFoundry(vault, body, index, mediaRefs);
|
|
634
694
|
html = await appendInstanceDocLink(html, vault, path, meta);
|
|
635
695
|
const segs = path.split("/");
|
|
636
696
|
const filename = segs.pop();
|
|
@@ -1177,7 +1237,10 @@ async function sync(host, vault, { forceFull = false } = {}) {
|
|
|
1177
1237
|
for (const f of files) {
|
|
1178
1238
|
if (f.meta && f.path.endsWith(".body.html")) bodyMetaIndex.set(f.path, f.meta);
|
|
1179
1239
|
}
|
|
1180
|
-
const
|
|
1240
|
+
const prevImages = forceFull ? /* @__PURE__ */ new Map() : new Map(Object.entries(lastSync.lastImageManifest || {}));
|
|
1241
|
+
const lastMediaRefs = lastSync.lastMediaRefs || {};
|
|
1242
|
+
const mediaStale = (bodyPath) => (lastMediaRefs[bodyPath] || []).some((m) => remote.get(m) !== prevImages.get(m));
|
|
1243
|
+
const toUpsert = bodyPaths.filter((p) => remote.get(p) !== local.get(p) || mediaStale(p));
|
|
1181
1244
|
const toDelete = [...local.keys()].filter((p) => p.endsWith(".body.html") && !remote.has(p));
|
|
1182
1245
|
if (forceFull) await host.setVaultState(vault.id, { lastImageManifest: {} });
|
|
1183
1246
|
let imageStats = { downloaded: 0, removed: 0, errors: 0 };
|
|
@@ -1215,6 +1278,8 @@ async function sync(host, vault, { forceFull = false } = {}) {
|
|
|
1215
1278
|
return { ok: false, refreshHandlerAssets: false };
|
|
1216
1279
|
}
|
|
1217
1280
|
let added = 0, modified = 0, instances = 0;
|
|
1281
|
+
const mediaRefs = {};
|
|
1282
|
+
for (const p of bodyPaths) if (lastMediaRefs[p]) mediaRefs[p] = lastMediaRefs[p];
|
|
1218
1283
|
for (const bodyPath of toUpsert) {
|
|
1219
1284
|
const html = bodies.get(bodyPath);
|
|
1220
1285
|
if (html == null) {
|
|
@@ -1227,7 +1292,9 @@ async function sync(host, vault, { forceFull = false } = {}) {
|
|
|
1227
1292
|
if (pageMeta?.foundry?.journal === false) {
|
|
1228
1293
|
await deleteFile(vault, logicalPath);
|
|
1229
1294
|
} else {
|
|
1230
|
-
const
|
|
1295
|
+
const refs = /* @__PURE__ */ new Set();
|
|
1296
|
+
const result = await upsertFile(vault, logicalPath, html, pathIndex, pageMeta, folderInfo, refs);
|
|
1297
|
+
mediaRefs[bodyPath] = [...refs];
|
|
1231
1298
|
if (result === "added") added++;
|
|
1232
1299
|
else modified++;
|
|
1233
1300
|
}
|
|
@@ -1258,7 +1325,7 @@ async function sync(host, vault, { forceFull = false } = {}) {
|
|
|
1258
1325
|
console.warn(`Vaults | delete instance failed for ${logicalPath}:`, err);
|
|
1259
1326
|
}
|
|
1260
1327
|
}
|
|
1261
|
-
await host.setVaultState(vault.id, { lastManifest: Object.fromEntries(remote) });
|
|
1328
|
+
await host.setVaultState(vault.id, { lastManifest: Object.fromEntries(remote), lastMediaRefs: mediaRefs });
|
|
1262
1329
|
await reconcileEntryPlacement(vault, folderInfo);
|
|
1263
1330
|
await reconcileOwnership(vault, bodyMetaIndex);
|
|
1264
1331
|
const seconds = ((Date.now() - start) / 1e3).toFixed(1);
|
package/package.json
CHANGED