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