@wizzlethorpe/vaults 0.14.0 → 0.15.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.
Files changed (67) hide show
  1. package/README.md +2 -2
  2. package/dist/asset-refs.js +18 -10
  3. package/dist/asset-refs.js.map +1 -1
  4. package/dist/build.js +159 -70
  5. package/dist/build.js.map +1 -1
  6. package/dist/commands/build.js +1 -54
  7. package/dist/commands/build.js.map +1 -1
  8. package/dist/config.js.map +1 -1
  9. package/dist/escape.js +20 -0
  10. package/dist/escape.js.map +1 -1
  11. package/dist/foundry-adventure.js +86 -0
  12. package/dist/foundry-adventure.js.map +1 -0
  13. package/dist/foundry-defaults.js +65 -0
  14. package/dist/foundry-defaults.js.map +1 -0
  15. package/dist/foundry-grafts.js +610 -0
  16. package/dist/foundry-grafts.js.map +1 -0
  17. package/dist/foundry-html.js +196 -0
  18. package/dist/foundry-html.js.map +1 -0
  19. package/dist/foundry-importer.bundle.js +39 -3
  20. package/dist/foundry-meta.js +15 -243
  21. package/dist/foundry-meta.js.map +1 -1
  22. package/dist/foundry-module-journal.js +24 -3
  23. package/dist/foundry-module-journal.js.map +1 -1
  24. package/dist/foundry-module.js +19 -12
  25. package/dist/foundry-module.js.map +1 -1
  26. package/dist/foundry-types.js +28 -0
  27. package/dist/foundry-types.js.map +1 -0
  28. package/dist/foundry-version.js +30 -0
  29. package/dist/foundry-version.js.map +1 -0
  30. package/dist/frontmatter-defaults.js +1 -1
  31. package/dist/frontmatter-defaults.js.map +1 -1
  32. package/dist/index.js +0 -4
  33. package/dist/index.js.map +1 -1
  34. package/dist/manifest.js +1 -1
  35. package/dist/manifest.js.map +1 -1
  36. package/dist/migrate/0.15-foundry-patch-keys.js +51 -0
  37. package/dist/migrate/0.15-foundry-patch-keys.js.map +1 -0
  38. package/dist/migrate/0.15-foundry-pinned-id.js +68 -0
  39. package/dist/migrate/0.15-foundry-pinned-id.js.map +1 -0
  40. package/dist/migrate/files.js +66 -0
  41. package/dist/migrate/files.js.map +1 -0
  42. package/dist/migrate/registry.js +4 -0
  43. package/dist/migrate/registry.js.map +1 -1
  44. package/dist/migrate/run.js +36 -2
  45. package/dist/migrate/run.js.map +1 -1
  46. package/dist/render/auth-template.js +9 -8
  47. package/dist/render/auth-template.js.map +1 -1
  48. package/dist/render/handlers/assets.js +5 -34
  49. package/dist/render/handlers/assets.js.map +1 -1
  50. package/dist/render/handlers/builtin/battlemap.js +4 -1
  51. package/dist/render/handlers/builtin/battlemap.js.map +1 -1
  52. package/dist/render/handlers/builtin/fm-code.js +2 -2
  53. package/dist/render/handlers/builtin/fm-code.js.map +1 -1
  54. package/dist/render/handlers/builtin/fvtt-link.js +39 -0
  55. package/dist/render/handlers/builtin/fvtt-link.js.map +1 -0
  56. package/dist/render/handlers/builtin/index.js +2 -1
  57. package/dist/render/handlers/builtin/index.js.map +1 -1
  58. package/dist/render/handlers/builtin/statblock.js +0 -4
  59. package/dist/render/handlers/builtin/statblock.js.map +1 -1
  60. package/dist/render/handlers/types.js.map +1 -1
  61. package/dist/scan.js +0 -8
  62. package/dist/scan.js.map +1 -1
  63. package/dist/settings.js +57 -28
  64. package/dist/settings.js.map +1 -1
  65. package/dist/zip.js +78 -0
  66. package/dist/zip.js.map +1 -0
  67. package/package.json +3 -3
@@ -0,0 +1,196 @@
1
+ // Turn a page's rendered article HTML into what a Foundry journal wants:
2
+ // links become Foundry UUIDs, media becomes `@vaults/<variant>/<path>`
3
+ // references the provider resolves on the reader's machine.
4
+ //
5
+ // The variant segment is load-bearing: each role's deploy holds only the
6
+ // files its pages reference, so resolving without it means guessing, and
7
+ // guessing upward hands a player a file their role was built to withhold.
8
+ // A `.foundry.html` suffix marks a body to inline; anything else is a file.
9
+ import { createHash } from "node:crypto";
10
+ import { htmlAttr, htmlUnescape } from "./escape.js";
11
+ const ANCHOR_RE = /<a\b([^>]*)>([\s\S]*?)<\/a>/gi;
12
+ const MEDIA_SRC_RE = /<(img|audio|video)\b([^>]*?)src="([^"]+)"([^>]*)>/gi;
13
+ const HREF_RE = /\bhref="([^"]+)"/i;
14
+ const CLASS_RE = /\bclass="([^"]+)"/i;
15
+ const TAG_RE = /<[^>]+>/g;
16
+ /** `"/Characters/Marlo"` back to `"Characters/Marlo.md"`. */
17
+ export function pathFromHref(href) {
18
+ // The entity pass comes first because it is the parser layer: the serializer
19
+ // wrote `'` as `&#x27;`, and only what it produces is percent-encoded.
20
+ const unescaped = htmlUnescape(href);
21
+ if (!unescaped.startsWith("/"))
22
+ return null;
23
+ const clean = unescaped.split("#")[0].split("?")[0];
24
+ let decoded;
25
+ try {
26
+ decoded = decodeURIComponent(clean);
27
+ }
28
+ catch {
29
+ decoded = clean;
30
+ }
31
+ const trimmed = decoded.replace(/^\/+/, "").replace(/\.html$/i, "");
32
+ return trimmed ? `${trimmed}.md` : null;
33
+ }
34
+ /** The UUID a link to `path` should carry, or null if nothing points there. */
35
+ export function uuidFor(path, index, prefer = "journal") {
36
+ const target = index.targets.get(path);
37
+ if (!target)
38
+ return null;
39
+ const docUuid = target.doc
40
+ ? (index.packaging === "adventure"
41
+ ? `${target.doc.type}.${target.doc.id}`
42
+ : `Compendium.${index.moduleId}.${target.doc.pack}.${target.doc.type}.${target.doc.id}`)
43
+ : null;
44
+ const pageUuid = target.entry && target.page
45
+ ? (index.packaging === "adventure"
46
+ ? `JournalEntry.${target.entry}.JournalEntryPage.${target.page}`
47
+ : `Compendium.${index.moduleId}.${index.journalPack}.JournalEntry.${target.entry}.JournalEntryPage.${target.page}`)
48
+ : null;
49
+ return prefer === "doc" ? (docUuid ?? pageUuid) : (pageUuid ?? docUuid);
50
+ }
51
+ const stripTags = (s) => s.replace(TAG_RE, "").trim();
52
+ /** `}` inside a label would close the enricher early. */
53
+ const escapeBraces = (s) => s.replace(/\{/g, "&lbrace;").replace(/\}/g, "&rbrace;");
54
+ /**
55
+ * Rewrite internal links to Foundry UUID enrichers.
56
+ *
57
+ * A link the index cannot place is left exactly as it is: an unresolved
58
+ * wikilink already renders as broken-styled text on the wiki, and turning it
59
+ * into a UUID that resolves to nothing would look worse in Foundry, not
60
+ * better.
61
+ */
62
+ export function rewriteLinks(html, index) {
63
+ return html.replace(ANCHOR_RE, (whole, attrs, inner) => {
64
+ const cls = CLASS_RE.exec(attrs)?.[1] ?? "";
65
+ const card = /\bbases-card\b/.test(cls);
66
+ if (!card && !/\binternal-link\b/.test(cls))
67
+ return whole;
68
+ if (/\bis-unresolved\b/.test(cls))
69
+ return whole;
70
+ const href = HREF_RE.exec(attrs)?.[1];
71
+ if (!href)
72
+ return whole;
73
+ const path = pathFromHref(href);
74
+ if (!path)
75
+ return whole;
76
+ const uuid = uuidFor(path, index, /\bfvtt-doc-link\b/.test(cls) ? "doc" : "journal");
77
+ if (!uuid)
78
+ return whole;
79
+ // A card's layout lives in its markup, which an @UUID enricher would
80
+ // flatten to a text link. A content-link anchor keeps the markup and
81
+ // Foundry's click handler opens the document all the same.
82
+ if (card) {
83
+ return `<a class="${htmlAttr(cls)} content-link" draggable="true" data-link="" data-uuid="${htmlAttr(uuid)}">${inner}</a>`;
84
+ }
85
+ const label = escapeBraces(stripTags(inner));
86
+ return label ? `@UUID[${uuid}]{${label}}` : `@UUID[${uuid}]`;
87
+ });
88
+ }
89
+ /**
90
+ * Point media at the variant-scoped file the provider should fetch.
91
+ *
92
+ * Where a file lands is a runtime fact — it depends on the world it is being
93
+ * built into — so the CLI names what it wants rather than guessing a path.
94
+ */
95
+ export function rewriteAssets(html, variant) {
96
+ const mark = (p) => `@vaults/${variant}/${p.replace(/^\/+/, "")}`;
97
+ let out = html.replace(MEDIA_SRC_RE, (whole, tag, before, src, after) => src.startsWith("/") ? `<${tag}${before}src="${htmlAttr(mark(src))}"${after}>` : whole);
98
+ out = out.replace(ANCHOR_RE, (whole, attrs, inner) => {
99
+ const cls = CLASS_RE.exec(attrs)?.[1] ?? "";
100
+ if (!/\bpassthrough-link\b/.test(cls))
101
+ return whole;
102
+ const href = HREF_RE.exec(attrs)?.[1];
103
+ if (!href?.startsWith("/"))
104
+ return whole;
105
+ return `<a${attrs.replace(HREF_RE, `href="${htmlAttr(mark(href))}"`)}>${inner}</a>`;
106
+ });
107
+ return out;
108
+ }
109
+ /**
110
+ * Rewrite `@vault/PATH` values inside a document patch to vault references.
111
+ *
112
+ * `@vault/` is the authoring form: written by hand in frontmatter, and left in
113
+ * the Scene sidecars exported from Foundry. It has no variant because the
114
+ * author is describing their own vault, where there is only one copy of the
115
+ * file. Resolving it means deciding which role's copy this document reads, and
116
+ * that is the same decision `visibility` already made for the body.
117
+ */
118
+ export function rewriteVaultRefs(value, variant) {
119
+ if (typeof value === "string") {
120
+ return (value.startsWith("@vault/")
121
+ ? `@vaults/${variant}/${value.slice("@vault/".length)}`
122
+ : value);
123
+ }
124
+ if (Array.isArray(value))
125
+ return value.map((v) => rewriteVaultRefs(v, variant));
126
+ if (value && typeof value === "object") {
127
+ return Object.fromEntries(Object.entries(value)
128
+ .map(([k, v]) => [k, rewriteVaultRefs(v, variant)]));
129
+ }
130
+ return value;
131
+ }
132
+ /** Everything a body needs before it can be a journal page. */
133
+ /**
134
+ * Wrap role-gated callouts in Foundry's own secret sections.
135
+ *
136
+ * A player-visible document carries the GM's body, so DM callouts reach the
137
+ * table inside it — hidden by Foundry from anyone below owner, which is what
138
+ * `<section class="secret">` means to a journal. Hidden, not absent: the text
139
+ * is in the document's data, the same trade the old sync made.
140
+ */
141
+ /** Where the element opening at `start` closes, balancing same-name tags. */
142
+ function elementEnd(html, start, tagName) {
143
+ const tag = new RegExp(`</?${tagName}\\b`, "g");
144
+ tag.lastIndex = start;
145
+ let depth = 0;
146
+ let t;
147
+ while ((t = tag.exec(html)) !== null) {
148
+ depth += t[0].startsWith("</") ? -1 : 1;
149
+ if (depth === 0)
150
+ return html.indexOf(">", t.index) + 1;
151
+ }
152
+ return -1;
153
+ }
154
+ /**
155
+ * A player-visible document's body: the GM's full rendering inside one
156
+ * secret section (stripped by Foundry below owner), then the player
157
+ * variant's in the open. The player gets the actual player render, so no
158
+ * difference between the two — base rows, transclusions, links — needs to
159
+ * be found and marked. The module's CSS hides the player copy for the GM.
160
+ */
161
+ export function dualVariantBody(gmHtml, playerHtml) {
162
+ const id = createHash("sha1").update(gmHtml).digest("hex").slice(0, 16);
163
+ return `<section class="secret vaults-gm" id="secret-${id}">${gmHtml}</section>`
164
+ + `<div class="vaults-player-view">${playerHtml}</div>`;
165
+ }
166
+ /**
167
+ * Drop every element carrying the `vaults-web-only` class from the Foundry
168
+ * body; the wiki keeps it. Handlers set it on their wrapper, and a page can
169
+ * put it on raw HTML of its own.
170
+ */
171
+ export function stripWebOnly(html) {
172
+ // The class matched as a whole token: \b treats a hyphen as a boundary, so
173
+ // it alone would also match "vaults-web-only-not".
174
+ const open = /<([a-zA-Z][a-zA-Z0-9-]*)\b[^>]*\bclass="(?:[^"]* )?vaults-web-only(?: [^"]*)?"[^>]*>/g;
175
+ let out = "";
176
+ let at = 0;
177
+ let m;
178
+ while ((m = open.exec(html)) !== null) {
179
+ if (m.index < at)
180
+ continue; // nested inside something already dropped
181
+ const end = VOID_TAGS.has(m[1].toLowerCase()) || m[0].endsWith("/>")
182
+ ? m.index + m[0].length
183
+ : elementEnd(html, m.index, m[1]);
184
+ if (end < 0)
185
+ continue; // unclosed: leave it, keep looking
186
+ out += html.slice(at, m.index);
187
+ at = end;
188
+ open.lastIndex = end;
189
+ }
190
+ return out + html.slice(at);
191
+ }
192
+ const VOID_TAGS = new Set(["img", "br", "hr", "input", "source", "track", "wbr", "embed", "area", "col", "link", "meta"]);
193
+ export function toFoundryHtml(html, index, variant) {
194
+ return rewriteAssets(rewriteLinks(stripWebOnly(html), index), variant);
195
+ }
196
+ //# sourceMappingURL=foundry-html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foundry-html.js","sourceRoot":"","sources":["../src/foundry-html.ts"],"names":[],"mappings":"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"}
@@ -85,6 +85,15 @@ var PACK_KEY = {
85
85
  Cards: "cards",
86
86
  Playlist: "playlists"
87
87
  };
88
+ var CORE_PAGE_TYPES = ["text", "image", "pdf", "video"];
89
+ function journalPageSpec(fm) {
90
+ const j = fm?.journal;
91
+ if (j === false) return null;
92
+ const overlay = j && typeof j === "object" && !Array.isArray(j) ? { ...j } : {};
93
+ const type = typeof overlay.type === "string" && overlay.type ? overlay.type : "text";
94
+ delete overlay.type;
95
+ return { type, overlay, dropsBody: type !== "text" };
96
+ }
88
97
  var BLANK_DOC_TYPES = [
89
98
  "Actor",
90
99
  "Item",
@@ -915,6 +924,28 @@ function buildFolderInfo(mdPaths) {
915
924
  function isIndexFile(filename) {
916
925
  return filename.replace(/\.md$/i, "") === INDEX_BASENAME;
917
926
  }
927
+ function resolvePageType(want, path) {
928
+ if (want === "text") return "text";
929
+ const known = /* @__PURE__ */ new Set([
930
+ ...CORE_PAGE_TYPES,
931
+ ...game.documentTypes?.JournalEntryPage ?? []
932
+ ]);
933
+ if (known.has(want)) return want;
934
+ console.warn(
935
+ `Vaults | ${path}: this world has no "${want}" journal page type (${game.system?.id ?? "no system"} provides ${[...known].join(", ")}). Imported as a text page instead.`
936
+ );
937
+ return "text";
938
+ }
939
+ function deepMergePage(target, patch) {
940
+ for (const [k, v] of Object.entries(patch)) {
941
+ if (v && typeof v === "object" && !Array.isArray(v) && target[k] && typeof target[k] === "object" && !Array.isArray(target[k])) {
942
+ deepMergePage(target[k], v);
943
+ } else {
944
+ target[k] = v;
945
+ }
946
+ }
947
+ return target;
948
+ }
918
949
  async function upsertFile(target, vault, path, body, index, meta, folderInfo, mediaRefs) {
919
950
  let html = await transformHtmlForFoundry(vault, body, index, mediaRefs);
920
951
  html = await appendInstanceDocLink(html, vault, path, meta);
@@ -932,15 +963,19 @@ async function upsertFile(target, vault, path, body, index, meta, folderInfo, me
932
963
  const pId = typeof idOverride === "string" && idOverride ? idOverride : await pageId(vault.id, path);
933
964
  const pageOwnership = pageOwnershipLevelFor(vault, meta?.role);
934
965
  const flags = { [MODULE_ID]: { vaultId: vault.id, path } };
966
+ const spec = journalPageSpec(meta?.foundry);
967
+ const type = resolvePageType(spec.type, path);
935
968
  const pageData = {
936
969
  _id: pId,
937
970
  name: pageName,
938
- type: "text",
939
- text: {
971
+ type,
972
+ // A page whose content is its `src` has nowhere to put an article, so the
973
+ // body is dropped rather than written somewhere it will not be read.
974
+ ...type === "text" ? { text: {
940
975
  content: html,
941
976
  format: 1
942
977
  /* HTML */
943
- },
978
+ } } : {},
944
979
  sort: isIndexFile(filename) ? 0 : NON_INDEX_SORT_BASE,
945
980
  flags,
946
981
  // Ownership is set on the *page*, not the parent entry: in the
@@ -952,6 +987,7 @@ async function upsertFile(target, vault, path, body, index, meta, folderInfo, me
952
987
  // page in it). See reconcileOwnership below.
953
988
  ...pageOwnership !== null ? { ownership: { default: pageOwnership } } : {}
954
989
  };
990
+ if (Object.keys(spec.overlay).length > 0) deepMergePage(pageData, spec.overlay);
955
991
  const existing = await target.get("JournalEntry", eId);
956
992
  if (existing) {
957
993
  const pages = (existing.pages ?? []).filter((pg) => pg._id !== pId);
@@ -1,50 +1,26 @@
1
- // Everything the CLI knows about the `foundry:` frontmatter block.
2
- //
3
- // Kept in one file because this is a shared contract, not a private detail:
4
- // foundry/scripts/ reads exactly these keys back out of the manifest, and the
5
- // two sides disagreeing is how `base: actor:npc` once created an Actor while
6
- // every inbound wikilink addressed a nonexistent "actor". The doc-type rule
7
- // here is held to foundry/scripts/foundry-base.mjs by
8
- // cli/test/foundry-base-conformance.test.ts.
1
+ // What the CLI knows about the `foundry:` frontmatter block: which document a
2
+ // `source` names, and whether two pages are fighting over the same one.
9
3
  import { readFile } from "node:fs/promises";
10
4
  import { join } from "node:path";
11
- /**
12
- * Whether a `foundry.base` will actually produce a document.
13
- *
14
- * Both forms do, for every type vaults can instantiate. This used to be
15
- * narrower — the module cloned from a UUID only for Actor and Item, so a lone
16
- * `Compendium.<pkg>.<pack>.Scene.<id>` created nothing and warning about a
17
- * collision would have described documents that never exist. That restriction
18
- * is gone (map packs ship their content as compendium Scenes, and those were
19
- * all being skipped), so a well-formed base always produces a document and
20
- * the only question left is whether the type is one we recognise.
21
- *
22
- * All entries name the same type by the time this runs, so `docType` answers
23
- * for the whole list.
24
- */
25
- function willInstantiate(_specs, docType) {
26
- return canonicalFoundryType(docType) !== null;
27
- }
5
+ import { DOC_TYPES } from "./foundry-types.js";
6
+ import { documentFolder, documentTypeOf, firstBase } from "./foundry-grafts.js";
28
7
  export function warnFoundryDocCollisions(pages) {
29
8
  const seen = new Map(); // key → first page path
30
9
  for (const p of pages) {
31
10
  const fo = p.frontmatter?.["foundry"];
32
11
  if (!fo || typeof fo !== "object" || Array.isArray(fo))
33
12
  continue;
34
- const base = fo["base"];
35
- const specs = (Array.isArray(base) ? base : [base])
36
- .filter((x) => typeof x === "string" && x.length > 0);
37
- if (specs.length === 0)
13
+ if (fo["sync"] === false)
38
14
  continue;
39
- const docType = foundryBaseDocName(specs[0]);
40
- if (!docType)
15
+ const spec = firstBase(fo["source"]);
16
+ if (!spec)
41
17
  continue;
42
- if (!willInstantiate(specs, docType))
18
+ // The same reading the emitter uses, so a warning never describes a
19
+ // document that will not exist: a type with no pack produces nothing.
20
+ const docType = documentTypeOf(spec);
21
+ if (!docType || !DOC_TYPES[docType])
43
22
  continue;
44
- const override = fo["folder"];
45
- const folder = typeof override === "string" && override.trim()
46
- ? override.trim().replace(/^\/+|\/+$/g, "")
47
- : p.path.split("/").slice(0, -1).join("/");
23
+ const folder = documentFolder({ path: p.path, foundry: fo });
48
24
  const name = p.title || p.path.split("/").pop().replace(/\.md$/i, "");
49
25
  const key = `${docType}\u0000${folder}\u0000${name}`;
50
26
  const previous = seen.get(key);
@@ -57,208 +33,7 @@ export function warnFoundryDocCollisions(pages) {
57
33
  + `separate them with foundry.folder.`);
58
34
  }
59
35
  }
60
- /**
61
- * The document type a `foundry.base` spec names, read off the string without
62
- * resolving anything. Every UUID form puts the type second-to-last
63
- * (`Actor.<id>`, `Compendium.<pkg>.<pack>.Actor.<id>`), and a blank-doc spec
64
- * (`Actor:npc`) carries it outright. `links.mjs` derives it the same way, so
65
- * both sides agree on where a wikilink to the page points.
66
- */
67
- export function foundryBaseDocName(spec) {
68
- // A Moulinette reference names no type. Checked before the UUID rule
69
- // because its file segment ends in ".json", which the dot test would
70
- // otherwise read as a UUID. The type comes from another entry in the list;
71
- // normalizeFoundryBase makes sure one is there.
72
- if (spec.startsWith("@moulinette/"))
73
- return null;
74
- if (spec.includes(".")) {
75
- const parts = spec.split(".");
76
- if (parts.length < 2)
77
- return null;
78
- const raw = parts[parts.length - 2];
79
- // Unknown types pass through: vaults can't instantiate a Combat, but
80
- // Foundry may still resolve the UUID, and reporting the type beats
81
- // claiming the spec names none.
82
- return canonicalFoundryType(raw) ?? raw ?? null;
83
- }
84
- // Blank-document form. An unrecognised type is not a base at all, so it is
85
- // rejected rather than passed through — matching the Foundry module, which
86
- // would create nothing for it.
87
- return canonicalFoundryType(spec.split(":")[0]);
88
- }
89
- /**
90
- * Fold a type segment to its canonical spelling, or null if vaults doesn't
91
- * instantiate it.
92
- *
93
- * Case matters downstream and is hand-typed here: `base: actor:npc` is
94
- * supported, but Foundry's `@UUID[...]` enricher does a case-sensitive
95
- * lookup. Returning "actor" made the CLI treat it as a different type from
96
- * "Actor" — so a list mixing the two failed the same-type check and had its
97
- * whole foundry.base dropped. Kept in step with
98
- * foundry/scripts/foundry-base.mjs by cli/test/foundry-base-conformance.test.ts.
99
- */
100
- export function canonicalFoundryType(raw) {
101
- if (!raw)
102
- return null;
103
- return FOUNDRY_BLANK_DOC_TYPES.find((t) => t.toLowerCase() === raw.toLowerCase()) ?? null;
104
- }
105
- export const FOUNDRY_BLANK_DOC_TYPES = [
106
- "Actor", "Item", "Scene", "JournalEntry",
107
- "RollTable", "Macro", "Cards", "Playlist",
108
- ];
109
- /**
110
- * Validate `foundry.base` and normalize it for the manifest: a single string
111
- * stays a string (so an older Foundry module keeps working), a list of two or
112
- * more stays a list. Returns null when the value can't be used, having said
113
- * why — the build continues, and the page syncs as a journal with no document.
114
- *
115
- * Every entry must name the same document type. The module reads that type
116
- * off the spec rather than off a resolved template, and `links.mjs` has to
117
- * reach the same answer with no lookup at all, so a list that disagrees with
118
- * itself has no single answer to give.
119
- */
120
- function normalizeFoundryBase(base, pagePath) {
121
- const raw = Array.isArray(base) ? base : [base];
122
- const specs = [];
123
- for (const entry of raw) {
124
- if (typeof entry !== "string" || entry.trim().length === 0) {
125
- console.warn(` ${pagePath}: foundry.base entries must be non-empty strings (a UUID like `
126
- + `"Compendium.<pkg>.<pack>.Actor.<id>", or a type like "Actor:npc"); `
127
- + `got ${entry === null ? "null" : typeof entry}. Ignoring foundry.base — this page `
128
- + `will sync as a journal but create no document.`);
129
- return null;
130
- }
131
- specs.push(entry.trim());
132
- }
133
- if (specs.length === 0) {
134
- console.warn(` ${pagePath}: foundry.base is an empty list; ignoring.`);
135
- return null;
136
- }
137
- const types = new Map(); // docName → first spec that named it
138
- for (const spec of specs) {
139
- // A Moulinette entry is deliberately typeless: only the reader's own
140
- // library knows whether that asset is a Scene or an Actor, and this runs
141
- // at build time with no library to ask.
142
- if (spec.startsWith("@moulinette/"))
143
- continue;
144
- const docName = foundryBaseDocName(spec);
145
- if (!docName) {
146
- console.warn(` ${pagePath}: foundry.base entry "${spec}" names no document type; ignoring foundry.base.`);
147
- return null;
148
- }
149
- if (!types.has(docName))
150
- types.set(docName, spec);
151
- }
152
- if (types.size === 0) {
153
- console.warn(` ${pagePath}: foundry.base names only Moulinette references, which carry no document `
154
- + `type. Add an entry naming the type (e.g. "Scene") — it is also what the page falls `
155
- + `back to for a reader without that pack. Ignoring foundry.base.`);
156
- return null;
157
- }
158
- if (types.size > 1) {
159
- const detail = [...types].map(([t, spec]) => `${t} (from "${spec}")`).join(", ");
160
- console.warn(` ${pagePath}: every foundry.base entry must name the same document type, got ${detail}. `
161
- + `Ignoring foundry.base — this page will sync as a journal but create no document.`);
162
- return null;
163
- }
164
- // A list whose last entry is a UUID can still fail on a world that lacks
165
- // every package named. A blank-doc tail is what makes the chain total.
166
- const last = specs[specs.length - 1];
167
- if (specs.length > 1 && (last.includes(".") || last.startsWith("@moulinette/"))) {
168
- console.warn(` ${pagePath}: foundry.base list ends with "${last}", so it can still resolve to nothing. `
169
- + `End with a blank-document entry (e.g. "${[...types.keys()][0]}:npc" or "${[...types.keys()][0]}") `
170
- + `to guarantee a document.`);
171
- }
172
- return specs.length === 1 ? specs[0] : specs;
173
- }
174
- export async function collectBodyMeta(p, vaultPath) {
175
- const fm = p.frontmatter ?? {};
176
- const out = { role: p.role };
177
- const basename = p.path.split("/").pop().replace(/\.md$/i, "");
178
- if (p.title && p.title !== basename)
179
- out.title = p.title;
180
- const fo = fm["foundry"];
181
- if (fo && typeof fo === "object" && !Array.isArray(fo)) {
182
- const block = {};
183
- // `base` is one spec, or a priority list the module tries in order so a
184
- // vault degrades across worlds with different content installed. A
185
- // malformed base is dropped with a warning rather than failing the build,
186
- // same as foundry.id below. Silence here is worse than it looks: the
187
- // module never receives the key, so it can't report the page either, and
188
- // the page syncs as a journal with no Actor/Item and no explanation.
189
- const base = fo["base"];
190
- if (base !== undefined && base !== null) {
191
- const normalized = normalizeFoundryBase(base, p.path);
192
- if (normalized !== null)
193
- block.base = normalized;
194
- }
195
- const embed = fo["embed"];
196
- if (typeof embed === "boolean")
197
- block.embed = embed;
198
- // foundry.sync: false keeps the page out of Foundry altogether — no
199
- // JournalEntryPage, no derived doc. The page still renders on the wiki.
200
- // Unlike `embed`, which only suppresses the article inside a derived
201
- // doc's description, this drops the page from the sync set entirely.
202
- const sync = fo["sync"];
203
- if (typeof sync === "boolean")
204
- block.sync = sync;
205
- // foundry.journal: false makes the derived doc without the JournalEntryPage
206
- // that normally accompanies it. For a page that exists to carry a Scene or
207
- // an Actor and has no article worth reading in the sidebar.
208
- const journal = fo["journal"];
209
- if (typeof journal === "boolean")
210
- block.journal = journal;
211
- // foundry.link: "doc" makes wikilinks to this page resolve to the document
212
- // it instantiates rather than to its journal page. Implied by
213
- // `journal: false`, where there is no journal page to link to.
214
- const link = fo["link"];
215
- if (link === "doc" || link === "journal")
216
- block.link = link;
217
- const data = fo["data"];
218
- if (data && typeof data === "object" && !Array.isArray(data))
219
- block.data = data;
220
- // foundry.folder: a "/"-separated folder path the instantiated doc is
221
- // filed under, nested inside the vault's own sidebar folder. Absent
222
- // means the vault folder itself, which is where everything used to land.
223
- const folder = fo["folder"];
224
- if (typeof folder === "string" && folder.trim().length > 0)
225
- block.folder = folder.trim();
226
- // foundry.id: an explicit Foundry document id for this page. When set,
227
- // overrides the SHA1-derived id used for both the JournalEntryPage and
228
- // (if foundry.base is present) the instantiated derived doc. Lets users
229
- // hardcode UUIDs that other Foundry-side code (macros, scene flags,
230
- // module integrations) needs to reference. Foundry ids are 16 chars from
231
- // [A-Za-z0-9]; a malformed value is dropped with a warning rather than
232
- // failing the build.
233
- const idVal = fo["id"];
234
- if (typeof idVal === "string") {
235
- const trimmed = idVal.trim();
236
- if (FOUNDRY_ID_RE.test(trimmed))
237
- block.id = trimmed;
238
- else if (trimmed.length > 0) {
239
- console.warn(` ${p.path}: foundry.id "${trimmed}" is not a valid Foundry id (16 chars [A-Za-z0-9]); ignoring`);
240
- }
241
- }
242
- // foundry.data_json: vault-relative path to a JSON file. Read + parse
243
- // at build time and inline into the meta as `data_json`. The Foundry
244
- // module deep-merges it onto the base doc BEFORE foundry.data, so a
245
- // user can layer hand-tuned overrides on top of an exported sheet.
246
- // Folding the parsed object into meta means the body-row hash already
247
- // changes when the JSON content does — no separate change-detection.
248
- const dataJsonPath = fo["data_json"];
249
- if (typeof dataJsonPath === "string" && dataJsonPath.trim().length > 0) {
250
- const parsed = await loadDataJson(vaultPath, dataJsonPath.trim(), p.path);
251
- if (parsed !== null)
252
- block.data_json = parsed;
253
- }
254
- if (Object.keys(block).length > 0)
255
- out.foundry = block;
256
- }
257
- if (p.coverImage)
258
- out.image = p.coverImage;
259
- return out;
260
- }
261
- /** Read + parse a vault-relative JSON file referenced by `foundry.data_json`.
36
+ /** Read + parse a vault-relative JSON file referenced by `foundry.patch_json`.
262
37
  * Warns on missing / unparseable file and returns null so the page renders
263
38
  * without the overlay rather than failing the build. */
264
39
  export async function loadDataJson(vaultPath, relPath, pagePath) {
@@ -270,15 +45,12 @@ export async function loadDataJson(vaultPath, relPath, pagePath) {
270
45
  catch (err) {
271
46
  const code = err.code;
272
47
  if (code === "ENOENT") {
273
- console.warn(` ${pagePath}: foundry.data_json "${relPath}" not found, skipping`);
48
+ console.warn(` ${pagePath}: foundry.patch_json "${relPath}" not found, skipping`);
274
49
  }
275
50
  else {
276
- console.warn(` ${pagePath}: foundry.data_json "${relPath}" failed to parse: ${err.message}`);
51
+ console.warn(` ${pagePath}: foundry.patch_json "${relPath}" failed to parse: ${err.message}`);
277
52
  }
278
53
  return null;
279
54
  }
280
55
  }
281
- /** Foundry document ids: exactly 16 chars from [A-Za-z0-9]. Validated when
282
- * authors set `foundry.id` to override the SHA1-derived default. */
283
- const FOUNDRY_ID_RE = /^[A-Za-z0-9]{16}$/;
284
56
  //# sourceMappingURL=foundry-meta.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"foundry-meta.js","sourceRoot":"","sources":["../src/foundry-meta.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"foundry-meta.js","sourceRoot":"","sources":["../src/foundry-meta.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,wEAAwE;AAExE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGhF,MAAM,UAAU,wBAAwB,CAAC,KAAiB;IACxD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC,CAAC,wBAAwB;IAChE,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,CAAC,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YAAE,SAAS;QACjE,IAAK,EAA8B,CAAC,MAAM,CAAC,KAAK,KAAK;YAAE,SAAS;QAChE,MAAM,IAAI,GAAG,SAAS,CAAE,EAA8B,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClE,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,oEAAoE;QACpE,sEAAsE;QACtE,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;YAAE,SAAS;QAE9C,MAAM,MAAM,GAAG,cAAc,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,EAAyB,EAAE,CAAC,CAAC;QACpF,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAEvE,MAAM,GAAG,GAAG,GAAG,OAAO,SAAS,MAAM,SAAS,IAAI,EAAE,CAAC;QACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;YACtB,SAAS;QACX,CAAC;QACD,OAAO,CAAC,IAAI,CACV,KAAK,CAAC,CAAC,IAAI,oBAAoB,OAAO,WAAW,IAAI,wBAAwB;cAC3E,YAAY,MAAM,IAAI,cAAc,UAAU,QAAQ,oBAAoB;cAC1E,oCAAoC,CACvC,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;yDAEyD;AACzD,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,SAAiB,EACjB,OAAe,EACf,QAAgB;IAEhB,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACxC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;QACjD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,KAAK,QAAQ,yBAAyB,OAAO,uBAAuB,CAAC,CAAC;QACrF,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,KAAK,QAAQ,yBAAyB,OAAO,sBAAuB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAC5G,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -70,6 +70,20 @@ export function transformForModule(html, moduleId, targets, assets) {
70
70
  * an entry named for the module — the same shape the sync path produces, where
71
71
  * a directory becomes an entry and its files become that entry's pages.
72
72
  */
73
+ /** Deep-merge an overlay onto page data, arrays and scalars replacing. */
74
+ function deepMergePage(target, patch) {
75
+ for (const [k, v] of Object.entries(patch)) {
76
+ const cur = target[k];
77
+ if (v && typeof v === "object" && !Array.isArray(v)
78
+ && cur && typeof cur === "object" && !Array.isArray(cur)) {
79
+ deepMergePage(cur, v);
80
+ }
81
+ else {
82
+ target[k] = v;
83
+ }
84
+ }
85
+ return target;
86
+ }
73
87
  export function buildJournalEntries(sources, ns, rootName, stats) {
74
88
  const byFolder = new Map();
75
89
  for (const s of sources) {
@@ -86,18 +100,25 @@ export function buildJournalEntries(sources, ns, rootName, stats) {
86
100
  name: folder ? folder.split("/").pop() : rootName,
87
101
  pages: pages.map((p, i) => {
88
102
  const pid = journalPageId(ns, p.path);
89
- return {
103
+ const type = p.spec?.type ?? "text";
104
+ const page = {
90
105
  _id: pid,
91
106
  name: p.title,
92
- type: "text",
107
+ type,
93
108
  title: { show: true, level: 1 },
94
- text: { format: 1, content: p.html },
109
+ // A page whose content is its `src` has nowhere to put an article.
110
+ // Unlike sync, the compiler cannot degrade an unknown type: it does
111
+ // not know the system of the world this module will be installed in.
112
+ ...(type === "text" ? { text: { format: 1, content: p.html } } : {}),
95
113
  sort: (i + 1) * 100000,
96
114
  ownership: { default: -1 },
97
115
  flags: {},
98
116
  _stats: stats,
99
117
  _key: `!journal.pages!${id}.${pid}`,
100
118
  };
119
+ if (p.spec && Object.keys(p.spec.overlay).length > 0)
120
+ deepMergePage(page, p.spec.overlay);
121
+ return page;
101
122
  }),
102
123
  folder: null,
103
124
  sort: 0,