@pep/term-deck 1.0.15 → 1.0.16
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 +826 -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,8 @@ async function loadDeckConfig(slidesDir) {
|
|
|
447
333
|
theme: DEFAULT_THEME
|
|
448
334
|
};
|
|
449
335
|
}
|
|
450
|
-
const
|
|
336
|
+
const cacheBuster = `${Date.now()}-${Math.random()}`;
|
|
337
|
+
const configModule = await import(configPath + "?t=" + cacheBuster);
|
|
451
338
|
if (!configModule.default) {
|
|
452
339
|
throw new Error("deck.config.ts must export default config");
|
|
453
340
|
}
|
|
@@ -471,50 +358,18 @@ async function loadDeck(slidesDir) {
|
|
|
471
358
|
basePath: slidesDir
|
|
472
359
|
};
|
|
473
360
|
}
|
|
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"() {
|
|
361
|
+
var DeckLoadError;
|
|
362
|
+
var init_deck_loader = __esm({
|
|
363
|
+
"src/core/deck-loader.ts"() {
|
|
492
364
|
init_esm_shims();
|
|
493
|
-
init_slide();
|
|
494
365
|
init_config();
|
|
495
366
|
init_validation();
|
|
496
367
|
init_theme();
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
NOTES_END_MARKER = "<!-- /notes -->";
|
|
500
|
-
MERMAID_BLOCK_PATTERN = /```mermaid\n([\s\S]*?)```/g;
|
|
501
|
-
SlideParseError = class extends Error {
|
|
368
|
+
init_slide2();
|
|
369
|
+
DeckLoadError = class extends Error {
|
|
502
370
|
/**
|
|
503
371
|
* @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
|
|
372
|
+
* @param slidesDir - Path to the directory that was being loaded
|
|
518
373
|
* @param cause - Optional underlying error that caused this failure
|
|
519
374
|
*/
|
|
520
375
|
constructor(message, slidesDir, cause) {
|
|
@@ -526,43 +381,14 @@ var init_slide2 = __esm({
|
|
|
526
381
|
};
|
|
527
382
|
}
|
|
528
383
|
});
|
|
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
384
|
function generateTrail(glyphs, length) {
|
|
559
385
|
return Array.from(
|
|
560
386
|
{ length },
|
|
561
387
|
() => glyphs[Math.floor(Math.random() * glyphs.length)]
|
|
562
388
|
);
|
|
563
389
|
}
|
|
564
|
-
function renderMatrixRain(
|
|
565
|
-
const {
|
|
390
|
+
function renderMatrixRain(screen, state) {
|
|
391
|
+
const { matrixBox, matrixDrops, theme } = state;
|
|
566
392
|
const width = Math.max(20, screen.width || 80);
|
|
567
393
|
const height = Math.max(10, screen.height || 24);
|
|
568
394
|
const grid = Array.from(
|
|
@@ -597,27 +423,32 @@ function renderMatrixRain(renderer) {
|
|
|
597
423
|
}
|
|
598
424
|
matrixBox.setContent(output);
|
|
599
425
|
}
|
|
600
|
-
function initMatrixRain(
|
|
601
|
-
const {
|
|
426
|
+
function initMatrixRain(screen, state) {
|
|
427
|
+
const { theme } = state;
|
|
602
428
|
const width = screen.width || 80;
|
|
603
429
|
const height = screen.height || 24;
|
|
604
430
|
const density = theme.animations.matrixDensity;
|
|
605
|
-
|
|
431
|
+
state.matrixDrops = [];
|
|
606
432
|
for (let i = 0; i < density; i++) {
|
|
607
|
-
|
|
433
|
+
state.matrixDrops.push({
|
|
608
434
|
x: Math.floor(Math.random() * width),
|
|
609
435
|
y: Math.floor(Math.random() * height),
|
|
610
436
|
speed: 0.3 + Math.random() * 0.7,
|
|
611
437
|
trail: generateTrail(theme.glyphs, 5 + Math.floor(Math.random() * 10))
|
|
612
438
|
});
|
|
613
439
|
}
|
|
614
|
-
|
|
615
|
-
renderMatrixRain(
|
|
616
|
-
|
|
440
|
+
state.matrixInterval = setInterval(() => {
|
|
441
|
+
renderMatrixRain(screen, state);
|
|
442
|
+
screen.render();
|
|
617
443
|
}, theme.animations.matrixInterval);
|
|
618
444
|
}
|
|
619
|
-
function
|
|
620
|
-
|
|
445
|
+
function stopMatrixRain(state) {
|
|
446
|
+
if (state.matrixInterval) {
|
|
447
|
+
clearInterval(state.matrixInterval);
|
|
448
|
+
state.matrixInterval = null;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function createMatrixBox(screen) {
|
|
621
452
|
const matrixBox = blessed.box({
|
|
622
453
|
top: 0,
|
|
623
454
|
left: 0,
|
|
@@ -626,28 +457,13 @@ function createRenderer(theme) {
|
|
|
626
457
|
tags: true
|
|
627
458
|
});
|
|
628
459
|
screen.append(matrixBox);
|
|
629
|
-
|
|
630
|
-
screen,
|
|
631
|
-
matrixBox,
|
|
632
|
-
windowStack: [],
|
|
633
|
-
theme,
|
|
634
|
-
matrixDrops: [],
|
|
635
|
-
matrixInterval: null
|
|
636
|
-
};
|
|
637
|
-
initMatrixRain(renderer);
|
|
638
|
-
return renderer;
|
|
460
|
+
return matrixBox;
|
|
639
461
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
renderer.matrixInterval = null;
|
|
644
|
-
}
|
|
645
|
-
for (const win of renderer.windowStack) {
|
|
646
|
-
win.destroy();
|
|
462
|
+
var init_matrix_rain = __esm({
|
|
463
|
+
"src/renderer/effects/matrix-rain.ts"() {
|
|
464
|
+
init_esm_shims();
|
|
647
465
|
}
|
|
648
|
-
|
|
649
|
-
renderer.screen.destroy();
|
|
650
|
-
}
|
|
466
|
+
});
|
|
651
467
|
function getWindowColor(index, theme) {
|
|
652
468
|
const colors = [
|
|
653
469
|
theme.colors.primary,
|
|
@@ -662,8 +478,7 @@ function getWindowColor(index, theme) {
|
|
|
662
478
|
];
|
|
663
479
|
return colors[index % colors.length];
|
|
664
480
|
}
|
|
665
|
-
function createWindow(
|
|
666
|
-
const { screen, windowStack, theme } = renderer;
|
|
481
|
+
function createWindow(screen, windowStack, theme, options) {
|
|
667
482
|
const windowIndex = windowStack.length;
|
|
668
483
|
const color = options.color ?? getWindowColor(windowIndex, theme);
|
|
669
484
|
const screenWidth = screen.width || 120;
|
|
@@ -699,144 +514,178 @@ function createWindow(renderer, options) {
|
|
|
699
514
|
windowStack.push(box);
|
|
700
515
|
return box;
|
|
701
516
|
}
|
|
702
|
-
function clearWindows(
|
|
703
|
-
for (const window of
|
|
517
|
+
function clearWindows(windowStack) {
|
|
518
|
+
for (const window of windowStack) {
|
|
704
519
|
window.destroy();
|
|
705
520
|
}
|
|
706
|
-
|
|
521
|
+
windowStack.length = 0;
|
|
707
522
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
523
|
+
var init_window_manager = __esm({
|
|
524
|
+
"src/renderer/window-manager.ts"() {
|
|
525
|
+
init_esm_shims();
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// src/core/theme-errors.ts
|
|
530
|
+
var ThemeError;
|
|
531
|
+
var init_theme_errors = __esm({
|
|
532
|
+
"src/core/theme-errors.ts"() {
|
|
533
|
+
init_esm_shims();
|
|
534
|
+
init_validation();
|
|
535
|
+
ThemeError = class extends Error {
|
|
536
|
+
/**
|
|
537
|
+
* @param message - The error message
|
|
538
|
+
* @param themeName - Optional name of the theme that caused the error
|
|
539
|
+
* @param path - Optional path to the theme file or package
|
|
540
|
+
*/
|
|
541
|
+
constructor(message, themeName, path2) {
|
|
542
|
+
super(message);
|
|
543
|
+
this.themeName = themeName;
|
|
544
|
+
this.path = path2;
|
|
545
|
+
this.name = "ThemeError";
|
|
714
546
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
function resolveColorToken(token, theme) {
|
|
551
|
+
switch (token) {
|
|
552
|
+
case "PRIMARY":
|
|
553
|
+
return theme.colors.primary;
|
|
554
|
+
case "SECONDARY":
|
|
555
|
+
return theme.colors.secondary ?? theme.colors.primary;
|
|
556
|
+
case "ACCENT":
|
|
557
|
+
return theme.colors.accent;
|
|
558
|
+
case "MUTED":
|
|
559
|
+
return theme.colors.muted;
|
|
560
|
+
case "TEXT":
|
|
561
|
+
return theme.colors.text;
|
|
562
|
+
case "BACKGROUND":
|
|
563
|
+
return theme.colors.background;
|
|
564
|
+
}
|
|
565
|
+
return BUILTIN_COLORS[token] ?? theme.colors.text;
|
|
728
566
|
}
|
|
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;
|
|
567
|
+
function colorTokensToBlessedTags(content, theme) {
|
|
568
|
+
return content.replace(
|
|
569
|
+
/\{(GREEN|ORANGE|CYAN|PINK|WHITE|GRAY|PRIMARY|SECONDARY|ACCENT|MUTED|TEXT|BACKGROUND|\/)\}/g,
|
|
570
|
+
(_, token) => {
|
|
571
|
+
if (token === "/") {
|
|
572
|
+
return "{/}";
|
|
740
573
|
}
|
|
574
|
+
const color = resolveColorToken(token, theme);
|
|
575
|
+
return `{${color}-fg}`;
|
|
741
576
|
}
|
|
742
|
-
|
|
743
|
-
screen.render();
|
|
744
|
-
await sleep(20);
|
|
745
|
-
}
|
|
577
|
+
);
|
|
746
578
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
579
|
+
var BUILTIN_COLORS;
|
|
580
|
+
var init_theme_colors = __esm({
|
|
581
|
+
"src/core/theme-colors.ts"() {
|
|
582
|
+
init_esm_shims();
|
|
583
|
+
BUILTIN_COLORS = {
|
|
584
|
+
GREEN: "#00cc66",
|
|
585
|
+
ORANGE: "#ff6600",
|
|
586
|
+
CYAN: "#00ccff",
|
|
587
|
+
PINK: "#ff0066",
|
|
588
|
+
WHITE: "#ffffff",
|
|
589
|
+
GRAY: "#666666"
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
var init_theme2 = __esm({
|
|
594
|
+
"src/core/theme.ts"() {
|
|
595
|
+
init_esm_shims();
|
|
596
|
+
init_theme();
|
|
597
|
+
init_validation();
|
|
598
|
+
init_theme_errors();
|
|
599
|
+
init_theme_colors();
|
|
760
600
|
}
|
|
601
|
+
});
|
|
602
|
+
function hasMermaidDiagrams(content) {
|
|
603
|
+
MERMAID_BLOCK_PATTERN.lastIndex = 0;
|
|
604
|
+
return MERMAID_BLOCK_PATTERN.test(content);
|
|
761
605
|
}
|
|
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);
|
|
606
|
+
function extractMermaidBlocks(content) {
|
|
607
|
+
const blocks = [];
|
|
608
|
+
let match;
|
|
609
|
+
MERMAID_BLOCK_PATTERN.lastIndex = 0;
|
|
610
|
+
while ((match = MERMAID_BLOCK_PATTERN.exec(content)) !== null) {
|
|
611
|
+
blocks.push(match[1].trim());
|
|
778
612
|
}
|
|
779
|
-
|
|
780
|
-
screen.render();
|
|
613
|
+
return blocks;
|
|
781
614
|
}
|
|
782
|
-
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
}
|
|
615
|
+
function formatMermaidError(code, _error) {
|
|
616
|
+
const lines = [
|
|
617
|
+
"\u250C\u2500 Diagram (parse error) \u2500\u2510",
|
|
618
|
+
"\u2502 \u2502"
|
|
619
|
+
];
|
|
620
|
+
const codeLines = code.split("\n").slice(0, 5);
|
|
621
|
+
for (const line of codeLines) {
|
|
622
|
+
const truncated = line.slice(0, 23).padEnd(23);
|
|
623
|
+
lines.push(`\u2502 ${truncated} \u2502`);
|
|
792
624
|
}
|
|
625
|
+
if (code.split("\n").length > 5) {
|
|
626
|
+
lines.push("\u2502 ... \u2502");
|
|
627
|
+
}
|
|
628
|
+
lines.push("\u2502 \u2502");
|
|
629
|
+
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");
|
|
630
|
+
return lines.join("\n");
|
|
793
631
|
}
|
|
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();
|
|
632
|
+
function mermaidToAscii(mermaidCode) {
|
|
633
|
+
try {
|
|
634
|
+
return mermaidToAscii$1(mermaidCode);
|
|
635
|
+
} catch (error) {
|
|
636
|
+
return formatMermaidError(mermaidCode);
|
|
812
637
|
}
|
|
813
638
|
}
|
|
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";
|
|
639
|
+
function escapeRegex(str) {
|
|
640
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
641
|
+
}
|
|
642
|
+
function processMermaidDiagrams(content) {
|
|
643
|
+
if (!hasMermaidDiagrams(content)) {
|
|
644
|
+
return content;
|
|
827
645
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
646
|
+
let result = content;
|
|
647
|
+
const blocks = extractMermaidBlocks(content);
|
|
648
|
+
for (const block of blocks) {
|
|
649
|
+
const ascii = mermaidToAscii(block);
|
|
650
|
+
result = result.replace(
|
|
651
|
+
new RegExp("```mermaid\\n" + escapeRegex(block) + "\\n?```", "g"),
|
|
652
|
+
ascii
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
return result;
|
|
833
656
|
}
|
|
834
|
-
var
|
|
835
|
-
var
|
|
836
|
-
"src/
|
|
657
|
+
var MERMAID_BLOCK_PATTERN;
|
|
658
|
+
var init_mermaid = __esm({
|
|
659
|
+
"src/core/utils/mermaid.ts"() {
|
|
837
660
|
init_esm_shims();
|
|
838
|
-
|
|
839
|
-
|
|
661
|
+
MERMAID_BLOCK_PATTERN = /```mermaid\n([\s\S]*?)```/g;
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// src/core/content-processor.ts
|
|
666
|
+
async function processSlideContent(body, theme) {
|
|
667
|
+
let processed = processMermaidDiagrams(body);
|
|
668
|
+
processed = colorTokensToBlessedTags(processed, theme);
|
|
669
|
+
return processed;
|
|
670
|
+
}
|
|
671
|
+
function normalizeBigText(bigText) {
|
|
672
|
+
if (!bigText) return [];
|
|
673
|
+
return Array.isArray(bigText) ? bigText : [bigText];
|
|
674
|
+
}
|
|
675
|
+
var init_content_processor = __esm({
|
|
676
|
+
"src/core/content-processor.ts"() {
|
|
677
|
+
init_esm_shims();
|
|
678
|
+
init_theme2();
|
|
679
|
+
init_mermaid();
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
// src/renderer/animations/constants.ts
|
|
684
|
+
var GLITCH_CHARS, PROTECTED_CHARS;
|
|
685
|
+
var init_constants = __esm({
|
|
686
|
+
"src/renderer/animations/constants.ts"() {
|
|
687
|
+
init_esm_shims();
|
|
688
|
+
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
689
|
PROTECTED_CHARS = /* @__PURE__ */ new Set([
|
|
841
690
|
" ",
|
|
842
691
|
" ",
|
|
@@ -915,65 +764,370 @@ var init_screen = __esm({
|
|
|
915
764
|
}
|
|
916
765
|
});
|
|
917
766
|
|
|
918
|
-
// src/
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
767
|
+
// src/renderer/animations/helpers/animation-utils.ts
|
|
768
|
+
function sleep(ms) {
|
|
769
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
770
|
+
}
|
|
771
|
+
function renderContent(box, screen, content) {
|
|
772
|
+
box.setContent(content);
|
|
773
|
+
screen.render();
|
|
774
|
+
}
|
|
775
|
+
var init_animation_utils = __esm({
|
|
776
|
+
"src/renderer/animations/helpers/animation-utils.ts"() {
|
|
777
|
+
init_esm_shims();
|
|
778
|
+
}
|
|
925
779
|
});
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
780
|
+
|
|
781
|
+
// src/renderer/animations/transitions/glitch-transition.ts
|
|
782
|
+
async function glitchLine(box, screen, currentLines, newLine, iterations = 5) {
|
|
783
|
+
for (let i = iterations; i >= 0; i--) {
|
|
784
|
+
const scrambleRatio = i / iterations;
|
|
785
|
+
let scrambledLine = "";
|
|
786
|
+
for (const char of newLine) {
|
|
787
|
+
if (PROTECTED_CHARS.has(char)) {
|
|
788
|
+
scrambledLine += char;
|
|
789
|
+
} else if (Math.random() < scrambleRatio) {
|
|
790
|
+
scrambledLine += GLITCH_CHARS[Math.floor(Math.random() * GLITCH_CHARS.length)];
|
|
791
|
+
} else {
|
|
792
|
+
scrambledLine += char;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
renderContent(box, screen, [...currentLines, scrambledLine].join("\n"));
|
|
796
|
+
await sleep(20);
|
|
930
797
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
798
|
+
}
|
|
799
|
+
async function lineByLineReveal(box, screen, content, theme) {
|
|
800
|
+
const lines = content.split("\n");
|
|
801
|
+
const revealedLines = [];
|
|
802
|
+
const lineDelay = theme.animations.lineDelay;
|
|
803
|
+
const glitchIterations = theme.animations.glitchIterations;
|
|
804
|
+
for (const line of lines) {
|
|
805
|
+
await glitchLine(box, screen, revealedLines, line, glitchIterations);
|
|
806
|
+
revealedLines.push(line);
|
|
807
|
+
renderContent(box, screen, revealedLines.join("\n"));
|
|
808
|
+
if (line.trim()) {
|
|
809
|
+
await sleep(lineDelay);
|
|
810
|
+
}
|
|
943
811
|
}
|
|
944
|
-
|
|
945
|
-
|
|
812
|
+
}
|
|
813
|
+
var init_glitch_transition = __esm({
|
|
814
|
+
"src/renderer/animations/transitions/glitch-transition.ts"() {
|
|
815
|
+
init_esm_shims();
|
|
816
|
+
init_constants();
|
|
817
|
+
init_animation_utils();
|
|
946
818
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
// src/renderer/animations/transitions/fade-transition.ts
|
|
822
|
+
async function fadeInReveal(box, screen, content, theme) {
|
|
823
|
+
const steps = 10;
|
|
824
|
+
const delay = theme.animations.lineDelay * 2 / steps;
|
|
825
|
+
for (let step = 0; step < steps; step++) {
|
|
826
|
+
const revealRatio = step / steps;
|
|
827
|
+
let revealed = "";
|
|
828
|
+
for (const char of content) {
|
|
829
|
+
if (char === "\n" || PROTECTED_CHARS.has(char) || Math.random() < revealRatio) {
|
|
830
|
+
revealed += char;
|
|
831
|
+
} else {
|
|
832
|
+
revealed += " ";
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
renderContent(box, screen, revealed);
|
|
836
|
+
await sleep(delay);
|
|
951
837
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
838
|
+
renderContent(box, screen, content);
|
|
839
|
+
}
|
|
840
|
+
var init_fade_transition = __esm({
|
|
841
|
+
"src/renderer/animations/transitions/fade-transition.ts"() {
|
|
842
|
+
init_esm_shims();
|
|
843
|
+
init_constants();
|
|
844
|
+
init_animation_utils();
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
// src/renderer/animations/transitions/typewriter-transition.ts
|
|
849
|
+
async function typewriterReveal(box, screen, content, theme) {
|
|
850
|
+
const charDelay = theme.animations.lineDelay / 5;
|
|
851
|
+
let revealed = "";
|
|
852
|
+
for (const char of content) {
|
|
853
|
+
revealed += char;
|
|
854
|
+
renderContent(box, screen, revealed);
|
|
855
|
+
if (char !== " " && char !== "\n") {
|
|
856
|
+
await sleep(charDelay);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
var init_typewriter_transition = __esm({
|
|
861
|
+
"src/renderer/animations/transitions/typewriter-transition.ts"() {
|
|
862
|
+
init_esm_shims();
|
|
863
|
+
init_animation_utils();
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
// src/renderer/animations/transitions/instant-transition.ts
|
|
868
|
+
function instantReveal(box, screen, content) {
|
|
869
|
+
renderContent(box, screen, content);
|
|
870
|
+
}
|
|
871
|
+
var init_instant_transition = __esm({
|
|
872
|
+
"src/renderer/animations/transitions/instant-transition.ts"() {
|
|
873
|
+
init_esm_shims();
|
|
874
|
+
init_animation_utils();
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
// src/renderer/animations/transition-orchestrator.ts
|
|
879
|
+
async function applyTransition(box, screen, content, transition, theme) {
|
|
880
|
+
switch (transition) {
|
|
881
|
+
case "glitch":
|
|
882
|
+
await lineByLineReveal(box, screen, content, theme);
|
|
883
|
+
break;
|
|
884
|
+
case "fade":
|
|
885
|
+
await fadeInReveal(box, screen, content, theme);
|
|
886
|
+
break;
|
|
887
|
+
case "instant":
|
|
888
|
+
instantReveal(box, screen, content);
|
|
889
|
+
break;
|
|
890
|
+
case "typewriter":
|
|
891
|
+
await typewriterReveal(box, screen, content, theme);
|
|
892
|
+
break;
|
|
893
|
+
default:
|
|
894
|
+
instantReveal(box, screen, content);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
var init_transition_orchestrator = __esm({
|
|
898
|
+
"src/renderer/animations/transition-orchestrator.ts"() {
|
|
899
|
+
init_esm_shims();
|
|
900
|
+
init_glitch_transition();
|
|
901
|
+
init_fade_transition();
|
|
902
|
+
init_typewriter_transition();
|
|
903
|
+
init_instant_transition();
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
// src/renderer/animations/transitions.ts
|
|
908
|
+
var init_transitions = __esm({
|
|
909
|
+
"src/renderer/animations/transitions.ts"() {
|
|
910
|
+
init_esm_shims();
|
|
911
|
+
init_transition_orchestrator();
|
|
912
|
+
init_glitch_transition();
|
|
913
|
+
init_fade_transition();
|
|
914
|
+
init_typewriter_transition();
|
|
915
|
+
init_instant_transition();
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
async function generateBigText(text, gradientColors, font = "Standard") {
|
|
919
|
+
return new Promise((resolve, reject) => {
|
|
920
|
+
figlet.text(text, { font }, (err, result) => {
|
|
921
|
+
if (err || !result) {
|
|
922
|
+
reject(err ?? new Error("Failed to generate figlet text"));
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
const gradientFn = gradient2(gradientColors);
|
|
926
|
+
resolve(gradientFn(result));
|
|
957
927
|
});
|
|
958
928
|
});
|
|
959
929
|
}
|
|
960
|
-
function
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
930
|
+
async function generateMultiLineBigText(lines, gradientColors, font = "Standard") {
|
|
931
|
+
const results = await Promise.all(
|
|
932
|
+
lines.map((line) => generateBigText(line, gradientColors, font))
|
|
933
|
+
);
|
|
934
|
+
return results.join("\n");
|
|
935
|
+
}
|
|
936
|
+
var init_text_generator = __esm({
|
|
937
|
+
"src/renderer/text-generator.ts"() {
|
|
938
|
+
init_esm_shims();
|
|
964
939
|
}
|
|
965
|
-
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
// src/renderer/slide-renderer.ts
|
|
943
|
+
async function renderSlide(screen, windowStack, theme, slide) {
|
|
944
|
+
const { frontmatter, body } = slide;
|
|
945
|
+
const window = createWindow(screen, windowStack, theme, {
|
|
946
|
+
title: frontmatter.title
|
|
947
|
+
});
|
|
948
|
+
let content = "";
|
|
949
|
+
const bigTextLines = normalizeBigText(frontmatter.bigText);
|
|
950
|
+
if (bigTextLines.length > 0) {
|
|
951
|
+
const gradientName = frontmatter.gradient ?? "fire";
|
|
952
|
+
const gradientColors = theme.gradients[gradientName] ?? theme.gradients.fire;
|
|
953
|
+
const bigText = await generateMultiLineBigText(bigTextLines, gradientColors);
|
|
954
|
+
content += bigText + "\n\n";
|
|
955
|
+
}
|
|
956
|
+
const processedBody = await processSlideContent(body, theme);
|
|
957
|
+
content += processedBody;
|
|
958
|
+
const transition = frontmatter.transition ?? "glitch";
|
|
959
|
+
await applyTransition(window, screen, content, transition, theme);
|
|
960
|
+
return window;
|
|
961
|
+
}
|
|
962
|
+
var init_slide_renderer = __esm({
|
|
963
|
+
"src/renderer/slide-renderer.ts"() {
|
|
964
|
+
init_esm_shims();
|
|
965
|
+
init_content_processor();
|
|
966
|
+
init_transitions();
|
|
967
|
+
init_text_generator();
|
|
968
|
+
init_window_manager();
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
function createScreen(title = "term-deck") {
|
|
972
|
+
const screen = blessed.screen({
|
|
973
|
+
smartCSR: true,
|
|
974
|
+
title,
|
|
975
|
+
fullUnicode: true,
|
|
976
|
+
mouse: false,
|
|
977
|
+
altScreen: true
|
|
978
|
+
});
|
|
979
|
+
return screen;
|
|
980
|
+
}
|
|
981
|
+
function createRenderer(theme) {
|
|
982
|
+
const screen = createScreen();
|
|
983
|
+
const matrixBox = createMatrixBox(screen);
|
|
984
|
+
const matrixRain = {
|
|
985
|
+
matrixBox,
|
|
986
|
+
matrixDrops: [],
|
|
987
|
+
matrixInterval: null,
|
|
988
|
+
theme
|
|
989
|
+
};
|
|
990
|
+
const renderer = {
|
|
991
|
+
screen,
|
|
992
|
+
windowStack: [],
|
|
993
|
+
theme,
|
|
994
|
+
matrixRain
|
|
995
|
+
};
|
|
996
|
+
initMatrixRain(screen, matrixRain);
|
|
997
|
+
return renderer;
|
|
998
|
+
}
|
|
999
|
+
function destroyRenderer(renderer) {
|
|
1000
|
+
stopMatrixRain(renderer.matrixRain);
|
|
1001
|
+
clearWindows(renderer.windowStack);
|
|
1002
|
+
renderer.screen.destroy();
|
|
1003
|
+
}
|
|
1004
|
+
function clearWindows2(renderer) {
|
|
1005
|
+
clearWindows(renderer.windowStack);
|
|
1006
|
+
}
|
|
1007
|
+
async function renderSlide2(renderer, slide) {
|
|
1008
|
+
return renderSlide(renderer.screen, renderer.windowStack, renderer.theme, slide);
|
|
1009
|
+
}
|
|
1010
|
+
var init_screen = __esm({
|
|
1011
|
+
"src/renderer/screen.ts"() {
|
|
1012
|
+
init_esm_shims();
|
|
1013
|
+
init_matrix_rain();
|
|
1014
|
+
init_window_manager();
|
|
1015
|
+
init_slide_renderer();
|
|
1016
|
+
init_transitions();
|
|
1017
|
+
init_window_manager();
|
|
1018
|
+
init_text_generator();
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
async function findAvailableTty() {
|
|
1022
|
+
const candidates = [
|
|
1023
|
+
"/dev/ttys001",
|
|
1024
|
+
"/dev/ttys002",
|
|
1025
|
+
"/dev/ttys003",
|
|
1026
|
+
"/dev/pts/1",
|
|
1027
|
+
"/dev/pts/2"
|
|
1028
|
+
];
|
|
1029
|
+
for (const tty of candidates) {
|
|
1030
|
+
try {
|
|
1031
|
+
await access(tty);
|
|
1032
|
+
return tty;
|
|
1033
|
+
} catch {
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
throw new Error(
|
|
1037
|
+
"Could not find available TTY for notes window. Open a second terminal, run `tty`, and pass the path with --notes-tty"
|
|
1038
|
+
);
|
|
966
1039
|
}
|
|
1040
|
+
async function createNotesWindow(ttyPath) {
|
|
1041
|
+
const blessed5 = (await import('neo-blessed')).default;
|
|
1042
|
+
const { openSync } = await import('fs');
|
|
1043
|
+
const tty = ttyPath ?? await findAvailableTty();
|
|
1044
|
+
const screen = blessed5.screen({
|
|
1045
|
+
smartCSR: true,
|
|
1046
|
+
title: "term-deck notes",
|
|
1047
|
+
fullUnicode: true,
|
|
1048
|
+
input: openSync(tty, "r"),
|
|
1049
|
+
output: openSync(tty, "w")
|
|
1050
|
+
});
|
|
1051
|
+
const contentBox = blessed5.box({
|
|
1052
|
+
top: 0,
|
|
1053
|
+
left: 0,
|
|
1054
|
+
width: "100%",
|
|
1055
|
+
height: "100%",
|
|
1056
|
+
tags: true,
|
|
1057
|
+
padding: 2,
|
|
1058
|
+
style: {
|
|
1059
|
+
fg: "#ffffff",
|
|
1060
|
+
bg: "#1a1a1a"
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
screen.append(contentBox);
|
|
1064
|
+
screen.render();
|
|
1065
|
+
return {
|
|
1066
|
+
screen,
|
|
1067
|
+
contentBox,
|
|
1068
|
+
tty
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function updateNotesWindow(notesWindow, currentSlide, nextSlide2, currentIndex, totalSlides) {
|
|
1072
|
+
const { contentBox, screen } = notesWindow;
|
|
1073
|
+
let content = "";
|
|
1074
|
+
content += `{bold}Slide ${currentIndex + 1} of ${totalSlides}{/bold}
|
|
1075
|
+
`;
|
|
1076
|
+
content += `{gray-fg}${currentSlide.frontmatter.title}{/}
|
|
1077
|
+
`;
|
|
1078
|
+
content += "\n";
|
|
1079
|
+
content += "\u2500".repeat(50) + "\n";
|
|
1080
|
+
content += "\n";
|
|
1081
|
+
if (currentSlide.notes) {
|
|
1082
|
+
content += "{bold}PRESENTER NOTES:{/bold}\n\n";
|
|
1083
|
+
content += currentSlide.notes + "\n";
|
|
1084
|
+
} else {
|
|
1085
|
+
content += "{gray-fg}No notes for this slide{/}\n";
|
|
1086
|
+
}
|
|
1087
|
+
content += "\n";
|
|
1088
|
+
content += "\u2500".repeat(50) + "\n";
|
|
1089
|
+
content += "\n";
|
|
1090
|
+
if (nextSlide2) {
|
|
1091
|
+
content += `{bold}NEXT:{/bold} "${nextSlide2.frontmatter.title}"
|
|
1092
|
+
`;
|
|
1093
|
+
} else {
|
|
1094
|
+
content += "{gray-fg}Last slide{/}\n";
|
|
1095
|
+
}
|
|
1096
|
+
contentBox.setContent(content);
|
|
1097
|
+
screen.render();
|
|
1098
|
+
}
|
|
1099
|
+
function toggleNotesVisibility(notesWindow) {
|
|
1100
|
+
const { contentBox, screen } = notesWindow;
|
|
1101
|
+
contentBox.toggle();
|
|
1102
|
+
screen.render();
|
|
1103
|
+
}
|
|
1104
|
+
function destroyNotesWindow(notesWindow) {
|
|
1105
|
+
notesWindow.screen.destroy();
|
|
1106
|
+
}
|
|
1107
|
+
var init_notes_window = __esm({
|
|
1108
|
+
"src/presenter/notes-window.ts"() {
|
|
1109
|
+
init_esm_shims();
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
// src/presenter/navigation.ts
|
|
967
1114
|
async function showSlide(presenter, index) {
|
|
968
1115
|
if (presenter.isAnimating) return;
|
|
969
1116
|
if (index < 0 || index >= presenter.deck.slides.length) return;
|
|
970
1117
|
presenter.isAnimating = true;
|
|
971
1118
|
presenter.currentSlide = index;
|
|
972
1119
|
const slide = presenter.deck.slides[index];
|
|
973
|
-
await
|
|
1120
|
+
await renderSlide2(presenter.renderer, slide);
|
|
974
1121
|
presenter.renderer.screen.render();
|
|
975
1122
|
if (presenter.notesWindow) {
|
|
976
|
-
|
|
1123
|
+
const nextSlide2 = presenter.deck.slides[index + 1];
|
|
1124
|
+
updateNotesWindow(
|
|
1125
|
+
presenter.notesWindow,
|
|
1126
|
+
slide,
|
|
1127
|
+
nextSlide2,
|
|
1128
|
+
index,
|
|
1129
|
+
presenter.deck.slides.length
|
|
1130
|
+
);
|
|
977
1131
|
}
|
|
978
1132
|
if (presenter.progressBar) {
|
|
979
1133
|
updateProgress(presenter.progressBar, presenter.currentSlide, presenter.deck.slides.length);
|
|
@@ -998,49 +1152,64 @@ async function prevSlide(presenter) {
|
|
|
998
1152
|
const loop = presenter.deck.config.settings?.loop ?? false;
|
|
999
1153
|
if (prevIndex < 0) {
|
|
1000
1154
|
if (loop) {
|
|
1001
|
-
|
|
1155
|
+
clearWindows2(presenter.renderer);
|
|
1002
1156
|
for (let i = 0; i < slides.length; i++) {
|
|
1003
|
-
await
|
|
1157
|
+
await renderSlide2(presenter.renderer, slides[i]);
|
|
1004
1158
|
}
|
|
1005
1159
|
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
|
-
}
|
|
1160
|
+
updateUIComponents(presenter, slides.length - 1);
|
|
1012
1161
|
presenter.renderer.screen.render();
|
|
1013
1162
|
}
|
|
1014
1163
|
return;
|
|
1015
1164
|
}
|
|
1016
|
-
|
|
1165
|
+
clearWindows2(presenter.renderer);
|
|
1017
1166
|
for (let i = 0; i <= prevIndex; i++) {
|
|
1018
|
-
await
|
|
1167
|
+
await renderSlide2(presenter.renderer, slides[i]);
|
|
1019
1168
|
}
|
|
1020
1169
|
presenter.currentSlide = prevIndex;
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1170
|
+
updateUIComponents(presenter, prevIndex);
|
|
1171
|
+
presenter.renderer.screen.render();
|
|
1172
|
+
}
|
|
1173
|
+
async function jumpToSlide(presenter, index) {
|
|
1174
|
+
if (index < 0 || index >= presenter.deck.slides.length) return;
|
|
1175
|
+
clearWindows2(presenter.renderer);
|
|
1176
|
+
for (let i = 0; i <= index; i++) {
|
|
1177
|
+
await renderSlide2(presenter.renderer, presenter.deck.slides[i]);
|
|
1026
1178
|
}
|
|
1179
|
+
presenter.currentSlide = index;
|
|
1180
|
+
updateUIComponents(presenter, index);
|
|
1027
1181
|
presenter.renderer.screen.render();
|
|
1028
1182
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1183
|
+
function updateProgress(progressBar, current, total) {
|
|
1184
|
+
const progress = (current + 1) / total * 100;
|
|
1185
|
+
progressBar.setProgress(progress);
|
|
1186
|
+
}
|
|
1187
|
+
function updateUIComponents(presenter, currentIndex) {
|
|
1188
|
+
const { slides } = presenter.deck;
|
|
1189
|
+
const currentSlide = slides[currentIndex];
|
|
1190
|
+
const nextSlide2 = slides[currentIndex + 1];
|
|
1036
1191
|
if (presenter.notesWindow) {
|
|
1037
|
-
updateNotesWindow(
|
|
1192
|
+
updateNotesWindow(
|
|
1193
|
+
presenter.notesWindow,
|
|
1194
|
+
currentSlide,
|
|
1195
|
+
nextSlide2,
|
|
1196
|
+
currentIndex,
|
|
1197
|
+
slides.length
|
|
1198
|
+
);
|
|
1038
1199
|
}
|
|
1039
1200
|
if (presenter.progressBar) {
|
|
1040
|
-
updateProgress(presenter.progressBar,
|
|
1201
|
+
updateProgress(presenter.progressBar, currentIndex, slides.length);
|
|
1041
1202
|
}
|
|
1042
|
-
presenter.renderer.screen.render();
|
|
1043
1203
|
}
|
|
1204
|
+
var init_navigation = __esm({
|
|
1205
|
+
"src/presenter/navigation.ts"() {
|
|
1206
|
+
init_esm_shims();
|
|
1207
|
+
init_screen();
|
|
1208
|
+
init_notes_window();
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
// src/presenter/keyboard-controls.ts
|
|
1044
1213
|
function setupControls(presenter) {
|
|
1045
1214
|
const { screen } = presenter.renderer;
|
|
1046
1215
|
screen.key(["space", "enter", "right", "n"], () => {
|
|
@@ -1097,93 +1266,61 @@ function showSlideList(presenter) {
|
|
|
1097
1266
|
jumpToSlide(presenter, parseInt(ch ?? "0", 10));
|
|
1098
1267
|
});
|
|
1099
1268
|
}
|
|
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
|
-
}
|
|
1269
|
+
var init_keyboard_controls = __esm({
|
|
1270
|
+
"src/presenter/keyboard-controls.ts"() {
|
|
1271
|
+
init_esm_shims();
|
|
1272
|
+
init_navigation();
|
|
1273
|
+
init_notes_window();
|
|
1119
1274
|
}
|
|
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
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
// src/presenter/main.ts
|
|
1278
|
+
var main_exports = {};
|
|
1279
|
+
__export(main_exports, {
|
|
1280
|
+
jumpToSlide: () => jumpToSlide,
|
|
1281
|
+
present: () => present,
|
|
1282
|
+
prevSlide: () => prevSlide
|
|
1283
|
+
});
|
|
1284
|
+
async function present(slidesDir, options = {}) {
|
|
1285
|
+
const deck = await loadDeck(slidesDir);
|
|
1286
|
+
if (deck.slides.length === 0) {
|
|
1287
|
+
throw new Error(`No slides found in ${slidesDir}`);
|
|
1288
|
+
}
|
|
1289
|
+
const renderer = createRenderer(deck.config.theme);
|
|
1290
|
+
const presenter = {
|
|
1291
|
+
deck,
|
|
1292
|
+
renderer,
|
|
1293
|
+
currentSlide: options.startSlide ?? deck.config.settings?.startSlide ?? 0,
|
|
1294
|
+
isAnimating: false,
|
|
1295
|
+
notesWindow: null,
|
|
1296
|
+
autoAdvanceTimer: null,
|
|
1297
|
+
progressBar: null
|
|
1153
1298
|
};
|
|
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";
|
|
1299
|
+
if (options.showNotes) {
|
|
1300
|
+
presenter.notesWindow = await createNotesWindow(options.notesTty);
|
|
1175
1301
|
}
|
|
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";
|
|
1302
|
+
if (deck.config.settings?.showProgress) {
|
|
1303
|
+
presenter.progressBar = createProgressBar(presenter);
|
|
1184
1304
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1305
|
+
setupControls(presenter);
|
|
1306
|
+
await showSlide(presenter, presenter.currentSlide);
|
|
1307
|
+
if (presenter.progressBar) {
|
|
1308
|
+
updateProgress(presenter.progressBar, presenter.currentSlide, deck.slides.length);
|
|
1309
|
+
}
|
|
1310
|
+
presenter.autoAdvanceTimer = startAutoAdvance(presenter);
|
|
1311
|
+
await new Promise((resolve) => {
|
|
1312
|
+
renderer.screen.key(["q", "C-c", "escape"], () => {
|
|
1313
|
+
cleanup(presenter);
|
|
1314
|
+
resolve();
|
|
1315
|
+
});
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
function cleanup(presenter) {
|
|
1319
|
+
stopAutoAdvance(presenter.autoAdvanceTimer);
|
|
1320
|
+
if (presenter.notesWindow) {
|
|
1321
|
+
destroyNotesWindow(presenter.notesWindow);
|
|
1322
|
+
}
|
|
1323
|
+
destroyRenderer(presenter.renderer);
|
|
1187
1324
|
}
|
|
1188
1325
|
function startAutoAdvance(presenter) {
|
|
1189
1326
|
const interval = presenter.deck.config.settings?.autoAdvance;
|
|
@@ -1217,15 +1354,15 @@ function createProgressBar(presenter) {
|
|
|
1217
1354
|
screen.append(progressBar);
|
|
1218
1355
|
return progressBar;
|
|
1219
1356
|
}
|
|
1220
|
-
function updateProgress(progressBar, current, total) {
|
|
1221
|
-
const progress = (current + 1) / total * 100;
|
|
1222
|
-
progressBar.setProgress(progress);
|
|
1223
|
-
}
|
|
1224
1357
|
var init_main = __esm({
|
|
1225
1358
|
"src/presenter/main.ts"() {
|
|
1226
1359
|
init_esm_shims();
|
|
1227
|
-
|
|
1360
|
+
init_deck_loader();
|
|
1228
1361
|
init_screen();
|
|
1362
|
+
init_notes_window();
|
|
1363
|
+
init_keyboard_controls();
|
|
1364
|
+
init_navigation();
|
|
1365
|
+
init_navigation();
|
|
1229
1366
|
}
|
|
1230
1367
|
});
|
|
1231
1368
|
|
|
@@ -1233,7 +1370,7 @@ var init_main = __esm({
|
|
|
1233
1370
|
init_esm_shims();
|
|
1234
1371
|
|
|
1235
1372
|
// package.json
|
|
1236
|
-
var version = "1.0.
|
|
1373
|
+
var version = "1.0.16";
|
|
1237
1374
|
|
|
1238
1375
|
// src/cli/commands/present.ts
|
|
1239
1376
|
init_esm_shims();
|
|
@@ -1243,6 +1380,7 @@ init_main();
|
|
|
1243
1380
|
init_esm_shims();
|
|
1244
1381
|
init_validation();
|
|
1245
1382
|
init_slide2();
|
|
1383
|
+
init_deck_loader();
|
|
1246
1384
|
init_theme2();
|
|
1247
1385
|
function handleError(error) {
|
|
1248
1386
|
if (error instanceof ValidationError) {
|
|
@@ -1316,6 +1454,44 @@ init_esm_shims();
|
|
|
1316
1454
|
|
|
1317
1455
|
// src/export/recorder.ts
|
|
1318
1456
|
init_esm_shims();
|
|
1457
|
+
|
|
1458
|
+
// src/export/recording-session.ts
|
|
1459
|
+
init_esm_shims();
|
|
1460
|
+
async function createRecordingSession(options) {
|
|
1461
|
+
const tempDir = join(tmpdir(), `term-deck-export-${Date.now()}`);
|
|
1462
|
+
await mkdir(tempDir, { recursive: true });
|
|
1463
|
+
return {
|
|
1464
|
+
tempDir,
|
|
1465
|
+
frameCount: 0,
|
|
1466
|
+
width: options.width ?? 120,
|
|
1467
|
+
height: options.height ?? 40,
|
|
1468
|
+
fps: options.fps ?? 30
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
async function saveFrame(session, png) {
|
|
1472
|
+
const frameNum = session.frameCount.toString().padStart(6, "0");
|
|
1473
|
+
const framePath = join(session.tempDir, `frame_${frameNum}.png`);
|
|
1474
|
+
await writeFile(framePath, png);
|
|
1475
|
+
session.frameCount++;
|
|
1476
|
+
}
|
|
1477
|
+
async function cleanupSession(session) {
|
|
1478
|
+
await rm(session.tempDir, { recursive: true, force: true });
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// src/export/presentation-exporter.ts
|
|
1482
|
+
init_esm_shims();
|
|
1483
|
+
init_deck_loader();
|
|
1484
|
+
init_screen();
|
|
1485
|
+
|
|
1486
|
+
// src/renderer/types/screen.ts
|
|
1487
|
+
init_esm_shims();
|
|
1488
|
+
function setScreenDimensions(screen, width, height) {
|
|
1489
|
+
screen.width = width;
|
|
1490
|
+
screen.height = height;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// src/export/utils/virtual-terminal.ts
|
|
1494
|
+
init_esm_shims();
|
|
1319
1495
|
var VirtualTerminal = class {
|
|
1320
1496
|
constructor(width, height) {
|
|
1321
1497
|
this.width = width;
|
|
@@ -1332,7 +1508,7 @@ var VirtualTerminal = class {
|
|
|
1332
1508
|
buffer;
|
|
1333
1509
|
colors;
|
|
1334
1510
|
/**
|
|
1335
|
-
* Set character at position
|
|
1511
|
+
* Set character at position with optional color
|
|
1336
1512
|
*/
|
|
1337
1513
|
setChar(x, y, char, color = "#ffffff") {
|
|
1338
1514
|
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
|
|
@@ -1341,7 +1517,7 @@ var VirtualTerminal = class {
|
|
|
1341
1517
|
}
|
|
1342
1518
|
}
|
|
1343
1519
|
/**
|
|
1344
|
-
* Clear the buffer
|
|
1520
|
+
* Clear the buffer to blank spaces
|
|
1345
1521
|
*/
|
|
1346
1522
|
clear() {
|
|
1347
1523
|
for (let y = 0; y < this.height; y++) {
|
|
@@ -1352,11 +1528,23 @@ var VirtualTerminal = class {
|
|
|
1352
1528
|
}
|
|
1353
1529
|
}
|
|
1354
1530
|
/**
|
|
1355
|
-
* Get buffer as
|
|
1531
|
+
* Get buffer contents as plain text string
|
|
1356
1532
|
*/
|
|
1357
1533
|
toString() {
|
|
1358
1534
|
return this.buffer.map((row) => row.join("")).join("\n");
|
|
1359
1535
|
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Get the raw character buffer
|
|
1538
|
+
*/
|
|
1539
|
+
getBuffer() {
|
|
1540
|
+
return this.buffer;
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Get the color buffer
|
|
1544
|
+
*/
|
|
1545
|
+
getColors() {
|
|
1546
|
+
return this.colors;
|
|
1547
|
+
}
|
|
1360
1548
|
/**
|
|
1361
1549
|
* Convert buffer to PNG image data
|
|
1362
1550
|
*/
|
|
@@ -1386,33 +1574,12 @@ async function renderTerminalToPng(buffer, colors, width, height) {
|
|
|
1386
1574
|
}
|
|
1387
1575
|
return canvas.toBuffer("image/png");
|
|
1388
1576
|
}
|
|
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
|
-
}
|
|
1577
|
+
|
|
1578
|
+
// src/export/capture/screen-capture.ts
|
|
1579
|
+
init_esm_shims();
|
|
1580
|
+
|
|
1581
|
+
// src/export/utils/color-conversion.ts
|
|
1582
|
+
init_esm_shims();
|
|
1416
1583
|
function ansi256ToHex(code) {
|
|
1417
1584
|
const standard16 = [
|
|
1418
1585
|
"#000000",
|
|
@@ -1446,31 +1613,38 @@ function ansi256ToHex(code) {
|
|
|
1446
1613
|
const hex = gray.toString(16).padStart(2, "0");
|
|
1447
1614
|
return `#${hex}${hex}${hex}`;
|
|
1448
1615
|
}
|
|
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++;
|
|
1616
|
+
function extractColor(attr) {
|
|
1617
|
+
if (!attr) return null;
|
|
1618
|
+
if (typeof attr === "object" && attr.fg !== void 0) {
|
|
1619
|
+
if (typeof attr.fg === "string" && attr.fg.startsWith("#")) {
|
|
1620
|
+
return attr.fg;
|
|
1621
|
+
}
|
|
1622
|
+
if (typeof attr.fg === "number") {
|
|
1623
|
+
return ansi256ToHex(attr.fg);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return null;
|
|
1469
1627
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1628
|
+
|
|
1629
|
+
// src/export/capture/screen-capture.ts
|
|
1630
|
+
function captureScreen(screen, vt) {
|
|
1631
|
+
const lines = screen.lines || [];
|
|
1632
|
+
for (let y = 0; y < Math.min(lines.length, vt.height); y++) {
|
|
1633
|
+
const line = lines[y];
|
|
1634
|
+
if (!line) continue;
|
|
1635
|
+
for (let x = 0; x < Math.min(line.length, vt.width); x++) {
|
|
1636
|
+
const cell = line[x];
|
|
1637
|
+
if (!cell) continue;
|
|
1638
|
+
const char = Array.isArray(cell) ? cell[0] : cell;
|
|
1639
|
+
const attr = Array.isArray(cell) ? cell[1] : null;
|
|
1640
|
+
const color = extractColor(attr) || "#ffffff";
|
|
1641
|
+
vt.setChar(x, y, char || " ", color);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1473
1644
|
}
|
|
1645
|
+
|
|
1646
|
+
// src/export/encoding/ffmpeg-encoder.ts
|
|
1647
|
+
init_esm_shims();
|
|
1474
1648
|
async function checkFfmpeg() {
|
|
1475
1649
|
try {
|
|
1476
1650
|
await execa("which", ["ffmpeg"]);
|
|
@@ -1487,36 +1661,38 @@ function detectFormat(output) {
|
|
|
1487
1661
|
`Unknown output format for ${output}. Use .mp4 or .gif extension.`
|
|
1488
1662
|
);
|
|
1489
1663
|
}
|
|
1490
|
-
async function encodeVideo(
|
|
1491
|
-
const {
|
|
1492
|
-
const inputPattern = join3(session.tempDir, "frame_%06d.png");
|
|
1664
|
+
async function encodeVideo(options) {
|
|
1665
|
+
const { inputPattern, output, format, fps, quality } = options;
|
|
1493
1666
|
if (format === "mp4") {
|
|
1494
|
-
await encodeMp4(inputPattern, output,
|
|
1667
|
+
await encodeMp4(inputPattern, output, fps, quality ?? 80);
|
|
1495
1668
|
} else {
|
|
1496
|
-
await encodeGif(inputPattern, output,
|
|
1669
|
+
await encodeGif(inputPattern, output, fps);
|
|
1497
1670
|
}
|
|
1498
1671
|
}
|
|
1499
1672
|
async function encodeMp4(input, output, fps, quality) {
|
|
1500
1673
|
const crf = Math.round(51 - quality / 100 * 33);
|
|
1501
1674
|
await execa("ffmpeg", [
|
|
1502
1675
|
"-y",
|
|
1676
|
+
// Overwrite output file
|
|
1503
1677
|
"-framerate",
|
|
1504
1678
|
fps.toString(),
|
|
1505
1679
|
"-i",
|
|
1506
1680
|
input,
|
|
1507
1681
|
"-c:v",
|
|
1508
1682
|
"libx264",
|
|
1683
|
+
// H.264 codec
|
|
1509
1684
|
"-crf",
|
|
1510
1685
|
crf.toString(),
|
|
1511
1686
|
"-pix_fmt",
|
|
1512
1687
|
"yuv420p",
|
|
1688
|
+
// Pixel format for compatibility
|
|
1513
1689
|
output
|
|
1514
1690
|
]);
|
|
1515
1691
|
}
|
|
1516
1692
|
async function encodeGif(input, output, fps) {
|
|
1517
|
-
const { tmpdir } = await import('os');
|
|
1518
|
-
const { join:
|
|
1519
|
-
const paletteFile =
|
|
1693
|
+
const { tmpdir: tmpdir2 } = await import('os');
|
|
1694
|
+
const { join: join5 } = await import('path');
|
|
1695
|
+
const paletteFile = join5(tmpdir2(), `palette-${Date.now()}.png`);
|
|
1520
1696
|
try {
|
|
1521
1697
|
await execa("ffmpeg", [
|
|
1522
1698
|
"-y",
|
|
@@ -1547,20 +1723,29 @@ async function encodeGif(input, output, fps) {
|
|
|
1547
1723
|
}
|
|
1548
1724
|
}
|
|
1549
1725
|
}
|
|
1726
|
+
|
|
1727
|
+
// src/export/presentation-exporter.ts
|
|
1728
|
+
async function encodeFramesToVideo(tempDir, output, format, fps, quality) {
|
|
1729
|
+
const inputPattern = join(tempDir, "frame_%06d.png");
|
|
1730
|
+
await encodeVideo({
|
|
1731
|
+
inputPattern,
|
|
1732
|
+
output,
|
|
1733
|
+
format,
|
|
1734
|
+
fps,
|
|
1735
|
+
quality
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1550
1738
|
async function exportPresentation(slidesDir, options) {
|
|
1551
1739
|
await checkFfmpeg();
|
|
1552
1740
|
const format = detectFormat(options.output);
|
|
1553
|
-
const
|
|
1554
|
-
const deck = await loadDeck2(slidesDir);
|
|
1741
|
+
const deck = await loadDeck(slidesDir);
|
|
1555
1742
|
if (deck.slides.length === 0) {
|
|
1556
1743
|
throw new Error(`No slides found in ${slidesDir}`);
|
|
1557
1744
|
}
|
|
1558
1745
|
const session = await createRecordingSession(options);
|
|
1559
1746
|
const vt = new VirtualTerminal(session.width, session.height);
|
|
1560
|
-
const
|
|
1561
|
-
|
|
1562
|
-
renderer.screen.width = session.width;
|
|
1563
|
-
renderer.screen.height = session.height;
|
|
1747
|
+
const renderer = createRenderer(deck.config.theme);
|
|
1748
|
+
setScreenDimensions(renderer.screen, session.width, session.height);
|
|
1564
1749
|
const slideTime = options.slideTime ?? 3;
|
|
1565
1750
|
const framesPerSlide = session.fps * slideTime;
|
|
1566
1751
|
console.log(`Exporting ${deck.slides.length} slides...`);
|
|
@@ -1577,14 +1762,25 @@ async function exportPresentation(slidesDir, options) {
|
|
|
1577
1762
|
}
|
|
1578
1763
|
}
|
|
1579
1764
|
console.log("Encoding video...");
|
|
1580
|
-
await
|
|
1765
|
+
await encodeFramesToVideo(
|
|
1766
|
+
session.tempDir,
|
|
1767
|
+
options.output,
|
|
1768
|
+
format,
|
|
1769
|
+
session.fps,
|
|
1770
|
+
options.quality
|
|
1771
|
+
);
|
|
1581
1772
|
console.log(`Exported to ${options.output}`);
|
|
1582
1773
|
} finally {
|
|
1583
|
-
|
|
1774
|
+
destroyRenderer(renderer);
|
|
1584
1775
|
await cleanupSession(session);
|
|
1585
1776
|
}
|
|
1586
1777
|
}
|
|
1587
1778
|
|
|
1779
|
+
// src/export/ansi-recorder.ts
|
|
1780
|
+
init_esm_shims();
|
|
1781
|
+
init_deck_loader();
|
|
1782
|
+
init_screen();
|
|
1783
|
+
|
|
1588
1784
|
// src/cli/commands/export.ts
|
|
1589
1785
|
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
1786
|
try {
|