vite-plugin-react-deck 1.1.5 → 1.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/index.cjs CHANGED
@@ -27,12 +27,411 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
 
28
28
  // src/index.ts
29
29
  var fs2 = __toESM(require("fs/promises"), 1);
30
+ var glob = __toESM(require("glob"), 1);
31
+ var import_gray_matter2 = __toESM(require("gray-matter"), 1);
32
+
33
+ // src/themes/green.ts
34
+ var green_exports = {};
35
+ __export(green_exports, {
36
+ themeTokens: () => themeTokens
37
+ });
38
+ var colors = {
39
+ primary: "#FFFFFF",
40
+ secondary: "#F49676",
41
+ tertiary: "#042F3B"
42
+ };
43
+ var themeTokens = {
44
+ colors
45
+ };
46
+
47
+ // src/themes/purple.ts
48
+ var purple_exports = {};
49
+ __export(purple_exports, {
50
+ themeTokens: () => themeTokens2
51
+ });
52
+ var colors2 = {
53
+ //primary: "#56D4F8",
54
+ primary: "#ffffff",
55
+ secondary: "#F530EC",
56
+ tertiary: "#2B135A"
57
+ };
58
+ var themeTokens2 = {
59
+ colors: colors2
60
+ };
61
+
62
+ // src/themes/solarized-light.ts
63
+ var solarized_light_exports = {};
64
+ __export(solarized_light_exports, {
65
+ themeTokens: () => themeTokens3
66
+ });
67
+ var colors3 = {
68
+ primary: "#073642",
69
+ // base02 - dark text on light background
70
+ secondary: "#268bd2",
71
+ // blue accent
72
+ tertiary: "#fdf6e3"
73
+ // base3 - light background
74
+ };
75
+ var fonts = {
76
+ header: '"Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif',
77
+ text: '"Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif'
78
+ };
79
+ var themeTokens3 = {
80
+ colors: colors3,
81
+ fonts
82
+ };
83
+
84
+ // src/helpers.ts
85
+ var themes = {
86
+ green: green_exports,
87
+ purple: purple_exports,
88
+ "solarized-light": solarized_light_exports
89
+ };
90
+ function createDecksIndexFile() {
91
+ return `<!DOCTYPE html>
92
+ <html lang="en">
93
+ <head>
94
+ <meta charset="utf-8" />
95
+ <title>Pestacle - Decks</title>
96
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
97
+ <link rel="icon" type="image/x-icon" href="favicon.ico" />
98
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
99
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
100
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
101
+ <style>* { margin: 0; padding: 0; box-sizing: border-box; }</style>
102
+ </head>
103
+ <body>
104
+ <div id="root"></div>
105
+ <script type="module" src="__decks.tsx"></script>
106
+ </body>
107
+ </html>
108
+ `;
109
+ }
110
+ function createDecksPageFile({
111
+ decks,
112
+ theme
113
+ }) {
114
+ var _a;
115
+ const themeModule = themes[theme];
116
+ const colors4 = (_a = themeModule == null ? void 0 : themeModule.themeTokens) == null ? void 0 : _a.colors;
117
+ const primary = (colors4 == null ? void 0 : colors4.primary) ?? "#ffffff";
118
+ const secondary = (colors4 == null ? void 0 : colors4.secondary) ?? "#F49676";
119
+ const tertiary = (colors4 == null ? void 0 : colors4.tertiary) ?? "#042F3B";
120
+ return `import React, { StrictMode, useState } from "react";
121
+ import * as ReactDOM from "react-dom/client";
122
+
123
+ const decks = ${JSON.stringify(decks)};
124
+
125
+ function DeckCard({ deck }) {
126
+ const [hovered, setHovered] = useState(false);
127
+ const title = deck.title || deck.name;
128
+ const hasMeta = deck.author || deck.slideCount > 0;
129
+
130
+ return (
131
+ <a
132
+ href={deck.path}
133
+ onMouseEnter={() => setHovered(true)}
134
+ onMouseLeave={() => setHovered(false)}
135
+ style={{
136
+ display: "flex",
137
+ flexDirection: "column",
138
+ padding: "1.5rem",
139
+ borderRadius: 16,
140
+ backgroundColor: hovered ? "${secondary}18" : "${secondary}0a",
141
+ border: hovered ? "1px solid ${secondary}88" : "1px solid ${secondary}22",
142
+ color: "${primary}",
143
+ textDecoration: "none",
144
+ transition: "all 0.2s ease",
145
+ transform: hovered ? "translateY(-2px)" : "translateY(0)",
146
+ boxShadow: hovered
147
+ ? "0 8px 24px rgba(0,0,0,0.2)"
148
+ : "0 2px 8px rgba(0,0,0,0.1)",
149
+ minHeight: 140,
150
+ justifyContent: "space-between",
151
+ }}
152
+ >
153
+ <div>
154
+ <div style={{
155
+ fontSize: "1.25rem",
156
+ fontWeight: 600,
157
+ marginBottom: "0.5rem",
158
+ lineHeight: 1.3,
159
+ color: "${primary}",
160
+ }}>
161
+ {title}
162
+ </div>
163
+ {deck.description && (
164
+ <div style={{
165
+ fontSize: "0.875rem",
166
+ opacity: 0.6,
167
+ lineHeight: 1.5,
168
+ marginBottom: "0.75rem",
169
+ }}>
170
+ {deck.description}
171
+ </div>
172
+ )}
173
+ </div>
174
+ <div style={{
175
+ display: "flex",
176
+ alignItems: "center",
177
+ gap: "1rem",
178
+ flexWrap: "wrap",
179
+ marginTop: "auto",
180
+ }}>
181
+ {deck.author && (
182
+ <span style={{
183
+ fontSize: "0.8rem",
184
+ opacity: 0.5,
185
+ display: "flex",
186
+ alignItems: "center",
187
+ gap: "0.35rem",
188
+ }}>
189
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
190
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
191
+ <circle cx="12" cy="7" r="4" />
192
+ </svg>
193
+ {deck.author}
194
+ </span>
195
+ )}
196
+ {deck.slideCount > 0 && (
197
+ <span style={{
198
+ fontSize: "0.8rem",
199
+ opacity: 0.5,
200
+ display: "flex",
201
+ alignItems: "center",
202
+ gap: "0.35rem",
203
+ }}>
204
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
205
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
206
+ <line x1="8" y1="21" x2="16" y2="21" />
207
+ <line x1="12" y1="17" x2="12" y2="21" />
208
+ </svg>
209
+ {deck.slideCount} slide{deck.slideCount !== 1 ? "s" : ""}
210
+ </span>
211
+ )}
212
+ {deck.date && (
213
+ <span style={{
214
+ fontSize: "0.8rem",
215
+ opacity: 0.5,
216
+ }}>
217
+ {deck.date}
218
+ </span>
219
+ )}
220
+ </div>
221
+ </a>
222
+ );
223
+ }
224
+
225
+ function DecksPage() {
226
+ const [search, setSearch] = useState("");
227
+ const [searchFocused, setSearchFocused] = useState(false);
228
+ const filtered = decks.filter((d) => {
229
+ const q = search.toLowerCase();
230
+ return (
231
+ d.name.toLowerCase().includes(q) ||
232
+ (d.title || "").toLowerCase().includes(q) ||
233
+ (d.author || "").toLowerCase().includes(q) ||
234
+ (d.description || "").toLowerCase().includes(q)
235
+ );
236
+ });
237
+
238
+ return (
239
+ <div style={{
240
+ minHeight: "100vh",
241
+ backgroundColor: "${tertiary}",
242
+ color: "${primary}",
243
+ fontFamily: "'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif",
244
+ }}>
245
+ <div style={{
246
+ maxWidth: 960,
247
+ margin: "0 auto",
248
+ padding: "4rem 2rem 3rem",
249
+ }}>
250
+ <div style={{ marginBottom: "3rem" }}>
251
+ <div style={{
252
+ display: "flex",
253
+ alignItems: "center",
254
+ gap: "0.75rem",
255
+ marginBottom: "0.75rem",
256
+ }}>
257
+ <svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="${secondary}" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
258
+ <polygon points="23 7 16 12 23 17 23 7" />
259
+ <rect x="1" y="5" width="15" height="14" rx="2" ry="2" />
260
+ </svg>
261
+ <h1 style={{
262
+ fontSize: "2.5rem",
263
+ fontWeight: 700,
264
+ margin: 0,
265
+ background: "linear-gradient(135deg, ${secondary}, ${primary})",
266
+ WebkitBackgroundClip: "text",
267
+ WebkitTextFillColor: "transparent",
268
+ backgroundClip: "text",
269
+ }}>Pestacle</h1>
270
+ </div>
271
+ <p style={{
272
+ fontSize: "1rem",
273
+ opacity: 0.5,
274
+ margin: 0,
275
+ }}>{decks.length} presentation{decks.length !== 1 ? "s" : ""} available</p>
276
+ </div>
277
+
278
+ <div style={{
279
+ position: "relative",
280
+ marginBottom: "2rem",
281
+ }}>
282
+ <svg
283
+ width="18" height="18"
284
+ viewBox="0 0 24 24"
285
+ fill="none"
286
+ stroke="${primary}"
287
+ strokeWidth="2"
288
+ strokeLinecap="round"
289
+ strokeLinejoin="round"
290
+ style={{
291
+ position: "absolute",
292
+ left: 16,
293
+ top: "50%",
294
+ transform: "translateY(-50%)",
295
+ opacity: 0.4,
296
+ }}
297
+ >
298
+ <circle cx="11" cy="11" r="8" />
299
+ <line x1="21" y1="21" x2="16.65" y2="16.65" />
300
+ </svg>
301
+ <input
302
+ type="text"
303
+ placeholder="Search presentations..."
304
+ value={search}
305
+ onChange={(e) => setSearch(e.target.value)}
306
+ onFocus={() => setSearchFocused(true)}
307
+ onBlur={() => setSearchFocused(false)}
308
+ autoFocus
309
+ style={{
310
+ width: "100%",
311
+ padding: "0.875rem 1rem 0.875rem 2.75rem",
312
+ fontSize: "1rem",
313
+ borderRadius: 12,
314
+ border: searchFocused ? "2px solid ${secondary}" : "2px solid ${secondary}33",
315
+ backgroundColor: "${secondary}08",
316
+ color: "${primary}",
317
+ outline: "none",
318
+ boxSizing: "border-box",
319
+ transition: "border-color 0.2s ease, background-color 0.2s ease",
320
+ }}
321
+ />
322
+ </div>
323
+
324
+ <div style={{
325
+ display: "grid",
326
+ gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
327
+ gap: "1rem",
328
+ }}>
329
+ {filtered.map((deck) => (
330
+ <DeckCard key={deck.path} deck={deck} />
331
+ ))}
332
+ </div>
333
+
334
+ {filtered.length === 0 && (
335
+ <div style={{
336
+ textAlign: "center",
337
+ padding: "4rem 2rem",
338
+ opacity: 0.4,
339
+ }}>
340
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ marginBottom: "1rem" }}>
341
+ <circle cx="11" cy="11" r="8" />
342
+ <line x1="21" y1="21" x2="16.65" y2="16.65" />
343
+ </svg>
344
+ <p style={{ fontSize: "1.1rem" }}>No presentations matching &ldquo;{search}&rdquo;</p>
345
+ </div>
346
+ )}
347
+ </div>
348
+ </div>
349
+ );
350
+ }
351
+
352
+ const root = ReactDOM.createRoot(
353
+ document.getElementById("root") as HTMLElement
354
+ );
355
+ root.render(
356
+ <StrictMode>
357
+ <DecksPage />
358
+ </StrictMode>
359
+ );
360
+ `;
361
+ }
362
+ function createIndexFile({ entrypoint }) {
363
+ return `<!DOCTYPE html>
364
+ <html lang="en">
365
+ <head>
366
+ <meta charset="utf-8" />
367
+ <title>Title</title>
368
+
369
+ <style>
370
+ html {
371
+ --code-preview-background-color: #222;
372
+ }
373
+ </style>
374
+
375
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
376
+ <link rel="icon" type="image/x-icon" href="favicon.ico" />
377
+ </head>
378
+ <body>
379
+ <div id="root"></div>
380
+ <script type="module" src="${entrypoint}"></script>
381
+ </body>
382
+ </html>
383
+ `;
384
+ }
385
+ function createAppDeckFile({
386
+ slidePath,
387
+ theme,
388
+ deckTheme,
389
+ config
390
+ }) {
391
+ const resolvedThemeName = deckTheme ?? theme;
392
+ const isBuiltinTheme = resolvedThemeName in themes;
393
+ const isCustomThemePath = !isBuiltinTheme && (resolvedThemeName.startsWith("./") || resolvedThemeName.startsWith("../") || resolvedThemeName.startsWith("/"));
394
+ if (!isBuiltinTheme && !isCustomThemePath) {
395
+ const available = Object.keys(themes).join(", ");
396
+ throw new Error(
397
+ `Theme "${resolvedThemeName}" not found. Available built-in themes: ${available}. For custom themes, use a relative path (e.g. "./pestacle/themes/my-theme").`
398
+ );
399
+ }
400
+ const layoutImport = config.layoutsFile ? `import layouts from "${config.layoutsFile}";` : "import { layouts } from '@gpichot/spectacle-deck';";
401
+ const themeCode = isBuiltinTheme ? `const theme = ${JSON.stringify(themes[resolvedThemeName])};` : `import { themeTokens as _customThemeTokens } from "${resolvedThemeName}";
402
+ const theme = { themeTokens: _customThemeTokens };`;
403
+ return `import React, { StrictMode } from "react";
404
+ import * as ReactDOM from "react-dom/client";
405
+ import { Deck } from '@gpichot/spectacle-deck';
406
+ ${layoutImport};
407
+
408
+ import deck from "${slidePath}";
409
+ ${themeCode}
410
+
411
+ const root = ReactDOM.createRoot(
412
+ document.getElementById("root") as HTMLElement
413
+ );
414
+ root.render(
415
+ <StrictMode>
416
+ <Deck deck={deck} theme={theme} layouts={layouts}${config.transition ? ` transition="${config.transition}"` : ""} />
417
+ </StrictMode>
418
+ )
419
+
420
+ let link = document.createElement('link')
421
+ link.rel = 'stylesheet'
422
+ link.type = 'text/css'
423
+ link.href = 'https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css'
424
+ document.head.appendChild(link)
425
+ `;
426
+ }
30
427
 
31
428
  // src/slides.ts
429
+ var import_node_fs = __toESM(require("fs"), 1);
430
+ var import_mdx = require("@mdx-js/mdx");
32
431
  var import_gray_matter = __toESM(require("gray-matter"), 1);
33
432
  var import_remark_directive = __toESM(require("remark-directive"), 1);
34
- var import_fs = __toESM(require("fs"), 1);
35
- var import_mdx = require("@mdx-js/mdx");
433
+ var import_remark_gfm = __toESM(require("remark-gfm"), 1);
434
+ var import_unist_util_visit = require("unist-util-visit");
36
435
 
37
436
  // src/codegen.ts
38
437
  var Patterns = {
@@ -70,13 +469,14 @@ ${header}
70
469
  }
71
470
 
72
471
  // src/slides.ts
73
- var import_remark_gfm = __toESM(require("remark-gfm"), 1);
74
- var import_unist_util_visit = require("unist-util-visit");
75
472
  function myRemarkPlugin() {
76
473
  return (tree) => {
77
474
  (0, import_unist_util_visit.visit)(tree, (node) => {
78
475
  if (node.type === "containerDirective" || node.type === "leafDirective" || node.type === "textDirective") {
79
- const data = node.data || (node.data = {});
476
+ if (!node.data) {
477
+ node.data = {};
478
+ }
479
+ const data = node.data;
80
480
  data.hName = "directive";
81
481
  data.hProperties = node.attributes;
82
482
  data.hProperties._name = node.name;
@@ -95,10 +495,9 @@ async function transformSlidesMdxToReact(sourceContent, {
95
495
  );
96
496
  const slides = finalContent.split("---\n");
97
497
  const enrichedSlides = [];
98
- const LAYOUT_REGEX = /\S*\nlayout: (.*)/g;
99
498
  let frontmatterForNextSlide = null;
100
499
  for (const slide of slides) {
101
- if (LAYOUT_REGEX.test(slide)) {
500
+ if (isFrontmatterBlock(slide)) {
102
501
  frontmatterForNextSlide = (0, import_gray_matter.default)(`---
103
502
  ${slide}
104
503
  ---`).data;
@@ -111,12 +510,12 @@ ${slide}
111
510
  frontmatterForNextSlide = null;
112
511
  }
113
512
  const compiledSlides = await Promise.all(
114
- enrichedSlides.map(async (slide, index) => {
513
+ enrichedSlides.map(async (slide, _index) => {
115
514
  const code = addInlineModules(slide.content, inlineModules);
116
515
  const normalizedCode = code.replace("process.env", "process\u200B.env");
117
516
  const result = await (0, import_mdx.compile)(normalizedCode, {
118
517
  outputFormat: "program",
119
- jsx: !isProd,
518
+ jsx: false,
120
519
  providerImportSource: "@mdx-js/react",
121
520
  ...options,
122
521
  remarkPlugins: [
@@ -127,7 +526,7 @@ ${slide}
127
526
  ]
128
527
  });
129
528
  const mainCode = extractMainCodeAsChildren(result.value.toString(), {
130
- isJsx: !isProd
529
+ isJsx: false
131
530
  });
132
531
  return {
133
532
  ...slide,
@@ -138,7 +537,8 @@ ${slide}
138
537
  const output = addInlineModules(
139
538
  `
140
539
  import React from 'react';
141
- ${isProd ? 'import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";import {useMDXComponents as _provideComponents} from "@mdx-js/react" ' : "import {useMDXComponents as _provideComponents} from '@mdx-js/react';"}
540
+ import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
541
+ import {useMDXComponents as _provideComponents} from "@mdx-js/react";
142
542
 
143
543
  ${compiledSlides.map(
144
544
  (slide, index) => `
@@ -163,7 +563,7 @@ Deck.slides = [
163
563
  `,
164
564
  inlineModules
165
565
  );
166
- import_fs.default.writeFileSync("slides.js", output);
566
+ import_node_fs.default.writeFileSync("slides.js", output);
167
567
  return output;
168
568
  }
169
569
  var MOD_REG = /\\`|`(?:\\`|[^`])*`|(^(?:import|export).*$)/gm;
@@ -186,122 +586,30 @@ ${[...inlineModules.keys()].join("\n")}
186
586
  ${source}
187
587
  `;
188
588
  }
589
+ function isFrontmatterBlock(text) {
590
+ const lines = text.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
591
+ return lines.length > 0 && lines.every((l) => /^[\w][\w-]*\s*:/.test(l));
592
+ }
189
593
  var CRLF = "\r\n";
190
594
  function normalizeNewline(input) {
191
595
  return input.replace(new RegExp(CRLF, "g"), "\n");
192
596
  }
193
597
 
194
- // src/themes/index.ts
195
- var themes_exports = {};
196
- __export(themes_exports, {
197
- green: () => green_exports,
198
- purple: () => purple_exports
199
- });
200
-
201
- // src/themes/green.ts
202
- var green_exports = {};
203
- __export(green_exports, {
204
- themeTokens: () => themeTokens
205
- });
206
- var colors = {
207
- primary: "#FFFFFF",
208
- secondary: "#F49676",
209
- tertiary: "#042F3B"
210
- };
211
- var themeTokens = {
212
- colors
213
- };
214
-
215
- // src/themes/purple.ts
216
- var purple_exports = {};
217
- __export(purple_exports, {
218
- themeTokens: () => themeTokens2
219
- });
220
- var colors2 = {
221
- //primary: "#56D4F8",
222
- primary: "#ffffff",
223
- secondary: "#F530EC",
224
- tertiary: "#2B135A"
225
- };
226
- var themeTokens2 = {
227
- colors: colors2
228
- };
229
-
230
- // src/helpers.ts
231
- function createIndexFile({ entrypoint }) {
232
- return `<!DOCTYPE html>
233
- <html lang="en">
234
- <head>
235
- <meta charset="utf-8" />
236
- <title>Title</title>
237
-
238
- <style>
239
- html {
240
- --code-preview-background-color: #222;
241
- }
242
- </style>
243
-
244
- <meta name="viewport" content="width=device-width, initial-scale=1" />
245
- <link rel="icon" type="image/x-icon" href="favicon.ico" />
246
- </head>
247
- <body>
248
- <div id="root"></div>
249
- <script type="module" src="${entrypoint}"></script>
250
- </body>
251
- </html>
252
- `;
253
- }
254
- function createAppDeckFile({
255
- slidePath,
256
- theme,
257
- config
258
- }) {
259
- if (!themes_exports[theme]) {
260
- throw new Error(`Theme ${theme} not found`);
261
- }
262
- const themeObject = themes_exports[theme];
263
- const layoutImport = config.layoutsFile ? `import layouts from "${config.layoutsFile}";` : "import { layouts } from '@gpichot/spectacle-deck';";
264
- return `import React, { StrictMode } from "react";
265
- import * as ReactDOM from "react-dom/client";
266
- import { Deck } from '@gpichot/spectacle-deck';
267
- ${layoutImport};
268
-
269
- import deck from "${slidePath}";
270
-
271
- const root = ReactDOM.createRoot(
272
- document.getElementById("root") as HTMLElement
273
- );
274
- root.render(
275
- <StrictMode>
276
- <Deck deck={deck} theme={${JSON.stringify(themeObject)}} layouts={layouts} />
277
- </StrictMode>
278
- )
279
-
280
- let link = document.createElement('link')
281
- link.rel = 'stylesheet'
282
- link.type = 'text/css'
283
- link.href = 'https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css'
284
- document.head.appendChild(link)
285
- `;
286
- }
287
-
288
- // src/index.ts
289
- var glob = __toESM(require("glob"), 1);
290
- var import_node_path = __toESM(require("path"), 1);
291
-
292
598
  // src/config.ts
293
599
  var import_lodash = __toESM(require("lodash"), 1);
294
600
 
295
601
  // src/types.ts
296
602
  var import_zod = require("zod");
297
603
  var PestacleConfigSchema = import_zod.z.object({
298
- theme: import_zod.z.enum(["green", "purple"]).default("green")
604
+ theme: import_zod.z.enum(["green", "purple", "solarized-light"]).default("green"),
605
+ startupPage: import_zod.z.boolean().optional(),
606
+ transition: import_zod.z.enum(["fade", "slide", "drop", "none"]).optional()
299
607
  });
300
608
 
301
609
  // src/index.ts
302
- async function checkIfDirectoryExists(path2) {
610
+ async function checkIfDirectoryExists(path) {
303
611
  try {
304
- await fs2.access(path2);
612
+ await fs2.access(path);
305
613
  return true;
306
614
  } catch {
307
615
  return false;
@@ -314,15 +622,45 @@ async function findDecks({ port }) {
314
622
  deckUrl: `http://localhost:${port}/${deck.replace("src/", "").replace("deck.mdx", "")}`
315
623
  }));
316
624
  }
317
- async function fileExists(name, path2) {
625
+ async function extractDeckMeta(filePath) {
626
+ try {
627
+ const raw = await fs2.readFile(filePath, "utf-8");
628
+ const { data, content } = (0, import_gray_matter2.default)(raw);
629
+ const slides = content.split("---\n");
630
+ const LAYOUT_REGEX = /\S*\nlayout: (.*)/g;
631
+ let slideCount = 0;
632
+ for (const slide of slides) {
633
+ if (!LAYOUT_REGEX.test(slide) && slide.trim().length > 0) {
634
+ slideCount++;
635
+ }
636
+ LAYOUT_REGEX.lastIndex = 0;
637
+ }
638
+ let title = data.title;
639
+ if (!title) {
640
+ const headingMatch = content.match(/^#\s+\*{0,2}(.+?)\*{0,2}\s*$/m);
641
+ if (headingMatch) {
642
+ title = headingMatch[1].replace(/\*+/g, "").trim();
643
+ }
644
+ }
645
+ return {
646
+ title,
647
+ author: data.author,
648
+ date: data.date,
649
+ description: data.description,
650
+ slideCount
651
+ };
652
+ } catch {
653
+ return { slideCount: 0 };
654
+ }
655
+ }
656
+ async function fileExists(_name, path) {
318
657
  const candidateExts = [".ts", ".tsx", ".js", ".jsx"];
319
658
  for await (const ext of candidateExts) {
320
- const fullPath = `${path2}${ext}`;
659
+ const fullPath = `${path}${ext}`;
321
660
  try {
322
661
  await fs2.access(fullPath);
323
662
  return fullPath.replace(/^\./, "");
324
663
  } catch {
325
- continue;
326
664
  }
327
665
  }
328
666
  }
@@ -334,30 +672,37 @@ async function loadCustomConfig() {
334
672
  }
335
673
  var src_default = async (options) => {
336
674
  let isProd = false;
675
+ const showStartupPage = options.startupPage;
337
676
  const deckConfig = {
338
677
  decks: []
339
678
  };
340
679
  return {
341
680
  name: "react-deck",
342
- async config(config) {
343
- var _a, _b, _c;
681
+ async config(config, env) {
682
+ var _a, _b;
344
683
  const decks = await glob.glob("./src/**/deck.mdx");
345
- const inputs = ((_b = (_a = config.build) == null ? void 0 : _a.rollupOptions) == null ? void 0 : _b.input) || {};
346
- deckConfig.decks = decks.map((deck) => ({
347
- originalFile: deck,
348
- index: deck.replace("/deck.mdx", "/index.html").replace("src/", "")
349
- }));
350
- const newInputs = decks.reduce((acc, deck) => {
684
+ const inputs = ((_b = (_a = config.build) == null ? void 0 : _a.rolldownOptions) == null ? void 0 : _b.input) || {};
685
+ deckConfig.decks = decks.map((deck) => {
686
+ const name = deck.replace("./src/", "").replace("/deck.mdx", "").replace(/\//g, " / ");
687
+ return {
688
+ originalFile: deck,
689
+ index: deck.replace("/deck.mdx", "/index.html").replace("src/", ""),
690
+ name
691
+ };
692
+ });
693
+ const isProduction = env.mode === "production";
694
+ const startupPageEnabled = showStartupPage !== void 0 ? showStartupPage : !isProduction;
695
+ const newInputs = decks.map((deck) => {
351
696
  const deckPath = deck.replace("/deck.mdx", "");
352
- return [...acc, `${deckPath.replace("src/", "")}/index.html`];
353
- }, []);
697
+ return `${deckPath.replace("src/", "")}/index.html`;
698
+ });
699
+ if (startupPageEnabled) {
700
+ newInputs.unshift("index.html");
701
+ }
354
702
  const finalInputs = typeof inputs === "string" ? [inputs, ...decks] : Array.isArray(inputs) ? [...inputs, ...decks] : { ...inputs, ...newInputs };
355
703
  return {
356
- ...config,
357
704
  build: {
358
- ...config.build,
359
- rollupOptions: {
360
- ...(_c = config.build) == null ? void 0 : _c.rollupOptions,
705
+ rolldownOptions: {
361
706
  input: finalInputs
362
707
  }
363
708
  }
@@ -367,6 +712,9 @@ var src_default = async (options) => {
367
712
  isProd = config.isProduction;
368
713
  },
369
714
  resolveId(id) {
715
+ if (id === "index.html" || id === "__decks.tsx" || id === "/__decks.tsx") {
716
+ return id.replace(/^\//, "");
717
+ }
370
718
  if (deckConfig.decks.some((deck) => deck.index === id)) {
371
719
  return id;
372
720
  }
@@ -378,6 +726,27 @@ var src_default = async (options) => {
378
726
  }
379
727
  },
380
728
  async load(id) {
729
+ const shouldShowStartupPage = showStartupPage !== void 0 ? showStartupPage : !isProd;
730
+ if (id === "index.html" && shouldShowStartupPage) {
731
+ return createDecksIndexFile();
732
+ }
733
+ if (id === "__decks.tsx") {
734
+ const decks = await Promise.all(
735
+ deckConfig.decks.map(async (d) => {
736
+ const meta = await extractDeckMeta(d.originalFile);
737
+ return {
738
+ name: d.name,
739
+ path: `/${d.index.replace("/index.html", "/")}`,
740
+ title: meta.title,
741
+ author: meta.author,
742
+ date: meta.date,
743
+ description: meta.description,
744
+ slideCount: meta.slideCount
745
+ };
746
+ })
747
+ );
748
+ return createDecksPageFile({ decks, theme: options.theme });
749
+ }
381
750
  const config = await loadCustomConfig();
382
751
  const deck = deckConfig.decks.find((deck2) => deck2.index === id);
383
752
  if (deck) {
@@ -387,18 +756,27 @@ var src_default = async (options) => {
387
756
  }
388
757
  if (id.endsWith("__deck.tsx")) {
389
758
  const directory = id.replace("/__deck.tsx", "");
390
- const dir2 = directory.startsWith(".") ? directory : `./${directory}`;
391
- const path2 = `${dir2}/deck.mdx`;
392
- if (!await checkIfDirectoryExists(path2)) {
393
- console.warn(`No deck.mdx file found in ${path2}`);
759
+ const dir = directory.startsWith(".") ? directory : `./${directory}`;
760
+ const deckMdxPath = `${dir}/deck.mdx`;
761
+ if (!await checkIfDirectoryExists(deckMdxPath)) {
762
+ this.warn(`No deck.mdx file found in ${deckMdxPath}`);
394
763
  return;
395
764
  }
765
+ let deckTheme;
766
+ try {
767
+ const raw = await fs2.readFile(deckMdxPath, "utf-8");
768
+ const { data: data2 } = (0, import_gray_matter2.default)(raw);
769
+ if (typeof data2.theme === "string") {
770
+ deckTheme = data2.theme;
771
+ }
772
+ } catch {
773
+ }
396
774
  const contentIndex = createAppDeckFile({
397
775
  slidePath: `${directory}/deck.mdx`,
398
776
  theme: options.theme,
399
- config
777
+ deckTheme,
778
+ config: { ...config, transition: options.transition }
400
779
  });
401
- console.log({ contentIndex });
402
780
  return contentIndex;
403
781
  }
404
782
  if (!id.endsWith("deck.mdx")) {
@@ -409,32 +787,76 @@ var src_default = async (options) => {
409
787
  production: isProd,
410
788
  ...options
411
789
  });
412
- const dir = import_node_path.default.relative(process.cwd(), id);
413
790
  return data;
414
791
  },
415
792
  transformIndexHtml: {
416
793
  order: "pre",
417
- enforce: "pre",
418
- transform: async (html, ctx) => {
794
+ handler: async (html, ctx) => {
419
795
  var _a;
420
796
  const originalUrl = ((_a = ctx.originalUrl) == null ? void 0 : _a.split("?")[0]) || "";
797
+ if (originalUrl === "/" || originalUrl === "") {
798
+ const shouldShow = showStartupPage !== void 0 ? showStartupPage : !isProd;
799
+ if (shouldShow) {
800
+ return html.replace("__SCRIPT__", `__decks.tsx`);
801
+ }
802
+ }
421
803
  const deckDir = ctx.path.replace("/index.html", "");
422
804
  const dir = originalUrl ? `./src${originalUrl}` : `.${deckDir}`;
423
- const path2 = `${dir}/deck.mdx`;
424
- if (!await checkIfDirectoryExists(path2)) {
425
- return html.replace("__SCRIPT__", `./src/main.tsx`);
805
+ const deckPath = `${dir}/deck.mdx`;
806
+ if (await checkIfDirectoryExists(deckPath)) {
807
+ const resolvedDeckPath = dir.startsWith(".") ? dir : `.${dir}`;
808
+ return html.replace("__SCRIPT__", `${resolvedDeckPath}/__deck.tsx`);
426
809
  }
427
- const deckPath = dir.startsWith(".") ? dir : `.${dir}`;
428
- return html.replace("__SCRIPT__", `${deckPath}/__deck.tsx`);
810
+ const decks = await glob.glob("./src/**/deck.mdx");
811
+ const deckLinks = decks.map((d) => {
812
+ const url = `/${d.replace("src/", "").replace("/deck.mdx", "")}/`;
813
+ const name = d.replace("src/", "").replace("/deck.mdx", "");
814
+ return `<li><a href="${url}">${name}</a></li>`;
815
+ }).join("\n");
816
+ return html.replace('<script type="module" src="__SCRIPT__"></script>', "").replace(
817
+ '<div id="root"></div>',
818
+ `<div id="root">
819
+ <h1>Available Decks</h1>
820
+ <ul>${deckLinks}</ul>
821
+ </div>`
822
+ );
429
823
  }
430
824
  },
431
825
  configureServer(server) {
432
826
  var _a;
827
+ const shouldShow = showStartupPage !== void 0 ? showStartupPage : true;
828
+ if (shouldShow) {
829
+ server.middlewares.use(async (req, res, next) => {
830
+ var _a2;
831
+ const url = ((_a2 = req.url) == null ? void 0 : _a2.split("?")[0]) || "";
832
+ if (url === "/" || url === "/index.html") {
833
+ const html = createDecksIndexFile();
834
+ const transformed = await server.transformIndexHtml(
835
+ url,
836
+ html,
837
+ req.originalUrl
838
+ );
839
+ res.setHeader("Content-Type", "text/html");
840
+ res.statusCode = 200;
841
+ res.end(transformed);
842
+ return;
843
+ }
844
+ next();
845
+ });
846
+ }
433
847
  (_a = server.httpServer) == null ? void 0 : _a.once("listening", async () => {
434
848
  const port = server.config.server.port || 5173;
435
- const decks = await findDecks({ port });
436
- for (const deck of decks) {
437
- console.log(`Deck available at ${deck.deckUrl}`);
849
+ if (shouldShow) {
850
+ server.config.logger.info(
851
+ `
852
+ Decks available at http://localhost:${port}/
853
+ `
854
+ );
855
+ } else {
856
+ const decks = await findDecks({ port });
857
+ for (const deck of decks) {
858
+ server.config.logger.info(`Deck available at ${deck.deckUrl}`);
859
+ }
438
860
  }
439
861
  });
440
862
  }