@pep/term-deck 1.0.15 → 1.0.17
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/dist/bin/term-deck.js +827 -630
- package/dist/bin/term-deck.js.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/term-deck.js
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import 'url';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import 'yaml';
|
|
6
|
-
import 'deepmerge';
|
|
7
|
-
import gradient2 from 'gradient-string';
|
|
8
5
|
import matter from 'gray-matter';
|
|
6
|
+
import { mkdir, writeFile, rm, access, readFile, unlink } from 'fs/promises';
|
|
9
7
|
import fg from 'fast-glob';
|
|
10
|
-
import { mkdir, writeFile, access, readFile, unlink } from 'fs/promises';
|
|
11
|
-
import { mermaidToAscii as mermaidToAscii$1 } from 'mermaid-ascii';
|
|
12
8
|
import blessed from 'neo-blessed';
|
|
9
|
+
import gradient2 from 'gradient-string';
|
|
10
|
+
import 'yaml';
|
|
11
|
+
import 'deepmerge';
|
|
12
|
+
import { mermaidToAscii as mermaidToAscii$1 } from 'mermaid-ascii';
|
|
13
13
|
import figlet from 'figlet';
|
|
14
14
|
import { Command } from 'commander';
|
|
15
|
+
import { tmpdir } from 'os';
|
|
15
16
|
import { execa } from 'execa';
|
|
16
17
|
|
|
17
18
|
var __defProp = Object.defineProperty;
|
|
@@ -27,52 +28,6 @@ var init_esm_shims = __esm({
|
|
|
27
28
|
"node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/esm_shims.js"() {
|
|
28
29
|
}
|
|
29
30
|
});
|
|
30
|
-
var SlideFrontmatterSchema, SlideSchema;
|
|
31
|
-
var init_slide = __esm({
|
|
32
|
-
"src/schemas/slide.ts"() {
|
|
33
|
-
init_esm_shims();
|
|
34
|
-
SlideFrontmatterSchema = z.object({
|
|
35
|
-
// Required: window title
|
|
36
|
-
title: z.string().min(1, {
|
|
37
|
-
message: "Slide must have a title"
|
|
38
|
-
}),
|
|
39
|
-
// ASCII art text (figlet) - can be a single line or multiple lines
|
|
40
|
-
bigText: z.union([
|
|
41
|
-
z.string(),
|
|
42
|
-
z.array(z.string())
|
|
43
|
-
]).optional(),
|
|
44
|
-
// Which gradient to use for bigText
|
|
45
|
-
gradient: z.string().optional(),
|
|
46
|
-
// Override theme for this slide
|
|
47
|
-
theme: z.string().optional(),
|
|
48
|
-
// Transition effect
|
|
49
|
-
transition: z.enum([
|
|
50
|
-
"glitch",
|
|
51
|
-
// Default: glitch reveal line by line
|
|
52
|
-
"fade",
|
|
53
|
-
// Fade in
|
|
54
|
-
"instant",
|
|
55
|
-
// No animation
|
|
56
|
-
"typewriter"
|
|
57
|
-
// Character by character
|
|
58
|
-
]).default("glitch"),
|
|
59
|
-
// Custom metadata (ignored by renderer, useful for tooling)
|
|
60
|
-
meta: z.record(z.string(), z.unknown()).optional()
|
|
61
|
-
});
|
|
62
|
-
SlideSchema = z.object({
|
|
63
|
-
// Parsed frontmatter
|
|
64
|
-
frontmatter: SlideFrontmatterSchema,
|
|
65
|
-
// Markdown body content
|
|
66
|
-
body: z.string(),
|
|
67
|
-
// Presenter notes (extracted from <!-- notes --> block)
|
|
68
|
-
notes: z.string().optional(),
|
|
69
|
-
// Source file path
|
|
70
|
-
sourcePath: z.string(),
|
|
71
|
-
// Slide index in deck (0-indexed)
|
|
72
|
-
index: z.number()
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
31
|
var HexColorSchema, GradientSchema, ThemeSchema, DEFAULT_THEME;
|
|
77
32
|
var init_theme = __esm({
|
|
78
33
|
"src/schemas/theme.ts"() {
|
|
@@ -244,144 +199,52 @@ var init_validation = __esm({
|
|
|
244
199
|
};
|
|
245
200
|
}
|
|
246
201
|
});
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
return theme.colors.primary;
|
|
251
|
-
case "SECONDARY":
|
|
252
|
-
return theme.colors.secondary ?? theme.colors.primary;
|
|
253
|
-
case "ACCENT":
|
|
254
|
-
return theme.colors.accent;
|
|
255
|
-
case "MUTED":
|
|
256
|
-
return theme.colors.muted;
|
|
257
|
-
case "TEXT":
|
|
258
|
-
return theme.colors.text;
|
|
259
|
-
case "BACKGROUND":
|
|
260
|
-
return theme.colors.background;
|
|
261
|
-
}
|
|
262
|
-
return BUILTIN_COLORS[token] ?? theme.colors.text;
|
|
263
|
-
}
|
|
264
|
-
function colorTokensToBlessedTags(content, theme) {
|
|
265
|
-
return content.replace(
|
|
266
|
-
/\{(GREEN|ORANGE|CYAN|PINK|WHITE|GRAY|PRIMARY|SECONDARY|ACCENT|MUTED|TEXT|BACKGROUND|\/)\}/g,
|
|
267
|
-
(_, token) => {
|
|
268
|
-
if (token === "/") {
|
|
269
|
-
return "{/}";
|
|
270
|
-
}
|
|
271
|
-
const color = resolveColorToken(token, theme);
|
|
272
|
-
return `{${color}-fg}`;
|
|
273
|
-
}
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
var ThemeError, BUILTIN_COLORS;
|
|
277
|
-
var init_theme2 = __esm({
|
|
278
|
-
"src/core/theme.ts"() {
|
|
202
|
+
var SlideFrontmatterSchema, SlideSchema;
|
|
203
|
+
var init_slide = __esm({
|
|
204
|
+
"src/schemas/slide.ts"() {
|
|
279
205
|
init_esm_shims();
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
206
|
+
SlideFrontmatterSchema = z.object({
|
|
207
|
+
// Required: window title
|
|
208
|
+
title: z.string().min(1, {
|
|
209
|
+
message: "Slide must have a title"
|
|
210
|
+
}),
|
|
211
|
+
// ASCII art text (figlet) - can be a single line or multiple lines
|
|
212
|
+
bigText: z.union([
|
|
213
|
+
z.string(),
|
|
214
|
+
z.array(z.string())
|
|
215
|
+
]).optional(),
|
|
216
|
+
// Which gradient to use for bigText
|
|
217
|
+
gradient: z.string().optional(),
|
|
218
|
+
// Override theme for this slide
|
|
219
|
+
theme: z.string().optional(),
|
|
220
|
+
// Transition effect
|
|
221
|
+
transition: z.enum([
|
|
222
|
+
"glitch",
|
|
223
|
+
// Default: glitch reveal line by line
|
|
224
|
+
"fade",
|
|
225
|
+
// Fade in
|
|
226
|
+
"instant",
|
|
227
|
+
// No animation
|
|
228
|
+
"typewriter"
|
|
229
|
+
// Character by character
|
|
230
|
+
]).default("glitch"),
|
|
231
|
+
// Custom metadata (ignored by renderer, useful for tooling)
|
|
232
|
+
meta: z.record(z.string(), z.unknown()).optional()
|
|
233
|
+
});
|
|
234
|
+
SlideSchema = z.object({
|
|
235
|
+
// Parsed frontmatter
|
|
236
|
+
frontmatter: SlideFrontmatterSchema,
|
|
237
|
+
// Markdown body content
|
|
238
|
+
body: z.string(),
|
|
239
|
+
// Presenter notes (extracted from <!-- notes --> block)
|
|
240
|
+
notes: z.string().optional(),
|
|
241
|
+
// Source file path
|
|
242
|
+
sourcePath: z.string(),
|
|
243
|
+
// Slide index in deck (0-indexed)
|
|
244
|
+
index: z.number()
|
|
245
|
+
});
|
|
303
246
|
}
|
|
304
247
|
});
|
|
305
|
-
|
|
306
|
-
// src/core/slide.ts
|
|
307
|
-
var slide_exports = {};
|
|
308
|
-
__export(slide_exports, {
|
|
309
|
-
DeckLoadError: () => DeckLoadError,
|
|
310
|
-
SlideParseError: () => SlideParseError,
|
|
311
|
-
extractMermaidBlocks: () => extractMermaidBlocks,
|
|
312
|
-
extractNotes: () => extractNotes,
|
|
313
|
-
findSlideFiles: () => findSlideFiles,
|
|
314
|
-
formatMermaidError: () => formatMermaidError,
|
|
315
|
-
formatSlideError: () => formatSlideError,
|
|
316
|
-
hasMermaidDiagrams: () => hasMermaidDiagrams,
|
|
317
|
-
loadDeck: () => loadDeck,
|
|
318
|
-
loadDeckConfig: () => loadDeckConfig,
|
|
319
|
-
mermaidToAscii: () => mermaidToAscii,
|
|
320
|
-
normalizeBigText: () => normalizeBigText,
|
|
321
|
-
parseSlide: () => parseSlide,
|
|
322
|
-
processMermaidDiagrams: () => processMermaidDiagrams,
|
|
323
|
-
processSlideContent: () => processSlideContent
|
|
324
|
-
});
|
|
325
|
-
function hasMermaidDiagrams(content) {
|
|
326
|
-
MERMAID_BLOCK_PATTERN.lastIndex = 0;
|
|
327
|
-
return MERMAID_BLOCK_PATTERN.test(content);
|
|
328
|
-
}
|
|
329
|
-
function extractMermaidBlocks(content) {
|
|
330
|
-
const blocks = [];
|
|
331
|
-
let match;
|
|
332
|
-
MERMAID_BLOCK_PATTERN.lastIndex = 0;
|
|
333
|
-
while ((match = MERMAID_BLOCK_PATTERN.exec(content)) !== null) {
|
|
334
|
-
blocks.push(match[1].trim());
|
|
335
|
-
}
|
|
336
|
-
return blocks;
|
|
337
|
-
}
|
|
338
|
-
function formatMermaidError(code, _error) {
|
|
339
|
-
const lines = [
|
|
340
|
-
"\u250C\u2500 Diagram (parse error) \u2500\u2510",
|
|
341
|
-
"\u2502 \u2502"
|
|
342
|
-
];
|
|
343
|
-
const codeLines = code.split("\n").slice(0, 5);
|
|
344
|
-
for (const line of codeLines) {
|
|
345
|
-
const truncated = line.slice(0, 23).padEnd(23);
|
|
346
|
-
lines.push(`\u2502 ${truncated} \u2502`);
|
|
347
|
-
}
|
|
348
|
-
if (code.split("\n").length > 5) {
|
|
349
|
-
lines.push("\u2502 ... \u2502");
|
|
350
|
-
}
|
|
351
|
-
lines.push("\u2502 \u2502");
|
|
352
|
-
lines.push("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
353
|
-
return lines.join("\n");
|
|
354
|
-
}
|
|
355
|
-
function mermaidToAscii(mermaidCode) {
|
|
356
|
-
try {
|
|
357
|
-
return mermaidToAscii$1(mermaidCode);
|
|
358
|
-
} catch (error) {
|
|
359
|
-
return formatMermaidError(mermaidCode);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
function escapeRegex(str) {
|
|
363
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
364
|
-
}
|
|
365
|
-
function processMermaidDiagrams(content) {
|
|
366
|
-
if (!hasMermaidDiagrams(content)) {
|
|
367
|
-
return content;
|
|
368
|
-
}
|
|
369
|
-
let result = content;
|
|
370
|
-
const blocks = extractMermaidBlocks(content);
|
|
371
|
-
for (const block of blocks) {
|
|
372
|
-
const ascii = mermaidToAscii(block);
|
|
373
|
-
result = result.replace(
|
|
374
|
-
new RegExp("```mermaid\\n" + escapeRegex(block) + "\\n?```", "g"),
|
|
375
|
-
ascii
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
return result;
|
|
379
|
-
}
|
|
380
|
-
async function processSlideContent(body, theme) {
|
|
381
|
-
let processed = processMermaidDiagrams(body);
|
|
382
|
-
processed = colorTokensToBlessedTags(processed, theme);
|
|
383
|
-
return processed;
|
|
384
|
-
}
|
|
385
248
|
function extractNotes(content) {
|
|
386
249
|
const notesStart = content.indexOf(NOTES_MARKER);
|
|
387
250
|
if (notesStart === -1) {
|
|
@@ -415,6 +278,29 @@ async function parseSlide(filePath, index) {
|
|
|
415
278
|
};
|
|
416
279
|
return safeParse(SlideSchema, slide, `slide ${filePath}`);
|
|
417
280
|
}
|
|
281
|
+
var NOTES_MARKER, NOTES_END_MARKER, SlideParseError;
|
|
282
|
+
var init_slide2 = __esm({
|
|
283
|
+
"src/core/slide.ts"() {
|
|
284
|
+
init_esm_shims();
|
|
285
|
+
init_slide();
|
|
286
|
+
init_validation();
|
|
287
|
+
NOTES_MARKER = "<!-- notes -->";
|
|
288
|
+
NOTES_END_MARKER = "<!-- /notes -->";
|
|
289
|
+
SlideParseError = class extends Error {
|
|
290
|
+
/**
|
|
291
|
+
* @param message - The error message describing what went wrong
|
|
292
|
+
* @param filePath - Path to the slide file that failed to parse
|
|
293
|
+
* @param cause - Optional underlying error that caused this failure
|
|
294
|
+
*/
|
|
295
|
+
constructor(message, filePath, cause) {
|
|
296
|
+
super(message);
|
|
297
|
+
this.filePath = filePath;
|
|
298
|
+
this.cause = cause;
|
|
299
|
+
this.name = "SlideParseError";
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
});
|
|
418
304
|
async function findSlideFiles(dir) {
|
|
419
305
|
const pattern = join(dir, "*.md");
|
|
420
306
|
const foundFiles = await fg(pattern, { onlyFiles: true });
|
|
@@ -447,7 +333,9 @@ async function loadDeckConfig(slidesDir) {
|
|
|
447
333
|
theme: DEFAULT_THEME
|
|
448
334
|
};
|
|
449
335
|
}
|
|
450
|
-
const
|
|
336
|
+
const isTest = process.env.NODE_ENV === "test" || process.env.VITEST === "true";
|
|
337
|
+
const cacheBuster = isTest ? `?t=${Date.now()}-${Math.random()}` : "";
|
|
338
|
+
const configModule = await import(configPath + cacheBuster);
|
|
451
339
|
if (!configModule.default) {
|
|
452
340
|
throw new Error("deck.config.ts must export default config");
|
|
453
341
|
}
|
|
@@ -471,50 +359,18 @@ async function loadDeck(slidesDir) {
|
|
|
471
359
|
basePath: slidesDir
|
|
472
360
|
};
|
|
473
361
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
function formatSlideError(error) {
|
|
479
|
-
let msg = `Error parsing slide: ${error.filePath}
|
|
480
|
-
`;
|
|
481
|
-
msg += ` ${error.message}
|
|
482
|
-
`;
|
|
483
|
-
if (error.cause) {
|
|
484
|
-
msg += ` Caused by: ${error.cause.message}
|
|
485
|
-
`;
|
|
486
|
-
}
|
|
487
|
-
return msg;
|
|
488
|
-
}
|
|
489
|
-
var NOTES_MARKER, NOTES_END_MARKER, MERMAID_BLOCK_PATTERN, SlideParseError, DeckLoadError;
|
|
490
|
-
var init_slide2 = __esm({
|
|
491
|
-
"src/core/slide.ts"() {
|
|
362
|
+
var DeckLoadError;
|
|
363
|
+
var init_deck_loader = __esm({
|
|
364
|
+
"src/core/deck-loader.ts"() {
|
|
492
365
|
init_esm_shims();
|
|
493
|
-
init_slide();
|
|
494
366
|
init_config();
|
|
495
367
|
init_validation();
|
|
496
368
|
init_theme();
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
NOTES_END_MARKER = "<!-- /notes -->";
|
|
500
|
-
MERMAID_BLOCK_PATTERN = /```mermaid\n([\s\S]*?)```/g;
|
|
501
|
-
SlideParseError = class extends Error {
|
|
369
|
+
init_slide2();
|
|
370
|
+
DeckLoadError = class extends Error {
|
|
502
371
|
/**
|
|
503
372
|
* @param message - The error message describing what went wrong
|
|
504
|
-
* @param
|
|
505
|
-
* @param cause - Optional underlying error that caused this failure
|
|
506
|
-
*/
|
|
507
|
-
constructor(message, filePath, cause) {
|
|
508
|
-
super(message);
|
|
509
|
-
this.filePath = filePath;
|
|
510
|
-
this.cause = cause;
|
|
511
|
-
this.name = "SlideParseError";
|
|
512
|
-
}
|
|
513
|
-
};
|
|
514
|
-
DeckLoadError = class extends Error {
|
|
515
|
-
/**
|
|
516
|
-
* @param message - The error message describing what went wrong
|
|
517
|
-
* @param slidesDir - Path to the directory that was being loaded
|
|
373
|
+
* @param slidesDir - Path to the directory that was being loaded
|
|
518
374
|
* @param cause - Optional underlying error that caused this failure
|
|
519
375
|
*/
|
|
520
376
|
constructor(message, slidesDir, cause) {
|
|
@@ -526,43 +382,14 @@ var init_slide2 = __esm({
|
|
|
526
382
|
};
|
|
527
383
|
}
|
|
528
384
|
});
|
|
529
|
-
|
|
530
|
-
// src/renderer/screen.ts
|
|
531
|
-
var screen_exports = {};
|
|
532
|
-
__export(screen_exports, {
|
|
533
|
-
applyTransition: () => applyTransition,
|
|
534
|
-
clearWindows: () => clearWindows,
|
|
535
|
-
createRenderer: () => createRenderer,
|
|
536
|
-
createScreen: () => createScreen,
|
|
537
|
-
createWindow: () => createWindow,
|
|
538
|
-
destroyRenderer: () => destroyRenderer,
|
|
539
|
-
generateBigText: () => generateBigText,
|
|
540
|
-
generateMultiLineBigText: () => generateMultiLineBigText,
|
|
541
|
-
getWindowColor: () => getWindowColor,
|
|
542
|
-
glitchLine: () => glitchLine,
|
|
543
|
-
initMatrixRain: () => initMatrixRain,
|
|
544
|
-
lineByLineReveal: () => lineByLineReveal,
|
|
545
|
-
renderMatrixRain: () => renderMatrixRain,
|
|
546
|
-
renderSlide: () => renderSlide
|
|
547
|
-
});
|
|
548
|
-
function createScreen(title = "term-deck") {
|
|
549
|
-
const screen = blessed.screen({
|
|
550
|
-
smartCSR: true,
|
|
551
|
-
title,
|
|
552
|
-
fullUnicode: true,
|
|
553
|
-
mouse: false,
|
|
554
|
-
altScreen: true
|
|
555
|
-
});
|
|
556
|
-
return screen;
|
|
557
|
-
}
|
|
558
385
|
function generateTrail(glyphs, length) {
|
|
559
386
|
return Array.from(
|
|
560
387
|
{ length },
|
|
561
388
|
() => glyphs[Math.floor(Math.random() * glyphs.length)]
|
|
562
389
|
);
|
|
563
390
|
}
|
|
564
|
-
function renderMatrixRain(
|
|
565
|
-
const {
|
|
391
|
+
function renderMatrixRain(screen, state) {
|
|
392
|
+
const { matrixBox, matrixDrops, theme } = state;
|
|
566
393
|
const width = Math.max(20, screen.width || 80);
|
|
567
394
|
const height = Math.max(10, screen.height || 24);
|
|
568
395
|
const grid = Array.from(
|
|
@@ -597,27 +424,32 @@ function renderMatrixRain(renderer) {
|
|
|
597
424
|
}
|
|
598
425
|
matrixBox.setContent(output);
|
|
599
426
|
}
|
|
600
|
-
function initMatrixRain(
|
|
601
|
-
const {
|
|
427
|
+
function initMatrixRain(screen, state) {
|
|
428
|
+
const { theme } = state;
|
|
602
429
|
const width = screen.width || 80;
|
|
603
430
|
const height = screen.height || 24;
|
|
604
431
|
const density = theme.animations.matrixDensity;
|
|
605
|
-
|
|
432
|
+
state.matrixDrops = [];
|
|
606
433
|
for (let i = 0; i < density; i++) {
|
|
607
|
-
|
|
434
|
+
state.matrixDrops.push({
|
|
608
435
|
x: Math.floor(Math.random() * width),
|
|
609
436
|
y: Math.floor(Math.random() * height),
|
|
610
437
|
speed: 0.3 + Math.random() * 0.7,
|
|
611
438
|
trail: generateTrail(theme.glyphs, 5 + Math.floor(Math.random() * 10))
|
|
612
439
|
});
|
|
613
440
|
}
|
|
614
|
-
|
|
615
|
-
renderMatrixRain(
|
|
616
|
-
|
|
441
|
+
state.matrixInterval = setInterval(() => {
|
|
442
|
+
renderMatrixRain(screen, state);
|
|
443
|
+
screen.render();
|
|
617
444
|
}, theme.animations.matrixInterval);
|
|
618
445
|
}
|
|
619
|
-
function
|
|
620
|
-
|
|
446
|
+
function stopMatrixRain(state) {
|
|
447
|
+
if (state.matrixInterval) {
|
|
448
|
+
clearInterval(state.matrixInterval);
|
|
449
|
+
state.matrixInterval = null;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function createMatrixBox(screen) {
|
|
621
453
|
const matrixBox = blessed.box({
|
|
622
454
|
top: 0,
|
|
623
455
|
left: 0,
|
|
@@ -626,28 +458,13 @@ function createRenderer(theme) {
|
|
|
626
458
|
tags: true
|
|
627
459
|
});
|
|
628
460
|
screen.append(matrixBox);
|
|
629
|
-
|
|
630
|
-
screen,
|
|
631
|
-
matrixBox,
|
|
632
|
-
windowStack: [],
|
|
633
|
-
theme,
|
|
634
|
-
matrixDrops: [],
|
|
635
|
-
matrixInterval: null
|
|
636
|
-
};
|
|
637
|
-
initMatrixRain(renderer);
|
|
638
|
-
return renderer;
|
|
461
|
+
return matrixBox;
|
|
639
462
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
renderer.matrixInterval = null;
|
|
644
|
-
}
|
|
645
|
-
for (const win of renderer.windowStack) {
|
|
646
|
-
win.destroy();
|
|
463
|
+
var init_matrix_rain = __esm({
|
|
464
|
+
"src/renderer/effects/matrix-rain.ts"() {
|
|
465
|
+
init_esm_shims();
|
|
647
466
|
}
|
|
648
|
-
|
|
649
|
-
renderer.screen.destroy();
|
|
650
|
-
}
|
|
467
|
+
});
|
|
651
468
|
function getWindowColor(index, theme) {
|
|
652
469
|
const colors = [
|
|
653
470
|
theme.colors.primary,
|
|
@@ -662,8 +479,7 @@ function getWindowColor(index, theme) {
|
|
|
662
479
|
];
|
|
663
480
|
return colors[index % colors.length];
|
|
664
481
|
}
|
|
665
|
-
function createWindow(
|
|
666
|
-
const { screen, windowStack, theme } = renderer;
|
|
482
|
+
function createWindow(screen, windowStack, theme, options) {
|
|
667
483
|
const windowIndex = windowStack.length;
|
|
668
484
|
const color = options.color ?? getWindowColor(windowIndex, theme);
|
|
669
485
|
const screenWidth = screen.width || 120;
|
|
@@ -699,144 +515,178 @@ function createWindow(renderer, options) {
|
|
|
699
515
|
windowStack.push(box);
|
|
700
516
|
return box;
|
|
701
517
|
}
|
|
702
|
-
function clearWindows(
|
|
703
|
-
for (const window of
|
|
518
|
+
function clearWindows(windowStack) {
|
|
519
|
+
for (const window of windowStack) {
|
|
704
520
|
window.destroy();
|
|
705
521
|
}
|
|
706
|
-
|
|
522
|
+
windowStack.length = 0;
|
|
707
523
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
524
|
+
var init_window_manager = __esm({
|
|
525
|
+
"src/renderer/window-manager.ts"() {
|
|
526
|
+
init_esm_shims();
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// src/core/theme-errors.ts
|
|
531
|
+
var ThemeError;
|
|
532
|
+
var init_theme_errors = __esm({
|
|
533
|
+
"src/core/theme-errors.ts"() {
|
|
534
|
+
init_esm_shims();
|
|
535
|
+
init_validation();
|
|
536
|
+
ThemeError = class extends Error {
|
|
537
|
+
/**
|
|
538
|
+
* @param message - The error message
|
|
539
|
+
* @param themeName - Optional name of the theme that caused the error
|
|
540
|
+
* @param path - Optional path to the theme file or package
|
|
541
|
+
*/
|
|
542
|
+
constructor(message, themeName, path2) {
|
|
543
|
+
super(message);
|
|
544
|
+
this.themeName = themeName;
|
|
545
|
+
this.path = path2;
|
|
546
|
+
this.name = "ThemeError";
|
|
714
547
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
function resolveColorToken(token, theme) {
|
|
552
|
+
switch (token) {
|
|
553
|
+
case "PRIMARY":
|
|
554
|
+
return theme.colors.primary;
|
|
555
|
+
case "SECONDARY":
|
|
556
|
+
return theme.colors.secondary ?? theme.colors.primary;
|
|
557
|
+
case "ACCENT":
|
|
558
|
+
return theme.colors.accent;
|
|
559
|
+
case "MUTED":
|
|
560
|
+
return theme.colors.muted;
|
|
561
|
+
case "TEXT":
|
|
562
|
+
return theme.colors.text;
|
|
563
|
+
case "BACKGROUND":
|
|
564
|
+
return theme.colors.background;
|
|
565
|
+
}
|
|
566
|
+
return BUILTIN_COLORS[token] ?? theme.colors.text;
|
|
728
567
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
scrambledLine += char;
|
|
736
|
-
} else if (Math.random() < scrambleRatio) {
|
|
737
|
-
scrambledLine += GLITCH_CHARS[Math.floor(Math.random() * GLITCH_CHARS.length)];
|
|
738
|
-
} else {
|
|
739
|
-
scrambledLine += char;
|
|
568
|
+
function colorTokensToBlessedTags(content, theme) {
|
|
569
|
+
return content.replace(
|
|
570
|
+
/\{(GREEN|ORANGE|CYAN|PINK|WHITE|GRAY|PRIMARY|SECONDARY|ACCENT|MUTED|TEXT|BACKGROUND|\/)\}/g,
|
|
571
|
+
(_, token) => {
|
|
572
|
+
if (token === "/") {
|
|
573
|
+
return "{/}";
|
|
740
574
|
}
|
|
575
|
+
const color = resolveColorToken(token, theme);
|
|
576
|
+
return `{${color}-fg}`;
|
|
741
577
|
}
|
|
742
|
-
|
|
743
|
-
screen.render();
|
|
744
|
-
await sleep(20);
|
|
745
|
-
}
|
|
578
|
+
);
|
|
746
579
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
580
|
+
var BUILTIN_COLORS;
|
|
581
|
+
var init_theme_colors = __esm({
|
|
582
|
+
"src/core/theme-colors.ts"() {
|
|
583
|
+
init_esm_shims();
|
|
584
|
+
BUILTIN_COLORS = {
|
|
585
|
+
GREEN: "#00cc66",
|
|
586
|
+
ORANGE: "#ff6600",
|
|
587
|
+
CYAN: "#00ccff",
|
|
588
|
+
PINK: "#ff0066",
|
|
589
|
+
WHITE: "#ffffff",
|
|
590
|
+
GRAY: "#666666"
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
var init_theme2 = __esm({
|
|
595
|
+
"src/core/theme.ts"() {
|
|
596
|
+
init_esm_shims();
|
|
597
|
+
init_theme();
|
|
598
|
+
init_validation();
|
|
599
|
+
init_theme_errors();
|
|
600
|
+
init_theme_colors();
|
|
760
601
|
}
|
|
602
|
+
});
|
|
603
|
+
function hasMermaidDiagrams(content) {
|
|
604
|
+
MERMAID_BLOCK_PATTERN.lastIndex = 0;
|
|
605
|
+
return MERMAID_BLOCK_PATTERN.test(content);
|
|
761
606
|
}
|
|
762
|
-
|
|
763
|
-
const
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
for (const char of content) {
|
|
769
|
-
if (char === "\n" || PROTECTED_CHARS.has(char) || Math.random() < revealRatio) {
|
|
770
|
-
revealed += char;
|
|
771
|
-
} else {
|
|
772
|
-
revealed += " ";
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
box.setContent(revealed);
|
|
776
|
-
screen.render();
|
|
777
|
-
await sleep(delay);
|
|
607
|
+
function extractMermaidBlocks(content) {
|
|
608
|
+
const blocks = [];
|
|
609
|
+
let match;
|
|
610
|
+
MERMAID_BLOCK_PATTERN.lastIndex = 0;
|
|
611
|
+
while ((match = MERMAID_BLOCK_PATTERN.exec(content)) !== null) {
|
|
612
|
+
blocks.push(match[1].trim());
|
|
778
613
|
}
|
|
779
|
-
|
|
780
|
-
screen.render();
|
|
614
|
+
return blocks;
|
|
781
615
|
}
|
|
782
|
-
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
}
|
|
616
|
+
function formatMermaidError(code, _error) {
|
|
617
|
+
const lines = [
|
|
618
|
+
"\u250C\u2500 Diagram (parse error) \u2500\u2510",
|
|
619
|
+
"\u2502 \u2502"
|
|
620
|
+
];
|
|
621
|
+
const codeLines = code.split("\n").slice(0, 5);
|
|
622
|
+
for (const line of codeLines) {
|
|
623
|
+
const truncated = line.slice(0, 23).padEnd(23);
|
|
624
|
+
lines.push(`\u2502 ${truncated} \u2502`);
|
|
792
625
|
}
|
|
626
|
+
if (code.split("\n").length > 5) {
|
|
627
|
+
lines.push("\u2502 ... \u2502");
|
|
628
|
+
}
|
|
629
|
+
lines.push("\u2502 \u2502");
|
|
630
|
+
lines.push("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
631
|
+
return lines.join("\n");
|
|
793
632
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
case "fade":
|
|
800
|
-
await fadeInReveal(box, screen, content, theme);
|
|
801
|
-
break;
|
|
802
|
-
case "instant":
|
|
803
|
-
box.setContent(content);
|
|
804
|
-
screen.render();
|
|
805
|
-
break;
|
|
806
|
-
case "typewriter":
|
|
807
|
-
await typewriterReveal(box, screen, content, theme);
|
|
808
|
-
break;
|
|
809
|
-
default:
|
|
810
|
-
box.setContent(content);
|
|
811
|
-
screen.render();
|
|
633
|
+
function mermaidToAscii(mermaidCode) {
|
|
634
|
+
try {
|
|
635
|
+
return mermaidToAscii$1(mermaidCode);
|
|
636
|
+
} catch (error) {
|
|
637
|
+
return formatMermaidError(mermaidCode);
|
|
812
638
|
}
|
|
813
639
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
let content = "";
|
|
821
|
-
const bigTextLines = normalizeBigText(frontmatter.bigText);
|
|
822
|
-
if (bigTextLines.length > 0) {
|
|
823
|
-
const gradientName = frontmatter.gradient ?? "fire";
|
|
824
|
-
const gradientColors = theme.gradients[gradientName] ?? theme.gradients.fire;
|
|
825
|
-
const bigText = await generateMultiLineBigText(bigTextLines, gradientColors);
|
|
826
|
-
content += bigText + "\n\n";
|
|
640
|
+
function escapeRegex(str) {
|
|
641
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
642
|
+
}
|
|
643
|
+
function processMermaidDiagrams(content) {
|
|
644
|
+
if (!hasMermaidDiagrams(content)) {
|
|
645
|
+
return content;
|
|
827
646
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
647
|
+
let result = content;
|
|
648
|
+
const blocks = extractMermaidBlocks(content);
|
|
649
|
+
for (const block of blocks) {
|
|
650
|
+
const ascii = mermaidToAscii(block);
|
|
651
|
+
result = result.replace(
|
|
652
|
+
new RegExp("```mermaid\\n" + escapeRegex(block) + "\\n?```", "g"),
|
|
653
|
+
ascii
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
return result;
|
|
833
657
|
}
|
|
834
|
-
var
|
|
835
|
-
var
|
|
836
|
-
"src/
|
|
658
|
+
var MERMAID_BLOCK_PATTERN;
|
|
659
|
+
var init_mermaid = __esm({
|
|
660
|
+
"src/core/utils/mermaid.ts"() {
|
|
837
661
|
init_esm_shims();
|
|
838
|
-
|
|
839
|
-
|
|
662
|
+
MERMAID_BLOCK_PATTERN = /```mermaid\n([\s\S]*?)```/g;
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// src/core/content-processor.ts
|
|
667
|
+
async function processSlideContent(body, theme) {
|
|
668
|
+
let processed = processMermaidDiagrams(body);
|
|
669
|
+
processed = colorTokensToBlessedTags(processed, theme);
|
|
670
|
+
return processed;
|
|
671
|
+
}
|
|
672
|
+
function normalizeBigText(bigText) {
|
|
673
|
+
if (!bigText) return [];
|
|
674
|
+
return Array.isArray(bigText) ? bigText : [bigText];
|
|
675
|
+
}
|
|
676
|
+
var init_content_processor = __esm({
|
|
677
|
+
"src/core/content-processor.ts"() {
|
|
678
|
+
init_esm_shims();
|
|
679
|
+
init_theme2();
|
|
680
|
+
init_mermaid();
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
// src/renderer/animations/constants.ts
|
|
685
|
+
var GLITCH_CHARS, PROTECTED_CHARS;
|
|
686
|
+
var init_constants = __esm({
|
|
687
|
+
"src/renderer/animations/constants.ts"() {
|
|
688
|
+
init_esm_shims();
|
|
689
|
+
GLITCH_CHARS = "\u2588\u2593\u2592\u2591\u2580\u2584\u258C\u2590\u25A0\u25A1\u25AA\u25AB\u25CF\u25CB\u25CA\u25D8\u25D9\u2666\u2663\u2660\u2665\u2605\u2606\u2302\u207F\xB2\xB3\xC6\xD8\u221E\u2248\u2260\xB1\xD7\xF7\u03B1\u03B2\u03B3\u03B4\u03B5\u03B6\u03B7\u03B8\u03BB\u03BC\u03C0\u03C3\u03C6\u03C9\u0394\u03A3\u03A9\uFF71\uFF72\uFF73\uFF74\uFF75\uFF76\uFF77\uFF78\uFF79\uFF7A\uFF7B\uFF7C\uFF7D\uFF7E\uFF7F\uFF80\uFF81\uFF82\uFF83\uFF84\uFF85\uFF86\uFF87\uFF88\uFF89\uFF8A\uFF8B\uFF8C\uFF8D\uFF8E\uFF8F\uFF90\uFF91\uFF92\uFF93\uFF94\uFF95\uFF96\uFF97\uFF98\uFF99\uFF9A\uFF9B\uFF9C\uFF9D";
|
|
840
690
|
PROTECTED_CHARS = /* @__PURE__ */ new Set([
|
|
841
691
|
" ",
|
|
842
692
|
" ",
|
|
@@ -915,65 +765,370 @@ var init_screen = __esm({
|
|
|
915
765
|
}
|
|
916
766
|
});
|
|
917
767
|
|
|
918
|
-
// src/
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
768
|
+
// src/renderer/animations/helpers/animation-utils.ts
|
|
769
|
+
function sleep(ms) {
|
|
770
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
771
|
+
}
|
|
772
|
+
function renderContent(box, screen, content) {
|
|
773
|
+
box.setContent(content);
|
|
774
|
+
screen.render();
|
|
775
|
+
}
|
|
776
|
+
var init_animation_utils = __esm({
|
|
777
|
+
"src/renderer/animations/helpers/animation-utils.ts"() {
|
|
778
|
+
init_esm_shims();
|
|
779
|
+
}
|
|
925
780
|
});
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
781
|
+
|
|
782
|
+
// src/renderer/animations/transitions/glitch-transition.ts
|
|
783
|
+
async function glitchLine(box, screen, currentLines, newLine, iterations = 5) {
|
|
784
|
+
for (let i = iterations; i >= 0; i--) {
|
|
785
|
+
const scrambleRatio = i / iterations;
|
|
786
|
+
let scrambledLine = "";
|
|
787
|
+
for (const char of newLine) {
|
|
788
|
+
if (PROTECTED_CHARS.has(char)) {
|
|
789
|
+
scrambledLine += char;
|
|
790
|
+
} else if (Math.random() < scrambleRatio) {
|
|
791
|
+
scrambledLine += GLITCH_CHARS[Math.floor(Math.random() * GLITCH_CHARS.length)];
|
|
792
|
+
} else {
|
|
793
|
+
scrambledLine += char;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
renderContent(box, screen, [...currentLines, scrambledLine].join("\n"));
|
|
797
|
+
await sleep(20);
|
|
930
798
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
799
|
+
}
|
|
800
|
+
async function lineByLineReveal(box, screen, content, theme) {
|
|
801
|
+
const lines = content.split("\n");
|
|
802
|
+
const revealedLines = [];
|
|
803
|
+
const lineDelay = theme.animations.lineDelay;
|
|
804
|
+
const glitchIterations = theme.animations.glitchIterations;
|
|
805
|
+
for (const line of lines) {
|
|
806
|
+
await glitchLine(box, screen, revealedLines, line, glitchIterations);
|
|
807
|
+
revealedLines.push(line);
|
|
808
|
+
renderContent(box, screen, revealedLines.join("\n"));
|
|
809
|
+
if (line.trim()) {
|
|
810
|
+
await sleep(lineDelay);
|
|
811
|
+
}
|
|
943
812
|
}
|
|
944
|
-
|
|
945
|
-
|
|
813
|
+
}
|
|
814
|
+
var init_glitch_transition = __esm({
|
|
815
|
+
"src/renderer/animations/transitions/glitch-transition.ts"() {
|
|
816
|
+
init_esm_shims();
|
|
817
|
+
init_constants();
|
|
818
|
+
init_animation_utils();
|
|
946
819
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
// src/renderer/animations/transitions/fade-transition.ts
|
|
823
|
+
async function fadeInReveal(box, screen, content, theme) {
|
|
824
|
+
const steps = 10;
|
|
825
|
+
const delay = theme.animations.lineDelay * 2 / steps;
|
|
826
|
+
for (let step = 0; step < steps; step++) {
|
|
827
|
+
const revealRatio = step / steps;
|
|
828
|
+
let revealed = "";
|
|
829
|
+
for (const char of content) {
|
|
830
|
+
if (char === "\n" || PROTECTED_CHARS.has(char) || Math.random() < revealRatio) {
|
|
831
|
+
revealed += char;
|
|
832
|
+
} else {
|
|
833
|
+
revealed += " ";
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
renderContent(box, screen, revealed);
|
|
837
|
+
await sleep(delay);
|
|
951
838
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
839
|
+
renderContent(box, screen, content);
|
|
840
|
+
}
|
|
841
|
+
var init_fade_transition = __esm({
|
|
842
|
+
"src/renderer/animations/transitions/fade-transition.ts"() {
|
|
843
|
+
init_esm_shims();
|
|
844
|
+
init_constants();
|
|
845
|
+
init_animation_utils();
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
// src/renderer/animations/transitions/typewriter-transition.ts
|
|
850
|
+
async function typewriterReveal(box, screen, content, theme) {
|
|
851
|
+
const charDelay = theme.animations.lineDelay / 5;
|
|
852
|
+
let revealed = "";
|
|
853
|
+
for (const char of content) {
|
|
854
|
+
revealed += char;
|
|
855
|
+
renderContent(box, screen, revealed);
|
|
856
|
+
if (char !== " " && char !== "\n") {
|
|
857
|
+
await sleep(charDelay);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
var init_typewriter_transition = __esm({
|
|
862
|
+
"src/renderer/animations/transitions/typewriter-transition.ts"() {
|
|
863
|
+
init_esm_shims();
|
|
864
|
+
init_animation_utils();
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
// src/renderer/animations/transitions/instant-transition.ts
|
|
869
|
+
function instantReveal(box, screen, content) {
|
|
870
|
+
renderContent(box, screen, content);
|
|
871
|
+
}
|
|
872
|
+
var init_instant_transition = __esm({
|
|
873
|
+
"src/renderer/animations/transitions/instant-transition.ts"() {
|
|
874
|
+
init_esm_shims();
|
|
875
|
+
init_animation_utils();
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
// src/renderer/animations/transition-orchestrator.ts
|
|
880
|
+
async function applyTransition(box, screen, content, transition, theme) {
|
|
881
|
+
switch (transition) {
|
|
882
|
+
case "glitch":
|
|
883
|
+
await lineByLineReveal(box, screen, content, theme);
|
|
884
|
+
break;
|
|
885
|
+
case "fade":
|
|
886
|
+
await fadeInReveal(box, screen, content, theme);
|
|
887
|
+
break;
|
|
888
|
+
case "instant":
|
|
889
|
+
instantReveal(box, screen, content);
|
|
890
|
+
break;
|
|
891
|
+
case "typewriter":
|
|
892
|
+
await typewriterReveal(box, screen, content, theme);
|
|
893
|
+
break;
|
|
894
|
+
default:
|
|
895
|
+
instantReveal(box, screen, content);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
var init_transition_orchestrator = __esm({
|
|
899
|
+
"src/renderer/animations/transition-orchestrator.ts"() {
|
|
900
|
+
init_esm_shims();
|
|
901
|
+
init_glitch_transition();
|
|
902
|
+
init_fade_transition();
|
|
903
|
+
init_typewriter_transition();
|
|
904
|
+
init_instant_transition();
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
// src/renderer/animations/transitions.ts
|
|
909
|
+
var init_transitions = __esm({
|
|
910
|
+
"src/renderer/animations/transitions.ts"() {
|
|
911
|
+
init_esm_shims();
|
|
912
|
+
init_transition_orchestrator();
|
|
913
|
+
init_glitch_transition();
|
|
914
|
+
init_fade_transition();
|
|
915
|
+
init_typewriter_transition();
|
|
916
|
+
init_instant_transition();
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
async function generateBigText(text, gradientColors, font = "Standard") {
|
|
920
|
+
return new Promise((resolve, reject) => {
|
|
921
|
+
figlet.text(text, { font }, (err, result) => {
|
|
922
|
+
if (err || !result) {
|
|
923
|
+
reject(err ?? new Error("Failed to generate figlet text"));
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
const gradientFn = gradient2(gradientColors);
|
|
927
|
+
resolve(gradientFn(result));
|
|
957
928
|
});
|
|
958
929
|
});
|
|
959
930
|
}
|
|
960
|
-
function
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
931
|
+
async function generateMultiLineBigText(lines, gradientColors, font = "Standard") {
|
|
932
|
+
const results = await Promise.all(
|
|
933
|
+
lines.map((line) => generateBigText(line, gradientColors, font))
|
|
934
|
+
);
|
|
935
|
+
return results.join("\n");
|
|
936
|
+
}
|
|
937
|
+
var init_text_generator = __esm({
|
|
938
|
+
"src/renderer/text-generator.ts"() {
|
|
939
|
+
init_esm_shims();
|
|
964
940
|
}
|
|
965
|
-
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
// src/renderer/slide-renderer.ts
|
|
944
|
+
async function renderSlide(screen, windowStack, theme, slide) {
|
|
945
|
+
const { frontmatter, body } = slide;
|
|
946
|
+
const window = createWindow(screen, windowStack, theme, {
|
|
947
|
+
title: frontmatter.title
|
|
948
|
+
});
|
|
949
|
+
let content = "";
|
|
950
|
+
const bigTextLines = normalizeBigText(frontmatter.bigText);
|
|
951
|
+
if (bigTextLines.length > 0) {
|
|
952
|
+
const gradientName = frontmatter.gradient ?? "fire";
|
|
953
|
+
const gradientColors = theme.gradients[gradientName] ?? theme.gradients.fire;
|
|
954
|
+
const bigText = await generateMultiLineBigText(bigTextLines, gradientColors);
|
|
955
|
+
content += bigText + "\n\n";
|
|
956
|
+
}
|
|
957
|
+
const processedBody = await processSlideContent(body, theme);
|
|
958
|
+
content += processedBody;
|
|
959
|
+
const transition = frontmatter.transition ?? "glitch";
|
|
960
|
+
await applyTransition(window, screen, content, transition, theme);
|
|
961
|
+
return window;
|
|
962
|
+
}
|
|
963
|
+
var init_slide_renderer = __esm({
|
|
964
|
+
"src/renderer/slide-renderer.ts"() {
|
|
965
|
+
init_esm_shims();
|
|
966
|
+
init_content_processor();
|
|
967
|
+
init_transitions();
|
|
968
|
+
init_text_generator();
|
|
969
|
+
init_window_manager();
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
function createScreen(title = "term-deck") {
|
|
973
|
+
const screen = blessed.screen({
|
|
974
|
+
smartCSR: true,
|
|
975
|
+
title,
|
|
976
|
+
fullUnicode: true,
|
|
977
|
+
mouse: false,
|
|
978
|
+
altScreen: true
|
|
979
|
+
});
|
|
980
|
+
return screen;
|
|
981
|
+
}
|
|
982
|
+
function createRenderer(theme) {
|
|
983
|
+
const screen = createScreen();
|
|
984
|
+
const matrixBox = createMatrixBox(screen);
|
|
985
|
+
const matrixRain = {
|
|
986
|
+
matrixBox,
|
|
987
|
+
matrixDrops: [],
|
|
988
|
+
matrixInterval: null,
|
|
989
|
+
theme
|
|
990
|
+
};
|
|
991
|
+
const renderer = {
|
|
992
|
+
screen,
|
|
993
|
+
windowStack: [],
|
|
994
|
+
theme,
|
|
995
|
+
matrixRain
|
|
996
|
+
};
|
|
997
|
+
initMatrixRain(screen, matrixRain);
|
|
998
|
+
return renderer;
|
|
999
|
+
}
|
|
1000
|
+
function destroyRenderer(renderer) {
|
|
1001
|
+
stopMatrixRain(renderer.matrixRain);
|
|
1002
|
+
clearWindows(renderer.windowStack);
|
|
1003
|
+
renderer.screen.destroy();
|
|
1004
|
+
}
|
|
1005
|
+
function clearWindows2(renderer) {
|
|
1006
|
+
clearWindows(renderer.windowStack);
|
|
1007
|
+
}
|
|
1008
|
+
async function renderSlide2(renderer, slide) {
|
|
1009
|
+
return renderSlide(renderer.screen, renderer.windowStack, renderer.theme, slide);
|
|
1010
|
+
}
|
|
1011
|
+
var init_screen = __esm({
|
|
1012
|
+
"src/renderer/screen.ts"() {
|
|
1013
|
+
init_esm_shims();
|
|
1014
|
+
init_matrix_rain();
|
|
1015
|
+
init_window_manager();
|
|
1016
|
+
init_slide_renderer();
|
|
1017
|
+
init_transitions();
|
|
1018
|
+
init_window_manager();
|
|
1019
|
+
init_text_generator();
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
async function findAvailableTty() {
|
|
1023
|
+
const candidates = [
|
|
1024
|
+
"/dev/ttys001",
|
|
1025
|
+
"/dev/ttys002",
|
|
1026
|
+
"/dev/ttys003",
|
|
1027
|
+
"/dev/pts/1",
|
|
1028
|
+
"/dev/pts/2"
|
|
1029
|
+
];
|
|
1030
|
+
for (const tty of candidates) {
|
|
1031
|
+
try {
|
|
1032
|
+
await access(tty);
|
|
1033
|
+
return tty;
|
|
1034
|
+
} catch {
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
throw new Error(
|
|
1038
|
+
"Could not find available TTY for notes window. Open a second terminal, run `tty`, and pass the path with --notes-tty"
|
|
1039
|
+
);
|
|
966
1040
|
}
|
|
1041
|
+
async function createNotesWindow(ttyPath) {
|
|
1042
|
+
const blessed5 = (await import('neo-blessed')).default;
|
|
1043
|
+
const { openSync } = await import('fs');
|
|
1044
|
+
const tty = ttyPath ?? await findAvailableTty();
|
|
1045
|
+
const screen = blessed5.screen({
|
|
1046
|
+
smartCSR: true,
|
|
1047
|
+
title: "term-deck notes",
|
|
1048
|
+
fullUnicode: true,
|
|
1049
|
+
input: openSync(tty, "r"),
|
|
1050
|
+
output: openSync(tty, "w")
|
|
1051
|
+
});
|
|
1052
|
+
const contentBox = blessed5.box({
|
|
1053
|
+
top: 0,
|
|
1054
|
+
left: 0,
|
|
1055
|
+
width: "100%",
|
|
1056
|
+
height: "100%",
|
|
1057
|
+
tags: true,
|
|
1058
|
+
padding: 2,
|
|
1059
|
+
style: {
|
|
1060
|
+
fg: "#ffffff",
|
|
1061
|
+
bg: "#1a1a1a"
|
|
1062
|
+
}
|
|
1063
|
+
});
|
|
1064
|
+
screen.append(contentBox);
|
|
1065
|
+
screen.render();
|
|
1066
|
+
return {
|
|
1067
|
+
screen,
|
|
1068
|
+
contentBox,
|
|
1069
|
+
tty
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
function updateNotesWindow(notesWindow, currentSlide, nextSlide2, currentIndex, totalSlides) {
|
|
1073
|
+
const { contentBox, screen } = notesWindow;
|
|
1074
|
+
let content = "";
|
|
1075
|
+
content += `{bold}Slide ${currentIndex + 1} of ${totalSlides}{/bold}
|
|
1076
|
+
`;
|
|
1077
|
+
content += `{gray-fg}${currentSlide.frontmatter.title}{/}
|
|
1078
|
+
`;
|
|
1079
|
+
content += "\n";
|
|
1080
|
+
content += "\u2500".repeat(50) + "\n";
|
|
1081
|
+
content += "\n";
|
|
1082
|
+
if (currentSlide.notes) {
|
|
1083
|
+
content += "{bold}PRESENTER NOTES:{/bold}\n\n";
|
|
1084
|
+
content += currentSlide.notes + "\n";
|
|
1085
|
+
} else {
|
|
1086
|
+
content += "{gray-fg}No notes for this slide{/}\n";
|
|
1087
|
+
}
|
|
1088
|
+
content += "\n";
|
|
1089
|
+
content += "\u2500".repeat(50) + "\n";
|
|
1090
|
+
content += "\n";
|
|
1091
|
+
if (nextSlide2) {
|
|
1092
|
+
content += `{bold}NEXT:{/bold} "${nextSlide2.frontmatter.title}"
|
|
1093
|
+
`;
|
|
1094
|
+
} else {
|
|
1095
|
+
content += "{gray-fg}Last slide{/}\n";
|
|
1096
|
+
}
|
|
1097
|
+
contentBox.setContent(content);
|
|
1098
|
+
screen.render();
|
|
1099
|
+
}
|
|
1100
|
+
function toggleNotesVisibility(notesWindow) {
|
|
1101
|
+
const { contentBox, screen } = notesWindow;
|
|
1102
|
+
contentBox.toggle();
|
|
1103
|
+
screen.render();
|
|
1104
|
+
}
|
|
1105
|
+
function destroyNotesWindow(notesWindow) {
|
|
1106
|
+
notesWindow.screen.destroy();
|
|
1107
|
+
}
|
|
1108
|
+
var init_notes_window = __esm({
|
|
1109
|
+
"src/presenter/notes-window.ts"() {
|
|
1110
|
+
init_esm_shims();
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
// src/presenter/navigation.ts
|
|
967
1115
|
async function showSlide(presenter, index) {
|
|
968
1116
|
if (presenter.isAnimating) return;
|
|
969
1117
|
if (index < 0 || index >= presenter.deck.slides.length) return;
|
|
970
1118
|
presenter.isAnimating = true;
|
|
971
1119
|
presenter.currentSlide = index;
|
|
972
1120
|
const slide = presenter.deck.slides[index];
|
|
973
|
-
await
|
|
1121
|
+
await renderSlide2(presenter.renderer, slide);
|
|
974
1122
|
presenter.renderer.screen.render();
|
|
975
1123
|
if (presenter.notesWindow) {
|
|
976
|
-
|
|
1124
|
+
const nextSlide2 = presenter.deck.slides[index + 1];
|
|
1125
|
+
updateNotesWindow(
|
|
1126
|
+
presenter.notesWindow,
|
|
1127
|
+
slide,
|
|
1128
|
+
nextSlide2,
|
|
1129
|
+
index,
|
|
1130
|
+
presenter.deck.slides.length
|
|
1131
|
+
);
|
|
977
1132
|
}
|
|
978
1133
|
if (presenter.progressBar) {
|
|
979
1134
|
updateProgress(presenter.progressBar, presenter.currentSlide, presenter.deck.slides.length);
|
|
@@ -998,49 +1153,64 @@ async function prevSlide(presenter) {
|
|
|
998
1153
|
const loop = presenter.deck.config.settings?.loop ?? false;
|
|
999
1154
|
if (prevIndex < 0) {
|
|
1000
1155
|
if (loop) {
|
|
1001
|
-
|
|
1156
|
+
clearWindows2(presenter.renderer);
|
|
1002
1157
|
for (let i = 0; i < slides.length; i++) {
|
|
1003
|
-
await
|
|
1158
|
+
await renderSlide2(presenter.renderer, slides[i]);
|
|
1004
1159
|
}
|
|
1005
1160
|
presenter.currentSlide = slides.length - 1;
|
|
1006
|
-
|
|
1007
|
-
updateNotesWindow(presenter);
|
|
1008
|
-
}
|
|
1009
|
-
if (presenter.progressBar) {
|
|
1010
|
-
updateProgress(presenter.progressBar, presenter.currentSlide, presenter.deck.slides.length);
|
|
1011
|
-
}
|
|
1161
|
+
updateUIComponents(presenter, slides.length - 1);
|
|
1012
1162
|
presenter.renderer.screen.render();
|
|
1013
1163
|
}
|
|
1014
1164
|
return;
|
|
1015
1165
|
}
|
|
1016
|
-
|
|
1166
|
+
clearWindows2(presenter.renderer);
|
|
1017
1167
|
for (let i = 0; i <= prevIndex; i++) {
|
|
1018
|
-
await
|
|
1168
|
+
await renderSlide2(presenter.renderer, slides[i]);
|
|
1019
1169
|
}
|
|
1020
1170
|
presenter.currentSlide = prevIndex;
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1171
|
+
updateUIComponents(presenter, prevIndex);
|
|
1172
|
+
presenter.renderer.screen.render();
|
|
1173
|
+
}
|
|
1174
|
+
async function jumpToSlide(presenter, index) {
|
|
1175
|
+
if (index < 0 || index >= presenter.deck.slides.length) return;
|
|
1176
|
+
clearWindows2(presenter.renderer);
|
|
1177
|
+
for (let i = 0; i <= index; i++) {
|
|
1178
|
+
await renderSlide2(presenter.renderer, presenter.deck.slides[i]);
|
|
1026
1179
|
}
|
|
1180
|
+
presenter.currentSlide = index;
|
|
1181
|
+
updateUIComponents(presenter, index);
|
|
1027
1182
|
presenter.renderer.screen.render();
|
|
1028
1183
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1184
|
+
function updateProgress(progressBar, current, total) {
|
|
1185
|
+
const progress = (current + 1) / total * 100;
|
|
1186
|
+
progressBar.setProgress(progress);
|
|
1187
|
+
}
|
|
1188
|
+
function updateUIComponents(presenter, currentIndex) {
|
|
1189
|
+
const { slides } = presenter.deck;
|
|
1190
|
+
const currentSlide = slides[currentIndex];
|
|
1191
|
+
const nextSlide2 = slides[currentIndex + 1];
|
|
1036
1192
|
if (presenter.notesWindow) {
|
|
1037
|
-
updateNotesWindow(
|
|
1193
|
+
updateNotesWindow(
|
|
1194
|
+
presenter.notesWindow,
|
|
1195
|
+
currentSlide,
|
|
1196
|
+
nextSlide2,
|
|
1197
|
+
currentIndex,
|
|
1198
|
+
slides.length
|
|
1199
|
+
);
|
|
1038
1200
|
}
|
|
1039
1201
|
if (presenter.progressBar) {
|
|
1040
|
-
updateProgress(presenter.progressBar,
|
|
1202
|
+
updateProgress(presenter.progressBar, currentIndex, slides.length);
|
|
1041
1203
|
}
|
|
1042
|
-
presenter.renderer.screen.render();
|
|
1043
1204
|
}
|
|
1205
|
+
var init_navigation = __esm({
|
|
1206
|
+
"src/presenter/navigation.ts"() {
|
|
1207
|
+
init_esm_shims();
|
|
1208
|
+
init_screen();
|
|
1209
|
+
init_notes_window();
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
// src/presenter/keyboard-controls.ts
|
|
1044
1214
|
function setupControls(presenter) {
|
|
1045
1215
|
const { screen } = presenter.renderer;
|
|
1046
1216
|
screen.key(["space", "enter", "right", "n"], () => {
|
|
@@ -1097,93 +1267,61 @@ function showSlideList(presenter) {
|
|
|
1097
1267
|
jumpToSlide(presenter, parseInt(ch ?? "0", 10));
|
|
1098
1268
|
});
|
|
1099
1269
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
async function findAvailableTty() {
|
|
1106
|
-
const candidates = [
|
|
1107
|
-
"/dev/ttys001",
|
|
1108
|
-
"/dev/ttys002",
|
|
1109
|
-
"/dev/ttys003",
|
|
1110
|
-
"/dev/pts/1",
|
|
1111
|
-
"/dev/pts/2"
|
|
1112
|
-
];
|
|
1113
|
-
for (const tty of candidates) {
|
|
1114
|
-
try {
|
|
1115
|
-
await access(tty);
|
|
1116
|
-
return tty;
|
|
1117
|
-
} catch {
|
|
1118
|
-
}
|
|
1270
|
+
var init_keyboard_controls = __esm({
|
|
1271
|
+
"src/presenter/keyboard-controls.ts"() {
|
|
1272
|
+
init_esm_shims();
|
|
1273
|
+
init_navigation();
|
|
1274
|
+
init_notes_window();
|
|
1119
1275
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
fg: "#ffffff",
|
|
1144
|
-
bg: "#1a1a1a"
|
|
1145
|
-
}
|
|
1146
|
-
});
|
|
1147
|
-
screen.append(contentBox);
|
|
1148
|
-
screen.render();
|
|
1149
|
-
return {
|
|
1150
|
-
screen,
|
|
1151
|
-
contentBox,
|
|
1152
|
-
tty
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
// src/presenter/main.ts
|
|
1279
|
+
var main_exports = {};
|
|
1280
|
+
__export(main_exports, {
|
|
1281
|
+
jumpToSlide: () => jumpToSlide,
|
|
1282
|
+
present: () => present,
|
|
1283
|
+
prevSlide: () => prevSlide
|
|
1284
|
+
});
|
|
1285
|
+
async function present(slidesDir, options = {}) {
|
|
1286
|
+
const deck = await loadDeck(slidesDir);
|
|
1287
|
+
if (deck.slides.length === 0) {
|
|
1288
|
+
throw new Error(`No slides found in ${slidesDir}`);
|
|
1289
|
+
}
|
|
1290
|
+
const renderer = createRenderer(deck.config.theme);
|
|
1291
|
+
const presenter = {
|
|
1292
|
+
deck,
|
|
1293
|
+
renderer,
|
|
1294
|
+
currentSlide: options.startSlide ?? deck.config.settings?.startSlide ?? 0,
|
|
1295
|
+
isAnimating: false,
|
|
1296
|
+
notesWindow: null,
|
|
1297
|
+
autoAdvanceTimer: null,
|
|
1298
|
+
progressBar: null
|
|
1153
1299
|
};
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
if (!presenter.notesWindow) return;
|
|
1157
|
-
const { contentBox, screen } = presenter.notesWindow;
|
|
1158
|
-
const { slides } = presenter.deck;
|
|
1159
|
-
const currentIndex = presenter.currentSlide;
|
|
1160
|
-
const currentSlide = slides[currentIndex];
|
|
1161
|
-
const nextSlide2 = slides[currentIndex + 1];
|
|
1162
|
-
let content = "";
|
|
1163
|
-
content += `{bold}Slide ${currentIndex + 1} of ${slides.length}{/bold}
|
|
1164
|
-
`;
|
|
1165
|
-
content += `{gray-fg}${currentSlide.frontmatter.title}{/}
|
|
1166
|
-
`;
|
|
1167
|
-
content += "\n";
|
|
1168
|
-
content += "\u2500".repeat(50) + "\n";
|
|
1169
|
-
content += "\n";
|
|
1170
|
-
if (currentSlide.notes) {
|
|
1171
|
-
content += "{bold}PRESENTER NOTES:{/bold}\n\n";
|
|
1172
|
-
content += currentSlide.notes + "\n";
|
|
1173
|
-
} else {
|
|
1174
|
-
content += "{gray-fg}No notes for this slide{/}\n";
|
|
1300
|
+
if (options.showNotes) {
|
|
1301
|
+
presenter.notesWindow = await createNotesWindow(options.notesTty);
|
|
1175
1302
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
content += "\n";
|
|
1179
|
-
if (nextSlide2) {
|
|
1180
|
-
content += `{bold}NEXT:{/bold} "${nextSlide2.frontmatter.title}"
|
|
1181
|
-
`;
|
|
1182
|
-
} else {
|
|
1183
|
-
content += "{gray-fg}Last slide{/}\n";
|
|
1303
|
+
if (deck.config.settings?.showProgress) {
|
|
1304
|
+
presenter.progressBar = createProgressBar(presenter);
|
|
1184
1305
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1306
|
+
setupControls(presenter);
|
|
1307
|
+
await showSlide(presenter, presenter.currentSlide);
|
|
1308
|
+
if (presenter.progressBar) {
|
|
1309
|
+
updateProgress(presenter.progressBar, presenter.currentSlide, deck.slides.length);
|
|
1310
|
+
}
|
|
1311
|
+
presenter.autoAdvanceTimer = startAutoAdvance(presenter);
|
|
1312
|
+
await new Promise((resolve) => {
|
|
1313
|
+
renderer.screen.key(["q", "C-c", "escape"], () => {
|
|
1314
|
+
cleanup(presenter);
|
|
1315
|
+
resolve();
|
|
1316
|
+
});
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
function cleanup(presenter) {
|
|
1320
|
+
stopAutoAdvance(presenter.autoAdvanceTimer);
|
|
1321
|
+
if (presenter.notesWindow) {
|
|
1322
|
+
destroyNotesWindow(presenter.notesWindow);
|
|
1323
|
+
}
|
|
1324
|
+
destroyRenderer(presenter.renderer);
|
|
1187
1325
|
}
|
|
1188
1326
|
function startAutoAdvance(presenter) {
|
|
1189
1327
|
const interval = presenter.deck.config.settings?.autoAdvance;
|
|
@@ -1217,15 +1355,15 @@ function createProgressBar(presenter) {
|
|
|
1217
1355
|
screen.append(progressBar);
|
|
1218
1356
|
return progressBar;
|
|
1219
1357
|
}
|
|
1220
|
-
function updateProgress(progressBar, current, total) {
|
|
1221
|
-
const progress = (current + 1) / total * 100;
|
|
1222
|
-
progressBar.setProgress(progress);
|
|
1223
|
-
}
|
|
1224
1358
|
var init_main = __esm({
|
|
1225
1359
|
"src/presenter/main.ts"() {
|
|
1226
1360
|
init_esm_shims();
|
|
1227
|
-
|
|
1361
|
+
init_deck_loader();
|
|
1228
1362
|
init_screen();
|
|
1363
|
+
init_notes_window();
|
|
1364
|
+
init_keyboard_controls();
|
|
1365
|
+
init_navigation();
|
|
1366
|
+
init_navigation();
|
|
1229
1367
|
}
|
|
1230
1368
|
});
|
|
1231
1369
|
|
|
@@ -1233,7 +1371,7 @@ var init_main = __esm({
|
|
|
1233
1371
|
init_esm_shims();
|
|
1234
1372
|
|
|
1235
1373
|
// package.json
|
|
1236
|
-
var version = "1.0.
|
|
1374
|
+
var version = "1.0.17";
|
|
1237
1375
|
|
|
1238
1376
|
// src/cli/commands/present.ts
|
|
1239
1377
|
init_esm_shims();
|
|
@@ -1243,6 +1381,7 @@ init_main();
|
|
|
1243
1381
|
init_esm_shims();
|
|
1244
1382
|
init_validation();
|
|
1245
1383
|
init_slide2();
|
|
1384
|
+
init_deck_loader();
|
|
1246
1385
|
init_theme2();
|
|
1247
1386
|
function handleError(error) {
|
|
1248
1387
|
if (error instanceof ValidationError) {
|
|
@@ -1316,6 +1455,44 @@ init_esm_shims();
|
|
|
1316
1455
|
|
|
1317
1456
|
// src/export/recorder.ts
|
|
1318
1457
|
init_esm_shims();
|
|
1458
|
+
|
|
1459
|
+
// src/export/recording-session.ts
|
|
1460
|
+
init_esm_shims();
|
|
1461
|
+
async function createRecordingSession(options) {
|
|
1462
|
+
const tempDir = join(tmpdir(), `term-deck-export-${Date.now()}`);
|
|
1463
|
+
await mkdir(tempDir, { recursive: true });
|
|
1464
|
+
return {
|
|
1465
|
+
tempDir,
|
|
1466
|
+
frameCount: 0,
|
|
1467
|
+
width: options.width ?? 120,
|
|
1468
|
+
height: options.height ?? 40,
|
|
1469
|
+
fps: options.fps ?? 30
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
async function saveFrame(session, png) {
|
|
1473
|
+
const frameNum = session.frameCount.toString().padStart(6, "0");
|
|
1474
|
+
const framePath = join(session.tempDir, `frame_${frameNum}.png`);
|
|
1475
|
+
await writeFile(framePath, png);
|
|
1476
|
+
session.frameCount++;
|
|
1477
|
+
}
|
|
1478
|
+
async function cleanupSession(session) {
|
|
1479
|
+
await rm(session.tempDir, { recursive: true, force: true });
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// src/export/presentation-exporter.ts
|
|
1483
|
+
init_esm_shims();
|
|
1484
|
+
init_deck_loader();
|
|
1485
|
+
init_screen();
|
|
1486
|
+
|
|
1487
|
+
// src/renderer/types/screen.ts
|
|
1488
|
+
init_esm_shims();
|
|
1489
|
+
function setScreenDimensions(screen, width, height) {
|
|
1490
|
+
screen.width = width;
|
|
1491
|
+
screen.height = height;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// src/export/utils/virtual-terminal.ts
|
|
1495
|
+
init_esm_shims();
|
|
1319
1496
|
var VirtualTerminal = class {
|
|
1320
1497
|
constructor(width, height) {
|
|
1321
1498
|
this.width = width;
|
|
@@ -1332,7 +1509,7 @@ var VirtualTerminal = class {
|
|
|
1332
1509
|
buffer;
|
|
1333
1510
|
colors;
|
|
1334
1511
|
/**
|
|
1335
|
-
* Set character at position
|
|
1512
|
+
* Set character at position with optional color
|
|
1336
1513
|
*/
|
|
1337
1514
|
setChar(x, y, char, color = "#ffffff") {
|
|
1338
1515
|
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
|
|
@@ -1341,7 +1518,7 @@ var VirtualTerminal = class {
|
|
|
1341
1518
|
}
|
|
1342
1519
|
}
|
|
1343
1520
|
/**
|
|
1344
|
-
* Clear the buffer
|
|
1521
|
+
* Clear the buffer to blank spaces
|
|
1345
1522
|
*/
|
|
1346
1523
|
clear() {
|
|
1347
1524
|
for (let y = 0; y < this.height; y++) {
|
|
@@ -1352,11 +1529,23 @@ var VirtualTerminal = class {
|
|
|
1352
1529
|
}
|
|
1353
1530
|
}
|
|
1354
1531
|
/**
|
|
1355
|
-
* Get buffer as
|
|
1532
|
+
* Get buffer contents as plain text string
|
|
1356
1533
|
*/
|
|
1357
1534
|
toString() {
|
|
1358
1535
|
return this.buffer.map((row) => row.join("")).join("\n");
|
|
1359
1536
|
}
|
|
1537
|
+
/**
|
|
1538
|
+
* Get the raw character buffer
|
|
1539
|
+
*/
|
|
1540
|
+
getBuffer() {
|
|
1541
|
+
return this.buffer;
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Get the color buffer
|
|
1545
|
+
*/
|
|
1546
|
+
getColors() {
|
|
1547
|
+
return this.colors;
|
|
1548
|
+
}
|
|
1360
1549
|
/**
|
|
1361
1550
|
* Convert buffer to PNG image data
|
|
1362
1551
|
*/
|
|
@@ -1386,33 +1575,12 @@ async function renderTerminalToPng(buffer, colors, width, height) {
|
|
|
1386
1575
|
}
|
|
1387
1576
|
return canvas.toBuffer("image/png");
|
|
1388
1577
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
const cell = line[x];
|
|
1396
|
-
if (!cell) continue;
|
|
1397
|
-
const char = Array.isArray(cell) ? cell[0] : cell;
|
|
1398
|
-
const attr = Array.isArray(cell) ? cell[1] : null;
|
|
1399
|
-
const color = extractColor(attr) || "#ffffff";
|
|
1400
|
-
vt.setChar(x, y, char || " ", color);
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
function extractColor(attr) {
|
|
1405
|
-
if (!attr) return null;
|
|
1406
|
-
if (typeof attr === "object" && attr.fg !== void 0) {
|
|
1407
|
-
if (typeof attr.fg === "string" && attr.fg.startsWith("#")) {
|
|
1408
|
-
return attr.fg;
|
|
1409
|
-
}
|
|
1410
|
-
if (typeof attr.fg === "number") {
|
|
1411
|
-
return ansi256ToHex(attr.fg);
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
return null;
|
|
1415
|
-
}
|
|
1578
|
+
|
|
1579
|
+
// src/export/capture/screen-capture.ts
|
|
1580
|
+
init_esm_shims();
|
|
1581
|
+
|
|
1582
|
+
// src/export/utils/color-conversion.ts
|
|
1583
|
+
init_esm_shims();
|
|
1416
1584
|
function ansi256ToHex(code) {
|
|
1417
1585
|
const standard16 = [
|
|
1418
1586
|
"#000000",
|
|
@@ -1446,31 +1614,38 @@ function ansi256ToHex(code) {
|
|
|
1446
1614
|
const hex = gray.toString(16).padStart(2, "0");
|
|
1447
1615
|
return `#${hex}${hex}${hex}`;
|
|
1448
1616
|
}
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
fps: options.fps ?? 30
|
|
1461
|
-
};
|
|
1462
|
-
}
|
|
1463
|
-
async function saveFrame(session, png) {
|
|
1464
|
-
const { join: join3 } = await import('path');
|
|
1465
|
-
const frameNum = session.frameCount.toString().padStart(6, "0");
|
|
1466
|
-
const framePath = join3(session.tempDir, `frame_${frameNum}.png`);
|
|
1467
|
-
await writeFile(framePath, png);
|
|
1468
|
-
session.frameCount++;
|
|
1617
|
+
function extractColor(attr) {
|
|
1618
|
+
if (!attr) return null;
|
|
1619
|
+
if (typeof attr === "object" && attr.fg !== void 0) {
|
|
1620
|
+
if (typeof attr.fg === "string" && attr.fg.startsWith("#")) {
|
|
1621
|
+
return attr.fg;
|
|
1622
|
+
}
|
|
1623
|
+
if (typeof attr.fg === "number") {
|
|
1624
|
+
return ansi256ToHex(attr.fg);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
return null;
|
|
1469
1628
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1629
|
+
|
|
1630
|
+
// src/export/capture/screen-capture.ts
|
|
1631
|
+
function captureScreen(screen, vt) {
|
|
1632
|
+
const lines = screen.lines || [];
|
|
1633
|
+
for (let y = 0; y < Math.min(lines.length, vt.height); y++) {
|
|
1634
|
+
const line = lines[y];
|
|
1635
|
+
if (!line) continue;
|
|
1636
|
+
for (let x = 0; x < Math.min(line.length, vt.width); x++) {
|
|
1637
|
+
const cell = line[x];
|
|
1638
|
+
if (!cell) continue;
|
|
1639
|
+
const char = Array.isArray(cell) ? cell[0] : cell;
|
|
1640
|
+
const attr = Array.isArray(cell) ? cell[1] : null;
|
|
1641
|
+
const color = extractColor(attr) || "#ffffff";
|
|
1642
|
+
vt.setChar(x, y, char || " ", color);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1473
1645
|
}
|
|
1646
|
+
|
|
1647
|
+
// src/export/encoding/ffmpeg-encoder.ts
|
|
1648
|
+
init_esm_shims();
|
|
1474
1649
|
async function checkFfmpeg() {
|
|
1475
1650
|
try {
|
|
1476
1651
|
await execa("which", ["ffmpeg"]);
|
|
@@ -1487,36 +1662,38 @@ function detectFormat(output) {
|
|
|
1487
1662
|
`Unknown output format for ${output}. Use .mp4 or .gif extension.`
|
|
1488
1663
|
);
|
|
1489
1664
|
}
|
|
1490
|
-
async function encodeVideo(
|
|
1491
|
-
const {
|
|
1492
|
-
const inputPattern = join3(session.tempDir, "frame_%06d.png");
|
|
1665
|
+
async function encodeVideo(options) {
|
|
1666
|
+
const { inputPattern, output, format, fps, quality } = options;
|
|
1493
1667
|
if (format === "mp4") {
|
|
1494
|
-
await encodeMp4(inputPattern, output,
|
|
1668
|
+
await encodeMp4(inputPattern, output, fps, quality ?? 80);
|
|
1495
1669
|
} else {
|
|
1496
|
-
await encodeGif(inputPattern, output,
|
|
1670
|
+
await encodeGif(inputPattern, output, fps);
|
|
1497
1671
|
}
|
|
1498
1672
|
}
|
|
1499
1673
|
async function encodeMp4(input, output, fps, quality) {
|
|
1500
1674
|
const crf = Math.round(51 - quality / 100 * 33);
|
|
1501
1675
|
await execa("ffmpeg", [
|
|
1502
1676
|
"-y",
|
|
1677
|
+
// Overwrite output file
|
|
1503
1678
|
"-framerate",
|
|
1504
1679
|
fps.toString(),
|
|
1505
1680
|
"-i",
|
|
1506
1681
|
input,
|
|
1507
1682
|
"-c:v",
|
|
1508
1683
|
"libx264",
|
|
1684
|
+
// H.264 codec
|
|
1509
1685
|
"-crf",
|
|
1510
1686
|
crf.toString(),
|
|
1511
1687
|
"-pix_fmt",
|
|
1512
1688
|
"yuv420p",
|
|
1689
|
+
// Pixel format for compatibility
|
|
1513
1690
|
output
|
|
1514
1691
|
]);
|
|
1515
1692
|
}
|
|
1516
1693
|
async function encodeGif(input, output, fps) {
|
|
1517
|
-
const { tmpdir } = await import('os');
|
|
1518
|
-
const { join:
|
|
1519
|
-
const paletteFile =
|
|
1694
|
+
const { tmpdir: tmpdir2 } = await import('os');
|
|
1695
|
+
const { join: join5 } = await import('path');
|
|
1696
|
+
const paletteFile = join5(tmpdir2(), `palette-${Date.now()}.png`);
|
|
1520
1697
|
try {
|
|
1521
1698
|
await execa("ffmpeg", [
|
|
1522
1699
|
"-y",
|
|
@@ -1547,20 +1724,29 @@ async function encodeGif(input, output, fps) {
|
|
|
1547
1724
|
}
|
|
1548
1725
|
}
|
|
1549
1726
|
}
|
|
1727
|
+
|
|
1728
|
+
// src/export/presentation-exporter.ts
|
|
1729
|
+
async function encodeFramesToVideo(tempDir, output, format, fps, quality) {
|
|
1730
|
+
const inputPattern = join(tempDir, "frame_%06d.png");
|
|
1731
|
+
await encodeVideo({
|
|
1732
|
+
inputPattern,
|
|
1733
|
+
output,
|
|
1734
|
+
format,
|
|
1735
|
+
fps,
|
|
1736
|
+
quality
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1550
1739
|
async function exportPresentation(slidesDir, options) {
|
|
1551
1740
|
await checkFfmpeg();
|
|
1552
1741
|
const format = detectFormat(options.output);
|
|
1553
|
-
const
|
|
1554
|
-
const deck = await loadDeck2(slidesDir);
|
|
1742
|
+
const deck = await loadDeck(slidesDir);
|
|
1555
1743
|
if (deck.slides.length === 0) {
|
|
1556
1744
|
throw new Error(`No slides found in ${slidesDir}`);
|
|
1557
1745
|
}
|
|
1558
1746
|
const session = await createRecordingSession(options);
|
|
1559
1747
|
const vt = new VirtualTerminal(session.width, session.height);
|
|
1560
|
-
const
|
|
1561
|
-
|
|
1562
|
-
renderer.screen.width = session.width;
|
|
1563
|
-
renderer.screen.height = session.height;
|
|
1748
|
+
const renderer = createRenderer(deck.config.theme);
|
|
1749
|
+
setScreenDimensions(renderer.screen, session.width, session.height);
|
|
1564
1750
|
const slideTime = options.slideTime ?? 3;
|
|
1565
1751
|
const framesPerSlide = session.fps * slideTime;
|
|
1566
1752
|
console.log(`Exporting ${deck.slides.length} slides...`);
|
|
@@ -1577,14 +1763,25 @@ async function exportPresentation(slidesDir, options) {
|
|
|
1577
1763
|
}
|
|
1578
1764
|
}
|
|
1579
1765
|
console.log("Encoding video...");
|
|
1580
|
-
await
|
|
1766
|
+
await encodeFramesToVideo(
|
|
1767
|
+
session.tempDir,
|
|
1768
|
+
options.output,
|
|
1769
|
+
format,
|
|
1770
|
+
session.fps,
|
|
1771
|
+
options.quality
|
|
1772
|
+
);
|
|
1581
1773
|
console.log(`Exported to ${options.output}`);
|
|
1582
1774
|
} finally {
|
|
1583
|
-
|
|
1775
|
+
destroyRenderer(renderer);
|
|
1584
1776
|
await cleanupSession(session);
|
|
1585
1777
|
}
|
|
1586
1778
|
}
|
|
1587
1779
|
|
|
1780
|
+
// src/export/ansi-recorder.ts
|
|
1781
|
+
init_esm_shims();
|
|
1782
|
+
init_deck_loader();
|
|
1783
|
+
init_screen();
|
|
1784
|
+
|
|
1588
1785
|
// src/cli/commands/export.ts
|
|
1589
1786
|
var exportCommand = new Command("export").description("Export presentation to GIF or MP4").argument("<dir>", "Slides directory").requiredOption("-o, --output <file>", "Output file (.mp4 or .gif)").option("-w, --width <n>", "Terminal width in characters", "120").option("-h, --height <n>", "Terminal height in characters", "40").option("--fps <n>", "Frames per second", "30").option("-t, --slide-time <n>", "Seconds per slide", "3").option("-q, --quality <n>", "Quality 1-100 (video only)", "80").action(async (dir, options) => {
|
|
1590
1787
|
try {
|