slidemind 0.3.12 → 0.3.14
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/AGENTS.md +86 -21
- package/README.md +14 -17
- package/bin/slidemind.mjs +65 -4969
- package/package.json +1 -1
- package/skills/create-slides/SKILL.md +113 -74
- package/skills/search/SKILL.md +25 -7
- package/src/bridge/build-and-push.mjs +108 -15
- package/src/bridge/bundle.mjs +69 -0
- package/src/bridge/capture.mjs +71 -8
- package/src/bridge/client/brands.mjs +622 -0
- package/src/bridge/client/engine-routing.mjs +237 -0
- package/src/bridge/client/engine-search.mjs +310 -0
- package/src/bridge/client/engine-slides.mjs +435 -0
- package/src/bridge/client/pane-ops.mjs +335 -0
- package/src/bridge/client/presentations.mjs +587 -0
- package/src/bridge/client/render.mjs +317 -0
- package/src/bridge/client/transport.mjs +695 -0
- package/src/bridge/client.mjs +80 -3452
- package/src/bridge/config.mjs +1 -1
- package/src/bridge/engine-download.mjs +15 -2
- package/src/builders/build.mjs +16 -669
- package/src/builders/draft-check.mjs +376 -0
- package/src/builders/lint.mjs +38 -2
- package/src/builders/preview.html +799 -0
- package/src/builders/review.mjs +23 -4
- package/src/builders/visual-families.mjs +6 -8
- package/src/cli/common.mjs +179 -0
- package/src/cli/concepts.mjs +310 -0
- package/src/cli/connection.mjs +147 -0
- package/src/cli/deck.mjs +608 -0
- package/src/cli/designs.mjs +113 -0
- package/src/cli/diagnose.mjs +141 -0
- package/src/cli/download-bundle.mjs +144 -0
- package/src/cli/engine-helpers.mjs +429 -0
- package/src/cli/engine-results.mjs +320 -0
- package/src/cli/help.mjs +201 -0
- package/src/cli/install.mjs +257 -0
- package/src/cli/read.mjs +120 -0
- package/src/cli/ready.mjs +805 -0
- package/src/cli/search.mjs +608 -0
- package/src/cli/session.mjs +177 -0
- package/src/cli/setup.mjs +282 -0
- package/src/cli/slide-reuse.mjs +334 -0
- package/src/cli/status.mjs +118 -0
- package/src/cli/workspace.mjs +302 -0
- package/src/installers/codex.mjs +1 -1
- package/src/update-check.mjs +231 -0
package/src/builders/build.mjs
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// Pure Node stdlib — no external dependencies.
|
|
4
4
|
//
|
|
5
5
|
// Usage:
|
|
6
|
-
// slidemind
|
|
7
|
-
//
|
|
6
|
+
// slidemind view-draft <deck> (the CLI entry point; it builds, then captures)
|
|
7
|
+
// node src/builders/build.mjs --deck <deck> [--open] [--fix]
|
|
8
8
|
//
|
|
9
9
|
// Convention:
|
|
10
10
|
// <deck>/
|
|
@@ -34,7 +34,7 @@ import path from "node:path";
|
|
|
34
34
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
35
35
|
|
|
36
36
|
import { resolveBundleDirForDeck } from "../bridge/bundle.mjs";
|
|
37
|
-
import { loadChartPreview } from "../bridge/capture.mjs";
|
|
37
|
+
import { loadChartPreview, loadConnectorPreview } from "../bridge/capture.mjs";
|
|
38
38
|
import { FAVICON_LINK } from "./gate-server.mjs";
|
|
39
39
|
import { lintSlidesOrThrow } from "./lint.mjs";
|
|
40
40
|
import { resolveSkillForDeck } from "./resolve-skill.mjs";
|
|
@@ -286,670 +286,13 @@ function collectSvgAssets(deckDir) {
|
|
|
286
286
|
return map;
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
<!-- Arimo: open-source Arial-metric-compatible font. We alias it to
|
|
297
|
-
"Arial" so master text pinned to Arial gets a font with the same
|
|
298
|
-
horizontal extents PowerPoint/LibreOffice use, instead of macOS
|
|
299
|
-
Chrome's slightly wider system Arial that pushes long strings
|
|
300
|
-
(addresses, single-line headers) past their bounding boxes. -->
|
|
301
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
302
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
303
|
-
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Arimo:ital,wght@0,400..700;1,400..700&display=swap" />
|
|
304
|
-
<link rel="stylesheet" href="./corporate-design.css" />
|
|
305
|
-
<link rel="stylesheet" href="./layouts.css" />
|
|
306
|
-
<style>
|
|
307
|
-
/* Compatibility aliases for decks/slides authored against the older
|
|
308
|
-
statworx token names. New materialized bundles expose semantic tokens
|
|
309
|
-
(--fg-*, --bg-*, --border-*); existing slide JSX may still refer
|
|
310
|
-
to --sw-*. Keep those declarations valid in the HTML preview. */
|
|
311
|
-
:root {
|
|
312
|
-
--sw-black: var(--fg-1, #000000);
|
|
313
|
-
--sw-white: var(--bg-1, #ffffff);
|
|
314
|
-
--sw-ink: var(--fg-2, #283440);
|
|
315
|
-
--sw-slate: var(--fg-3, #6C7D8C);
|
|
316
|
-
--sw-gray: var(--fg-4, #B6BDCC);
|
|
317
|
-
--sw-fog: var(--bg-2, #EBF0F2);
|
|
318
|
-
--sw-ice-blue: var(--bg-3, #E6E6FF);
|
|
319
|
-
--sw-tech-blue: var(--fg-accent, #0000FF);
|
|
320
|
-
--sw-tech-blue-10: #E6E6FF;
|
|
321
|
-
--sw-tech-blue-20: #CCCCFF;
|
|
322
|
-
--sw-tech-blue-40: #9999FF;
|
|
323
|
-
--sw-tech-blue-80: #3333FF;
|
|
324
|
-
--sw-magenta: #E6007E;
|
|
325
|
-
}
|
|
326
|
-
</style>
|
|
327
|
-
<style>
|
|
328
|
-
/* Substitute Arial with Arimo (a metric clone) so wrap behaviour
|
|
329
|
-
matches PowerPoint's rendering. Local Arial wins only if Arimo
|
|
330
|
-
hasn't loaded yet. */
|
|
331
|
-
@font-face {
|
|
332
|
-
font-family: "Arial";
|
|
333
|
-
src: local("Arimo"), local("Arial");
|
|
334
|
-
font-weight: 400;
|
|
335
|
-
font-style: normal;
|
|
336
|
-
}
|
|
337
|
-
@font-face {
|
|
338
|
-
font-family: "Arial";
|
|
339
|
-
src: local("Arimo Bold"), local("Arial Bold");
|
|
340
|
-
font-weight: 700;
|
|
341
|
-
font-style: normal;
|
|
342
|
-
}
|
|
343
|
-
@font-face {
|
|
344
|
-
font-family: "Arial";
|
|
345
|
-
src: local("Arimo Italic"), local("Arial Italic");
|
|
346
|
-
font-weight: 400;
|
|
347
|
-
font-style: italic;
|
|
348
|
-
}
|
|
349
|
-
@font-face {
|
|
350
|
-
font-family: "Arial";
|
|
351
|
-
src: local("Arimo Bold Italic"), local("Arial Bold Italic");
|
|
352
|
-
font-weight: 700;
|
|
353
|
-
font-style: italic;
|
|
354
|
-
}
|
|
355
|
-
</style>
|
|
356
|
-
<style id="brand-font-faces">__BRAND_FONT_FACES__</style>
|
|
357
|
-
<!-- Chart palette for the <Chart> stand-in (from the bundle's capture engine;
|
|
358
|
-
empty when the bundle carries no chart runtime). -->
|
|
359
|
-
<style id="chart-theme-tokens">__CHART_THEME_TOKENS__</style>
|
|
360
|
-
<style>
|
|
361
|
-
html, body { margin: 0; padding: 0; height: 100%; background: #0a0a0a; overflow: hidden; }
|
|
362
|
-
:root {
|
|
363
|
-
--deck-rail-w: 220px;
|
|
364
|
-
--deck-chrome-h: 62px;
|
|
365
|
-
--deck-chrome-bg: #111113;
|
|
366
|
-
--deck-chrome-bg-2: #161618;
|
|
367
|
-
--deck-chrome-border: #2b2b30;
|
|
368
|
-
--deck-chrome-fg: #ededed;
|
|
369
|
-
--deck-chrome-dim: #9a9aa0;
|
|
370
|
-
}
|
|
371
|
-
.deck {
|
|
372
|
-
position: fixed;
|
|
373
|
-
inset: 0 0 0 var(--deck-rail-w);
|
|
374
|
-
display: flex;
|
|
375
|
-
align-items: center;
|
|
376
|
-
justify-content: center;
|
|
377
|
-
}
|
|
378
|
-
.deck__stage {
|
|
379
|
-
width: 1280px;
|
|
380
|
-
height: 720px;
|
|
381
|
-
transform-origin: center center;
|
|
382
|
-
background: var(--bg-1);
|
|
383
|
-
box-shadow: 0 20px 60px rgba(0,0,0,0.35);
|
|
384
|
-
}
|
|
385
|
-
.deck__nav {
|
|
386
|
-
position: fixed;
|
|
387
|
-
bottom: 16px;
|
|
388
|
-
right: 16px;
|
|
389
|
-
padding: 6px 12px;
|
|
390
|
-
border-radius: 999px;
|
|
391
|
-
color: rgba(255,255,255,0.65);
|
|
392
|
-
background: rgba(255,255,255,0.08);
|
|
393
|
-
backdrop-filter: blur(6px);
|
|
394
|
-
font-family: var(--font-sans, sans-serif);
|
|
395
|
-
font-size: 12px;
|
|
396
|
-
letter-spacing: 0.02em;
|
|
397
|
-
user-select: none;
|
|
398
|
-
}
|
|
399
|
-
.deck__rail {
|
|
400
|
-
position: fixed;
|
|
401
|
-
top: 0;
|
|
402
|
-
left: 0;
|
|
403
|
-
bottom: 0;
|
|
404
|
-
width: var(--deck-rail-w);
|
|
405
|
-
z-index: 2147483590;
|
|
406
|
-
display: flex;
|
|
407
|
-
flex-direction: column;
|
|
408
|
-
background: var(--deck-chrome-bg);
|
|
409
|
-
border-right: 1px solid var(--deck-chrome-border);
|
|
410
|
-
color: var(--deck-chrome-fg);
|
|
411
|
-
font-family: var(--font-sans, sans-serif);
|
|
412
|
-
}
|
|
413
|
-
.deck__thumbs {
|
|
414
|
-
flex: 1 1 auto;
|
|
415
|
-
overflow-y: auto;
|
|
416
|
-
padding: 12px 14px 18px;
|
|
417
|
-
scrollbar-width: none;
|
|
418
|
-
-ms-overflow-style: none;
|
|
419
|
-
}
|
|
420
|
-
.deck__thumbs::-webkit-scrollbar {
|
|
421
|
-
width: 0;
|
|
422
|
-
height: 0;
|
|
423
|
-
display: none;
|
|
424
|
-
}
|
|
425
|
-
.deck__thumb {
|
|
426
|
-
width: 100%;
|
|
427
|
-
margin: 0 0 12px;
|
|
428
|
-
padding: 8px;
|
|
429
|
-
border: 1px solid var(--deck-chrome-border);
|
|
430
|
-
border-radius: 8px;
|
|
431
|
-
background: rgba(255,255,255,0.025);
|
|
432
|
-
color: inherit;
|
|
433
|
-
cursor: pointer;
|
|
434
|
-
text-align: left;
|
|
435
|
-
transition: border-color 120ms ease, background 120ms ease, box-shadow 120ms ease;
|
|
436
|
-
}
|
|
437
|
-
.deck__thumb:hover {
|
|
438
|
-
background: rgba(255,255,255,0.06);
|
|
439
|
-
border-color: #3a3a40;
|
|
440
|
-
}
|
|
441
|
-
.deck__thumb.is-active {
|
|
442
|
-
border-color: rgba(237,237,237,0.7);
|
|
443
|
-
background: rgba(255,255,255,0.085);
|
|
444
|
-
box-shadow: 0 0 0 2px rgba(237,237,237,0.08);
|
|
445
|
-
}
|
|
446
|
-
.deck__thumb-preview {
|
|
447
|
-
width: 176px;
|
|
448
|
-
height: 99px;
|
|
449
|
-
overflow: hidden;
|
|
450
|
-
background: #fff;
|
|
451
|
-
box-shadow: 0 4px 14px rgba(0,0,0,0.28);
|
|
452
|
-
pointer-events: none;
|
|
453
|
-
}
|
|
454
|
-
.deck__thumb-frame {
|
|
455
|
-
display: block;
|
|
456
|
-
width: 176px;
|
|
457
|
-
height: 99px;
|
|
458
|
-
border: 0;
|
|
459
|
-
background: #fff;
|
|
460
|
-
pointer-events: none;
|
|
461
|
-
}
|
|
462
|
-
.deck__thumb-meta {
|
|
463
|
-
display: flex;
|
|
464
|
-
align-items: baseline;
|
|
465
|
-
gap: 7px;
|
|
466
|
-
margin-top: 7px;
|
|
467
|
-
min-width: 0;
|
|
468
|
-
font-size: 11px;
|
|
469
|
-
line-height: 1.25;
|
|
470
|
-
}
|
|
471
|
-
.deck__thumb-number {
|
|
472
|
-
flex: 0 0 auto;
|
|
473
|
-
color: var(--deck-chrome-dim);
|
|
474
|
-
font-variant-numeric: tabular-nums;
|
|
475
|
-
}
|
|
476
|
-
.deck__thumb-name {
|
|
477
|
-
min-width: 0;
|
|
478
|
-
overflow: hidden;
|
|
479
|
-
text-overflow: ellipsis;
|
|
480
|
-
white-space: nowrap;
|
|
481
|
-
}
|
|
482
|
-
.deck__error {
|
|
483
|
-
padding: 32px;
|
|
484
|
-
color: #ff6b6b;
|
|
485
|
-
background: #1a0000;
|
|
486
|
-
font-family: var(--font-mono, monospace);
|
|
487
|
-
font-size: 14px;
|
|
488
|
-
white-space: pre-wrap;
|
|
489
|
-
}
|
|
490
|
-
/* Export mode (?export=1) — used by the PPTX exporter's headless Chrome
|
|
491
|
-
so it captures a clean 1280x720 stage with no letterbox/nav/scaling. */
|
|
492
|
-
body.deck--export { background: #ffffff !important; overflow: hidden; }
|
|
493
|
-
body.deck--export .deck { position: static; display: block; inset: auto; }
|
|
494
|
-
body.deck--export .deck__stage {
|
|
495
|
-
transform: none !important;
|
|
496
|
-
box-shadow: none !important;
|
|
497
|
-
margin: 0;
|
|
498
|
-
position: static;
|
|
499
|
-
}
|
|
500
|
-
body.deck--export .deck__nav,
|
|
501
|
-
body.deck--export .deck__rail { display: none !important; }
|
|
502
|
-
/* Thumbnail mode runs inside the left rail's isolated iframes. It must
|
|
503
|
-
render exactly one slide and no chrome, so per-slide <style> tags do not
|
|
504
|
-
leak into the main review document. */
|
|
505
|
-
body.deck--thumb { background: transparent !important; overflow: hidden; }
|
|
506
|
-
body.deck--thumb .deck { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; }
|
|
507
|
-
body.deck--thumb .deck__stage { box-shadow: none !important; }
|
|
508
|
-
body.deck--thumb .deck__nav,
|
|
509
|
-
body.deck--thumb .deck__rail { display: none !important; }
|
|
510
|
-
/* Print mode (?print=1) — stacks every slide as its own page so the
|
|
511
|
-
browser's Print / Cmd-P renders one slide per page (proofing). */
|
|
512
|
-
body.deck--print {
|
|
513
|
-
background: #ffffff !important;
|
|
514
|
-
overflow: visible !important;
|
|
515
|
-
height: auto !important;
|
|
516
|
-
}
|
|
517
|
-
body.deck--print .deck,
|
|
518
|
-
body.deck--print .deck__nav,
|
|
519
|
-
body.deck--print .deck__rail { display: none !important; }
|
|
520
|
-
.print-deck { background: #ffffff; }
|
|
521
|
-
.print-page {
|
|
522
|
-
width: 1280px;
|
|
523
|
-
height: 720px;
|
|
524
|
-
position: relative;
|
|
525
|
-
overflow: hidden;
|
|
526
|
-
background: var(--bg-1);
|
|
527
|
-
page-break-after: always;
|
|
528
|
-
break-after: page;
|
|
529
|
-
}
|
|
530
|
-
.print-page:last-child {
|
|
531
|
-
page-break-after: auto;
|
|
532
|
-
break-after: auto;
|
|
533
|
-
}
|
|
534
|
-
.print-page__error {
|
|
535
|
-
padding: 32px;
|
|
536
|
-
color: #ff6b6b;
|
|
537
|
-
background: #1a0000;
|
|
538
|
-
font-family: var(--font-mono, monospace);
|
|
539
|
-
font-size: 14px;
|
|
540
|
-
white-space: pre-wrap;
|
|
541
|
-
width: 100%;
|
|
542
|
-
height: 100%;
|
|
543
|
-
box-sizing: border-box;
|
|
544
|
-
}
|
|
545
|
-
@media print {
|
|
546
|
-
@page { size: 1280px 720px; margin: 0; }
|
|
547
|
-
html, body { background: #ffffff !important; }
|
|
548
|
-
}
|
|
549
|
-
</style>
|
|
550
|
-
</head>
|
|
551
|
-
<body>
|
|
552
|
-
<aside class="deck__rail" id="slide-rail" aria-label="Slide thumbnails">
|
|
553
|
-
<div class="deck__thumbs" id="slide-thumbs"></div>
|
|
554
|
-
</aside>
|
|
555
|
-
<div class="deck">
|
|
556
|
-
<div class="deck__stage" id="stage"></div>
|
|
557
|
-
</div>
|
|
558
|
-
<div class="deck__nav" id="nav">loading…</div>
|
|
559
|
-
|
|
560
|
-
<!-- Layout sources (inlined from skills/create-slides/layouts/*.jsx).
|
|
561
|
-
Each one is evaluated once and exposed under its binding name
|
|
562
|
-
(SlideCover, SlideAgendaBlack, ...) so slides can reference it. -->
|
|
563
|
-
__LAYOUTS__
|
|
564
|
-
|
|
565
|
-
<!-- Slide sources (inlined from this deck's slides/ folder, filename order). -->
|
|
566
|
-
__SLIDES__
|
|
567
|
-
|
|
568
|
-
<!-- Runtime: React + Babel-standalone from CDN. -->
|
|
569
|
-
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
|
570
|
-
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
|
571
|
-
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
|
572
|
-
<!-- The <Chart> stand-in runtime: the add-in's own vendored scripts, inlined
|
|
573
|
-
from the bundle's capture engine (this CLI ships no chart source). They
|
|
574
|
-
install window.SlidemindChart, which the bootstrap below binds as Chart
|
|
575
|
-
before any slide is evaluated. -->
|
|
576
|
-
__CHART_PREVIEW_SCRIPTS__
|
|
577
|
-
<script>window.__SLIDE_EDITOR_OVERRIDES__ = __OVERRIDES__;</script>
|
|
578
|
-
<script>window.SVG_ASSETS = __SVG_ASSETS__;</script>
|
|
579
|
-
<script>
|
|
580
|
-
__RUNTIME_APPLIER__
|
|
581
|
-
</script>
|
|
582
|
-
|
|
583
|
-
<script>
|
|
584
|
-
(function () {
|
|
585
|
-
// Map kebab-case filename stem -> PascalCase layout binding name.
|
|
586
|
-
function layoutBindingName(stem) {
|
|
587
|
-
return "Slide" + stem.split("-").map(function (p) {
|
|
588
|
-
return p ? p[0].toUpperCase() + p.slice(1) : "";
|
|
589
|
-
}).join("");
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// Stamp every intrinsic JSX element with data-sm-loc="<prefix>:<line>:<col>"
|
|
593
|
-
// so the preview-review overlay (review-overlay.js) can resolve a click back
|
|
594
|
-
// to the exact slide source location. Runs as a Babel plugin BEFORE the react
|
|
595
|
-
// preset (plugins run before presets) so it sees JSX nodes, not createElement
|
|
596
|
-
// calls. Inert without the overlay. Mirrors the add-in's instrument-source-loc.
|
|
597
|
-
function makeSourceLocPlugin(locPrefix) {
|
|
598
|
-
return function (babel) {
|
|
599
|
-
var t = babel.types;
|
|
600
|
-
return {
|
|
601
|
-
visitor: {
|
|
602
|
-
JSXOpeningElement: function (path) {
|
|
603
|
-
var node = path.node;
|
|
604
|
-
var nameNode = node.name;
|
|
605
|
-
// Intrinsic elements only: a lowercase JSXIdentifier. Skip
|
|
606
|
-
// components (<SlideCover>), member/namespaced names, fragments.
|
|
607
|
-
// <Chart> is the one component that opts in (as in the add-in's
|
|
608
|
-
// instrument-source-loc): it forwards the attribute onto its
|
|
609
|
-
// root div, so a review pin on a chart resolves to its tag.
|
|
610
|
-
if (!nameNode || nameNode.type !== "JSXIdentifier") return;
|
|
611
|
-
if (!/^[a-z]/.test(nameNode.name) && nameNode.name !== "Chart") return;
|
|
612
|
-
for (var i = 0; i < node.attributes.length; i++) {
|
|
613
|
-
var a = node.attributes[i];
|
|
614
|
-
if (a.type === "JSXAttribute" && a.name &&
|
|
615
|
-
a.name.name === "data-sm-loc") return;
|
|
616
|
-
}
|
|
617
|
-
var start = node.loc && node.loc.start;
|
|
618
|
-
if (!start) return;
|
|
619
|
-
var value = locPrefix + ":" + start.line + ":" + (start.column + 1);
|
|
620
|
-
node.attributes.push(
|
|
621
|
-
t.jsxAttribute(t.jsxIdentifier("data-sm-loc"),
|
|
622
|
-
t.stringLiteral(value))
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
};
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
function evalJsx(src, filename, extra) {
|
|
631
|
-
// The inliner wraps each source as "\\n" + src + "\\n" inside the <script>
|
|
632
|
-
// block; strip that one leading newline so Babel's line numbers (and thus
|
|
633
|
-
// data-sm-loc) align with the author's pristine workspace file.
|
|
634
|
-
if (src.charAt(0) === "\\n") src = src.slice(1);
|
|
635
|
-
var result = Babel.transform(src, {
|
|
636
|
-
presets: [["react", { runtime: "classic" }]],
|
|
637
|
-
plugins: [makeSourceLocPlugin(filename)],
|
|
638
|
-
sourceType: "module",
|
|
639
|
-
filename: filename
|
|
640
|
-
});
|
|
641
|
-
var body = result.code.replace(
|
|
642
|
-
/export\\s+default\\s+/g,
|
|
643
|
-
"__module.default = "
|
|
644
|
-
);
|
|
645
|
-
var names = ["__module", "React"].concat(Object.keys(extra || {}));
|
|
646
|
-
var values = [{}, React].concat(Object.keys(extra || {}).map(function (k) {
|
|
647
|
-
return extra[k];
|
|
648
|
-
}));
|
|
649
|
-
var factory = new Function(
|
|
650
|
-
names.join(","),
|
|
651
|
-
body + "\\nreturn __module.default;"
|
|
652
|
-
);
|
|
653
|
-
return factory.apply(null, values);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// Preview parity: replace <img src="*.svg"> whose file we inlined into
|
|
657
|
-
// window.SVG_ASSETS with the SVG markup itself, carrying the <img>'s box
|
|
658
|
-
// and color onto a wrapper so fill="currentColor" resolves — the same
|
|
659
|
-
// tint the .pptx renderer applies. Re-runs on every React render via a
|
|
660
|
-
// MutationObserver so navigating slides keeps icons inlined.
|
|
661
|
-
function smInlineSvgAssets() {
|
|
662
|
-
var ASSETS = (typeof window !== "undefined" && window.SVG_ASSETS) || null;
|
|
663
|
-
if (!ASSETS || !Object.keys(ASSETS).length) return;
|
|
664
|
-
|
|
665
|
-
function lookup(src) {
|
|
666
|
-
if (!src) return null;
|
|
667
|
-
var s = src.split("?")[0].split("#")[0];
|
|
668
|
-
var alt = s.indexOf("./") === 0 ? s.slice(2) : s;
|
|
669
|
-
var base = s.split("/").pop();
|
|
670
|
-
return ASSETS[s] || ASSETS[alt] || ASSETS[base] || null;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
function inlineWithin(root) {
|
|
674
|
-
if (!root || !root.querySelectorAll) return;
|
|
675
|
-
var imgs = root.querySelectorAll("img");
|
|
676
|
-
for (var i = 0; i < imgs.length; i++) {
|
|
677
|
-
var img = imgs[i];
|
|
678
|
-
if (img.getAttribute("data-sm-inlined") === "1") continue;
|
|
679
|
-
var markup = lookup(img.getAttribute("src"));
|
|
680
|
-
if (!markup) continue;
|
|
681
|
-
var span = document.createElement("span");
|
|
682
|
-
span.setAttribute("data-sm-inlined", "1");
|
|
683
|
-
if (img.className) span.className = img.className;
|
|
684
|
-
var styleAttr = img.getAttribute("style");
|
|
685
|
-
if (styleAttr) span.setAttribute("style", styleAttr);
|
|
686
|
-
// Pull the laid-out box + colour even when they came from CSS, not
|
|
687
|
-
// an inline style, so sizing holds and currentColor resolves.
|
|
688
|
-
var cs = window.getComputedStyle ? window.getComputedStyle(img) : null;
|
|
689
|
-
var hasW = !!span.style.width;
|
|
690
|
-
var hasH = !!span.style.height;
|
|
691
|
-
if (cs) {
|
|
692
|
-
if (!hasW && cs.width && cs.width !== "auto" && cs.width !== "0px") {
|
|
693
|
-
span.style.width = cs.width;
|
|
694
|
-
hasW = true;
|
|
695
|
-
}
|
|
696
|
-
if (!hasH && cs.height && cs.height !== "auto" && cs.height !== "0px") {
|
|
697
|
-
span.style.height = cs.height;
|
|
698
|
-
hasH = true;
|
|
699
|
-
}
|
|
700
|
-
if (!span.style.color && cs.color) span.style.color = cs.color;
|
|
701
|
-
}
|
|
702
|
-
if (!span.style.display || span.style.display === "inline") {
|
|
703
|
-
span.style.display = "inline-flex";
|
|
704
|
-
}
|
|
705
|
-
if (!span.style.alignItems) span.style.alignItems = "center";
|
|
706
|
-
if (!span.style.justifyContent) span.style.justifyContent = "center";
|
|
707
|
-
span.innerHTML = markup;
|
|
708
|
-
var svg = span.querySelector("svg");
|
|
709
|
-
if (svg) {
|
|
710
|
-
svg.style.display = "block";
|
|
711
|
-
if (hasW || hasH) {
|
|
712
|
-
svg.style.width = "100%";
|
|
713
|
-
svg.style.height = "100%";
|
|
714
|
-
svg.removeAttribute("width");
|
|
715
|
-
svg.removeAttribute("height");
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
if (img.parentNode) img.parentNode.replaceChild(span, img);
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
var scheduled = false;
|
|
723
|
-
var raf =
|
|
724
|
-
window.requestAnimationFrame ||
|
|
725
|
-
function (cb) { return setTimeout(cb, 16); };
|
|
726
|
-
function schedule() {
|
|
727
|
-
if (scheduled) return;
|
|
728
|
-
scheduled = true;
|
|
729
|
-
raf(function () {
|
|
730
|
-
scheduled = false;
|
|
731
|
-
inlineWithin(document.body);
|
|
732
|
-
});
|
|
733
|
-
}
|
|
734
|
-
if (typeof MutationObserver !== "undefined") {
|
|
735
|
-
new MutationObserver(schedule).observe(document.body, {
|
|
736
|
-
childList: true,
|
|
737
|
-
subtree: true,
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
schedule();
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
var LAYOUTS = {};
|
|
744
|
-
document.querySelectorAll('script[data-kind="layout"]').forEach(function (s) {
|
|
745
|
-
var name = s.dataset.binding || layoutBindingName(s.dataset.name);
|
|
746
|
-
try {
|
|
747
|
-
LAYOUTS[name] = evalJsx(
|
|
748
|
-
s.textContent,
|
|
749
|
-
"layouts/" + s.dataset.name + ".jsx",
|
|
750
|
-
{}
|
|
751
|
-
);
|
|
752
|
-
window[name] = LAYOUTS[name];
|
|
753
|
-
} catch (err) {
|
|
754
|
-
console.error("Layout " + name + " failed to load:", err);
|
|
755
|
-
throw err;
|
|
756
|
-
}
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
// The Chart binding slide jsx is compiled against — installed by the
|
|
760
|
-
// bundle's chart-preview scripts above; without them a chart renders
|
|
761
|
-
// nothing, exactly as the add-in's page behaves.
|
|
762
|
-
var Chart = window.SlidemindChart
|
|
763
|
-
? window.SlidemindChart.createComponent(React)
|
|
764
|
-
: function () { return null; };
|
|
765
|
-
var slideScope = Object.assign({ Chart: Chart }, LAYOUTS);
|
|
766
|
-
|
|
767
|
-
var slides = [];
|
|
768
|
-
document.querySelectorAll('script[data-kind="slide"]').forEach(function (s) {
|
|
769
|
-
var idx = parseInt(s.dataset.index, 10);
|
|
770
|
-
var name = s.dataset.name;
|
|
771
|
-
try {
|
|
772
|
-
var Component = evalJsx(
|
|
773
|
-
s.textContent,
|
|
774
|
-
"slides/" + name + ".jsx",
|
|
775
|
-
slideScope
|
|
776
|
-
);
|
|
777
|
-
slides[idx] = { name: name, Component: Component };
|
|
778
|
-
} catch (err) {
|
|
779
|
-
console.error("Slide " + name + " failed to load:", err);
|
|
780
|
-
slides[idx] = { name: name, error: err };
|
|
781
|
-
}
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
var stage = document.getElementById("stage");
|
|
785
|
-
var nav = document.getElementById("nav");
|
|
786
|
-
var rail = document.getElementById("slide-rail");
|
|
787
|
-
var thumbs = document.getElementById("slide-thumbs");
|
|
788
|
-
var thumbButtons = [];
|
|
789
|
-
var params = new URLSearchParams(location.search);
|
|
790
|
-
var exportMode = params.get("export") === "1";
|
|
791
|
-
var printMode = params.get("print") === "1";
|
|
792
|
-
var thumbMode = params.get("thumb") === "1";
|
|
793
|
-
if (exportMode) document.body.classList.add("deck--export");
|
|
794
|
-
if (thumbMode) document.body.classList.add("deck--thumb");
|
|
795
|
-
|
|
796
|
-
smInlineSvgAssets();
|
|
797
|
-
|
|
798
|
-
if (printMode) {
|
|
799
|
-
document.body.classList.add("deck--print");
|
|
800
|
-
var printDeck = document.createElement("div");
|
|
801
|
-
printDeck.className = "print-deck";
|
|
802
|
-
document.body.appendChild(printDeck);
|
|
803
|
-
slides.forEach(function (slide) {
|
|
804
|
-
var page = document.createElement("div");
|
|
805
|
-
page.className = "print-page";
|
|
806
|
-
printDeck.appendChild(page);
|
|
807
|
-
if (slide.error) {
|
|
808
|
-
var errEl = document.createElement("div");
|
|
809
|
-
errEl.className = "print-page__error";
|
|
810
|
-
errEl.textContent = slide.name + ".jsx failed:\\n" + slide.error.message;
|
|
811
|
-
page.appendChild(errEl);
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
ReactDOM.createRoot(page).render(
|
|
815
|
-
React.createElement(slide.Component, { assetsPath: "./assets" })
|
|
816
|
-
);
|
|
817
|
-
});
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
var startIndex = parseInt(params.get("slide") || "0", 10);
|
|
822
|
-
if (isNaN(startIndex) || startIndex < 0) startIndex = 0;
|
|
823
|
-
if (startIndex >= slides.length) startIndex = slides.length - 1;
|
|
824
|
-
var current = startIndex;
|
|
825
|
-
var root = ReactDOM.createRoot(stage);
|
|
826
|
-
|
|
827
|
-
function buildThumbs() {
|
|
828
|
-
if (!thumbs || printMode || exportMode || thumbMode) return;
|
|
829
|
-
thumbs.innerHTML = "";
|
|
830
|
-
thumbButtons = [];
|
|
831
|
-
slides.forEach(function (slide, idx) {
|
|
832
|
-
var btn = document.createElement("button");
|
|
833
|
-
btn.type = "button";
|
|
834
|
-
btn.className = "deck__thumb";
|
|
835
|
-
btn.setAttribute("aria-label", "Go to slide " + (idx + 1) + ": " + slide.name);
|
|
836
|
-
|
|
837
|
-
var preview = document.createElement("div");
|
|
838
|
-
preview.className = "deck__thumb-preview";
|
|
839
|
-
var frame = document.createElement("iframe");
|
|
840
|
-
frame.className = "deck__thumb-frame";
|
|
841
|
-
frame.loading = "lazy";
|
|
842
|
-
frame.setAttribute("aria-hidden", "true");
|
|
843
|
-
frame.tabIndex = -1;
|
|
844
|
-
frame.src = location.pathname + "?slide=" + idx + "&thumb=1";
|
|
845
|
-
preview.appendChild(frame);
|
|
846
|
-
|
|
847
|
-
var meta = document.createElement("div");
|
|
848
|
-
meta.className = "deck__thumb-meta";
|
|
849
|
-
var num = document.createElement("span");
|
|
850
|
-
num.className = "deck__thumb-number";
|
|
851
|
-
num.textContent = String(idx + 1).padStart(2, "0");
|
|
852
|
-
var name = document.createElement("span");
|
|
853
|
-
name.className = "deck__thumb-name";
|
|
854
|
-
name.textContent = slide.name;
|
|
855
|
-
meta.appendChild(num);
|
|
856
|
-
meta.appendChild(name);
|
|
857
|
-
|
|
858
|
-
btn.appendChild(preview);
|
|
859
|
-
btn.appendChild(meta);
|
|
860
|
-
btn.addEventListener("click", function () {
|
|
861
|
-
setCurrent(idx);
|
|
862
|
-
stage.focus && stage.focus();
|
|
863
|
-
});
|
|
864
|
-
thumbs.appendChild(btn);
|
|
865
|
-
thumbButtons[idx] = btn;
|
|
866
|
-
});
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
function updateThumbs() {
|
|
870
|
-
thumbButtons.forEach(function (btn, idx) {
|
|
871
|
-
if (!btn) return;
|
|
872
|
-
var active = idx === current;
|
|
873
|
-
btn.classList.toggle("is-active", active);
|
|
874
|
-
btn.setAttribute("aria-current", active ? "true" : "false");
|
|
875
|
-
if (active && thumbs) {
|
|
876
|
-
var top = btn.offsetTop;
|
|
877
|
-
var bottom = top + btn.offsetHeight;
|
|
878
|
-
if (top < thumbs.scrollTop) {
|
|
879
|
-
thumbs.scrollTop = Math.max(0, top - 12);
|
|
880
|
-
} else if (bottom > thumbs.scrollTop + thumbs.clientHeight) {
|
|
881
|
-
thumbs.scrollTop = bottom - thumbs.clientHeight + 12;
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
function render() {
|
|
888
|
-
var slide = slides[current];
|
|
889
|
-
if (!slide) return;
|
|
890
|
-
nav.textContent =
|
|
891
|
-
(current + 1) + " / " + slides.length + " · " + slide.name;
|
|
892
|
-
if (slide.error) {
|
|
893
|
-
root.render(
|
|
894
|
-
React.createElement(
|
|
895
|
-
"div",
|
|
896
|
-
{ className: "deck__error" },
|
|
897
|
-
slide.name + ".jsx failed:\\n" + slide.error.message
|
|
898
|
-
)
|
|
899
|
-
);
|
|
900
|
-
updateThumbs();
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
root.render(
|
|
904
|
-
React.createElement(slide.Component, { assetsPath: "./assets" })
|
|
905
|
-
);
|
|
906
|
-
updateThumbs();
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
function setCurrent(index) {
|
|
910
|
-
current = Math.max(0, Math.min(index, slides.length - 1));
|
|
911
|
-
render();
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
function fit() {
|
|
915
|
-
if (exportMode) {
|
|
916
|
-
stage.style.transform = "none";
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
var railW = rail && getComputedStyle(rail).display !== "none" ? rail.offsetWidth : 0;
|
|
920
|
-
var sx = Math.max(1, window.innerWidth - railW) / 1280;
|
|
921
|
-
var sy = window.innerHeight / 720;
|
|
922
|
-
var s = Math.min(sx, sy) * (thumbMode ? 1 : 0.95);
|
|
923
|
-
stage.style.transform = "scale(" + s + ")";
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
window.addEventListener("resize", fit);
|
|
927
|
-
buildThumbs();
|
|
928
|
-
fit();
|
|
929
|
-
render();
|
|
930
|
-
|
|
931
|
-
document.addEventListener("keydown", function (e) {
|
|
932
|
-
var k = e.key;
|
|
933
|
-
if (k === "ArrowRight" || k === "PageDown" || k === " ") {
|
|
934
|
-
setCurrent(current + 1);
|
|
935
|
-
e.preventDefault();
|
|
936
|
-
} else if (k === "ArrowLeft" || k === "PageUp") {
|
|
937
|
-
setCurrent(current - 1);
|
|
938
|
-
e.preventDefault();
|
|
939
|
-
} else if (k === "Home") {
|
|
940
|
-
setCurrent(0);
|
|
941
|
-
} else if (k === "End") {
|
|
942
|
-
setCurrent(slides.length - 1);
|
|
943
|
-
} else if (k === "f" || k === "F") {
|
|
944
|
-
if (document.fullscreenElement) document.exitFullscreen();
|
|
945
|
-
else document.documentElement.requestFullscreen();
|
|
946
|
-
}
|
|
947
|
-
});
|
|
948
|
-
})();
|
|
949
|
-
</script>
|
|
950
|
-
</body>
|
|
951
|
-
</html>
|
|
952
|
-
`;
|
|
289
|
+
// The preview page. Kept as a real .html file (syntax-highlighted, diffable)
|
|
290
|
+
// rather than an 800-line template string; the __PLACEHOLDER__ slots are
|
|
291
|
+
// filled by buildDeck below. Function replacer: a string replacement would
|
|
292
|
+
// expand `$&`-style patterns in the substituted value.
|
|
293
|
+
const HTML_TEMPLATE = fs
|
|
294
|
+
.readFileSync(path.join(__dirname, "preview.html"), "utf8")
|
|
295
|
+
.replace("__FAVICON_LINK__", () => FAVICON_LINK);
|
|
953
296
|
|
|
954
297
|
export async function buildDeck(deckDir, opts = {}) {
|
|
955
298
|
const skill = resolveSkillForDeck(deckDir);
|
|
@@ -1017,6 +360,7 @@ export async function buildDeck(deckDir, opts = {}) {
|
|
|
1017
360
|
// loadChartPreview) — resolved after lint, which already proved the bundle
|
|
1018
361
|
// is materialized.
|
|
1019
362
|
const chartPreview = await loadChartPreview(resolveBundleDirForDeck(deckDir));
|
|
363
|
+
const connectorPreview = await loadConnectorPreview(resolveBundleDirForDeck(deckDir));
|
|
1020
364
|
|
|
1021
365
|
copyDesignSystem(deckDir, skill);
|
|
1022
366
|
|
|
@@ -1043,6 +387,9 @@ export async function buildDeck(deckDir, opts = {}) {
|
|
|
1043
387
|
.map((script) => ` <script>\n${safeInline(script.source)}\n </script>`)
|
|
1044
388
|
.join("\n")
|
|
1045
389
|
: "",
|
|
390
|
+
)
|
|
391
|
+
.replace("__CONNECTOR_PREVIEW_SCRIPT__", () =>
|
|
392
|
+
connectorPreview ? ` <script>\n${safeInline(connectorPreview.source)}\n </script>` : "",
|
|
1046
393
|
);
|
|
1047
394
|
|
|
1048
395
|
const out = path.join(deckDir, "presentation.html");
|
|
@@ -1176,8 +523,8 @@ function parseArgs(argv) {
|
|
|
1176
523
|
args.fix = true;
|
|
1177
524
|
} else if (a === "-h" || a === "--help") {
|
|
1178
525
|
process.stdout.write(
|
|
1179
|
-
"Usage:
|
|
1180
|
-
"
|
|
526
|
+
"Usage: node src/builders/build.mjs --deck <deck> [--open] [--fix]\n" +
|
|
527
|
+
" (the CLI entry point is `slidemind view-draft <deck> [--fix]`)\n" +
|
|
1181
528
|
" --fix apply the deterministic lint autofixes to slides/*.jsx before building\n" +
|
|
1182
529
|
" (pin inline-svg px size from its viewBox; letter-spacing em→px where the\n" +
|
|
1183
530
|
" block's px font-size makes the conversion exact)\n",
|