@inkly-org/cli 0.5.1
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 +94 -0
- package/dist/capture-listener.js +320 -0
- package/dist/cli.js +5501 -0
- package/dist/template/demo.html +567 -0
- package/dist/template/hub-index.html +406 -0
- package/package.json +53 -0
- package/template/demo.html +567 -0
- package/template/hub-index.html +406 -0
- package/vendor/react-dom-client.mjs +48 -0
- package/vendor/react-dom.mjs +17 -0
- package/vendor/react-jsx-runtime.mjs +16 -0
- package/vendor/react.mjs +16 -0
- package/vendor/vendor-manifest.json +27 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>inkly dev — demo</title>
|
|
7
|
+
<!-- Default to the Inkly platform favicon; replaced below if the hub
|
|
8
|
+
sets its own `brand.favicon`. -->
|
|
9
|
+
<link rel="icon" href="https://www.inklyai.dev/favicon.ico" />
|
|
10
|
+
<link rel="stylesheet" href="/__inkly/runtime.css" />
|
|
11
|
+
<script type="importmap">{"imports":{"react":"/__inkly/vendor/react.mjs","react-dom":"/__inkly/vendor/react-dom.mjs","react-dom/client":"/__inkly/vendor/react-dom-client.mjs","react/jsx-runtime":"/__inkly/vendor/react-jsx-runtime.mjs"}}</script>
|
|
12
|
+
<link rel="modulepreload" href="/__inkly/vendor/react.mjs" crossorigin="anonymous" />
|
|
13
|
+
<link rel="modulepreload" href="/__inkly/vendor/react-dom.mjs" crossorigin="anonymous" />
|
|
14
|
+
<link rel="modulepreload" href="/__inkly/vendor/react-dom-client.mjs" crossorigin="anonymous" />
|
|
15
|
+
<link rel="modulepreload" href="/__inkly/vendor/react-jsx-runtime.mjs" crossorigin="anonymous" />
|
|
16
|
+
<!-- Curated theme preset from @inkly/demo-react/themes.
|
|
17
|
+
Loaded after runtime.css so the preset's scoped CSS wins cascade.
|
|
18
|
+
The dev server serves this from the preset registry; the published
|
|
19
|
+
bundle ships the same string baked into its stylesheet. -->
|
|
20
|
+
<link id="inkly-hub-theme-css" rel="stylesheet" href="/__inkly/theme/inkly.css" />
|
|
21
|
+
<script id="__inkly_hub_meta" type="application/json">{"hub":{"name":"","brand":null,"tokens":null,"theme":null,"layout":null},"demoSlug":"","demoTitle":""}</script>
|
|
22
|
+
<style>
|
|
23
|
+
/*
|
|
24
|
+
* Player chrome matches the canonical /d/[slug] viewer:
|
|
25
|
+
* - light surface header (56px) with brand pill + demo name + CTA
|
|
26
|
+
* - dotted-grid canvas below
|
|
27
|
+
* - Demo runtime sits transparently on top of the canvas
|
|
28
|
+
* Hub branding (logo, CTA) layers as overrides.
|
|
29
|
+
*/
|
|
30
|
+
:root {
|
|
31
|
+
--surface-2: #fcfcfc;
|
|
32
|
+
--line-soft: #dddddd;
|
|
33
|
+
--ink-strong: #1f1f1f;
|
|
34
|
+
--ink-muted: #6b6b6b;
|
|
35
|
+
--canvas: #f5f5f5;
|
|
36
|
+
}
|
|
37
|
+
* { box-sizing: border-box; }
|
|
38
|
+
html, body { margin: 0; height: 100%; }
|
|
39
|
+
body {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
min-height: 100vh;
|
|
43
|
+
background: var(--canvas);
|
|
44
|
+
color: var(--ink-strong);
|
|
45
|
+
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
46
|
+
font-size: 14px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
header.inkly-bar {
|
|
50
|
+
flex: 0 0 auto;
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
gap: 12px;
|
|
54
|
+
height: 56px;
|
|
55
|
+
padding: 0 20px;
|
|
56
|
+
background: var(--surface-2);
|
|
57
|
+
border-bottom: 1px solid var(--line-soft);
|
|
58
|
+
}
|
|
59
|
+
.inkly-btn {
|
|
60
|
+
display: inline-flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
justify-content: center;
|
|
63
|
+
height: 32px;
|
|
64
|
+
border-radius: 8px;
|
|
65
|
+
border: 1px solid transparent;
|
|
66
|
+
background: transparent;
|
|
67
|
+
color: var(--ink-strong);
|
|
68
|
+
font-size: 13px;
|
|
69
|
+
text-decoration: none;
|
|
70
|
+
}
|
|
71
|
+
.inkly-btn:hover { background: rgba(0, 0, 0, 0.04); }
|
|
72
|
+
|
|
73
|
+
.brand {
|
|
74
|
+
min-width: 32px;
|
|
75
|
+
padding: 0 8px;
|
|
76
|
+
font-weight: 700;
|
|
77
|
+
}
|
|
78
|
+
.brand img { display: block; max-height: 24px; max-width: 96px; }
|
|
79
|
+
.brand-mark { line-height: 1; }
|
|
80
|
+
|
|
81
|
+
.divider {
|
|
82
|
+
width: 1px;
|
|
83
|
+
height: 20px;
|
|
84
|
+
background: var(--line-soft);
|
|
85
|
+
flex: 0 0 auto;
|
|
86
|
+
}
|
|
87
|
+
.demo-name {
|
|
88
|
+
flex: 1 1 auto;
|
|
89
|
+
min-width: 0;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
color: var(--ink-strong);
|
|
92
|
+
overflow: hidden;
|
|
93
|
+
text-overflow: ellipsis;
|
|
94
|
+
white-space: nowrap;
|
|
95
|
+
}
|
|
96
|
+
.hub-name {
|
|
97
|
+
min-width: 0;
|
|
98
|
+
max-width: 34vw;
|
|
99
|
+
font-weight: 600;
|
|
100
|
+
color: var(--ink-strong);
|
|
101
|
+
overflow: hidden;
|
|
102
|
+
text-overflow: ellipsis;
|
|
103
|
+
white-space: nowrap;
|
|
104
|
+
text-decoration: none;
|
|
105
|
+
}
|
|
106
|
+
.hub-name:hover { text-decoration: underline; }
|
|
107
|
+
.slash {
|
|
108
|
+
color: var(--ink-muted);
|
|
109
|
+
flex: 0 0 auto;
|
|
110
|
+
}
|
|
111
|
+
.cta {
|
|
112
|
+
padding: 0 12px;
|
|
113
|
+
font-weight: 500;
|
|
114
|
+
}
|
|
115
|
+
.inkly-button-scope {
|
|
116
|
+
margin-left: auto;
|
|
117
|
+
display: inline-flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: 8px;
|
|
120
|
+
background: transparent !important;
|
|
121
|
+
background-image: none !important;
|
|
122
|
+
color: inherit;
|
|
123
|
+
font-family: inherit;
|
|
124
|
+
}
|
|
125
|
+
.inkly-button-scope .inkly-hub-pa-cta,
|
|
126
|
+
.inkly-button-scope .inkly-hub-editorial-cta,
|
|
127
|
+
.inkly-button-scope .inkly-hub-substack-cta,
|
|
128
|
+
.inkly-button-scope .inkly-hub-mono-cta {
|
|
129
|
+
margin: 0;
|
|
130
|
+
white-space: nowrap;
|
|
131
|
+
}
|
|
132
|
+
.inkly-button-scope [hidden] {
|
|
133
|
+
display: none !important;
|
|
134
|
+
}
|
|
135
|
+
/* Canvas fills remaining viewport vertically AND has an explicit
|
|
136
|
+
* definite width — this is what gives the Demo runtime's
|
|
137
|
+
* `width: min(100%, 960px, …)` something concrete to resolve
|
|
138
|
+
* against. Without it, Demo collapses to its min-content. */
|
|
139
|
+
#root {
|
|
140
|
+
flex: 1 1 auto;
|
|
141
|
+
min-height: 0;
|
|
142
|
+
width: 100%;
|
|
143
|
+
display: flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
justify-content: center;
|
|
146
|
+
background: var(--canvas);
|
|
147
|
+
background-image: radial-gradient(circle at 1px 1px, rgba(0, 0, 0, 0.10) 1px, transparent 1.6px);
|
|
148
|
+
background-size: 14px 14px;
|
|
149
|
+
container-type: size;
|
|
150
|
+
}
|
|
151
|
+
/* Plain block container — NOT a flex parent — that gives .demo-root
|
|
152
|
+
* a definite 100% width to resolve against. Matches the canonical
|
|
153
|
+
* bundle App.tsx exactly:
|
|
154
|
+
* <main className="min-h-screen w-full flex items-center justify-center">
|
|
155
|
+
* <div className="w-full"> <Demo /> </div>
|
|
156
|
+
* </main>
|
|
157
|
+
* If you make this flex/grid, Demo becomes a flex/grid child of
|
|
158
|
+
* a centering container and shrinks to min-content — the original
|
|
159
|
+
* collapse bug. */
|
|
160
|
+
.demo-wrap {
|
|
161
|
+
width: 100%;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.inkly-loading, .inkly-error {
|
|
165
|
+
padding: 24px;
|
|
166
|
+
max-width: 720px;
|
|
167
|
+
margin: 48px auto;
|
|
168
|
+
background: #fff;
|
|
169
|
+
border: 1px solid var(--line-soft);
|
|
170
|
+
border-radius: 8px;
|
|
171
|
+
line-height: 1.5;
|
|
172
|
+
color: var(--ink-strong);
|
|
173
|
+
}
|
|
174
|
+
.inkly-error { border-color: #ef4444; background: #fff5f5; }
|
|
175
|
+
.inkly-error pre {
|
|
176
|
+
background: #fafafa;
|
|
177
|
+
padding: 12px;
|
|
178
|
+
border-radius: 6px;
|
|
179
|
+
overflow: auto;
|
|
180
|
+
white-space: pre-wrap;
|
|
181
|
+
word-break: break-word;
|
|
182
|
+
font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
|
|
183
|
+
font-size: 12px;
|
|
184
|
+
}
|
|
185
|
+
</style>
|
|
186
|
+
<!-- Demo config is injected by the dev server right into this script tag
|
|
187
|
+
(parsed at render time, no fetch round-trip). Empty default so the
|
|
188
|
+
page parses standalone if served raw without the middleware. -->
|
|
189
|
+
<script id="__inkly_demo_config" type="application/json">null</script>
|
|
190
|
+
<!-- assets.json's `assets[]` — paired with the resolver below to turn
|
|
191
|
+
`asset:<id>` URIs inside demo.config into fetchable asset URLs.
|
|
192
|
+
Empty default for pre-capture sample demos. -->
|
|
193
|
+
<script id="__inkly_demo_assets" type="application/json">[]</script>
|
|
194
|
+
</head>
|
|
195
|
+
<body>
|
|
196
|
+
<header class="inkly-bar">
|
|
197
|
+
<a href="/" class="inkly-btn brand" id="brand-link" aria-label="Inkly" hidden>
|
|
198
|
+
</a>
|
|
199
|
+
<span class="divider" id="brand-divider" aria-hidden="true" hidden></span>
|
|
200
|
+
<a href="/" class="hub-name" id="hub-link" hidden></a>
|
|
201
|
+
<span class="slash" id="hub-slash" aria-hidden="true" hidden>/</span>
|
|
202
|
+
<span class="demo-name" id="demo-name"></span>
|
|
203
|
+
<span class="inkly-button-scope hub-index" id="inkly-button-scope" data-theme="inkly">
|
|
204
|
+
<a href="/" class="inkly-hub-pa-cta is-secondary" id="secondary-cta-link" hidden></a>
|
|
205
|
+
<a href="/" class="inkly-hub-pa-cta is-primary" id="cta-link">Build your own</a>
|
|
206
|
+
</span>
|
|
207
|
+
</header>
|
|
208
|
+
<div id="root">
|
|
209
|
+
<div class="inkly-loading">Loading demo runtime…</div>
|
|
210
|
+
</div>
|
|
211
|
+
<script>
|
|
212
|
+
(function () {
|
|
213
|
+
var node = document.getElementById('__inkly_hub_meta');
|
|
214
|
+
if (!node) return;
|
|
215
|
+
var meta;
|
|
216
|
+
try { meta = JSON.parse(node.textContent || '{}'); } catch (e) { return; }
|
|
217
|
+
var hub = meta.hub || {};
|
|
218
|
+
var brand = hub.brand || {};
|
|
219
|
+
var tokens = hub.tokens || {};
|
|
220
|
+
var demoConfig = null;
|
|
221
|
+
var configNode = document.getElementById('__inkly_demo_config');
|
|
222
|
+
try { demoConfig = JSON.parse(configNode && configNode.textContent || 'null'); } catch (e) {}
|
|
223
|
+
|
|
224
|
+
var headerThemeId = hub.theme || 'inkly';
|
|
225
|
+
var themeLink = document.getElementById('inkly-hub-theme-css');
|
|
226
|
+
if (themeLink) {
|
|
227
|
+
themeLink.href = '/__inkly/theme/' + encodeURIComponent(headerThemeId) + '.css';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
var buttonScope = document.getElementById('inkly-button-scope');
|
|
231
|
+
if (buttonScope) {
|
|
232
|
+
buttonScope.dataset.theme = headerThemeId;
|
|
233
|
+
if (tokens.secondary) buttonScope.style.setProperty('--demo-secondary', tokens.secondary);
|
|
234
|
+
if (tokens.font) buttonScope.style.setProperty('--demo-font', tokens.font);
|
|
235
|
+
if (tokens.radius) buttonScope.style.setProperty('--demo-radius', tokens.radius);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
document.title = (meta.demoTitle || meta.demoSlug || 'demo') + ' — ' + (hub.name || 'inkly dev');
|
|
239
|
+
|
|
240
|
+
// Hub favicon replaces the browser tab icon if set.
|
|
241
|
+
if (brand.favicon) {
|
|
242
|
+
var iconLink = document.querySelector('link[rel="icon"]');
|
|
243
|
+
if (!iconLink) {
|
|
244
|
+
iconLink = document.createElement('link');
|
|
245
|
+
iconLink.rel = 'icon';
|
|
246
|
+
document.head.appendChild(iconLink);
|
|
247
|
+
}
|
|
248
|
+
iconLink.href = brand.favicon;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Header center slot. `chrome.showHubBreadcrumb` opts into a
|
|
252
|
+
// clickable hub crumb; otherwise the local shell shows only the
|
|
253
|
+
// demo name.
|
|
254
|
+
var showHubBreadcrumb = !!(
|
|
255
|
+
demoConfig &&
|
|
256
|
+
demoConfig.chrome &&
|
|
257
|
+
demoConfig.chrome.showHubBreadcrumb
|
|
258
|
+
);
|
|
259
|
+
var hubLink = document.getElementById('hub-link');
|
|
260
|
+
var hubSlash = document.getElementById('hub-slash');
|
|
261
|
+
if (showHubBreadcrumb && hubLink && hubSlash) {
|
|
262
|
+
hubLink.textContent = hub.name || 'Demo hub';
|
|
263
|
+
hubLink.hidden = false;
|
|
264
|
+
hubSlash.hidden = false;
|
|
265
|
+
}
|
|
266
|
+
var nameEl = document.getElementById('demo-name');
|
|
267
|
+
if (nameEl) nameEl.textContent = meta.demoTitle || meta.demoSlug || '';
|
|
268
|
+
function resolveHubPublicUrl(src) {
|
|
269
|
+
if (!src || typeof src !== 'string') return '';
|
|
270
|
+
var value = src.trim();
|
|
271
|
+
if (!value) return '';
|
|
272
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i.test(value)) return value;
|
|
273
|
+
if (value.charAt(0) === '/') return value;
|
|
274
|
+
if (value.indexOf('public/') === 0) return '/' + value.slice('public/'.length);
|
|
275
|
+
return '/' + value.replace(/^\.\/+/, '');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Hub logo replaces the default Inkly brand mark if set.
|
|
279
|
+
var brandLink = document.getElementById('brand-link');
|
|
280
|
+
var brandDivider = document.getElementById('brand-divider');
|
|
281
|
+
var defaultBrandLogoSrc = 'https://www.inklyai.dev/assets/logo.png';
|
|
282
|
+
var brandLogoSrc = resolveHubPublicUrl(brand.logo) || defaultBrandLogoSrc;
|
|
283
|
+
if (brandLink && brandLogoSrc) {
|
|
284
|
+
var brandMark = document.createElement('img');
|
|
285
|
+
brandMark.className = 'brand-mark';
|
|
286
|
+
brandMark.style.cssText = 'height:22px;width:22px;border-radius:0;object-fit:contain;display:block';
|
|
287
|
+
brandMark.src = brandLogoSrc;
|
|
288
|
+
brandMark.alt = hub.name || '';
|
|
289
|
+
brandMark.onerror = function () {
|
|
290
|
+
brandLink.hidden = true;
|
|
291
|
+
if (brandDivider) brandDivider.hidden = true;
|
|
292
|
+
};
|
|
293
|
+
brandLink.appendChild(brandMark);
|
|
294
|
+
brandLink.hidden = false;
|
|
295
|
+
if (brandDivider) brandDivider.hidden = false;
|
|
296
|
+
if (hub.name) brandLink.setAttribute('aria-label', hub.name);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function ctaClassForTheme(themeId, kind) {
|
|
300
|
+
var baseByTheme = {
|
|
301
|
+
editorial: 'inkly-hub-editorial-cta',
|
|
302
|
+
mono: 'inkly-hub-mono-cta',
|
|
303
|
+
'inkly': 'inkly-hub-pa-cta',
|
|
304
|
+
substack: 'inkly-hub-substack-cta'
|
|
305
|
+
};
|
|
306
|
+
var base = baseByTheme[themeId] || baseByTheme['inkly'];
|
|
307
|
+
return base + ' is-' + kind;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function configureExternalLink(el, link) {
|
|
311
|
+
el.href = link.href;
|
|
312
|
+
el.target = '_blank';
|
|
313
|
+
el.rel = 'noopener noreferrer';
|
|
314
|
+
el.textContent = link.label;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Hub CTA buttons mirror the index-page theme classes.
|
|
318
|
+
var ctaLink = document.getElementById('cta-link');
|
|
319
|
+
if (ctaLink) {
|
|
320
|
+
ctaLink.className = ctaClassForTheme(headerThemeId, 'primary');
|
|
321
|
+
if (brand.cta && brand.cta.href && brand.cta.label) {
|
|
322
|
+
configureExternalLink(ctaLink, brand.cta);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
var secondaryCtaLink = document.getElementById('secondary-cta-link');
|
|
326
|
+
if (secondaryCtaLink) {
|
|
327
|
+
secondaryCtaLink.className = ctaClassForTheme(headerThemeId, 'secondary');
|
|
328
|
+
if (brand.secondaryCta && brand.secondaryCta.href && brand.secondaryCta.label) {
|
|
329
|
+
configureExternalLink(secondaryCtaLink, brand.secondaryCta);
|
|
330
|
+
secondaryCtaLink.hidden = false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Expose hub-level theme cascade pieces so the runtime mount
|
|
335
|
+
// below can resolve the effective theme id without a refetch.
|
|
336
|
+
// `demo.theme.preset` (from the inlined config) wins; the hub's
|
|
337
|
+
// `theme` is the next-best default.
|
|
338
|
+
window.__INKLY_HUB_THEME__ = hub.theme || null;
|
|
339
|
+
window.__INKLY_HUB_TOKENS__ = hub.tokens || null;
|
|
340
|
+
window.__INKLY_DEMO_SLUG__ = meta.demoSlug || '';
|
|
341
|
+
})();
|
|
342
|
+
</script>
|
|
343
|
+
<script type="module">
|
|
344
|
+
// runtime.js externalizes React. The import map above resolves
|
|
345
|
+
// those bare specifiers to the vendored ESM files served by
|
|
346
|
+
// inkly dev.
|
|
347
|
+
let createElement;
|
|
348
|
+
let createRoot;
|
|
349
|
+
let Demo;
|
|
350
|
+
try {
|
|
351
|
+
({ createElement, createRoot, Demo } = await import('/__inkly/runtime.js'));
|
|
352
|
+
} catch (err) {
|
|
353
|
+
const errorEl = document.createElement('div');
|
|
354
|
+
errorEl.className = 'inkly-error';
|
|
355
|
+
const title = document.createElement('h2');
|
|
356
|
+
title.textContent = 'Runtime bundle missing';
|
|
357
|
+
const body = document.createElement('pre');
|
|
358
|
+
body.textContent =
|
|
359
|
+
'The demo loaded, but the Inkly runtime did not.\n\n' +
|
|
360
|
+
'Check /__inkly/runtime.js and /__inkly/vendor/react*.mjs, then restart inkly dev.\n\n' +
|
|
361
|
+
String(err && err.stack ? err.stack : err);
|
|
362
|
+
errorEl.append(title, body);
|
|
363
|
+
const rootEl = document.getElementById('root');
|
|
364
|
+
if (rootEl) rootEl.replaceChildren(errorEl);
|
|
365
|
+
throw err;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const rootEl = document.getElementById('root');
|
|
369
|
+
const root = createRoot(rootEl);
|
|
370
|
+
|
|
371
|
+
function readInlineConfig() {
|
|
372
|
+
const node = document.getElementById('__inkly_demo_config');
|
|
373
|
+
if (!node) return null;
|
|
374
|
+
try { return JSON.parse(node.textContent || 'null'); } catch (e) { return null; }
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function readInlineAssets() {
|
|
378
|
+
const node = document.getElementById('__inkly_demo_assets');
|
|
379
|
+
if (!node) return [];
|
|
380
|
+
try {
|
|
381
|
+
const parsed = JSON.parse(node.textContent || '[]');
|
|
382
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
383
|
+
} catch (e) { return []; }
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Managed assets must carry the URL they should render from.
|
|
387
|
+
// Local ZIPs use `/<slug>/<file>`; synced assets use a CDN URL.
|
|
388
|
+
function resolveAssetUrlForCli(entry) {
|
|
389
|
+
if (entry && typeof entry.publicUrl === 'string' && entry.publicUrl) {
|
|
390
|
+
return entry.publicUrl;
|
|
391
|
+
}
|
|
392
|
+
return '';
|
|
393
|
+
}
|
|
394
|
+
function resolveHtmlUrlForCli(path) {
|
|
395
|
+
return '/__inkly/snapshot/' +
|
|
396
|
+
String(window.__INKLY_DEMO_SLUG__ || '').split('/').map(encodeURIComponent).join('/') +
|
|
397
|
+
'/' +
|
|
398
|
+
String(path || '').split('/').map(encodeURIComponent).join('/');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function renderError(title, body) {
|
|
402
|
+
root.render(
|
|
403
|
+
createElement(
|
|
404
|
+
'div',
|
|
405
|
+
{ className: 'inkly-error' },
|
|
406
|
+
createElement('h2', null, title),
|
|
407
|
+
createElement('pre', null, body),
|
|
408
|
+
),
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function renderConfig(config, assets) {
|
|
413
|
+
if (!config) {
|
|
414
|
+
renderError(
|
|
415
|
+
'No demo config',
|
|
416
|
+
'The page was served without an inlined config. This usually means the dev server middleware is not running.',
|
|
417
|
+
);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
// Resolve the per-demo theme cascade. Per the spec:
|
|
421
|
+
// effective.theme = demo.theme.preset ?? hub.theme ?? "inkly"
|
|
422
|
+
// The hub fields are stashed onto `window` by the inline meta
|
|
423
|
+
// script above; the demo's own preset (if any) wins.
|
|
424
|
+
var demoTheme = (config && config.theme) || {};
|
|
425
|
+
var themeId =
|
|
426
|
+
(demoTheme && demoTheme.preset) ||
|
|
427
|
+
window.__INKLY_HUB_THEME__ ||
|
|
428
|
+
'inkly';
|
|
429
|
+
|
|
430
|
+
// Make sure the preset's CSS is loaded — the CLI serves it at
|
|
431
|
+
// /__inkly/theme/<id>.css. Inject once per theme id.
|
|
432
|
+
if (!document.querySelector('link[data-inkly-theme="' + themeId + '"]')) {
|
|
433
|
+
var link = document.createElement('link');
|
|
434
|
+
link.rel = 'stylesheet';
|
|
435
|
+
link.href = '/__inkly/theme/' + encodeURIComponent(themeId) + '.css';
|
|
436
|
+
link.setAttribute('data-inkly-theme', themeId);
|
|
437
|
+
document.head.appendChild(link);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function applyThemeCanvas(themeId) {
|
|
441
|
+
rootEl.style.background = '';
|
|
442
|
+
rootEl.style.backgroundColor = '';
|
|
443
|
+
rootEl.style.backgroundImage = '';
|
|
444
|
+
rootEl.style.backgroundPosition = '';
|
|
445
|
+
rootEl.style.backgroundRepeat = '';
|
|
446
|
+
rootEl.style.backgroundSize = '';
|
|
447
|
+
|
|
448
|
+
if (themeId === 'editorial') {
|
|
449
|
+
rootEl.style.background = '#faf9f5';
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (themeId === 'mono') {
|
|
453
|
+
rootEl.style.background = '#f7f7f7';
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (themeId === 'substack') {
|
|
457
|
+
rootEl.style.background = '#fff7f2';
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (themeId === 'inkly') {
|
|
461
|
+
rootEl.style.background = '#f5f5f5';
|
|
462
|
+
rootEl.style.backgroundImage = 'radial-gradient(circle at 1px 1px, rgba(0, 0, 0, 0.10) 1px, transparent 1.6px)';
|
|
463
|
+
rootEl.style.backgroundSize = '14px 14px';
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
rootEl.style.background = '#f5f5f7';
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function resolveCanvasAssetUri(uri) {
|
|
470
|
+
if (!uri) return uri;
|
|
471
|
+
if (/^(https?:|data:|blob:)/i.test(uri)) return uri;
|
|
472
|
+
var list = Array.isArray(assets) ? assets : [];
|
|
473
|
+
if (String(uri).startsWith('asset:')) {
|
|
474
|
+
var id = String(uri).slice('asset:'.length);
|
|
475
|
+
var found = list.find(function (entry) { return entry && entry.id === id; });
|
|
476
|
+
return found ? resolveAssetUrlForCli(found) : uri;
|
|
477
|
+
}
|
|
478
|
+
return uri;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
var demoBackground = config && config.background;
|
|
482
|
+
var canvasColor =
|
|
483
|
+
demoBackground && demoBackground.type === 'color' && demoBackground.color
|
|
484
|
+
? demoBackground.color
|
|
485
|
+
: demoBackground && demoBackground.type === 'solid'
|
|
486
|
+
? demoBackground.color
|
|
487
|
+
: config && typeof config.backgroundColor === 'string'
|
|
488
|
+
? config.backgroundColor
|
|
489
|
+
: '';
|
|
490
|
+
applyThemeCanvas(themeId);
|
|
491
|
+
if (
|
|
492
|
+
demoBackground &&
|
|
493
|
+
(
|
|
494
|
+
(demoBackground.type === 'color' && demoBackground.from && demoBackground.to) ||
|
|
495
|
+
demoBackground.type === 'gradient'
|
|
496
|
+
)
|
|
497
|
+
) {
|
|
498
|
+
rootEl.style.background =
|
|
499
|
+
'linear-gradient(135deg, ' + demoBackground.from + ', ' + demoBackground.to + ')';
|
|
500
|
+
} else if (
|
|
501
|
+
demoBackground &&
|
|
502
|
+
(demoBackground.type === 'image' || demoBackground.type === 'wallpaper') &&
|
|
503
|
+
demoBackground.src
|
|
504
|
+
) {
|
|
505
|
+
rootEl.style.backgroundColor = '#f5f5f5';
|
|
506
|
+
rootEl.style.backgroundImage = 'url(\"' + resolveCanvasAssetUri(demoBackground.src) + '\")';
|
|
507
|
+
rootEl.style.backgroundPosition = 'center';
|
|
508
|
+
rootEl.style.backgroundRepeat = 'no-repeat';
|
|
509
|
+
rootEl.style.backgroundSize = 'cover';
|
|
510
|
+
} else if (demoBackground && demoBackground.type === 'none') {
|
|
511
|
+
// Keep the active theme's default player canvas.
|
|
512
|
+
} else if (canvasColor) {
|
|
513
|
+
rootEl.style.background = canvasColor;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// The .demo-wrap div gives <Demo> a definite-width containing block
|
|
517
|
+
// so its internal `width: min(100%, 960px, ...)` resolves correctly.
|
|
518
|
+
// Mirrors the canonical bundle App.tsx pattern.
|
|
519
|
+
root.render(
|
|
520
|
+
createElement(
|
|
521
|
+
'div',
|
|
522
|
+
{ className: 'demo-wrap' },
|
|
523
|
+
createElement(Demo, {
|
|
524
|
+
config: config,
|
|
525
|
+
themeId: themeId,
|
|
526
|
+
themeTokens: window.__INKLY_HUB_TOKENS__ || undefined,
|
|
527
|
+
assets: assets || [],
|
|
528
|
+
resolveAssetUrl: resolveAssetUrlForCli,
|
|
529
|
+
resolveHtmlUrl: resolveHtmlUrlForCli,
|
|
530
|
+
allowHtmlSnapshotScripts: false,
|
|
531
|
+
}),
|
|
532
|
+
),
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// First paint uses the config + assets inlined into the HTML — no fetch.
|
|
537
|
+
renderConfig(readInlineConfig(), readInlineAssets());
|
|
538
|
+
|
|
539
|
+
// On HMR change events, refetch the config (the inlined copy is
|
|
540
|
+
// now stale). The endpoint now returns `{ demo, assets }` so the
|
|
541
|
+
// resolver sees the latest assets.json without a separate fetch.
|
|
542
|
+
async function refetchAndRender() {
|
|
543
|
+
try {
|
|
544
|
+
const res = await fetch('/__inkly/demo/' + encodeURIComponent(window.__INKLY_DEMO_SLUG__ || ''), { cache: 'no-store' });
|
|
545
|
+
if (!res.ok) {
|
|
546
|
+
renderError('demo.config.json is invalid', await res.text());
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const payload = await res.json();
|
|
550
|
+
// Endpoint returns `{ demo, assets }`; tolerate older response
|
|
551
|
+
// shapes (raw config) for resilience during the transition.
|
|
552
|
+
if (payload && typeof payload === 'object' && 'demo' in payload) {
|
|
553
|
+
renderConfig(payload.demo, Array.isArray(payload.assets) ? payload.assets : []);
|
|
554
|
+
} else {
|
|
555
|
+
renderConfig(payload, []);
|
|
556
|
+
}
|
|
557
|
+
} catch (err) {
|
|
558
|
+
renderError('Failed to load config', String(err && err.stack ? err.stack : err));
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (import.meta.hot) {
|
|
562
|
+
import.meta.hot.on('inkly:hub-changed', refetchAndRender);
|
|
563
|
+
import.meta.hot.on('inkly:config-changed', refetchAndRender);
|
|
564
|
+
}
|
|
565
|
+
</script>
|
|
566
|
+
</body>
|
|
567
|
+
</html>
|