vite-plugin-react-deck 1.1.5 → 1.4.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/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} />
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;
@@ -111,12 +511,12 @@ ${slide}
111
511
  frontmatterForNextSlide = null;
112
512
  }
113
513
  const compiledSlides = await Promise.all(
114
- enrichedSlides.map(async (slide, index) => {
514
+ enrichedSlides.map(async (slide, _index) => {
115
515
  const code = addInlineModules(slide.content, inlineModules);
116
516
  const normalizedCode = code.replace("process.env", "process\u200B.env");
117
517
  const result = await (0, import_mdx.compile)(normalizedCode, {
118
518
  outputFormat: "program",
119
- jsx: !isProd,
519
+ jsx: false,
120
520
  providerImportSource: "@mdx-js/react",
121
521
  ...options,
122
522
  remarkPlugins: [
@@ -127,7 +527,7 @@ ${slide}
127
527
  ]
128
528
  });
129
529
  const mainCode = extractMainCodeAsChildren(result.value.toString(), {
130
- isJsx: !isProd
530
+ isJsx: false
131
531
  });
132
532
  return {
133
533
  ...slide,
@@ -138,7 +538,8 @@ ${slide}
138
538
  const output = addInlineModules(
139
539
  `
140
540
  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';"}
541
+ import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
542
+ import {useMDXComponents as _provideComponents} from "@mdx-js/react";
142
543
 
143
544
  ${compiledSlides.map(
144
545
  (slide, index) => `
@@ -163,7 +564,7 @@ Deck.slides = [
163
564
  `,
164
565
  inlineModules
165
566
  );
166
- import_fs.default.writeFileSync("slides.js", output);
567
+ import_node_fs.default.writeFileSync("slides.js", output);
167
568
  return output;
168
569
  }
169
570
  var MOD_REG = /\\`|`(?:\\`|[^`])*`|(^(?:import|export).*$)/gm;
@@ -191,117 +592,20 @@ function normalizeNewline(input) {
191
592
  return input.replace(new RegExp(CRLF, "g"), "\n");
192
593
  }
193
594
 
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
595
  // src/config.ts
293
596
  var import_lodash = __toESM(require("lodash"), 1);
294
597
 
295
598
  // src/types.ts
296
599
  var import_zod = require("zod");
297
600
  var PestacleConfigSchema = import_zod.z.object({
298
- theme: import_zod.z.enum(["green", "purple"]).default("green")
601
+ theme: import_zod.z.enum(["green", "purple", "solarized-light"]).default("green"),
602
+ startupPage: import_zod.z.boolean().optional()
299
603
  });
300
604
 
301
605
  // src/index.ts
302
- async function checkIfDirectoryExists(path2) {
606
+ async function checkIfDirectoryExists(path) {
303
607
  try {
304
- await fs2.access(path2);
608
+ await fs2.access(path);
305
609
  return true;
306
610
  } catch {
307
611
  return false;
@@ -314,15 +618,45 @@ async function findDecks({ port }) {
314
618
  deckUrl: `http://localhost:${port}/${deck.replace("src/", "").replace("deck.mdx", "")}`
315
619
  }));
316
620
  }
317
- async function fileExists(name, path2) {
621
+ async function extractDeckMeta(filePath) {
622
+ try {
623
+ const raw = await fs2.readFile(filePath, "utf-8");
624
+ const { data, content } = (0, import_gray_matter2.default)(raw);
625
+ const slides = content.split("---\n");
626
+ const LAYOUT_REGEX = /\S*\nlayout: (.*)/g;
627
+ let slideCount = 0;
628
+ for (const slide of slides) {
629
+ if (!LAYOUT_REGEX.test(slide) && slide.trim().length > 0) {
630
+ slideCount++;
631
+ }
632
+ LAYOUT_REGEX.lastIndex = 0;
633
+ }
634
+ let title = data.title;
635
+ if (!title) {
636
+ const headingMatch = content.match(/^#\s+\*{0,2}(.+?)\*{0,2}\s*$/m);
637
+ if (headingMatch) {
638
+ title = headingMatch[1].replace(/\*+/g, "").trim();
639
+ }
640
+ }
641
+ return {
642
+ title,
643
+ author: data.author,
644
+ date: data.date,
645
+ description: data.description,
646
+ slideCount
647
+ };
648
+ } catch {
649
+ return { slideCount: 0 };
650
+ }
651
+ }
652
+ async function fileExists(_name, path) {
318
653
  const candidateExts = [".ts", ".tsx", ".js", ".jsx"];
319
654
  for await (const ext of candidateExts) {
320
- const fullPath = `${path2}${ext}`;
655
+ const fullPath = `${path}${ext}`;
321
656
  try {
322
657
  await fs2.access(fullPath);
323
658
  return fullPath.replace(/^\./, "");
324
659
  } catch {
325
- continue;
326
660
  }
327
661
  }
328
662
  }
@@ -334,30 +668,37 @@ async function loadCustomConfig() {
334
668
  }
335
669
  var src_default = async (options) => {
336
670
  let isProd = false;
671
+ const showStartupPage = options.startupPage;
337
672
  const deckConfig = {
338
673
  decks: []
339
674
  };
340
675
  return {
341
676
  name: "react-deck",
342
- async config(config) {
343
- var _a, _b, _c;
677
+ async config(config, env) {
678
+ var _a, _b;
344
679
  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) => {
680
+ const inputs = ((_b = (_a = config.build) == null ? void 0 : _a.rolldownOptions) == null ? void 0 : _b.input) || {};
681
+ deckConfig.decks = decks.map((deck) => {
682
+ const name = deck.replace("./src/", "").replace("/deck.mdx", "").replace(/\//g, " / ");
683
+ return {
684
+ originalFile: deck,
685
+ index: deck.replace("/deck.mdx", "/index.html").replace("src/", ""),
686
+ name
687
+ };
688
+ });
689
+ const isProduction = env.mode === "production";
690
+ const startupPageEnabled = showStartupPage !== void 0 ? showStartupPage : !isProduction;
691
+ const newInputs = decks.map((deck) => {
351
692
  const deckPath = deck.replace("/deck.mdx", "");
352
- return [...acc, `${deckPath.replace("src/", "")}/index.html`];
353
- }, []);
693
+ return `${deckPath.replace("src/", "")}/index.html`;
694
+ });
695
+ if (startupPageEnabled) {
696
+ newInputs.unshift("index.html");
697
+ }
354
698
  const finalInputs = typeof inputs === "string" ? [inputs, ...decks] : Array.isArray(inputs) ? [...inputs, ...decks] : { ...inputs, ...newInputs };
355
699
  return {
356
- ...config,
357
700
  build: {
358
- ...config.build,
359
- rollupOptions: {
360
- ...(_c = config.build) == null ? void 0 : _c.rollupOptions,
701
+ rolldownOptions: {
361
702
  input: finalInputs
362
703
  }
363
704
  }
@@ -367,6 +708,9 @@ var src_default = async (options) => {
367
708
  isProd = config.isProduction;
368
709
  },
369
710
  resolveId(id) {
711
+ if (id === "index.html" || id === "__decks.tsx" || id === "/__decks.tsx") {
712
+ return id.replace(/^\//, "");
713
+ }
370
714
  if (deckConfig.decks.some((deck) => deck.index === id)) {
371
715
  return id;
372
716
  }
@@ -378,6 +722,27 @@ var src_default = async (options) => {
378
722
  }
379
723
  },
380
724
  async load(id) {
725
+ const shouldShowStartupPage = showStartupPage !== void 0 ? showStartupPage : !isProd;
726
+ if (id === "index.html" && shouldShowStartupPage) {
727
+ return createDecksIndexFile();
728
+ }
729
+ if (id === "__decks.tsx") {
730
+ const decks = await Promise.all(
731
+ deckConfig.decks.map(async (d) => {
732
+ const meta = await extractDeckMeta(d.originalFile);
733
+ return {
734
+ name: d.name,
735
+ path: `/${d.index.replace("/index.html", "/")}`,
736
+ title: meta.title,
737
+ author: meta.author,
738
+ date: meta.date,
739
+ description: meta.description,
740
+ slideCount: meta.slideCount
741
+ };
742
+ })
743
+ );
744
+ return createDecksPageFile({ decks, theme: options.theme });
745
+ }
381
746
  const config = await loadCustomConfig();
382
747
  const deck = deckConfig.decks.find((deck2) => deck2.index === id);
383
748
  if (deck) {
@@ -387,18 +752,27 @@ var src_default = async (options) => {
387
752
  }
388
753
  if (id.endsWith("__deck.tsx")) {
389
754
  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}`);
755
+ const dir = directory.startsWith(".") ? directory : `./${directory}`;
756
+ const deckMdxPath = `${dir}/deck.mdx`;
757
+ if (!await checkIfDirectoryExists(deckMdxPath)) {
758
+ this.warn(`No deck.mdx file found in ${deckMdxPath}`);
394
759
  return;
395
760
  }
761
+ let deckTheme;
762
+ try {
763
+ const raw = await fs2.readFile(deckMdxPath, "utf-8");
764
+ const { data: data2 } = (0, import_gray_matter2.default)(raw);
765
+ if (typeof data2.theme === "string") {
766
+ deckTheme = data2.theme;
767
+ }
768
+ } catch {
769
+ }
396
770
  const contentIndex = createAppDeckFile({
397
771
  slidePath: `${directory}/deck.mdx`,
398
772
  theme: options.theme,
773
+ deckTheme,
399
774
  config
400
775
  });
401
- console.log({ contentIndex });
402
776
  return contentIndex;
403
777
  }
404
778
  if (!id.endsWith("deck.mdx")) {
@@ -409,32 +783,76 @@ var src_default = async (options) => {
409
783
  production: isProd,
410
784
  ...options
411
785
  });
412
- const dir = import_node_path.default.relative(process.cwd(), id);
413
786
  return data;
414
787
  },
415
788
  transformIndexHtml: {
416
789
  order: "pre",
417
- enforce: "pre",
418
- transform: async (html, ctx) => {
790
+ handler: async (html, ctx) => {
419
791
  var _a;
420
792
  const originalUrl = ((_a = ctx.originalUrl) == null ? void 0 : _a.split("?")[0]) || "";
793
+ if (originalUrl === "/" || originalUrl === "") {
794
+ const shouldShow = showStartupPage !== void 0 ? showStartupPage : !isProd;
795
+ if (shouldShow) {
796
+ return html.replace("__SCRIPT__", `__decks.tsx`);
797
+ }
798
+ }
421
799
  const deckDir = ctx.path.replace("/index.html", "");
422
800
  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`);
801
+ const deckPath = `${dir}/deck.mdx`;
802
+ if (await checkIfDirectoryExists(deckPath)) {
803
+ const resolvedDeckPath = dir.startsWith(".") ? dir : `.${dir}`;
804
+ return html.replace("__SCRIPT__", `${resolvedDeckPath}/__deck.tsx`);
426
805
  }
427
- const deckPath = dir.startsWith(".") ? dir : `.${dir}`;
428
- return html.replace("__SCRIPT__", `${deckPath}/__deck.tsx`);
806
+ const decks = await glob.glob("./src/**/deck.mdx");
807
+ const deckLinks = decks.map((d) => {
808
+ const url = `/${d.replace("src/", "").replace("/deck.mdx", "")}/`;
809
+ const name = d.replace("src/", "").replace("/deck.mdx", "");
810
+ return `<li><a href="${url}">${name}</a></li>`;
811
+ }).join("\n");
812
+ return html.replace('<script type="module" src="__SCRIPT__"></script>', "").replace(
813
+ '<div id="root"></div>',
814
+ `<div id="root">
815
+ <h1>Available Decks</h1>
816
+ <ul>${deckLinks}</ul>
817
+ </div>`
818
+ );
429
819
  }
430
820
  },
431
821
  configureServer(server) {
432
822
  var _a;
823
+ const shouldShow = showStartupPage !== void 0 ? showStartupPage : true;
824
+ if (shouldShow) {
825
+ server.middlewares.use(async (req, res, next) => {
826
+ var _a2;
827
+ const url = ((_a2 = req.url) == null ? void 0 : _a2.split("?")[0]) || "";
828
+ if (url === "/" || url === "/index.html") {
829
+ const html = createDecksIndexFile();
830
+ const transformed = await server.transformIndexHtml(
831
+ url,
832
+ html,
833
+ req.originalUrl
834
+ );
835
+ res.setHeader("Content-Type", "text/html");
836
+ res.statusCode = 200;
837
+ res.end(transformed);
838
+ return;
839
+ }
840
+ next();
841
+ });
842
+ }
433
843
  (_a = server.httpServer) == null ? void 0 : _a.once("listening", async () => {
434
844
  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}`);
845
+ if (shouldShow) {
846
+ server.config.logger.info(
847
+ `
848
+ Decks available at http://localhost:${port}/
849
+ `
850
+ );
851
+ } else {
852
+ const decks = await findDecks({ port });
853
+ for (const deck of decks) {
854
+ server.config.logger.info(`Deck available at ${deck.deckUrl}`);
855
+ }
438
856
  }
439
857
  });
440
858
  }