@wizzlethorpe/vaults 0.7.0 → 0.7.2
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 +8 -7
- package/dist/build.js +450 -123
- package/dist/build.js.map +1 -1
- package/dist/commands/build.js +2 -1
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/password.js +1 -1
- package/dist/commands/password.js.map +1 -1
- package/dist/commands/patreon.js +24 -20
- package/dist/commands/patreon.js.map +1 -1
- package/dist/commands/preview.js +5 -4
- package/dist/commands/preview.js.map +1 -1
- package/dist/commands/push.js +7 -8
- package/dist/commands/push.js.map +1 -1
- package/dist/config.js +21 -10
- package/dist/config.js.map +1 -1
- package/dist/escape.js +29 -0
- package/dist/escape.js.map +1 -0
- package/dist/favicon.js +3 -36
- package/dist/favicon.js.map +1 -1
- package/dist/foundry-importer.js +61 -0
- package/dist/foundry-importer.js.map +1 -0
- package/dist/images.js +0 -30
- package/dist/images.js.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/migrate/0.6-legacy-auth-settings.js +3 -5
- package/dist/migrate/0.6-legacy-auth-settings.js.map +1 -1
- package/dist/migrate/0.7-vaults-dir.js +4 -0
- package/dist/migrate/0.7-vaults-dir.js.map +1 -1
- package/dist/migrate/registry.js +1 -7
- package/dist/migrate/registry.js.map +1 -1
- package/dist/paths.js +21 -6
- package/dist/paths.js.map +1 -1
- package/dist/render/auth-template.js +21 -142
- package/dist/render/auth-template.js.map +1 -1
- package/dist/render/bases.js +56 -44
- package/dist/render/bases.js.map +1 -1
- package/dist/render/callouts.js +29 -10
- package/dist/render/callouts.js.map +1 -1
- package/dist/render/embed.js +124 -26
- package/dist/render/embed.js.map +1 -1
- package/dist/render/extensions.js +68 -0
- package/dist/render/extensions.js.map +1 -0
- package/dist/render/external-links.js +32 -0
- package/dist/render/external-links.js.map +1 -0
- package/dist/render/frontmatter.js +17 -0
- package/dist/render/frontmatter.js.map +1 -0
- package/dist/render/handlers/assets.js +48 -15
- package/dist/render/handlers/assets.js.map +1 -1
- package/dist/render/handlers/builtin/battlemap.js +199 -0
- package/dist/render/handlers/builtin/battlemap.js.map +1 -0
- package/dist/render/handlers/builtin/dice.js +1 -1
- package/dist/render/handlers/builtin/dice.js.map +1 -1
- package/dist/render/handlers/builtin/fm-code.js +50 -0
- package/dist/render/handlers/builtin/fm-code.js.map +1 -0
- package/dist/render/handlers/builtin/fm.js +6 -12
- package/dist/render/handlers/builtin/fm.js.map +1 -1
- package/dist/render/handlers/builtin/gallery.js +103 -0
- package/dist/render/handlers/builtin/gallery.js.map +1 -0
- package/dist/render/handlers/builtin/index.js +6 -1
- package/dist/render/handlers/builtin/index.js.map +1 -1
- package/dist/render/handlers/builtin/inline-format.js +26 -0
- package/dist/render/handlers/builtin/inline-format.js.map +1 -0
- package/dist/render/handlers/builtin/statblock.js +158 -18
- package/dist/render/handlers/builtin/statblock.js.map +1 -1
- package/dist/render/handlers/dispatch.js +15 -20
- package/dist/render/handlers/dispatch.js.map +1 -1
- package/dist/render/handlers/types.js +41 -21
- package/dist/render/handlers/types.js.map +1 -1
- package/dist/render/image-srcs.js +42 -0
- package/dist/render/image-srcs.js.map +1 -0
- package/dist/render/layout.js +60 -9
- package/dist/render/layout.js.map +1 -1
- package/dist/render/pipeline.js +23 -10
- package/dist/render/pipeline.js.map +1 -1
- package/dist/render/preview.js +53 -18
- package/dist/render/preview.js.map +1 -1
- package/dist/render/slug.js +5 -0
- package/dist/render/slug.js.map +1 -1
- package/dist/render/styles.js +92 -14
- package/dist/render/styles.js.map +1 -1
- package/dist/render/wikilink.js +15 -4
- package/dist/render/wikilink.js.map +1 -1
- package/dist/scan.js +1 -1
- package/dist/scan.js.map +1 -1
- package/dist/settings.js +16 -1
- package/dist/settings.js.map +1 -1
- package/dist/version.js +36 -0
- package/dist/version.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Built-in `battlemap` code-block handler.
|
|
2
|
+
//
|
|
3
|
+
// Renders a layered, multi-level battle map with controls to switch level,
|
|
4
|
+
// toggle a grid overlay, and download the current level as a PNG. Syntax:
|
|
5
|
+
//
|
|
6
|
+
// ```battlemap
|
|
7
|
+
// grid: 140 # px per grid cell at the image's native size (optional)
|
|
8
|
+
// default_level: 1 # 0-based index of the level shown first (optional)
|
|
9
|
+
// name: Wizard Prison # download-filename prefix (optional)
|
|
10
|
+
// levels:
|
|
11
|
+
// - name: Rock
|
|
12
|
+
// layers: # composited bottom-to-top
|
|
13
|
+
// - "attachments/foundry/wizard-prison/...-Rock-...webp"
|
|
14
|
+
// - name: First Floor
|
|
15
|
+
// layers:
|
|
16
|
+
// - "attachments/foundry/wizard-prison/...-Rock-...webp"
|
|
17
|
+
// - "attachments/foundry/wizard-prison/...-First Floor-...webp"
|
|
18
|
+
// ```
|
|
19
|
+
//
|
|
20
|
+
// The browser runtime (BATTLEMAP_RUNTIME below) ships as a built-in asset
|
|
21
|
+
// concatenated into _handlers.js; styles go into _handlers.css.
|
|
22
|
+
//
|
|
23
|
+
// Layer paths are vault-relative and resolve to the absolute served URL. They
|
|
24
|
+
// must also be staged into the deploy — true when the page already references
|
|
25
|
+
// them (e.g. a Scene's foundry.data_json), which is the common case.
|
|
26
|
+
import yaml from "js-yaml";
|
|
27
|
+
import { htmlEscape } from "../../../escape.js";
|
|
28
|
+
import { registerBuiltinAssets } from "../assets.js";
|
|
29
|
+
/** Vault-relative path -> absolute, percent-encoded served URL. */
|
|
30
|
+
function servedSrc(path) {
|
|
31
|
+
return "/" + path.replace(/^\/+/, "").split("/").map(encodeURIComponent).join("/");
|
|
32
|
+
}
|
|
33
|
+
function errorBox(message) {
|
|
34
|
+
return { html: `<div class="vaults-bm-error">${htmlEscape(message)}</div>` };
|
|
35
|
+
}
|
|
36
|
+
export const battlemapHandler = {
|
|
37
|
+
codeBlock: "battlemap",
|
|
38
|
+
render(content) {
|
|
39
|
+
let spec;
|
|
40
|
+
try {
|
|
41
|
+
spec = (yaml.load(content) ?? {});
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return errorBox("battlemap: could not parse YAML");
|
|
45
|
+
}
|
|
46
|
+
const levels = (Array.isArray(spec.levels) ? spec.levels : [])
|
|
47
|
+
.map((lv) => ({
|
|
48
|
+
name: typeof lv?.name === "string" ? lv.name : "",
|
|
49
|
+
layers: Array.isArray(lv?.layers)
|
|
50
|
+
? lv.layers.filter((s) => typeof s === "string" && s.length > 0)
|
|
51
|
+
: [],
|
|
52
|
+
}))
|
|
53
|
+
.filter((lv) => lv.layers.length > 0);
|
|
54
|
+
if (levels.length === 0)
|
|
55
|
+
return errorBox("battlemap: no levels with layers");
|
|
56
|
+
const grid = Number(spec.grid) > 0 ? Number(spec.grid) : 0;
|
|
57
|
+
let active = Number.isInteger(Number(spec.default_level)) ? Number(spec.default_level) : 0;
|
|
58
|
+
if (active < 0 || active >= levels.length)
|
|
59
|
+
active = 0;
|
|
60
|
+
const mapName = typeof spec.name === "string" ? spec.name.trim() : "";
|
|
61
|
+
const levelBtns = levels
|
|
62
|
+
.map((lv, i) => `<button type="button" class="vaults-bm-level" role="tab" data-level="${i}"`
|
|
63
|
+
+ ` aria-selected="${i === active ? "true" : "false"}">${htmlEscape(lv.name || `Level ${i + 1}`)}</button>`)
|
|
64
|
+
.join("");
|
|
65
|
+
const tools = (grid ? `<button type="button" class="vaults-bm-grid" aria-pressed="false" title="Toggle grid">Grid</button>` : "")
|
|
66
|
+
+ `<button type="button" class="vaults-bm-download" title="Download this level as an image">Download</button>`;
|
|
67
|
+
const panes = levels
|
|
68
|
+
.map((lv, i) => {
|
|
69
|
+
const imgs = lv.layers
|
|
70
|
+
.map((p, j) => `<img src="${servedSrc(p)}" alt="${htmlEscape(lv.name)} layer ${j + 1}" loading="lazy">`)
|
|
71
|
+
.join("");
|
|
72
|
+
return `<div class="vaults-bm-pane${i === active ? " is-active" : ""}" data-level="${i}"`
|
|
73
|
+
+ ` data-name="${htmlEscape(lv.name)}">${imgs}</div>`;
|
|
74
|
+
})
|
|
75
|
+
.join("");
|
|
76
|
+
const overlay = grid ? `<div class="vaults-bm-grid-overlay" aria-hidden="true"></div>` : "";
|
|
77
|
+
const html = `<div class="vaults-battlemap"${grid ? ` data-grid="${grid}"` : ""}`
|
|
78
|
+
+ `${mapName ? ` data-name="${htmlEscape(mapName)}"` : ""}>`
|
|
79
|
+
+ `<div class="vaults-bm-bar">`
|
|
80
|
+
+ `<div class="vaults-bm-levels" role="tablist">${levelBtns}</div>`
|
|
81
|
+
+ `<div class="vaults-bm-tools">${tools}</div>`
|
|
82
|
+
+ `</div>`
|
|
83
|
+
+ `<div class="vaults-bm-stage">${panes}${overlay}</div>`
|
|
84
|
+
+ `</div>`;
|
|
85
|
+
return { html };
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
// ── Browser runtime ───────────────────────────────────────────────────────
|
|
89
|
+
const BATTLEMAP_RUNTIME = `
|
|
90
|
+
(function () {
|
|
91
|
+
function initMap(root) {
|
|
92
|
+
var grid = parseFloat(root.getAttribute('data-grid')) || 0;
|
|
93
|
+
var overlay = root.querySelector('.vaults-bm-grid-overlay');
|
|
94
|
+
var panes = root.querySelectorAll('.vaults-bm-pane');
|
|
95
|
+
var btns = root.querySelectorAll('.vaults-bm-level');
|
|
96
|
+
|
|
97
|
+
function active() { return root.querySelector('.vaults-bm-pane.is-active'); }
|
|
98
|
+
|
|
99
|
+
function sizeGrid() {
|
|
100
|
+
if (!grid || !overlay) return;
|
|
101
|
+
var pane = active();
|
|
102
|
+
var img = pane && pane.querySelector('img');
|
|
103
|
+
if (!img || !img.naturalWidth) return;
|
|
104
|
+
overlay.style.backgroundSize =
|
|
105
|
+
(grid / img.naturalWidth * 100) + '% ' + (grid / img.naturalHeight * 100) + '%';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function setLevel(i) {
|
|
109
|
+
panes.forEach(function (p) { p.classList.toggle('is-active', p.getAttribute('data-level') === i); });
|
|
110
|
+
btns.forEach(function (b) { b.setAttribute('aria-selected', b.getAttribute('data-level') === i ? 'true' : 'false'); });
|
|
111
|
+
sizeGrid();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
btns.forEach(function (b) {
|
|
115
|
+
b.addEventListener('click', function () { setLevel(b.getAttribute('data-level')); });
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
var gridBtn = root.querySelector('.vaults-bm-grid');
|
|
119
|
+
if (gridBtn) gridBtn.addEventListener('click', function () {
|
|
120
|
+
var on = root.classList.toggle('show-grid');
|
|
121
|
+
gridBtn.setAttribute('aria-pressed', on ? 'true' : 'false');
|
|
122
|
+
if (on) sizeGrid();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
var dlBtn = root.querySelector('.vaults-bm-download');
|
|
126
|
+
if (dlBtn) dlBtn.addEventListener('click', function () { download(root); });
|
|
127
|
+
|
|
128
|
+
root.querySelectorAll('img').forEach(function (img) {
|
|
129
|
+
if (img.complete) sizeGrid(); else img.addEventListener('load', sizeGrid);
|
|
130
|
+
});
|
|
131
|
+
window.addEventListener('resize', sizeGrid);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function download(root) {
|
|
135
|
+
var pane = root.querySelector('.vaults-bm-pane.is-active');
|
|
136
|
+
var imgs = pane ? pane.querySelectorAll('img') : [];
|
|
137
|
+
if (!imgs.length || !imgs[0].naturalWidth) return;
|
|
138
|
+
var canvas = document.createElement('canvas');
|
|
139
|
+
canvas.width = imgs[0].naturalWidth;
|
|
140
|
+
canvas.height = imgs[0].naturalHeight;
|
|
141
|
+
var ctx = canvas.getContext('2d');
|
|
142
|
+
imgs.forEach(function (img) { ctx.drawImage(img, 0, 0, canvas.width, canvas.height); });
|
|
143
|
+
|
|
144
|
+
// Bake in the grid only when it's currently shown, matching the overlay.
|
|
145
|
+
var grid = parseFloat(root.getAttribute('data-grid')) || 0;
|
|
146
|
+
if (grid > 0 && root.classList.contains('show-grid')) {
|
|
147
|
+
// Scale the line to the on-screen 1px so the baked grid looks the same.
|
|
148
|
+
var scale = imgs[0].width ? imgs[0].naturalWidth / imgs[0].width : 1;
|
|
149
|
+
ctx.strokeStyle = 'rgba(0,0,0,0.5)';
|
|
150
|
+
ctx.lineWidth = Math.max(1, scale);
|
|
151
|
+
ctx.beginPath();
|
|
152
|
+
for (var gx = 0; gx <= canvas.width; gx += grid) { ctx.moveTo(gx, 0); ctx.lineTo(gx, canvas.height); }
|
|
153
|
+
for (var gy = 0; gy <= canvas.height; gy += grid) { ctx.moveTo(0, gy); ctx.lineTo(canvas.width, gy); }
|
|
154
|
+
ctx.stroke();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
var prefix = root.getAttribute('data-name');
|
|
158
|
+
var name = (prefix ? prefix + ' - ' : '') + (pane.getAttribute('data-name') || 'map');
|
|
159
|
+
try {
|
|
160
|
+
canvas.toBlob(function (blob) {
|
|
161
|
+
if (!blob) return;
|
|
162
|
+
var url = URL.createObjectURL(blob);
|
|
163
|
+
var a = document.createElement('a');
|
|
164
|
+
a.href = url; a.download = name + '.png';
|
|
165
|
+
document.body.appendChild(a); a.click(); a.remove();
|
|
166
|
+
setTimeout(function () { URL.revokeObjectURL(url); }, 1000);
|
|
167
|
+
}, 'image/png');
|
|
168
|
+
} catch (e) {
|
|
169
|
+
// Tainted canvas (cross-origin image): fall back to opening the base layer.
|
|
170
|
+
window.open(imgs[0].src, '_blank');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function initAll() { document.querySelectorAll('.vaults-battlemap').forEach(initMap); }
|
|
175
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initAll);
|
|
176
|
+
else initAll();
|
|
177
|
+
})();
|
|
178
|
+
`;
|
|
179
|
+
const BATTLEMAP_STYLES = `
|
|
180
|
+
.vaults-battlemap { margin: 1rem 0; }
|
|
181
|
+
.vaults-bm-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; justify-content: space-between; margin-bottom: .5rem; }
|
|
182
|
+
.vaults-bm-levels, .vaults-bm-tools { display: flex; flex-wrap: wrap; gap: .25rem; }
|
|
183
|
+
.vaults-battlemap button { font: inherit; font-size: .85rem; line-height: 1.2; padding: .3rem .7rem; border: 1px solid var(--rule, #ccc); border-radius: 4px; background: var(--bg, #fff); color: var(--fg, #222); cursor: pointer; }
|
|
184
|
+
.vaults-battlemap button:hover { border-color: var(--accent, #888); }
|
|
185
|
+
.vaults-bm-level[aria-selected="true"], .vaults-bm-grid[aria-pressed="true"] { background: var(--accent, #333); color: var(--accent-fg, #fff); border-color: var(--accent, #333); }
|
|
186
|
+
.vaults-bm-stage { position: relative; line-height: 0; border: 1px solid var(--rule, #ccc); border-radius: 4px; overflow: hidden; background: #15151a; }
|
|
187
|
+
.vaults-bm-pane { display: none; position: relative; }
|
|
188
|
+
.vaults-bm-pane.is-active { display: block; }
|
|
189
|
+
.vaults-bm-pane img { display: block; width: 100%; height: auto; -webkit-user-drag: none; user-select: none; }
|
|
190
|
+
.vaults-bm-pane img:not(:first-child) { position: absolute; inset: 0; }
|
|
191
|
+
.vaults-bm-grid-overlay { position: absolute; inset: 0; display: none; pointer-events: none; background-image: linear-gradient(to right, rgba(0,0,0,.5) 0 1px, transparent 1px), linear-gradient(to bottom, rgba(0,0,0,.5) 0 1px, transparent 1px); }
|
|
192
|
+
.vaults-battlemap.show-grid .vaults-bm-grid-overlay { display: block; }
|
|
193
|
+
.vaults-bm-error { padding: .5rem .75rem; border: 1px solid #b94a3a; border-radius: 4px; color: #b94a3a; font-size: .85rem; }
|
|
194
|
+
`;
|
|
195
|
+
registerBuiltinAssets(battlemapHandler, {
|
|
196
|
+
scripts: [{ source: "builtin/battlemap.runtime.js", content: BATTLEMAP_RUNTIME }],
|
|
197
|
+
styles: [{ source: "builtin/battlemap.css", content: BATTLEMAP_STYLES }],
|
|
198
|
+
});
|
|
199
|
+
//# sourceMappingURL=battlemap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"battlemap.js","sourceRoot":"","sources":["../../../../src/render/handlers/builtin/battlemap.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
//
|
|
12
12
|
// Subset of Dice Roller's syntax: basic XdY +/- Z arithmetic, optional X.
|
|
13
13
|
// More complex modifiers (kh, kl, explode, etc.) are not supported.
|
|
14
|
-
import { htmlEscape } from "
|
|
14
|
+
import { htmlEscape } from "../../../escape.js";
|
|
15
15
|
import { registerBuiltinAssets } from "../assets.js";
|
|
16
16
|
// Formula validation: optional X, then 'd', then Y, then optional ± integer.
|
|
17
17
|
// Only used at build time to decide whether render() emits a clickable
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dice.js","sourceRoot":"","sources":["../../../../src/render/handlers/builtin/dice.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,EAAE;AACF,mEAAmE;AACnE,EAAE;AACF,qEAAqE;AACrE,EAAE;AACF,0EAA0E;AAC1E,oEAAoE;AACpE,2EAA2E;AAC3E,kDAAkD;AAClD,EAAE;AACF,0EAA0E;AAC1E,oEAAoE;AAGpE,OAAO,EAAE,UAAU,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"dice.js","sourceRoot":"","sources":["../../../../src/render/handlers/builtin/dice.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,EAAE;AACF,mEAAmE;AACnE,EAAE;AACF,qEAAqE;AACrE,EAAE;AACF,0EAA0E;AAC1E,oEAAoE;AACpE,2EAA2E;AAC3E,kDAAkD;AAClD,EAAE;AACF,0EAA0E;AAC1E,oEAAoE;AAGpE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAErD,6EAA6E;AAC7E,uEAAuE;AACvE,wEAAwE;AACxE,4DAA4D;AAC5D,MAAM,UAAU,GAAG,gCAAgC,CAAC;AAEpD,MAAM,CAAC,MAAM,WAAW,GAAkB;IACxC,MAAM,EAAE,MAAM;IACd,MAAM,CAAC,OAAe;QACpB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,iEAAiE;YACjE,oDAAoD;YACpD,OAAO;gBACL,IAAI,EAAE,+EAA+E,OAAO,SAAS;aACtG,CAAC;QACJ,CAAC;QACD,OAAO;YACL,IAAI,EAAE,yDAAyD,OAAO,2BAA2B,OAAO,WAAW;SACpH,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,6EAA6E;AAC7E,wEAAwE;AACxE,uEAAuE;AACvE,yDAAyD;AAEzD,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgC3B,CAAC;AAEF,qBAAqB,CAAC,WAAW,EAAE;IACjC,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,yBAAyB,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC;CAC/E,CAAC,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Built-in code-block `fm` handler — renders a frontmatter value as a fenced
|
|
2
|
+
// code block, with an optional language hint via the fence meta string.
|
|
3
|
+
//
|
|
4
|
+
// Usage:
|
|
5
|
+
//
|
|
6
|
+
// ```fm javascript
|
|
7
|
+
// foundry.data.command
|
|
8
|
+
// ```
|
|
9
|
+
//
|
|
10
|
+
// Renders as:
|
|
11
|
+
//
|
|
12
|
+
// <pre><code class="language-javascript">…value of frontmatter.foundry.data.command…</code></pre>
|
|
13
|
+
//
|
|
14
|
+
// Companion to the inline `fm:` handler. Inline form is used in prose to
|
|
15
|
+
// pull a frontmatter value into a sentence; this code-block form is used
|
|
16
|
+
// when the value IS code (or otherwise wants a `<pre>` wrapper) and you
|
|
17
|
+
// want a single source of truth for that code rather than duplicating it
|
|
18
|
+
// between the frontmatter and the page body.
|
|
19
|
+
//
|
|
20
|
+
// The body of the fence is the dot-path (single token, leading/trailing
|
|
21
|
+
// whitespace tolerated). Anything beyond the lang on the fence is the
|
|
22
|
+
// optional language hint — `lang className="language-…"` so client-side
|
|
23
|
+
// highlighters can pick it up. Missing path or non-string value renders
|
|
24
|
+
// as a visible warning marker rather than failing the build, same policy
|
|
25
|
+
// as the inline handler.
|
|
26
|
+
import { htmlEscape } from "../../../escape.js";
|
|
27
|
+
import { lookup } from "./fm.js";
|
|
28
|
+
export const fmCodeHandler = {
|
|
29
|
+
codeBlock: "fm",
|
|
30
|
+
render(content, ctx) {
|
|
31
|
+
const path = content.trim();
|
|
32
|
+
if (!path)
|
|
33
|
+
return missing("(empty)");
|
|
34
|
+
const value = lookup(ctx.frontmatter, path);
|
|
35
|
+
if (typeof value !== "string")
|
|
36
|
+
return missing(`${path}: not a string`);
|
|
37
|
+
const langClass = ctx.codeBlockMeta
|
|
38
|
+
? ` class="language-${htmlEscape(ctx.codeBlockMeta)}"`
|
|
39
|
+
: "";
|
|
40
|
+
return {
|
|
41
|
+
html: `<pre><code${langClass}>${htmlEscape(value)}</code></pre>`,
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
function missing(key) {
|
|
46
|
+
return {
|
|
47
|
+
html: `<pre><code class="fm-missing" title="frontmatter key not found">{{${htmlEscape(key)}}}</code></pre>`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=fm-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fm-code.js","sourceRoot":"","sources":["../../../../src/render/handlers/builtin/fm-code.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,wEAAwE;AACxE,EAAE;AACF,SAAS;AACT,EAAE;AACF,qBAAqB;AACrB,yBAAyB;AACzB,QAAQ;AACR,EAAE;AACF,cAAc;AACd,EAAE;AACF,oGAAoG;AACpG,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AACzE,wEAAwE;AACxE,yEAAyE;AACzE,6CAA6C;AAC7C,EAAE;AACF,wEAAwE;AACxE,sEAAsE;AACtE,wEAAwE;AACxE,wEAAwE;AACxE,yEAAyE;AACzE,yBAAyB;AAGzB,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,MAAM,CAAC,MAAM,aAAa,GAAqB;IAC7C,SAAS,EAAE,IAAI;IACf,MAAM,CAAC,OAAO,EAAE,GAAG;QACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI;YAAE,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,OAAO,CAAC,GAAG,IAAI,gBAAgB,CAAC,CAAC;QACvE,MAAM,SAAS,GAAG,GAAG,CAAC,aAAa;YACjC,CAAC,CAAC,oBAAoB,UAAU,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG;YACtD,CAAC,CAAC,EAAE,CAAC;QACP,OAAO;YACL,IAAI,EAAE,aAAa,SAAS,IAAI,UAAU,CAAC,KAAK,CAAC,eAAe;SACjE,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,SAAS,OAAO,CAAC,GAAW;IAC1B,OAAO;QACL,IAAI,EAAE,qEAAqE,UAAU,CAAC,GAAG,CAAC,iBAAiB;KAC5G,CAAC;AACJ,CAAC"}
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
// Arrays are joined with ", ". Missing keys and unsupported value shapes
|
|
27
27
|
// (plain objects, null) render a visible warning marker so the page
|
|
28
28
|
// surfaces the problem instead of silently emitting "undefined".
|
|
29
|
-
import { htmlEscape } from "
|
|
29
|
+
import { htmlEscape } from "../../../escape.js";
|
|
30
|
+
import { formatInline } from "./inline-format.js";
|
|
30
31
|
export const fmHandler = {
|
|
31
32
|
inline: "fm",
|
|
32
33
|
render(content, ctx) {
|
|
@@ -48,7 +49,10 @@ export const fmHandler = {
|
|
|
48
49
|
// wrapping in a <span> keeps the value visible. The span is invisible in
|
|
49
50
|
// the final layout so it doesn't change typography.
|
|
50
51
|
function wrap(inner) { return `<span class="fm-value">${inner}</span>`; }
|
|
51
|
-
|
|
52
|
+
/** Walk a dot-path into the page's frontmatter; numeric segments index
|
|
53
|
+
* arrays. Exported so the sibling fm code-block handler reuses the same
|
|
54
|
+
* resolution semantics. */
|
|
55
|
+
export function lookup(root, path) {
|
|
52
56
|
const segments = path.split(".");
|
|
53
57
|
let cursor = root;
|
|
54
58
|
for (const seg of segments) {
|
|
@@ -60,16 +64,6 @@ function lookup(root, path) {
|
|
|
60
64
|
}
|
|
61
65
|
return cursor;
|
|
62
66
|
}
|
|
63
|
-
// HTML-escape, then a tiny inline-markdown pass: bold (** **) before italic
|
|
64
|
-
// (* *) so bold doesn't get eaten, then code spans. Matches the same subset
|
|
65
|
-
// the statblock handler's formatInline supports — keep these in sync.
|
|
66
|
-
function formatInline(s) {
|
|
67
|
-
let out = htmlEscape(s);
|
|
68
|
-
out = out.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
69
|
-
out = out.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, "$1<em>$2</em>");
|
|
70
|
-
out = out.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
71
|
-
return out;
|
|
72
|
-
}
|
|
73
67
|
function formatScalar(v) {
|
|
74
68
|
if (v === null || v === undefined)
|
|
75
69
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fm.js","sourceRoot":"","sources":["../../../../src/render/handlers/builtin/fm.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,QAAQ;AACR,kBAAkB;AAClB,aAAa;AACb,WAAW;AACX,aAAa;AACb,QAAQ;AACR,iEAAiE;AACjE,EAAE;AACF,kDAAkD;AAClD,EAAE;AACF,wEAAwE;AACxE,yEAAyE;AACzE,6CAA6C;AAC7C,EAAE;AACF,yEAAyE;AACzE,wEAAwE;AACxE,0EAA0E;AAC1E,0EAA0E;AAC1E,yEAAyE;AACzE,2EAA2E;AAC3E,8BAA8B;AAC9B,EAAE;AACF,0EAA0E;AAC1E,yEAAyE;AACzE,oEAAoE;AACpE,iEAAiE;AAGjE,OAAO,EAAE,UAAU,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"fm.js","sourceRoot":"","sources":["../../../../src/render/handlers/builtin/fm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Built-in `gallery` code-block handler.
|
|
2
|
+
//
|
|
3
|
+
// Renders a responsive grid of image thumbnails. Syntax: one image per line,
|
|
4
|
+
// referenced by name (Obsidian-basename style, like a `![[file]]` embed), with
|
|
5
|
+
// an optional caption after a pipe:
|
|
6
|
+
//
|
|
7
|
+
// ```gallery
|
|
8
|
+
// great-hall.webp | Great Hall
|
|
9
|
+
// goblin-bank.webp | Goblin Bank
|
|
10
|
+
// forest-river.webp
|
|
11
|
+
// ```
|
|
12
|
+
//
|
|
13
|
+
// Lines beginning with `#` are comments. Images resolve through the same index
|
|
14
|
+
// as `![[ ]]` embeds, so a referenced file is staged into the deploy (the
|
|
15
|
+
// build's per-variant scanner reads `gallery` blocks for exactly this). Styles
|
|
16
|
+
// ship as a built-in asset concatenated into _handlers.css.
|
|
17
|
+
import { registerBuiltinAssets } from "../assets.js";
|
|
18
|
+
import { slugify } from "../../slug.js";
|
|
19
|
+
/** Vault-relative output path -> absolute, percent-encoded served URL. */
|
|
20
|
+
function servedSrc(path) {
|
|
21
|
+
return "/" + path.replace(/^\/+/, "").split("/").map(encodeURIComponent).join("/");
|
|
22
|
+
}
|
|
23
|
+
/** Parse one `name | caption` line into its parts, or null to skip it. */
|
|
24
|
+
export function parseGalleryLine(line) {
|
|
25
|
+
const trimmed = line.trim();
|
|
26
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
27
|
+
return null;
|
|
28
|
+
const sep = trimmed.indexOf("|");
|
|
29
|
+
const name = (sep === -1 ? trimmed : trimmed.slice(0, sep)).trim();
|
|
30
|
+
if (!name)
|
|
31
|
+
return null;
|
|
32
|
+
const caption = sep === -1 ? "" : trimmed.slice(sep + 1).trim();
|
|
33
|
+
return { name, caption };
|
|
34
|
+
}
|
|
35
|
+
export const galleryHandler = {
|
|
36
|
+
codeBlock: "gallery",
|
|
37
|
+
render(content, ctx) {
|
|
38
|
+
const items = [];
|
|
39
|
+
for (const line of content.split("\n")) {
|
|
40
|
+
const parsed = parseGalleryLine(line);
|
|
41
|
+
if (!parsed)
|
|
42
|
+
continue;
|
|
43
|
+
const { name, caption } = parsed;
|
|
44
|
+
// Resolve the file to its staged output path the same way `![[ ]]`
|
|
45
|
+
// embeds do; fall back to the bare name so a typo still points somewhere.
|
|
46
|
+
const entry = ctx.render.images.get(slugify(name.split("/").pop() ?? name));
|
|
47
|
+
const src = ctx.escape(servedSrc(entry?.outputPath ?? name));
|
|
48
|
+
const alt = ctx.escape(caption || name);
|
|
49
|
+
const cap = caption
|
|
50
|
+
? `<span class="vaults-gallery-caption">${ctx.escape(caption)}</span>`
|
|
51
|
+
: "";
|
|
52
|
+
// The tile is a link to the full image: CSS crops the <img> to a uniform
|
|
53
|
+
// grid cell, and the runtime opens the full image in a lightbox on click.
|
|
54
|
+
// With JS off, the link still opens the full image directly.
|
|
55
|
+
items.push(`<a class="vaults-gallery-item" href="${src}"><img src="${src}" alt="${alt}" loading="lazy">${cap}</a>`);
|
|
56
|
+
}
|
|
57
|
+
if (items.length === 0) {
|
|
58
|
+
return { html: `<div class="vaults-gallery-error">Empty gallery block.</div>` };
|
|
59
|
+
}
|
|
60
|
+
return { html: `<div class="vaults-gallery">${items.join("")}</div>` };
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
const GALLERY_STYLES = `
|
|
64
|
+
.vaults-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: .6rem; margin: 1.25rem 0; }
|
|
65
|
+
.vaults-gallery-item { display: block; text-decoration: none; color: inherit; cursor: zoom-in; }
|
|
66
|
+
.vaults-gallery-item img { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: 8px; }
|
|
67
|
+
.vaults-gallery-caption { display: block; margin-top: .25rem; font-size: .8rem; text-align: center; opacity: .8; }
|
|
68
|
+
.vaults-gallery-error { padding: .5rem .75rem; border: 1px solid #b94a3a; border-radius: 4px; color: #b94a3a; font-size: .85rem; }
|
|
69
|
+
.vaults-lightbox { position: fixed; inset: 0; z-index: 9999; display: flex; align-items: center; justify-content: center; padding: 2rem; background: rgba(0,0,0,.85); cursor: zoom-out; }
|
|
70
|
+
.vaults-lightbox img { max-width: 100%; max-height: 100%; border-radius: 6px; box-shadow: 0 4px 30px rgba(0,0,0,.5); }
|
|
71
|
+
`;
|
|
72
|
+
// Click a tile -> open its full image in a lightbox overlay. Event-delegated so
|
|
73
|
+
// it covers every gallery on the page, and progressive: with JS disabled the
|
|
74
|
+
// tile's <a href> just opens the full image. Wrapped in an IIFE.
|
|
75
|
+
const GALLERY_RUNTIME = `
|
|
76
|
+
(function () {
|
|
77
|
+
function open(src, alt) {
|
|
78
|
+
var ov = document.createElement("div");
|
|
79
|
+
ov.className = "vaults-lightbox";
|
|
80
|
+
var img = document.createElement("img");
|
|
81
|
+
img.src = src;
|
|
82
|
+
img.alt = alt || "";
|
|
83
|
+
ov.appendChild(img);
|
|
84
|
+
function close() { ov.remove(); document.removeEventListener("keydown", onKey); }
|
|
85
|
+
function onKey(e) { if (e.key === "Escape") close(); }
|
|
86
|
+
ov.addEventListener("click", close);
|
|
87
|
+
document.addEventListener("keydown", onKey);
|
|
88
|
+
document.body.appendChild(ov);
|
|
89
|
+
}
|
|
90
|
+
document.addEventListener("click", function (e) {
|
|
91
|
+
var a = e.target && e.target.closest ? e.target.closest("a.vaults-gallery-item") : null;
|
|
92
|
+
if (!a) return;
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
var img = a.querySelector("img");
|
|
95
|
+
open(a.getAttribute("href"), img ? img.getAttribute("alt") : "");
|
|
96
|
+
});
|
|
97
|
+
})();
|
|
98
|
+
`;
|
|
99
|
+
registerBuiltinAssets(galleryHandler, {
|
|
100
|
+
scripts: [{ source: "builtin/gallery.runtime.js", content: GALLERY_RUNTIME }],
|
|
101
|
+
styles: [{ source: "builtin/gallery.css", content: GALLERY_STYLES }],
|
|
102
|
+
});
|
|
103
|
+
//# sourceMappingURL=gallery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gallery.js","sourceRoot":"","sources":["../../../../src/render/handlers/builtin/gallery.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,oCAAoC;AACpC,EAAE;AACF,eAAe;AACf,iCAAiC;AACjC,mCAAmC;AACnC,sBAAsB;AACtB,QAAQ;AACR,EAAE;AACF,+EAA+E;AAC/E,0EAA0E;AAC1E,+EAA+E;AAC/E,4DAA4D;AAG5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,0EAA0E;AAC1E,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACrF,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACrD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACnE,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,MAAM,OAAO,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAChE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAqB;IAC9C,SAAS,EAAE,SAAS;IACpB,MAAM,CAAC,OAAe,EAAE,GAAmB;QACzC,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,CAAC,MAAM;gBAAE,SAAS;YACtB,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;YACjC,mEAAmE;YACnE,0EAA0E;YAC1E,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC;YAC5E,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC;YAC7D,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC;YACxC,MAAM,GAAG,GAAG,OAAO;gBACjB,CAAC,CAAC,wCAAwC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS;gBACtE,CAAC,CAAC,EAAE,CAAC;YACP,yEAAyE;YACzE,0EAA0E;YAC1E,6DAA6D;YAC7D,KAAK,CAAC,IAAI,CACR,wCAAwC,GAAG,eAAe,GAAG,UAAU,GAAG,oBAAoB,GAAG,MAAM,CACxG,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,EAAE,IAAI,EAAE,8DAA8D,EAAE,CAAC;QAClF,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,+BAA+B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC;IACzE,CAAC;CACF,CAAC;AAEF,MAAM,cAAc,GAAG;;;;;;;;CAQtB,CAAC;AAEF,gFAAgF;AAChF,6EAA6E;AAC7E,iEAAiE;AACjE,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;CAuBvB,CAAC;AAEF,qBAAqB,CAAC,cAAc,EAAE;IACpC,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,4BAA4B,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC;IAC7E,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,qBAAqB,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;CACrE,CAAC,CAAC"}
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
//
|
|
3
3
|
// User-defined handlers (loaded from .vaults/handlers/) override built-ins
|
|
4
4
|
// of the same name; see buildRegistry() in handlers/types.ts.
|
|
5
|
+
import { battlemapHandler } from "./battlemap.js";
|
|
5
6
|
import { diceHandler } from "./dice.js";
|
|
6
7
|
import { fmHandler } from "./fm.js";
|
|
8
|
+
import { fmCodeHandler } from "./fm-code.js";
|
|
9
|
+
import { galleryHandler } from "./gallery.js";
|
|
7
10
|
import { statblockHandler } from "./statblock.js";
|
|
8
|
-
export const BUILTIN_HANDLERS = [
|
|
11
|
+
export const BUILTIN_HANDLERS = [
|
|
12
|
+
diceHandler, fmHandler, fmCodeHandler, statblockHandler, battlemapHandler, galleryHandler,
|
|
13
|
+
];
|
|
9
14
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/render/handlers/builtin/index.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,2EAA2E;AAC3E,8DAA8D;AAG9D,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,CAAC,MAAM,gBAAgB,GAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/render/handlers/builtin/index.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,2EAA2E;AAC3E,8DAA8D;AAG9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,CAAC,MAAM,gBAAgB,GAAc;IACzC,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,cAAc;CAC1F,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Tiny inline-markdown formatter shared by the built-in `fm` and `statblock`
|
|
2
|
+
// handlers. Both render frontmatter scalars or bespoke content directly to
|
|
3
|
+
// HTML rather than re-feeding it through the full markdown pipeline, but
|
|
4
|
+
// authors still expect basic ** ** / * * / `` markup to survive. This is
|
|
5
|
+
// the smallest formatter that supports that.
|
|
6
|
+
//
|
|
7
|
+
// Bold runs first (** **) so the italic regex can't gobble its asterisks.
|
|
8
|
+
// Italic uses a leading non-`*` guard so `**bold**` doesn't match. Code
|
|
9
|
+
// spans run last; their delimiter (`) is HTML-safe after htmlEscape.
|
|
10
|
+
//
|
|
11
|
+
// Wikilinks are intentionally NOT processed here — wikilink resolution
|
|
12
|
+
// requires the full RenderContext and lives in render/wikilink.ts. Authors
|
|
13
|
+
// who need wikilinks should put them in regular prose.
|
|
14
|
+
import { htmlEscape } from "../../../escape.js";
|
|
15
|
+
/**
|
|
16
|
+
* HTML-escape `s`, then apply a small inline-markdown subset
|
|
17
|
+
* (bold, italic, code spans). Returns ready-to-insert HTML.
|
|
18
|
+
*/
|
|
19
|
+
export function formatInline(s) {
|
|
20
|
+
let out = htmlEscape(s);
|
|
21
|
+
out = out.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
22
|
+
out = out.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, "$1<em>$2</em>");
|
|
23
|
+
out = out.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=inline-format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline-format.js","sourceRoot":"","sources":["../../../../src/render/handlers/builtin/inline-format.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,2EAA2E;AAC3E,yEAAyE;AACzE,yEAAyE;AACzE,6CAA6C;AAC7C,EAAE;AACF,0EAA0E;AAC1E,wEAAwE;AACxE,qEAAqE;AACrE,EAAE;AACF,uEAAuE;AACvE,2EAA2E;AAC3E,uDAAuD;AAEvD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,IAAI,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IACxB,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,kBAAkB,EAAE,qBAAqB,CAAC,CAAC;IAC7D,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,8BAA8B,EAAE,eAAe,CAAC,CAAC;IACnE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC;IACnD,OAAO,GAAG,CAAC;AACb,CAAC"}
|