kitfly 0.1.2 → 0.2.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.
- package/CHANGELOG.md +34 -0
- package/README.md +63 -16
- package/VERSION +1 -1
- package/dist/_raw/content/deployment/preflight.md +134 -0
- package/dist/_raw/content/deployment/recipes/aws-s3.md +128 -0
- package/dist/_raw/content/deployment/recipes/cloudflare-pages.md +73 -0
- package/dist/_raw/content/deployment/recipes/cloudflare-r2.md +156 -0
- package/dist/_raw/content/deployment/recipes/fly-io.md +57 -0
- package/dist/_raw/content/deployment/recipes/github-pages.md +112 -0
- package/dist/_raw/content/deployment/recipes/netlify.md +99 -0
- package/dist/_raw/content/deployment/recipes/vercel.md +88 -0
- package/dist/_raw/content/deployment/secrets-and-env-vars.md +75 -0
- package/dist/_raw/content/deployment.md +128 -0
- package/dist/_raw/content/guide/approaches.md +182 -0
- package/dist/_raw/content/guide/features.md +121 -0
- package/dist/_raw/content/guide/getting-started.md +112 -0
- package/dist/_raw/content/guide/kitfly-overview.md +209 -0
- package/dist/_raw/content/reference/configuration.md +259 -0
- package/dist/_raw/content/reference/design-catalog.md +167 -0
- package/dist/_raw/content/reference/environment-variables.md +66 -0
- package/dist/_raw/content/reference/glossary.md +92 -0
- package/dist/_raw/content/reference/key-concepts.md +118 -0
- package/dist/_raw/content/reference/plugins.md +220 -0
- package/dist/_raw/content/reference/structure.md +166 -0
- package/dist/_raw/content/reference.md +19 -0
- package/dist/_raw/content/templates/crucible.md +192 -0
- package/dist/_raw/content/templates/handbook.md +83 -0
- package/dist/_raw/content/templates/minimal.md +138 -0
- package/dist/_raw/content/templates/overview.md +187 -0
- package/dist/_raw/content/templates/pipeline.md +151 -0
- package/dist/_raw/content/templates/productbook.md +187 -0
- package/dist/_raw/content/templates/runbook.md +193 -0
- package/dist/_raw/content/templates/servicebook.md +163 -0
- package/dist/_raw/docs/decisions/ADR-0001-minimalist-site-code.md +118 -0
- package/dist/_raw/docs/decisions/ADR-0002-ai-accessibility.md +153 -0
- package/dist/_raw/docs/decisions/ADR-0003-single-file-bundle.md +93 -0
- package/dist/_raw/docs/decisions/ADR-0004-bun-runtime.md +98 -0
- package/dist/_raw/docs/decisions/ADR-0005-plugin-contract-and-distribution.md +110 -0
- package/dist/_raw/docs/decisions/DDR-0001-viewport-locked-layout.md +111 -0
- package/dist/_raw/docs/decisions/DDR-0002-theme-system.md +131 -0
- package/dist/_raw/docs/decisions/DDR-0003-bounded-logo-slot.md +106 -0
- package/dist/_raw/docs/decisions/DDR-0004-slides-rendering-model.md +113 -0
- package/dist/_raw/docs/decisions/DDR-0005-deterministic-layout-boundary.md +107 -0
- package/dist/_raw/docs/userguide/cli/build.md +85 -0
- package/dist/_raw/docs/userguide/cli/bundle.md +81 -0
- package/dist/_raw/docs/userguide/cli/dev.md +92 -0
- package/dist/_raw/docs/userguide/cli/init.md +116 -0
- package/dist/_raw/docs/userguide/cli/servers.md +69 -0
- package/dist/_raw/docs/userguide/cli/stop.md +76 -0
- package/dist/_raw/docs/userguide/cli/update.md +78 -0
- package/dist/_raw/docs/userguide/cli/version.md +65 -0
- package/dist/_raw/docs/userguide/cli.md +34 -0
- package/dist/_raw/docs/userguide/sharing.md +94 -0
- package/dist/_raw/schemas/plugin-schemas-notes.md +71 -0
- package/dist/_raw/schemas.md +42 -0
- package/dist/assets/brand/kitfly-favicon-32.png +0 -0
- package/dist/assets/brand/kitfly-icon-64.png +0 -0
- package/dist/assets/brand/kitfly-logo-128.png +0 -0
- package/dist/assets/brand/kitfly-logo-512.png +0 -0
- package/dist/assets/brand/kitfly-logo.svg +12132 -0
- package/dist/assets/brand/kitfly-neon-128.png +0 -0
- package/dist/assets/brand/kitfly-neon-192.png +0 -0
- package/dist/assets/brand/kitfly-neon-256.png +0 -0
- package/dist/assets/brand/kitfly-neon.png +0 -0
- package/dist/assets/brand/palette.md +75 -0
- package/dist/content/deployment/index.html +11 -0
- package/dist/content/deployment/preflight.html +418 -0
- package/dist/content/deployment/recipes/aws-s3.html +421 -0
- package/dist/content/deployment/recipes/cloudflare-pages.html +372 -0
- package/dist/content/deployment/recipes/cloudflare-r2.html +443 -0
- package/dist/content/deployment/recipes/fly-io.html +356 -0
- package/dist/content/deployment/recipes/github-pages.html +414 -0
- package/dist/content/deployment/recipes/index.html +11 -0
- package/dist/content/deployment/recipes/netlify.html +394 -0
- package/dist/content/deployment/recipes/vercel.html +382 -0
- package/dist/content/deployment/secrets-and-env-vars.html +380 -0
- package/dist/content/deployment.html +426 -0
- package/dist/content/guide/approaches.html +501 -0
- package/dist/content/guide/features.html +436 -0
- package/dist/content/guide/getting-started.html +403 -0
- package/dist/content/guide/index.html +11 -0
- package/dist/content/guide/kitfly-overview.html +544 -0
- package/dist/content/index.html +11 -0
- package/dist/content/reference/configuration.html +580 -0
- package/dist/content/reference/design-catalog.html +449 -0
- package/dist/content/reference/environment-variables.html +367 -0
- package/dist/content/reference/glossary.html +368 -0
- package/dist/content/reference/index.html +11 -0
- package/dist/content/reference/key-concepts.html +399 -0
- package/dist/content/reference/plugins.html +491 -0
- package/dist/content/reference/structure.html +463 -0
- package/dist/content/reference.html +334 -0
- package/dist/content/templates/crucible.html +546 -0
- package/dist/content/templates/handbook.html +405 -0
- package/dist/content/templates/index.html +11 -0
- package/dist/content/templates/minimal.html +447 -0
- package/dist/content/templates/overview.html +558 -0
- package/dist/content/templates/pipeline.html +494 -0
- package/dist/content/templates/productbook.html +540 -0
- package/dist/content/templates/runbook.html +543 -0
- package/dist/content/templates/servicebook.html +523 -0
- package/dist/content-index.json +540 -0
- package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +491 -0
- package/dist/docs/decisions/ADR-0002-ai-accessibility.html +434 -0
- package/dist/docs/decisions/ADR-0003-single-file-bundle.html +412 -0
- package/dist/docs/decisions/ADR-0004-bun-runtime.html +409 -0
- package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +402 -0
- package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +459 -0
- package/dist/docs/decisions/DDR-0002-theme-system.html +452 -0
- package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +423 -0
- package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +399 -0
- package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +422 -0
- package/dist/docs/decisions/index.html +11 -0
- package/dist/docs/userguide/cli/build.html +408 -0
- package/dist/docs/userguide/cli/bundle.html +419 -0
- package/dist/docs/userguide/cli/dev.html +428 -0
- package/dist/docs/userguide/cli/index.html +11 -0
- package/dist/docs/userguide/cli/init.html +436 -0
- package/dist/docs/userguide/cli/servers.html +393 -0
- package/dist/docs/userguide/cli/stop.html +408 -0
- package/dist/docs/userguide/cli/update.html +406 -0
- package/dist/docs/userguide/cli/version.html +406 -0
- package/dist/docs/userguide/cli.html +386 -0
- package/dist/docs/userguide/index.html +11 -0
- package/dist/docs/userguide/sharing.html +465 -0
- package/dist/index.html +387 -0
- package/dist/llms.txt +18 -0
- package/dist/provenance.json +7 -0
- package/dist/schemas/index.html +11 -0
- package/dist/schemas/plugin-registry.schema.html +327 -0
- package/dist/schemas/plugin-schemas-notes.html +364 -0
- package/dist/schemas/plugin.schema.html +327 -0
- package/dist/schemas/plugins.schema.html +327 -0
- package/dist/schemas/v0/common.schema.html +386 -0
- package/dist/schemas/v0/index.html +11 -0
- package/dist/schemas/v0/plugin-registry.schema.html +547 -0
- package/dist/schemas/v0/plugin.schema.html +497 -0
- package/dist/schemas/v0/plugins.schema.html +406 -0
- package/dist/schemas/v0/site.schema.html +541 -0
- package/dist/schemas/v0/theme.schema.html +615 -0
- package/dist/schemas.html +351 -0
- package/dist/styles.css +1262 -0
- package/package.json +4 -2
- package/plugins-dist/callouts.css +32 -0
- package/plugins-dist/callouts.js +46 -0
- package/plugins-dist/slides-visuals.css +224 -0
- package/plugins-dist/slides-visuals.js +598 -0
- package/registry/plugins.yaml +35 -0
- package/schemas/README.md +10 -0
- package/schemas/plugin-registry.schema.json +5 -0
- package/schemas/plugin-schemas-notes.md +71 -0
- package/schemas/plugin.schema.json +5 -0
- package/schemas/plugins.schema.json +5 -0
- package/schemas/v0/common.schema.json +64 -0
- package/schemas/v0/plugin-registry.schema.json +225 -0
- package/schemas/v0/plugin.schema.json +175 -0
- package/schemas/v0/plugins.schema.json +84 -0
- package/schemas/v0/site.schema.json +56 -9
- package/schemas/v0/theme.schema.json +105 -22
- package/scripts/build.ts +155 -3
- package/scripts/bundle.ts +258 -95
- package/scripts/dev.ts +203 -1
- package/src/__tests__/build.test.ts +158 -1
- package/src/__tests__/bundle.test.ts +31 -0
- package/src/__tests__/cli.test.ts +14 -3
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/bad-list-indent.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/blank-line.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/compare-object-items.md +9 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/indented-fence.md +4 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/stat-grid-missing-fields.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/unknown-type.md +3 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/compare.md +10 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/comparison-table.md +14 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/funnel.md +7 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/kpi.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/layer-cake.md +6 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/pyramid.md +6 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/quadrant-grid.md +8 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/scorecard.md +13 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/stat-grid.md +8 -0
- package/src/__tests__/init.test.ts +35 -0
- package/src/__tests__/plugin-loader.test.ts +221 -0
- package/src/__tests__/shared.test.ts +428 -0
- package/src/__tests__/slides-visuals-fence-contract.test.ts +28 -0
- package/src/__tests__/slides-visuals-runtime-regressions.bun.test.ts +114 -0
- package/src/__tests__/styles.test.ts +35 -0
- package/src/cli.ts +9 -4
- package/src/plugin-loader.ts +245 -0
- package/src/shared.ts +614 -7
- package/src/site/styles.css +331 -0
- package/src/site/template.html +66 -5
- package/src/templates/deck.ts +186 -0
- package/src/templates/driver.ts +11 -1
- package/src/templates/minimal.ts +1 -0
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
const BLOCK_RE = /^:::\s*([a-z0-9-]+)\s*$/i;
|
|
3
|
+
const CLOSE_RE = /^:::\s*$/;
|
|
4
|
+
|
|
5
|
+
function parseScalar(raw) {
|
|
6
|
+
let v = String(raw ?? "").trim();
|
|
7
|
+
v = v.replace(/\s*:::\s*$/, "").trim();
|
|
8
|
+
if (!v) return "";
|
|
9
|
+
const m = v.match(/^"(.*)"$/) || v.match(/^'(.*)'$/);
|
|
10
|
+
if (m) return m[1];
|
|
11
|
+
return v;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseFence(text) {
|
|
15
|
+
const raw = String(text ?? "");
|
|
16
|
+
const trimmed = raw.trim();
|
|
17
|
+
if (!trimmed.startsWith(":::")) return null;
|
|
18
|
+
const lines = trimmed.split(/\r?\n/);
|
|
19
|
+
if (lines.length < 2) return null;
|
|
20
|
+
const head = lines[0].trim();
|
|
21
|
+
const tail = lines[lines.length - 1].trim();
|
|
22
|
+
const m = head.match(BLOCK_RE);
|
|
23
|
+
if (!m) return null;
|
|
24
|
+
if (!CLOSE_RE.test(tail)) return null;
|
|
25
|
+
const type = m[1].toLowerCase();
|
|
26
|
+
const body = lines.slice(1, -1);
|
|
27
|
+
return { type, data: parseLinesToObject(body) };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseLinesToObject(lines) {
|
|
31
|
+
const out = {};
|
|
32
|
+
let pendingKey = null;
|
|
33
|
+
for (const raw of lines) {
|
|
34
|
+
const line = String(raw ?? "");
|
|
35
|
+
const trimmed = line.trim();
|
|
36
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
37
|
+
const kv = trimmed.match(/^([a-z0-9_-]+)\s*:\s*(.*)$/i);
|
|
38
|
+
if (!kv) continue;
|
|
39
|
+
const key = kv[1];
|
|
40
|
+
const value = kv[2];
|
|
41
|
+
if (value) {
|
|
42
|
+
out[key] = parseScalar(value);
|
|
43
|
+
pendingKey = null;
|
|
44
|
+
} else {
|
|
45
|
+
pendingKey = key;
|
|
46
|
+
out[key] = out[key] ?? [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseListItemToValue(li) {
|
|
53
|
+
const parts = [];
|
|
54
|
+
for (const child of li.querySelectorAll(":scope > p")) {
|
|
55
|
+
const t = (child.textContent || "").trim();
|
|
56
|
+
if (t) parts.push(t);
|
|
57
|
+
}
|
|
58
|
+
const raw = parts.length ? parts.join("\n") : (li.textContent || "").trim();
|
|
59
|
+
const lines = raw
|
|
60
|
+
.split(/\r?\n/)
|
|
61
|
+
.map((l) => l.trim())
|
|
62
|
+
.filter((l) => l && l !== ":::");
|
|
63
|
+
const obj = {};
|
|
64
|
+
let any = false;
|
|
65
|
+
for (const line of lines) {
|
|
66
|
+
const kv = line.match(/^([a-z0-9_-]+)\s*:\s*(.*)$/i);
|
|
67
|
+
if (!kv) continue;
|
|
68
|
+
any = true;
|
|
69
|
+
obj[kv[1]] = parseScalar(kv[2]);
|
|
70
|
+
}
|
|
71
|
+
return any ? obj : parseScalar(raw);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function asTextItem(value) {
|
|
75
|
+
if (typeof value === "string") return value;
|
|
76
|
+
if (!value || typeof value !== "object") return String(value ?? "");
|
|
77
|
+
if (typeof value.text === "string") return value.text;
|
|
78
|
+
if (typeof value.label === "string" && typeof value.value === "string") return `${value.label}: ${value.value}`;
|
|
79
|
+
const parts = [];
|
|
80
|
+
for (const [k, v] of Object.entries(value)) {
|
|
81
|
+
if (typeof v === "string" && v.trim()) parts.push(`${k}: ${v}`);
|
|
82
|
+
}
|
|
83
|
+
return parts.length ? parts.join(" · ") : "";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function listKeysForType(type) {
|
|
87
|
+
switch (type) {
|
|
88
|
+
case "compare":
|
|
89
|
+
return ["left", "right"];
|
|
90
|
+
case "comparison-table":
|
|
91
|
+
return ["headers", "rows"];
|
|
92
|
+
default:
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function scalarKeysForType(type) {
|
|
98
|
+
switch (type) {
|
|
99
|
+
case "compare":
|
|
100
|
+
return ["left-title", "right-title"];
|
|
101
|
+
default:
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isListKeyMarkerText(text, allowedKeys) {
|
|
107
|
+
if (!allowedKeys || allowedKeys.length === 0) return null;
|
|
108
|
+
const m = text.match(/^([a-z0-9_-]+)\s*:\s*$/i);
|
|
109
|
+
if (!m) return null;
|
|
110
|
+
const key = m[1].toLowerCase();
|
|
111
|
+
return allowedKeys.includes(key) ? key : null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function parseScalarMarkerText(text, allowedScalarKeys) {
|
|
115
|
+
if (!allowedScalarKeys || allowedScalarKeys.length === 0) return null;
|
|
116
|
+
const m = text.match(/^([a-z0-9_-]+)\s*:\s*(.+)$/i);
|
|
117
|
+
if (!m) return null;
|
|
118
|
+
const key = m[1].toLowerCase();
|
|
119
|
+
if (!allowedScalarKeys.includes(key)) return null;
|
|
120
|
+
return { key, value: parseScalar(m[2]) };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function asTextItem(value) {
|
|
124
|
+
if (typeof value === "string") return value;
|
|
125
|
+
if (!value || typeof value !== "object") return String(value ?? "");
|
|
126
|
+
if (typeof value.text === "string") return value.text;
|
|
127
|
+
if (typeof value.label === "string" && typeof value.value === "string") return `${value.label}: ${value.value}`;
|
|
128
|
+
const parts = [];
|
|
129
|
+
for (const [k, v] of Object.entries(value)) {
|
|
130
|
+
if (typeof v === "string" && v.trim()) parts.push(`${k}: ${v}`);
|
|
131
|
+
}
|
|
132
|
+
return parts.length ? parts.join(" · ") : "";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function listKeysForType(type) {
|
|
136
|
+
switch (type) {
|
|
137
|
+
case "compare":
|
|
138
|
+
return ["left", "right"];
|
|
139
|
+
case "comparison-table":
|
|
140
|
+
return ["headers", "rows"];
|
|
141
|
+
default:
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function isListKeyMarker(li, allowedKeys) {
|
|
147
|
+
if (!allowedKeys || allowedKeys.length === 0) return null;
|
|
148
|
+
const raw = (li.textContent || "").trim();
|
|
149
|
+
const m = raw.match(/^([a-z0-9_-]+)\s*:\s*$/i);
|
|
150
|
+
if (!m) return null;
|
|
151
|
+
const key = m[1].toLowerCase();
|
|
152
|
+
return allowedKeys.includes(key) ? key : null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function parseBodyNodes(nodes) {
|
|
156
|
+
const out = {};
|
|
157
|
+
let pendingKey = null;
|
|
158
|
+
|
|
159
|
+
for (const node of nodes) {
|
|
160
|
+
const tag = String(node.tagName || "").toUpperCase();
|
|
161
|
+
if (tag === "P") {
|
|
162
|
+
const lines = (node.textContent || "").split(/\r?\n/);
|
|
163
|
+
for (const rawLine of lines) {
|
|
164
|
+
const trimmed = rawLine.trim();
|
|
165
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
166
|
+
const kv = trimmed.match(/^([a-z0-9_-]+)\s*:\s*(.*)$/i);
|
|
167
|
+
if (!kv) continue;
|
|
168
|
+
const key = kv[1];
|
|
169
|
+
const value = kv[2];
|
|
170
|
+
if (value) {
|
|
171
|
+
out[key] = parseScalar(value);
|
|
172
|
+
pendingKey = null;
|
|
173
|
+
} else {
|
|
174
|
+
pendingKey = key;
|
|
175
|
+
out[key] = [];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if ((tag === "UL" || tag === "OL") && pendingKey) {
|
|
182
|
+
const items = [];
|
|
183
|
+
for (const li of node.querySelectorAll(":scope > li")) {
|
|
184
|
+
items.push(parseListItemToValue(li));
|
|
185
|
+
}
|
|
186
|
+
out[pendingKey] = items;
|
|
187
|
+
pendingKey = null;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function el(tag, className, text) {
|
|
196
|
+
const node = document.createElement(tag);
|
|
197
|
+
if (className) node.className = className;
|
|
198
|
+
if (text != null && text !== "") node.textContent = String(text);
|
|
199
|
+
return node;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function renderKpi(data) {
|
|
203
|
+
const root = el("div", "kitfly-visual kitfly-kpi");
|
|
204
|
+
root.appendChild(el("div", "kitfly-kpi-label", data.label || ""));
|
|
205
|
+
root.appendChild(el("div", "kitfly-kpi-value", data.value || ""));
|
|
206
|
+
const trend = String(data.trend || "").trim();
|
|
207
|
+
const trendEl = el("div", "kitfly-kpi-trend", trend);
|
|
208
|
+
if (/^\+/.test(trend)) trendEl.classList.add("pos");
|
|
209
|
+
if (/^-/.test(trend)) trendEl.classList.add("neg");
|
|
210
|
+
root.appendChild(trendEl);
|
|
211
|
+
return root;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function renderStatGrid(data) {
|
|
215
|
+
const items = Array.isArray(data.metrics) ? data.metrics : Array.isArray(data.items) ? data.items : [];
|
|
216
|
+
const root = el("div", "kitfly-visual kitfly-stat-grid");
|
|
217
|
+
for (const it of items) {
|
|
218
|
+
const item = typeof it === "object" && it ? it : { label: String(it ?? "") };
|
|
219
|
+
const card = el("div", "kitfly-stat");
|
|
220
|
+
card.appendChild(el("div", "kitfly-stat-label", item.label || ""));
|
|
221
|
+
card.appendChild(el("div", "kitfly-stat-value", item.value || ""));
|
|
222
|
+
const trend = String(item.trend || "").trim();
|
|
223
|
+
if (trend) {
|
|
224
|
+
const t = el("div", "kitfly-stat-trend", trend);
|
|
225
|
+
if (/^\+/.test(trend)) t.classList.add("pos");
|
|
226
|
+
if (/^-/.test(trend)) t.classList.add("neg");
|
|
227
|
+
card.appendChild(t);
|
|
228
|
+
}
|
|
229
|
+
root.appendChild(card);
|
|
230
|
+
}
|
|
231
|
+
return root;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function renderCompare(data) {
|
|
235
|
+
const root = el("div", "kitfly-visual kitfly-compare");
|
|
236
|
+
const left = el("div", "kitfly-compare-col");
|
|
237
|
+
const right = el("div", "kitfly-compare-col");
|
|
238
|
+
left.appendChild(el("div", "kitfly-compare-title", data["left-title"] || data.leftTitle || "Left"));
|
|
239
|
+
right.appendChild(el("div", "kitfly-compare-title", data["right-title"] || data.rightTitle || "Right"));
|
|
240
|
+
|
|
241
|
+
const leftItems = Array.isArray(data.left) ? data.left : [];
|
|
242
|
+
const rightItems = Array.isArray(data.right) ? data.right : [];
|
|
243
|
+
|
|
244
|
+
const ulL = el("ul", "kitfly-compare-list");
|
|
245
|
+
const ulR = el("ul", "kitfly-compare-list");
|
|
246
|
+
for (const item of leftItems) ulL.appendChild(el("li", "", asTextItem(item)));
|
|
247
|
+
for (const item of rightItems) ulR.appendChild(el("li", "", asTextItem(item)));
|
|
248
|
+
left.appendChild(ulL);
|
|
249
|
+
right.appendChild(ulR);
|
|
250
|
+
root.appendChild(left);
|
|
251
|
+
root.appendChild(right);
|
|
252
|
+
return root;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function renderQuadrantGrid(data) {
|
|
256
|
+
const root = el("div", "kitfly-visual kitfly-quadrant");
|
|
257
|
+
const grid = el("div", "kitfly-quadrant-grid");
|
|
258
|
+
grid.appendChild(el("div", "kitfly-quadrant-cell tl block", data.tl || ""));
|
|
259
|
+
grid.appendChild(el("div", "kitfly-quadrant-cell tr block", data.tr || ""));
|
|
260
|
+
grid.appendChild(el("div", "kitfly-quadrant-cell bl block", data.bl || ""));
|
|
261
|
+
grid.appendChild(el("div", "kitfly-quadrant-cell br block", data.br || ""));
|
|
262
|
+
root.appendChild(grid);
|
|
263
|
+
|
|
264
|
+
const axisX = el("div", "kitfly-quadrant-axis axis-x", data["axis-x"] || data.axisX || "");
|
|
265
|
+
const axisY = el("div", "kitfly-quadrant-axis axis-y", data["axis-y"] || data.axisY || "");
|
|
266
|
+
if (axisX.textContent) root.appendChild(axisX);
|
|
267
|
+
if (axisY.textContent) root.appendChild(axisY);
|
|
268
|
+
return root;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function renderScorecard(data) {
|
|
272
|
+
const metrics = Array.isArray(data.metrics) ? data.metrics : [];
|
|
273
|
+
const root = el("div", "kitfly-visual kitfly-scorecard");
|
|
274
|
+
for (const m of metrics) {
|
|
275
|
+
const item = typeof m === "object" && m ? m : { label: String(m ?? "") };
|
|
276
|
+
const card = el("div", "kitfly-scorecard-metric");
|
|
277
|
+
card.appendChild(el("div", "kitfly-scorecard-label", item.label || ""));
|
|
278
|
+
card.appendChild(el("div", "kitfly-scorecard-value", item.value || ""));
|
|
279
|
+
const trend = String(item.trend || "").trim();
|
|
280
|
+
if (trend) {
|
|
281
|
+
const t = el("div", "kitfly-scorecard-trend", trend);
|
|
282
|
+
if (/^\+/.test(trend)) t.classList.add("pos");
|
|
283
|
+
if (/^-/.test(trend)) t.classList.add("neg");
|
|
284
|
+
card.appendChild(t);
|
|
285
|
+
}
|
|
286
|
+
root.appendChild(card);
|
|
287
|
+
}
|
|
288
|
+
return root;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function rowCells(row) {
|
|
292
|
+
if (typeof row === "string") {
|
|
293
|
+
const t = row.trim();
|
|
294
|
+
if (t.startsWith("[") && t.endsWith("]")) {
|
|
295
|
+
try {
|
|
296
|
+
const parsed = JSON.parse(t);
|
|
297
|
+
if (Array.isArray(parsed)) return parsed.map((v) => String(v ?? ""));
|
|
298
|
+
} catch {
|
|
299
|
+
// fall through
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return row.split("|").map((s) => s.trim()).filter(Boolean);
|
|
303
|
+
}
|
|
304
|
+
if (typeof row === "object" && row && Array.isArray(row.cells)) return row.cells.map((s) => String(s ?? ""));
|
|
305
|
+
return [String(row ?? "")];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function renderComparisonTable(data) {
|
|
309
|
+
const headers = Array.isArray(data.headers) ? data.headers : [];
|
|
310
|
+
const rows = Array.isArray(data.rows) ? data.rows : [];
|
|
311
|
+
const root = el("div", "kitfly-visual kitfly-comparison-table");
|
|
312
|
+
|
|
313
|
+
const headRow = el("div", "kitfly-table-row kitfly-table-head");
|
|
314
|
+
for (const h of headers) headRow.appendChild(el("div", "kitfly-table-cell", asTextItem(h)));
|
|
315
|
+
root.appendChild(headRow);
|
|
316
|
+
|
|
317
|
+
for (const r of rows) {
|
|
318
|
+
const row = el("div", "kitfly-table-row");
|
|
319
|
+
for (const c of rowCells(r)) row.appendChild(el("div", "kitfly-table-cell", asTextItem(c)));
|
|
320
|
+
root.appendChild(row);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return root;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function renderLayerCake(data) {
|
|
327
|
+
const layers = Array.isArray(data.layers) ? data.layers : [];
|
|
328
|
+
const root = el("div", "kitfly-visual kitfly-layer-cake");
|
|
329
|
+
layers.forEach((layer, idx) => {
|
|
330
|
+
const band = el("div", "kitfly-layer", layer);
|
|
331
|
+
band.style.setProperty("--kitfly-layer-idx", String(idx));
|
|
332
|
+
root.appendChild(band);
|
|
333
|
+
});
|
|
334
|
+
return root;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function renderPyramid(data) {
|
|
338
|
+
const levels = Array.isArray(data.levels) ? data.levels : [];
|
|
339
|
+
const root = el("div", "kitfly-visual kitfly-pyramid");
|
|
340
|
+
const total = Math.max(levels.length, 1);
|
|
341
|
+
const min = 55;
|
|
342
|
+
const max = 100;
|
|
343
|
+
levels.forEach((lvl, idx) => {
|
|
344
|
+
const row = el("div", "kitfly-pyramid-level", lvl);
|
|
345
|
+
const t = Math.max(total - 1, 1);
|
|
346
|
+
const width = min + (idx / t) * (max - min);
|
|
347
|
+
row.style.width = `${width.toFixed(2)}%`;
|
|
348
|
+
root.appendChild(row);
|
|
349
|
+
});
|
|
350
|
+
return root;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function renderFunnel(data) {
|
|
354
|
+
const stages = Array.isArray(data.stages) ? data.stages : [];
|
|
355
|
+
const root = el("div", "kitfly-visual kitfly-funnel");
|
|
356
|
+
const total = Math.max(stages.length, 1);
|
|
357
|
+
const min = 55;
|
|
358
|
+
const max = 100;
|
|
359
|
+
stages.forEach((stage, idx) => {
|
|
360
|
+
const row = el("div", "kitfly-funnel-stage", stage);
|
|
361
|
+
const t = Math.max(total - 1, 1);
|
|
362
|
+
const width = max - (idx / t) * (max - min);
|
|
363
|
+
row.style.width = `${width.toFixed(2)}%`;
|
|
364
|
+
root.appendChild(row);
|
|
365
|
+
});
|
|
366
|
+
return root;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function renderBlock(type, data) {
|
|
370
|
+
switch (type) {
|
|
371
|
+
case "kpi":
|
|
372
|
+
return renderKpi(data);
|
|
373
|
+
case "stat-grid":
|
|
374
|
+
return renderStatGrid(data);
|
|
375
|
+
case "compare":
|
|
376
|
+
return renderCompare(data);
|
|
377
|
+
case "quadrant-grid":
|
|
378
|
+
return renderQuadrantGrid(data);
|
|
379
|
+
case "scorecard":
|
|
380
|
+
return renderScorecard(data);
|
|
381
|
+
case "comparison-table":
|
|
382
|
+
return renderComparisonTable(data);
|
|
383
|
+
case "layer-cake":
|
|
384
|
+
return renderLayerCake(data);
|
|
385
|
+
case "pyramid":
|
|
386
|
+
return renderPyramid(data);
|
|
387
|
+
case "funnel":
|
|
388
|
+
return renderFunnel(data);
|
|
389
|
+
default:
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function tryReplaceSingleNode(node) {
|
|
395
|
+
const txt = node.textContent || "";
|
|
396
|
+
if (!txt.trimStart().startsWith(":::")) return false;
|
|
397
|
+
const parsed = parseFence(txt);
|
|
398
|
+
if (!parsed) return false;
|
|
399
|
+
const rendered = renderBlock(parsed.type, parsed.data);
|
|
400
|
+
if (!rendered) return false;
|
|
401
|
+
rendered.setAttribute("data-kitfly-visual", parsed.type);
|
|
402
|
+
node.replaceWith(rendered);
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function tryReplaceFragmentedFence(start) {
|
|
407
|
+
const startTxt = String(start.textContent || "");
|
|
408
|
+
const lines = startTxt.split(/\r?\n/);
|
|
409
|
+
const first = (lines[0] || "").trim();
|
|
410
|
+
const m = first.match(BLOCK_RE);
|
|
411
|
+
if (!m) return false;
|
|
412
|
+
const type = m[1].toLowerCase();
|
|
413
|
+
|
|
414
|
+
const between = [];
|
|
415
|
+
let end = null;
|
|
416
|
+
let cur = start.nextElementSibling;
|
|
417
|
+
while (cur) {
|
|
418
|
+
const allLines = String(cur.textContent || "")
|
|
419
|
+
.split(/\r?\n/)
|
|
420
|
+
.map((l) => l.trim())
|
|
421
|
+
.filter(Boolean);
|
|
422
|
+
if (allLines.some((l) => l === ":::")) {
|
|
423
|
+
end = cur;
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
between.push(cur);
|
|
427
|
+
cur = cur.nextElementSibling;
|
|
428
|
+
}
|
|
429
|
+
if (!end) return false;
|
|
430
|
+
|
|
431
|
+
const data = parseBodyNodesWithFirstLines(lines.slice(1), between, end, type);
|
|
432
|
+
const rendered = renderBlock(type, data);
|
|
433
|
+
if (!rendered) return false;
|
|
434
|
+
rendered.setAttribute("data-kitfly-visual", type);
|
|
435
|
+
|
|
436
|
+
start.parentNode.insertBefore(rendered, start);
|
|
437
|
+
const toRemove = [start, ...between, end];
|
|
438
|
+
for (const n of toRemove) n.remove();
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function parseBodyNodesWithFirstLines(firstLines, between, end, type) {
|
|
443
|
+
const out = {};
|
|
444
|
+
let pendingKey = null;
|
|
445
|
+
|
|
446
|
+
const seed = parseLinesToObject(firstLines);
|
|
447
|
+
for (const k of Object.keys(seed)) {
|
|
448
|
+
out[k] = seed[k];
|
|
449
|
+
if (Array.isArray(out[k])) pendingKey = k;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const allowedKeys = listKeysForType(type);
|
|
453
|
+
const allowedScalarKeys = scalarKeysForType(type);
|
|
454
|
+
|
|
455
|
+
const nodes = [...between, end];
|
|
456
|
+
for (const node of nodes) {
|
|
457
|
+
const tag = String(node.tagName || "").toUpperCase();
|
|
458
|
+
if (tag === "P") {
|
|
459
|
+
const lines = (node.textContent || "").split(/\r?\n/);
|
|
460
|
+
for (const rawLine of lines) {
|
|
461
|
+
const trimmed = rawLine.trim();
|
|
462
|
+
if (!trimmed || trimmed === ":::" || trimmed.startsWith("#")) continue;
|
|
463
|
+
const kv = trimmed.match(/^([a-z0-9_-]+)\s*:\s*(.*)$/i);
|
|
464
|
+
if (!kv) continue;
|
|
465
|
+
const key = kv[1];
|
|
466
|
+
const value = kv[2];
|
|
467
|
+
if (value) {
|
|
468
|
+
out[key] = parseScalar(value);
|
|
469
|
+
pendingKey = null;
|
|
470
|
+
} else {
|
|
471
|
+
pendingKey = key;
|
|
472
|
+
out[key] = [];
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if ((tag === "UL" || tag === "OL") && pendingKey) {
|
|
479
|
+
let currentKey = pendingKey;
|
|
480
|
+
const buckets = {};
|
|
481
|
+
buckets[currentKey] = [];
|
|
482
|
+
|
|
483
|
+
for (const li of node.querySelectorAll(":scope > li")) {
|
|
484
|
+
if ((type === "stat-grid" || type === "scorecard") && currentKey === "metrics") {
|
|
485
|
+
buckets[currentKey].push(parseListItemToValue(li));
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const textLines = String(li.textContent || "")
|
|
490
|
+
.split(/\r?\n/)
|
|
491
|
+
.map((l) => l.trim())
|
|
492
|
+
.filter((l) => l && l !== ":::");
|
|
493
|
+
|
|
494
|
+
let anyContent = false;
|
|
495
|
+
let itemBucketKey = currentKey;
|
|
496
|
+
const itemLines = [];
|
|
497
|
+
|
|
498
|
+
function flushItemLines() {
|
|
499
|
+
if (!itemLines.length) return;
|
|
500
|
+
buckets[itemBucketKey] = buckets[itemBucketKey] || [];
|
|
501
|
+
buckets[itemBucketKey].push(parseScalar(itemLines.join(" ")));
|
|
502
|
+
itemLines.length = 0;
|
|
503
|
+
anyContent = true;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
for (const line of textLines) {
|
|
507
|
+
const listMarker = isListKeyMarkerText(line, allowedKeys);
|
|
508
|
+
if (listMarker) {
|
|
509
|
+
flushItemLines();
|
|
510
|
+
currentKey = listMarker;
|
|
511
|
+
itemBucketKey = currentKey;
|
|
512
|
+
buckets[currentKey] = buckets[currentKey] || [];
|
|
513
|
+
anyContent = true;
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
const scalarMarker = parseScalarMarkerText(line, allowedScalarKeys);
|
|
517
|
+
if (scalarMarker) {
|
|
518
|
+
flushItemLines();
|
|
519
|
+
out[scalarMarker.key] = scalarMarker.value;
|
|
520
|
+
if (type === "compare" && scalarMarker.key === "right-title") {
|
|
521
|
+
currentKey = "right";
|
|
522
|
+
itemBucketKey = currentKey;
|
|
523
|
+
buckets[currentKey] = buckets[currentKey] || [];
|
|
524
|
+
}
|
|
525
|
+
if (type === "compare" && scalarMarker.key === "left-title") {
|
|
526
|
+
currentKey = "left";
|
|
527
|
+
itemBucketKey = currentKey;
|
|
528
|
+
buckets[currentKey] = buckets[currentKey] || [];
|
|
529
|
+
}
|
|
530
|
+
anyContent = true;
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
itemLines.push(line);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
flushItemLines();
|
|
537
|
+
if (anyContent) continue;
|
|
538
|
+
|
|
539
|
+
// Fallback: absorbed key/value object in a <li>
|
|
540
|
+
if (!anyContent) {
|
|
541
|
+
const v = parseListItemToValue(li);
|
|
542
|
+
if (typeof v === "string") buckets[currentKey].push(v);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
for (const [k, items] of Object.entries(buckets)) out[k] = items;
|
|
547
|
+
pendingKey = null;
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return out;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function apply(root) {
|
|
556
|
+
const containers = root.querySelectorAll(".slide");
|
|
557
|
+
for (const container of containers) {
|
|
558
|
+
// First pass: handle single-node fences (flat key-values)
|
|
559
|
+
for (const node of container.querySelectorAll("p, pre, code")) {
|
|
560
|
+
if (node.closest(".kitfly-visual")) continue;
|
|
561
|
+
tryReplaceSingleNode(node);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Second pass: handle fences split across siblings (lists turn into <ul><li>...)
|
|
565
|
+
let changed = true;
|
|
566
|
+
while (changed) {
|
|
567
|
+
changed = false;
|
|
568
|
+
const elems = container.querySelectorAll("p");
|
|
569
|
+
for (const p of elems) {
|
|
570
|
+
if (p.closest(".kitfly-visual")) continue;
|
|
571
|
+
const t = (p.textContent || "").trim();
|
|
572
|
+
if (!t.startsWith(":::")) continue;
|
|
573
|
+
if (tryReplaceFragmentedFence(p)) {
|
|
574
|
+
changed = true;
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (typeof document === "undefined") {
|
|
583
|
+
globalThis.__kitflySlidesVisualsTest = {
|
|
584
|
+
parseBodyNodesWithFirstLines,
|
|
585
|
+
rowCells,
|
|
586
|
+
};
|
|
587
|
+
} else {
|
|
588
|
+
function start() {
|
|
589
|
+
apply(document);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (document.readyState === "loading") {
|
|
593
|
+
document.addEventListener("DOMContentLoaded", start);
|
|
594
|
+
} else {
|
|
595
|
+
start();
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
})();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# yaml-language-server: $schema=../schemas/v0/plugin-registry.schema.json
|
|
2
|
+
version: 1
|
|
3
|
+
updated: "2026-02-13"
|
|
4
|
+
baseUrl: "https://github.com/3leaps/kitfly/releases/download"
|
|
5
|
+
# Canonical plugin registry (v0.2.0-alpha)
|
|
6
|
+
plugins:
|
|
7
|
+
callouts:
|
|
8
|
+
name: "Callout Boxes"
|
|
9
|
+
description: "Turns blockquotes starting with NOTE:/TIP:/WARNING: into styled callouts"
|
|
10
|
+
version: "0.2.0"
|
|
11
|
+
contract: "1"
|
|
12
|
+
kitfly: ">=0.2.0 <1.0.0"
|
|
13
|
+
license: MIT
|
|
14
|
+
verified: true
|
|
15
|
+
assets:
|
|
16
|
+
js: "plugins-dist/callouts.js"
|
|
17
|
+
css: "plugins-dist/callouts.css"
|
|
18
|
+
assetSha256:
|
|
19
|
+
js: "sha256:94cc1a1a0ef4107b531defe915c6fbcc848e2b60c13c8cce7c5bbe6ae9130148"
|
|
20
|
+
css: "sha256:7f8eb0c7f0aa75c9ce74b1f242e548e2fefcd4109c4ae93c93d479cbc08c1849"
|
|
21
|
+
slides-visuals:
|
|
22
|
+
name: "Slides Visuals"
|
|
23
|
+
description: "Widgets and composed figures for Kitfly slides (::: blocks)"
|
|
24
|
+
version: "0.2.0"
|
|
25
|
+
contract: "1"
|
|
26
|
+
kitfly: ">=0.2.0 <1.0.0"
|
|
27
|
+
license: MIT
|
|
28
|
+
verified: true
|
|
29
|
+
modes: ["slides"]
|
|
30
|
+
assets:
|
|
31
|
+
js: "plugins-dist/slides-visuals.js"
|
|
32
|
+
css: "plugins-dist/slides-visuals.css"
|
|
33
|
+
assetSha256:
|
|
34
|
+
js: "sha256:d122e0a23d0a5edda4521e210b9d7ff98021c9690b9545635b1167f8a3db2f6a"
|
|
35
|
+
css: "sha256:99f020d59c0c006c4941566d351067189eb69c53fe4ffa422fe7d62fc30d62d2"
|
package/schemas/README.md
CHANGED
|
@@ -30,3 +30,13 @@ Standalone sites are detached copies, so schemas must be self-describing.
|
|
|
30
30
|
|
|
31
31
|
For convenience, `schemas/site.schema.json` and `schemas/theme.schema.json` remain as thin wrappers
|
|
32
32
|
that `$ref` the latest `schemas/v0/*` schemas.
|
|
33
|
+
|
|
34
|
+
Plugin-related schemas follow the same pattern:
|
|
35
|
+
|
|
36
|
+
- `schemas/plugin.schema.json` → `schemas/v0/plugin.schema.json`
|
|
37
|
+
- `schemas/plugin-registry.schema.json` → `schemas/v0/plugin-registry.schema.json`
|
|
38
|
+
- `schemas/plugins.schema.json` → `schemas/v0/plugins.schema.json`
|
|
39
|
+
|
|
40
|
+
Shared definitions live in:
|
|
41
|
+
|
|
42
|
+
- `schemas/v0/common.schema.json` (shared `$defs` referenced by multiple schemas)
|