kitfly 0.2.1 → 0.2.3
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/CHANGELOG.md +56 -0
- package/README.md +25 -10
- package/VERSION +1 -1
- package/dist/_raw/content/guide/branding.md +146 -0
- package/dist/_raw/content/guide/data-driven-content.md +204 -0
- package/dist/_raw/content/reference/configuration.md +145 -7
- package/dist/_raw/content/reference/environment-variables.md +26 -1
- package/dist/_raw/content/reference/glossary.md +25 -1
- package/dist/_raw/content/reference/key-concepts.md +30 -2
- package/dist/_raw/content/reference/plugins.md +14 -0
- package/dist/_raw/docs/decisions/ADR-0006-data-driven-content.md +350 -0
- package/dist/content/deployment/preflight.html +10 -6
- package/dist/content/deployment/recipes/aws-s3.html +10 -6
- package/dist/content/deployment/recipes/cloudflare-pages.html +10 -6
- package/dist/content/deployment/recipes/cloudflare-r2.html +10 -6
- package/dist/content/deployment/recipes/fly-io.html +10 -6
- package/dist/content/deployment/recipes/github-pages.html +10 -6
- package/dist/content/deployment/recipes/netlify.html +10 -6
- package/dist/content/deployment/recipes/vercel.html +10 -6
- package/dist/content/deployment/secrets-and-env-vars.html +10 -6
- package/dist/content/deployment.html +10 -6
- package/dist/content/guide/approaches.html +10 -6
- package/dist/content/guide/branding.html +510 -0
- package/dist/content/guide/data-driven-content.html +543 -0
- package/dist/content/guide/features.html +10 -6
- package/dist/content/guide/getting-started.html +10 -6
- package/dist/content/guide/kitfly-overview.html +10 -6
- package/dist/content/reference/configuration.html +135 -9
- package/dist/content/reference/design-catalog.html +10 -6
- package/dist/content/reference/environment-variables.html +50 -8
- package/dist/content/reference/glossary.html +24 -8
- package/dist/content/reference/key-concepts.html +33 -9
- package/dist/content/reference/plugins.html +22 -7
- package/dist/content/reference/slides-authoring-guidelines.html +10 -6
- package/dist/content/reference/structure.html +10 -6
- package/dist/content/reference.html +10 -6
- package/dist/content/templates/crucible.html +10 -6
- package/dist/content/templates/handbook.html +10 -6
- package/dist/content/templates/minimal.html +10 -6
- package/dist/content/templates/overview.html +10 -6
- package/dist/content/templates/pipeline.html +10 -6
- package/dist/content/templates/productbook.html +10 -6
- package/dist/content/templates/runbook.html +10 -6
- package/dist/content/templates/servicebook.html +10 -6
- package/dist/content-index.json +29 -2
- package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +10 -6
- package/dist/docs/decisions/ADR-0002-ai-accessibility.html +10 -6
- package/dist/docs/decisions/ADR-0003-single-file-bundle.html +10 -6
- package/dist/docs/decisions/ADR-0004-bun-runtime.html +10 -6
- package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +10 -6
- package/dist/docs/decisions/ADR-0006-data-driven-content.html +752 -0
- package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +10 -6
- package/dist/docs/decisions/DDR-0002-theme-system.html +10 -6
- package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +10 -6
- package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +10 -6
- package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +10 -6
- package/dist/docs/userguide/cli/build.html +10 -6
- package/dist/docs/userguide/cli/bundle.html +10 -6
- package/dist/docs/userguide/cli/dev.html +10 -6
- package/dist/docs/userguide/cli/init.html +10 -6
- package/dist/docs/userguide/cli/servers.html +10 -6
- package/dist/docs/userguide/cli/stop.html +10 -6
- package/dist/docs/userguide/cli/update.html +10 -6
- package/dist/docs/userguide/cli/version.html +10 -6
- package/dist/docs/userguide/cli.html +10 -6
- package/dist/docs/userguide/sharing.html +10 -6
- package/dist/index.html +10 -6
- package/dist/llms.txt +3 -3
- package/dist/provenance.json +4 -4
- package/dist/schemas/plugin-registry.schema.html +10 -6
- package/dist/schemas/plugin-schemas-notes.html +10 -6
- package/dist/schemas/plugin.schema.html +10 -6
- package/dist/schemas/plugins.schema.html +10 -6
- package/dist/schemas/v0/common.schema.html +14 -10
- package/dist/schemas/v0/plugin-registry.schema.html +13 -9
- package/dist/schemas/v0/plugin.schema.html +13 -9
- package/dist/schemas/v0/plugins.schema.html +13 -9
- package/dist/schemas/v0/site.schema.html +67 -7
- package/dist/schemas/v0/theme.schema.html +21 -17
- package/dist/schemas.html +10 -6
- package/dist/styles.css +39 -4
- package/package.json +1 -1
- package/plugins-dist/latex-runtime.js +140 -0
- package/plugins-dist/latex.js +178 -0
- package/plugins-dist/slides-charts-lite-runtime.js +179 -0
- package/plugins-dist/slides-charts-lite.js +198 -0
- package/registry/plugins.yaml +25 -0
- package/schemas/v0/site.schema.json +56 -0
- package/scripts/build.ts +191 -69
- package/scripts/bundle.ts +118 -10
- package/scripts/dev.ts +245 -166
- package/src/__tests__/brief.test.ts +151 -0
- package/src/__tests__/build.test.ts +169 -1
- package/src/__tests__/bundle.test.ts +134 -0
- package/src/__tests__/init.test.ts +51 -2
- package/src/__tests__/latex-runtime.bun.test.ts +35 -0
- package/src/__tests__/shared.test.ts +598 -1
- package/src/__tests__/slides-charts-lite-runtime.bun.test.ts +45 -0
- package/src/cli.ts +11 -4
- package/src/commands/init.ts +1 -1
- package/src/shared.ts +725 -18
- package/src/site/styles.css +39 -4
- package/src/site/template.html +5 -2
- package/src/templates/brief.ts +486 -0
- package/src/templates/deck.ts +59 -0
- package/src/templates/driver.ts +46 -13
- package/src/templates/handbook.ts +32 -0
- package/src/templates/runbook.ts +32 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
function isCurrency(expr) {
|
|
3
|
+
return /^\d[\d,]*(?:\.\d+)?(?:\s*(?:to|-)\s*\d[\d,]*(?:\.\d+)?)?$/.test(expr.trim());
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function shouldTreatAsLiteralInline(expr, text, closingIndex) {
|
|
7
|
+
if (/^\s|\s$/.test(expr) || /\n/.test(expr) || isCurrency(expr)) return true;
|
|
8
|
+
// Guard common currency range form "$5-$10" where expr becomes "5-".
|
|
9
|
+
if (/-\s*$/.test(expr)) {
|
|
10
|
+
const tail = text.slice(closingIndex + 1);
|
|
11
|
+
if (/^\d/.test(tail)) return true;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function findClosing(text, start, display) {
|
|
17
|
+
for (let i = start; i < text.length; i++) {
|
|
18
|
+
if (text[i] === "\\" && text[i + 1] === "$") {
|
|
19
|
+
i++;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (display) {
|
|
23
|
+
if (text[i] === "$" && text[i + 1] === "$") return i;
|
|
24
|
+
} else if (text[i] === "$" && text[i - 1] !== "$" && text[i + 1] !== "$") {
|
|
25
|
+
return i;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return -1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function splitMath(text) {
|
|
32
|
+
const out = [];
|
|
33
|
+
let buf = "";
|
|
34
|
+
let i = 0;
|
|
35
|
+
|
|
36
|
+
while (i < text.length) {
|
|
37
|
+
if (text[i] === "\\" && text[i + 1] === "$") {
|
|
38
|
+
buf += "$";
|
|
39
|
+
i += 2;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (text[i] !== "$") {
|
|
43
|
+
buf += text[i++];
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const display = text[i + 1] === "$";
|
|
48
|
+
const openLen = display ? 2 : 1;
|
|
49
|
+
const end = findClosing(text, i + openLen, display);
|
|
50
|
+
if (end < 0) {
|
|
51
|
+
buf += display ? "$$" : "$";
|
|
52
|
+
i += openLen;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const expr = text.slice(i + openLen, end);
|
|
57
|
+
if (!display && shouldTreatAsLiteralInline(expr, text, end)) {
|
|
58
|
+
buf += `$${expr}$`;
|
|
59
|
+
i = end + 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (buf) out.push({ text: buf });
|
|
64
|
+
out.push({ math: expr, display });
|
|
65
|
+
buf = "";
|
|
66
|
+
i = end + openLen;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (buf) out.push({ text: buf });
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function renderFencedMath() {
|
|
74
|
+
document.querySelectorAll("pre > code.language-math").forEach((code) => {
|
|
75
|
+
const pre = code.parentElement;
|
|
76
|
+
if (!pre || pre.tagName !== "PRE") return;
|
|
77
|
+
const host = document.createElement("div");
|
|
78
|
+
host.className = "kitfly-katex-display";
|
|
79
|
+
window.katex.render(code.textContent || "", host, { displayMode: true, throwOnError: false });
|
|
80
|
+
pre.replaceWith(host);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function renderDelimitedMath() {
|
|
85
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
86
|
+
const nodes = [];
|
|
87
|
+
while (walker.nextNode()) {
|
|
88
|
+
const node = walker.currentNode;
|
|
89
|
+
const parent = node.parentElement;
|
|
90
|
+
if (!parent || parent.closest("pre, code, script, style, textarea, .katex")) continue;
|
|
91
|
+
if (!(node.textContent || "").includes("$")) continue;
|
|
92
|
+
nodes.push(node);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const node of nodes) {
|
|
96
|
+
const parts = splitMath(node.textContent || "");
|
|
97
|
+
if (!parts.some((p) => p.math)) continue;
|
|
98
|
+
const frag = document.createDocumentFragment();
|
|
99
|
+
for (const p of parts) {
|
|
100
|
+
if (p.text != null) {
|
|
101
|
+
frag.appendChild(document.createTextNode(p.text));
|
|
102
|
+
} else if (p.math != null) {
|
|
103
|
+
const host = document.createElement("span");
|
|
104
|
+
host.className = p.display ? "kitfly-katex-display" : "kitfly-katex-inline";
|
|
105
|
+
window.katex.render(p.math, host, { displayMode: Boolean(p.display), throwOnError: false });
|
|
106
|
+
frag.appendChild(host);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
node.replaceWith(frag);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function apply() {
|
|
114
|
+
const styleId = "kitfly-latex-style";
|
|
115
|
+
if (!document.getElementById(styleId)) {
|
|
116
|
+
const style = document.createElement("style");
|
|
117
|
+
style.id = styleId;
|
|
118
|
+
style.textContent = ".kitfly-katex-display{display:block;text-align:center;margin:1em 0;overflow-x:auto}";
|
|
119
|
+
document.head.appendChild(style);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!window.katex?.render) {
|
|
123
|
+
console.warn("[kitfly:latex] KaTeX unavailable");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
renderFencedMath();
|
|
128
|
+
renderDelimitedMath();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (typeof document === "undefined") {
|
|
132
|
+
globalThis.__kitflyLatexTest = { splitMath, isCurrency, shouldTreatAsLiteralInline };
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (document.readyState === "loading") {
|
|
136
|
+
document.addEventListener("DOMContentLoaded", apply);
|
|
137
|
+
} else {
|
|
138
|
+
apply();
|
|
139
|
+
}
|
|
140
|
+
})();
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
const KATEX_CSS_URL = "https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css";
|
|
3
|
+
const KATEX_CSS_SRI = "sha384-zh0CIslj+VczCZtlzBcjt5ppRcsAmDnRem7ESsYwWwg3m/OaJ2l4x7YBZl9Kxxib";
|
|
4
|
+
const KATEX_JS_URL = "https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.js";
|
|
5
|
+
const KATEX_JS_SRI = "sha384-Rma6DA2IPUwhNxmrB/7S3Tno0YY7sFu9WSYMCuulLhIqYSGZ2gKCJWIqhBWqMQfh";
|
|
6
|
+
|
|
7
|
+
function isCurrency(expr) {
|
|
8
|
+
return /^\d[\d,]*(?:\.\d+)?(?:\s*(?:to|-)\s*\d[\d,]*(?:\.\d+)?)?$/.test(expr.trim());
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function shouldTreatAsLiteralInline(expr, text, closingIndex) {
|
|
12
|
+
if (/^\s|\s$/.test(expr) || /\n/.test(expr) || isCurrency(expr)) return true;
|
|
13
|
+
// Guard common currency range form "$5-$10" where expr becomes "5-".
|
|
14
|
+
if (/-\s*$/.test(expr)) {
|
|
15
|
+
const tail = text.slice(closingIndex + 1);
|
|
16
|
+
if (/^\d/.test(tail)) return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function findClosing(text, start, display) {
|
|
22
|
+
for (let i = start; i < text.length; i++) {
|
|
23
|
+
if (text[i] === "\\" && text[i + 1] === "$") {
|
|
24
|
+
i++;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (display) {
|
|
28
|
+
if (text[i] === "$" && text[i + 1] === "$") return i;
|
|
29
|
+
} else if (text[i] === "$" && text[i - 1] !== "$" && text[i + 1] !== "$") {
|
|
30
|
+
return i;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return -1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function splitMath(text) {
|
|
37
|
+
const out = [];
|
|
38
|
+
let buf = "";
|
|
39
|
+
let i = 0;
|
|
40
|
+
|
|
41
|
+
while (i < text.length) {
|
|
42
|
+
if (text[i] === "\\" && text[i + 1] === "$") {
|
|
43
|
+
buf += "$";
|
|
44
|
+
i += 2;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (text[i] !== "$") {
|
|
48
|
+
buf += text[i++];
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const display = text[i + 1] === "$";
|
|
53
|
+
const openLen = display ? 2 : 1;
|
|
54
|
+
const end = findClosing(text, i + openLen, display);
|
|
55
|
+
if (end < 0) {
|
|
56
|
+
buf += display ? "$$" : "$";
|
|
57
|
+
i += openLen;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const expr = text.slice(i + openLen, end);
|
|
62
|
+
if (!display && shouldTreatAsLiteralInline(expr, text, end)) {
|
|
63
|
+
buf += `$${expr}$`;
|
|
64
|
+
i = end + 1;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (buf) out.push({ text: buf });
|
|
69
|
+
out.push({ math: expr, display });
|
|
70
|
+
buf = "";
|
|
71
|
+
i = end + openLen;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (buf) out.push({ text: buf });
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function ensureTag(tag, attrs) {
|
|
79
|
+
const id = attrs["data-kitfly-latex"];
|
|
80
|
+
const existing = document.querySelector(`${tag}[data-kitfly-latex="${id}"]`);
|
|
81
|
+
if (existing) return Promise.resolve(existing);
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const el = document.createElement(tag);
|
|
84
|
+
Object.entries(attrs).forEach(([k, v]) => el.setAttribute(k, v));
|
|
85
|
+
el.addEventListener("load", () => resolve(el), { once: true });
|
|
86
|
+
el.addEventListener("error", () => reject(new Error(`Failed to load ${attrs.href || attrs.src}`)), {
|
|
87
|
+
once: true,
|
|
88
|
+
});
|
|
89
|
+
document.head.appendChild(el);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function renderFencedMath() {
|
|
94
|
+
document.querySelectorAll("pre > code.language-math").forEach((code) => {
|
|
95
|
+
const pre = code.parentElement;
|
|
96
|
+
if (!pre || pre.tagName !== "PRE") return;
|
|
97
|
+
const host = document.createElement("div");
|
|
98
|
+
host.className = "kitfly-katex-display";
|
|
99
|
+
window.katex.render(code.textContent || "", host, { displayMode: true, throwOnError: false });
|
|
100
|
+
pre.replaceWith(host);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function renderDelimitedMath() {
|
|
105
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
106
|
+
const nodes = [];
|
|
107
|
+
while (walker.nextNode()) {
|
|
108
|
+
const node = walker.currentNode;
|
|
109
|
+
const parent = node.parentElement;
|
|
110
|
+
if (!parent || parent.closest("pre, code, script, style, textarea, .katex")) continue;
|
|
111
|
+
if (!(node.textContent || "").includes("$")) continue;
|
|
112
|
+
nodes.push(node);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const node of nodes) {
|
|
116
|
+
const parts = splitMath(node.textContent || "");
|
|
117
|
+
if (!parts.some((p) => p.math)) continue;
|
|
118
|
+
const frag = document.createDocumentFragment();
|
|
119
|
+
for (const p of parts) {
|
|
120
|
+
if (p.text != null) {
|
|
121
|
+
frag.appendChild(document.createTextNode(p.text));
|
|
122
|
+
} else if (p.math != null) {
|
|
123
|
+
const host = document.createElement("span");
|
|
124
|
+
host.className = p.display ? "kitfly-katex-display" : "kitfly-katex-inline";
|
|
125
|
+
window.katex.render(p.math, host, { displayMode: Boolean(p.display), throwOnError: false });
|
|
126
|
+
frag.appendChild(host);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
node.replaceWith(frag);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function apply() {
|
|
134
|
+
const styleId = "kitfly-latex-style";
|
|
135
|
+
if (!document.getElementById(styleId)) {
|
|
136
|
+
const style = document.createElement("style");
|
|
137
|
+
style.id = styleId;
|
|
138
|
+
style.textContent = ".kitfly-katex-display{display:block;text-align:center;margin:1em 0;overflow-x:auto}";
|
|
139
|
+
document.head.appendChild(style);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
Promise.all([
|
|
143
|
+
ensureTag("link", {
|
|
144
|
+
rel: "stylesheet",
|
|
145
|
+
href: KATEX_CSS_URL,
|
|
146
|
+
integrity: KATEX_CSS_SRI,
|
|
147
|
+
crossorigin: "anonymous",
|
|
148
|
+
"data-kitfly-latex": "css",
|
|
149
|
+
}),
|
|
150
|
+
ensureTag("script", {
|
|
151
|
+
src: KATEX_JS_URL,
|
|
152
|
+
integrity: KATEX_JS_SRI,
|
|
153
|
+
crossorigin: "anonymous",
|
|
154
|
+
defer: "",
|
|
155
|
+
"data-kitfly-latex": "js",
|
|
156
|
+
}),
|
|
157
|
+
])
|
|
158
|
+
.then(() => {
|
|
159
|
+
if (!window.katex?.render) {
|
|
160
|
+
console.warn("[kitfly:latex] KaTeX unavailable");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
renderFencedMath();
|
|
164
|
+
renderDelimitedMath();
|
|
165
|
+
})
|
|
166
|
+
.catch((e) => console.warn("[kitfly:latex]", e instanceof Error ? e.message : String(e)));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (typeof document === "undefined") {
|
|
170
|
+
globalThis.__kitflyLatexTest = { splitMath, isCurrency, shouldTreatAsLiteralInline };
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (document.readyState === "loading") {
|
|
174
|
+
document.addEventListener("DOMContentLoaded", apply);
|
|
175
|
+
} else {
|
|
176
|
+
apply();
|
|
177
|
+
}
|
|
178
|
+
})();
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
const KIND_SET = new Set(["bar", "line", "pie"]);
|
|
3
|
+
|
|
4
|
+
function parseValue(raw) {
|
|
5
|
+
const v = String(raw ?? "").trim();
|
|
6
|
+
if (!v) return "";
|
|
7
|
+
if ((v.startsWith("[") && v.endsWith("]")) || (v.startsWith("{") && v.endsWith("}"))) {
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(v);
|
|
10
|
+
} catch {
|
|
11
|
+
return v;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
if (/^-?\d+(?:\.\d+)?$/.test(v)) return Number(v);
|
|
15
|
+
const m = v.match(/^"(.*)"$/) || v.match(/^'(.*)'$/);
|
|
16
|
+
return m ? m[1] : v;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseSpec(text) {
|
|
20
|
+
const out = {};
|
|
21
|
+
const lines = String(text ?? "").split(/\r?\n/);
|
|
22
|
+
for (const raw of lines) {
|
|
23
|
+
const line = raw.trim();
|
|
24
|
+
if (!line || line.startsWith("#")) continue;
|
|
25
|
+
const kv = line.match(/^([a-z0-9_-]+)\s*:\s*(.*)$/i);
|
|
26
|
+
if (!kv) continue;
|
|
27
|
+
out[kv[1]] = parseValue(kv[2]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const kind = String(out.kind || "").toLowerCase();
|
|
31
|
+
const labels = Array.isArray(out.labels) ? out.labels.map((x) => String(x ?? "")) : [];
|
|
32
|
+
const data = Array.isArray(out.data)
|
|
33
|
+
? out.data
|
|
34
|
+
.map((x) => Number(x))
|
|
35
|
+
.filter((n) => Number.isFinite(n))
|
|
36
|
+
: [];
|
|
37
|
+
|
|
38
|
+
if (!KIND_SET.has(kind)) return null;
|
|
39
|
+
if (!labels.length || !data.length || labels.length !== data.length) return null;
|
|
40
|
+
|
|
41
|
+
const height = typeof out.height === "number" && Number.isFinite(out.height) ? out.height : 300;
|
|
42
|
+
const color = typeof out.color === "string" ? out.color.trim().toLowerCase() : "primary";
|
|
43
|
+
const title = typeof out.title === "string" ? out.title : "";
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
kind,
|
|
47
|
+
labels,
|
|
48
|
+
data,
|
|
49
|
+
title,
|
|
50
|
+
color,
|
|
51
|
+
height: Math.max(160, Math.min(640, Math.round(height))),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function pickThemeColor(hint) {
|
|
56
|
+
const style = getComputedStyle(document.documentElement);
|
|
57
|
+
const map = {
|
|
58
|
+
primary: style.getPropertyValue("--color-primary").trim() || "#2563eb",
|
|
59
|
+
accent: style.getPropertyValue("--color-accent").trim() || "#f59e0b",
|
|
60
|
+
muted: style.getPropertyValue("--color-text-muted")?.trim() || "#6b7280",
|
|
61
|
+
text: style.getPropertyValue("--color-text")?.trim() || "#111827",
|
|
62
|
+
border: style.getPropertyValue("--color-border")?.trim() || "#d1d5db",
|
|
63
|
+
surface: style.getPropertyValue("--color-surface")?.trim() || "#f9fafb",
|
|
64
|
+
};
|
|
65
|
+
return map[hint] || map.primary;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function configFromSpec(spec) {
|
|
69
|
+
const base = pickThemeColor(spec.color);
|
|
70
|
+
const isPie = spec.kind === "pie";
|
|
71
|
+
const bg = isPie
|
|
72
|
+
? spec.labels.map((_, i) => `color-mix(in srgb, ${base} ${Math.max(20, 90 - i * 12)}%, white)`)
|
|
73
|
+
: base;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
type: spec.kind,
|
|
77
|
+
data: {
|
|
78
|
+
labels: spec.labels,
|
|
79
|
+
datasets: [
|
|
80
|
+
{
|
|
81
|
+
label: spec.title || "Series",
|
|
82
|
+
data: spec.data,
|
|
83
|
+
backgroundColor: bg,
|
|
84
|
+
borderColor: base,
|
|
85
|
+
borderWidth: 2,
|
|
86
|
+
tension: spec.kind === "line" ? 0.25 : 0,
|
|
87
|
+
fill: spec.kind === "line" ? false : true,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
options: {
|
|
92
|
+
responsive: true,
|
|
93
|
+
maintainAspectRatio: false,
|
|
94
|
+
plugins: {
|
|
95
|
+
legend: { display: spec.kind === "pie", labels: { color: pickThemeColor("text") } },
|
|
96
|
+
title: { display: Boolean(spec.title), text: spec.title, color: pickThemeColor("text") },
|
|
97
|
+
},
|
|
98
|
+
scales:
|
|
99
|
+
spec.kind === "pie"
|
|
100
|
+
? undefined
|
|
101
|
+
: {
|
|
102
|
+
x: { ticks: { color: pickThemeColor("text") }, grid: { color: pickThemeColor("border") } },
|
|
103
|
+
y: { ticks: { color: pickThemeColor("text") }, grid: { color: pickThemeColor("border") } },
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function renderWrapper(wrapper, spec) {
|
|
110
|
+
let canvas = wrapper.querySelector("canvas");
|
|
111
|
+
if (!canvas) {
|
|
112
|
+
canvas = document.createElement("canvas");
|
|
113
|
+
wrapper.appendChild(canvas);
|
|
114
|
+
}
|
|
115
|
+
wrapper.style.height = `${spec.height}px`;
|
|
116
|
+
if (wrapper.__kitflyChart && typeof wrapper.__kitflyChart.destroy === "function") {
|
|
117
|
+
wrapper.__kitflyChart.destroy();
|
|
118
|
+
}
|
|
119
|
+
wrapper.__kitflyChart = new window.Chart(canvas, configFromSpec(spec));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function transformCodeBlock(code) {
|
|
123
|
+
const pre = code.parentElement;
|
|
124
|
+
if (!pre || pre.tagName !== "PRE") return;
|
|
125
|
+
const spec = parseSpec(code.textContent || "");
|
|
126
|
+
if (!spec) {
|
|
127
|
+
console.warn("[kitfly:slides-charts-lite] Invalid chart block, leaving as code");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const wrapper = document.createElement("div");
|
|
132
|
+
wrapper.className = "kitfly-chart-wrapper";
|
|
133
|
+
wrapper.setAttribute("data-kitfly-chart-spec", JSON.stringify(spec));
|
|
134
|
+
pre.replaceWith(wrapper);
|
|
135
|
+
renderWrapper(wrapper, spec);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function renderAll() {
|
|
139
|
+
if (!window.Chart) {
|
|
140
|
+
console.warn("[kitfly:slides-charts-lite] Chart.js unavailable");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const styleId = "kitfly-charts-lite-style";
|
|
145
|
+
if (!document.getElementById(styleId)) {
|
|
146
|
+
const style = document.createElement("style");
|
|
147
|
+
style.id = styleId;
|
|
148
|
+
style.textContent = ".kitfly-chart-wrapper{position:relative;width:100%;max-width:100%;margin:1rem 0}";
|
|
149
|
+
document.head.appendChild(style);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
document.querySelectorAll("pre > code.language-chart").forEach(transformCodeBlock);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function reinitCharts() {
|
|
156
|
+
document.querySelectorAll(".kitfly-chart-wrapper[data-kitfly-chart-spec]").forEach((wrapper) => {
|
|
157
|
+
const raw = wrapper.getAttribute("data-kitfly-chart-spec") || "";
|
|
158
|
+
try {
|
|
159
|
+
const spec = JSON.parse(raw);
|
|
160
|
+
renderWrapper(wrapper, spec);
|
|
161
|
+
} catch {
|
|
162
|
+
// ignore broken metadata
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (typeof document === "undefined") {
|
|
168
|
+
globalThis.__kitflyChartsLiteTest = { parseSpec };
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
window.reinitCharts = reinitCharts;
|
|
173
|
+
|
|
174
|
+
if (document.readyState === "loading") {
|
|
175
|
+
document.addEventListener("DOMContentLoaded", renderAll);
|
|
176
|
+
} else {
|
|
177
|
+
renderAll();
|
|
178
|
+
}
|
|
179
|
+
})();
|