docrev 0.8.1 → 0.8.5
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/.claude/settings.local.json +9 -0
- package/PLAN-tables-and-postprocess.md +850 -0
- package/README.md +33 -0
- package/bin/rev.js +12 -131
- package/bin/rev.ts +145 -0
- package/dist/bin/rev.d.ts +9 -0
- package/dist/bin/rev.d.ts.map +1 -0
- package/dist/bin/rev.js +118 -0
- package/dist/bin/rev.js.map +1 -0
- package/dist/lib/annotations.d.ts +91 -0
- package/dist/lib/annotations.d.ts.map +1 -0
- package/dist/lib/annotations.js +554 -0
- package/dist/lib/annotations.js.map +1 -0
- package/dist/lib/build.d.ts +171 -0
- package/dist/lib/build.d.ts.map +1 -0
- package/dist/lib/build.js +755 -0
- package/dist/lib/build.js.map +1 -0
- package/dist/lib/citations.d.ts +34 -0
- package/dist/lib/citations.d.ts.map +1 -0
- package/dist/lib/citations.js +140 -0
- package/dist/lib/citations.js.map +1 -0
- package/dist/lib/commands/build.d.ts +13 -0
- package/dist/lib/commands/build.d.ts.map +1 -0
- package/dist/lib/commands/build.js +678 -0
- package/dist/lib/commands/build.js.map +1 -0
- package/dist/lib/commands/citations.d.ts +11 -0
- package/dist/lib/commands/citations.d.ts.map +1 -0
- package/dist/lib/commands/citations.js +428 -0
- package/dist/lib/commands/citations.js.map +1 -0
- package/dist/lib/commands/comments.d.ts +11 -0
- package/dist/lib/commands/comments.d.ts.map +1 -0
- package/dist/lib/commands/comments.js +883 -0
- package/dist/lib/commands/comments.js.map +1 -0
- package/dist/lib/commands/context.d.ts +35 -0
- package/dist/lib/commands/context.d.ts.map +1 -0
- package/dist/lib/commands/context.js +59 -0
- package/dist/lib/commands/context.js.map +1 -0
- package/dist/lib/commands/core.d.ts +11 -0
- package/dist/lib/commands/core.d.ts.map +1 -0
- package/dist/lib/commands/core.js +246 -0
- package/dist/lib/commands/core.js.map +1 -0
- package/dist/lib/commands/doi.d.ts +11 -0
- package/dist/lib/commands/doi.d.ts.map +1 -0
- package/dist/lib/commands/doi.js +373 -0
- package/dist/lib/commands/doi.js.map +1 -0
- package/dist/lib/commands/history.d.ts +11 -0
- package/dist/lib/commands/history.d.ts.map +1 -0
- package/dist/lib/commands/history.js +245 -0
- package/dist/lib/commands/history.js.map +1 -0
- package/dist/lib/commands/index.d.ts +28 -0
- package/dist/lib/commands/index.d.ts.map +1 -0
- package/dist/lib/commands/index.js +35 -0
- package/dist/lib/commands/index.js.map +1 -0
- package/dist/lib/commands/init.d.ts +11 -0
- package/dist/lib/commands/init.d.ts.map +1 -0
- package/dist/lib/commands/init.js +209 -0
- package/dist/lib/commands/init.js.map +1 -0
- package/dist/lib/commands/response.d.ts +11 -0
- package/dist/lib/commands/response.d.ts.map +1 -0
- package/dist/lib/commands/response.js +317 -0
- package/dist/lib/commands/response.js.map +1 -0
- package/dist/lib/commands/sections.d.ts +11 -0
- package/dist/lib/commands/sections.d.ts.map +1 -0
- package/dist/lib/commands/sections.js +1071 -0
- package/dist/lib/commands/sections.js.map +1 -0
- package/dist/lib/commands/utilities.d.ts +19 -0
- package/dist/lib/commands/utilities.d.ts.map +1 -0
- package/dist/lib/commands/utilities.js +2009 -0
- package/dist/lib/commands/utilities.js.map +1 -0
- package/dist/lib/comment-realign.d.ts +50 -0
- package/dist/lib/comment-realign.d.ts.map +1 -0
- package/dist/lib/comment-realign.js +372 -0
- package/dist/lib/comment-realign.js.map +1 -0
- package/dist/lib/config.d.ts +41 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +76 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/crossref.d.ts +108 -0
- package/dist/lib/crossref.d.ts.map +1 -0
- package/dist/lib/crossref.js +597 -0
- package/dist/lib/crossref.js.map +1 -0
- package/dist/lib/dependencies.d.ts +30 -0
- package/dist/lib/dependencies.d.ts.map +1 -0
- package/dist/lib/dependencies.js +95 -0
- package/dist/lib/dependencies.js.map +1 -0
- package/dist/lib/doi-cache.d.ts +29 -0
- package/dist/lib/doi-cache.d.ts.map +1 -0
- package/dist/lib/doi-cache.js +104 -0
- package/dist/lib/doi-cache.js.map +1 -0
- package/dist/lib/doi.d.ts +65 -0
- package/dist/lib/doi.d.ts.map +1 -0
- package/dist/lib/doi.js +710 -0
- package/dist/lib/doi.js.map +1 -0
- package/dist/lib/equations.d.ts +61 -0
- package/dist/lib/equations.d.ts.map +1 -0
- package/dist/lib/equations.js +445 -0
- package/dist/lib/equations.js.map +1 -0
- package/dist/lib/errors.d.ts +60 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +303 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/format.d.ts +104 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +416 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/git.d.ts +88 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +304 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/grammar.d.ts +62 -0
- package/dist/lib/grammar.d.ts.map +1 -0
- package/dist/lib/grammar.js +244 -0
- package/dist/lib/grammar.js.map +1 -0
- package/dist/lib/image-registry.d.ts +68 -0
- package/dist/lib/image-registry.d.ts.map +1 -0
- package/dist/lib/image-registry.js +112 -0
- package/dist/lib/image-registry.js.map +1 -0
- package/dist/lib/import.d.ts +184 -0
- package/dist/lib/import.d.ts.map +1 -0
- package/dist/lib/import.js +1581 -0
- package/dist/lib/import.js.map +1 -0
- package/dist/lib/journals.d.ts +55 -0
- package/dist/lib/journals.d.ts.map +1 -0
- package/dist/lib/journals.js +417 -0
- package/dist/lib/journals.js.map +1 -0
- package/dist/lib/merge.d.ts +138 -0
- package/dist/lib/merge.d.ts.map +1 -0
- package/dist/lib/merge.js +603 -0
- package/dist/lib/merge.js.map +1 -0
- package/dist/lib/orcid.d.ts +36 -0
- package/dist/lib/orcid.d.ts.map +1 -0
- package/dist/lib/orcid.js +117 -0
- package/dist/lib/orcid.js.map +1 -0
- package/dist/lib/pdf-comments.d.ts +95 -0
- package/dist/lib/pdf-comments.d.ts.map +1 -0
- package/dist/lib/pdf-comments.js +192 -0
- package/dist/lib/pdf-comments.js.map +1 -0
- package/dist/lib/pdf-import.d.ts +118 -0
- package/dist/lib/pdf-import.d.ts.map +1 -0
- package/dist/lib/pdf-import.js +397 -0
- package/dist/lib/pdf-import.js.map +1 -0
- package/dist/lib/plugins.d.ts +76 -0
- package/dist/lib/plugins.d.ts.map +1 -0
- package/dist/lib/plugins.js +235 -0
- package/dist/lib/plugins.js.map +1 -0
- package/dist/lib/postprocess.d.ts +42 -0
- package/dist/lib/postprocess.d.ts.map +1 -0
- package/dist/lib/postprocess.js +138 -0
- package/dist/lib/postprocess.js.map +1 -0
- package/dist/lib/pptx-template.d.ts +59 -0
- package/dist/lib/pptx-template.d.ts.map +1 -0
- package/dist/lib/pptx-template.js +613 -0
- package/dist/lib/pptx-template.js.map +1 -0
- package/dist/lib/pptx-themes.d.ts +80 -0
- package/dist/lib/pptx-themes.d.ts.map +1 -0
- package/dist/lib/pptx-themes.js +818 -0
- package/dist/lib/pptx-themes.js.map +1 -0
- package/dist/lib/protect-restore.d.ts +137 -0
- package/dist/lib/protect-restore.d.ts.map +1 -0
- package/dist/lib/protect-restore.js +394 -0
- package/dist/lib/protect-restore.js.map +1 -0
- package/dist/lib/rate-limiter.d.ts +27 -0
- package/dist/lib/rate-limiter.d.ts.map +1 -0
- package/dist/lib/rate-limiter.js +79 -0
- package/dist/lib/rate-limiter.js.map +1 -0
- package/dist/lib/response.d.ts +41 -0
- package/dist/lib/response.d.ts.map +1 -0
- package/dist/lib/response.js +150 -0
- package/dist/lib/response.js.map +1 -0
- package/dist/lib/review.d.ts +35 -0
- package/dist/lib/review.d.ts.map +1 -0
- package/dist/lib/review.js +263 -0
- package/dist/lib/review.js.map +1 -0
- package/dist/lib/schema.d.ts +66 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +339 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/scientific-words.d.ts +6 -0
- package/dist/lib/scientific-words.d.ts.map +1 -0
- package/dist/lib/scientific-words.js +66 -0
- package/dist/lib/scientific-words.js.map +1 -0
- package/dist/lib/sections.d.ts +40 -0
- package/dist/lib/sections.d.ts.map +1 -0
- package/dist/lib/sections.js +288 -0
- package/dist/lib/sections.js.map +1 -0
- package/dist/lib/slides.d.ts +86 -0
- package/dist/lib/slides.d.ts.map +1 -0
- package/dist/lib/slides.js +676 -0
- package/dist/lib/slides.js.map +1 -0
- package/dist/lib/spelling.d.ts +76 -0
- package/dist/lib/spelling.d.ts.map +1 -0
- package/dist/lib/spelling.js +272 -0
- package/dist/lib/spelling.js.map +1 -0
- package/dist/lib/templates.d.ts +30 -0
- package/dist/lib/templates.d.ts.map +1 -0
- package/dist/lib/templates.js +504 -0
- package/dist/lib/templates.js.map +1 -0
- package/dist/lib/themes.d.ts +85 -0
- package/dist/lib/themes.d.ts.map +1 -0
- package/dist/lib/themes.js +652 -0
- package/dist/lib/themes.js.map +1 -0
- package/dist/lib/trackchanges.d.ts +51 -0
- package/dist/lib/trackchanges.d.ts.map +1 -0
- package/dist/lib/trackchanges.js +202 -0
- package/dist/lib/trackchanges.js.map +1 -0
- package/dist/lib/tui.d.ts +76 -0
- package/dist/lib/tui.d.ts.map +1 -0
- package/dist/lib/tui.js +377 -0
- package/dist/lib/tui.js.map +1 -0
- package/dist/lib/types.d.ts +447 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +6 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/undo.d.ts +57 -0
- package/dist/lib/undo.d.ts.map +1 -0
- package/dist/lib/undo.js +185 -0
- package/dist/lib/undo.js.map +1 -0
- package/dist/lib/utils.d.ts +16 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +40 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/variables.d.ts +42 -0
- package/dist/lib/variables.d.ts.map +1 -0
- package/dist/lib/variables.js +141 -0
- package/dist/lib/variables.js.map +1 -0
- package/dist/lib/word.d.ts +80 -0
- package/dist/lib/word.d.ts.map +1 -0
- package/dist/lib/word.js +360 -0
- package/dist/lib/word.js.map +1 -0
- package/dist/lib/wordcomments.d.ts +51 -0
- package/dist/lib/wordcomments.d.ts.map +1 -0
- package/dist/lib/wordcomments.js +587 -0
- package/dist/lib/wordcomments.js.map +1 -0
- package/eslint.config.js +27 -0
- package/lib/annotations.ts +622 -0
- package/lib/apply-buildup-colors.py +88 -0
- package/lib/build.ts +1013 -0
- package/lib/{citations.js → citations.ts} +38 -27
- package/lib/commands/{build.js → build.ts} +80 -27
- package/lib/commands/{citations.js → citations.ts} +36 -18
- package/lib/commands/{comments.js → comments.ts} +187 -54
- package/lib/commands/{context.js → context.ts} +18 -8
- package/lib/commands/{core.js → core.ts} +34 -20
- package/lib/commands/{doi.js → doi.ts} +32 -16
- package/lib/commands/{history.js → history.ts} +25 -12
- package/lib/commands/{index.js → index.ts} +9 -5
- package/lib/commands/{init.js → init.ts} +20 -8
- package/lib/commands/{response.js → response.ts} +47 -20
- package/lib/commands/{sections.js → sections.ts} +273 -68
- package/lib/commands/{utilities.js → utilities.ts} +338 -158
- package/lib/{comment-realign.js → comment-realign.ts} +117 -45
- package/lib/config.ts +84 -0
- package/lib/{crossref.js → crossref.ts} +213 -138
- package/lib/dependencies.ts +106 -0
- package/lib/doi-cache.ts +115 -0
- package/lib/{doi.js → doi.ts} +115 -281
- package/lib/{equations.js → equations.ts} +60 -64
- package/lib/{errors.js → errors.ts} +56 -48
- package/lib/{format.js → format.ts} +137 -63
- package/lib/{git.js → git.ts} +66 -63
- package/lib/{grammar.js → grammar.ts} +45 -32
- package/lib/image-registry.ts +180 -0
- package/lib/import.ts +2060 -0
- package/lib/journals.ts +505 -0
- package/lib/{merge.js → merge.ts} +185 -135
- package/lib/{orcid.js → orcid.ts} +17 -22
- package/lib/{pdf-comments.js → pdf-comments.ts} +76 -18
- package/lib/{pdf-import.js → pdf-import.ts} +148 -70
- package/lib/{plugins.js → plugins.ts} +82 -39
- package/lib/postprocess.ts +188 -0
- package/lib/pptx-color-filter.lua +37 -0
- package/lib/pptx-template.ts +625 -0
- package/lib/pptx-themes/academic.pptx +0 -0
- package/lib/pptx-themes/corporate.pptx +0 -0
- package/lib/pptx-themes/dark.pptx +0 -0
- package/lib/pptx-themes/default.pptx +0 -0
- package/lib/pptx-themes/minimal.pptx +0 -0
- package/lib/pptx-themes/plant.pptx +0 -0
- package/lib/pptx-themes.ts +896 -0
- package/lib/protect-restore.ts +516 -0
- package/lib/rate-limiter.ts +94 -0
- package/lib/{response.js → response.ts} +36 -21
- package/lib/{review.js → review.ts} +53 -43
- package/lib/{schema.js → schema.ts} +70 -25
- package/lib/{sections.js → sections.ts} +71 -76
- package/lib/slides.ts +793 -0
- package/lib/{spelling.js → spelling.ts} +43 -59
- package/lib/{templates.js → templates.ts} +20 -17
- package/lib/themes.ts +742 -0
- package/lib/{trackchanges.js → trackchanges.ts} +52 -23
- package/lib/types.ts +509 -0
- package/lib/{undo.js → undo.ts} +75 -52
- package/lib/utils.ts +41 -0
- package/lib/{variables.js → variables.ts} +60 -54
- package/lib/word.ts +428 -0
- package/lib/{wordcomments.js → wordcomments.ts} +94 -40
- package/package.json +15 -5
- package/skill/REFERENCE.md +67 -0
- package/tsconfig.json +26 -0
- package/lib/annotations.js +0 -414
- package/lib/build.js +0 -639
- package/lib/config.js +0 -79
- package/lib/import.js +0 -1145
- package/lib/journals.js +0 -629
- package/lib/word.js +0 -225
- /package/lib/{scientific-words.js → scientific-words.ts} +0 -0
package/lib/slides.ts
ADDED
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slide processing for Beamer and PPTX output
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - ::: step blocks for incremental reveals
|
|
6
|
+
* - ::: buildup blocks for progressive bullet reveals with greying
|
|
7
|
+
* - ::: notes blocks for speaker notes
|
|
8
|
+
* - Slide boundaries (---)
|
|
9
|
+
* - Slide styles: {.dark}, {.light}, {.accent}, {.inverse}
|
|
10
|
+
* - Special slides: {.cover}, {.thanks}, {.section}, {.plain}
|
|
11
|
+
*
|
|
12
|
+
* Syntax examples:
|
|
13
|
+
* ## Title {.dark} - Dark background slide
|
|
14
|
+
* ## Welcome {.cover} - Cover slide (no numbering, centered)
|
|
15
|
+
* ## Thank You {.thanks} - Thanks slide (no numbering)
|
|
16
|
+
* # Part 1 {.section} - Section divider slide
|
|
17
|
+
* ## Image {.plain} - No header/footer, full content
|
|
18
|
+
* ## Highlight {.accent .nonumber} - Accent color, no slide number
|
|
19
|
+
*
|
|
20
|
+
* Buildup syntax:
|
|
21
|
+
* ::: buildup
|
|
22
|
+
* - First point
|
|
23
|
+
* - Sub A
|
|
24
|
+
* - Sub B
|
|
25
|
+
* - Second point
|
|
26
|
+
* :::
|
|
27
|
+
*
|
|
28
|
+
* Generates slides where current point is colored, previous are greyed out.
|
|
29
|
+
* Subpoints appear sequentially within their parent.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
interface Step {
|
|
33
|
+
index: number;
|
|
34
|
+
content: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface SlideStyle {
|
|
38
|
+
background: string | null;
|
|
39
|
+
type: string | null;
|
|
40
|
+
nonumber: boolean;
|
|
41
|
+
center: boolean;
|
|
42
|
+
classes: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface Slide {
|
|
46
|
+
title: string;
|
|
47
|
+
titleLevel: number;
|
|
48
|
+
steps: Step[];
|
|
49
|
+
notes: string | null;
|
|
50
|
+
preamble: string;
|
|
51
|
+
style: SlideStyle;
|
|
52
|
+
_frontmatter?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface BulletItem {
|
|
56
|
+
text: string;
|
|
57
|
+
indent: number;
|
|
58
|
+
children: BulletItem[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface BuildupStep {
|
|
62
|
+
itemIndex: number;
|
|
63
|
+
subIndex: number | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface BuildupResult {
|
|
67
|
+
content: string;
|
|
68
|
+
afterContent?: string;
|
|
69
|
+
buildupSteps: string[] | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface StepWithBuildup extends Step {
|
|
73
|
+
beforeBuildup?: string;
|
|
74
|
+
afterBuildup?: string;
|
|
75
|
+
buildupSteps?: string[] | null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Grey color for "completed" buildup items
|
|
80
|
+
*/
|
|
81
|
+
const GREY_COLOR = '#888888';
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Background styles mapped to Beamer options
|
|
85
|
+
*/
|
|
86
|
+
const BEAMER_BACKGROUNDS: Record<string, string> = {
|
|
87
|
+
dark: '\\setbeamercolor{background canvas}{bg=black}\\setbeamercolor{normal text}{fg=white}\\usebeamercolor[fg]{normal text}',
|
|
88
|
+
light: '\\setbeamercolor{background canvas}{bg=white}\\setbeamercolor{normal text}{fg=black}\\usebeamercolor[fg]{normal text}',
|
|
89
|
+
accent: '\\setbeamercolor{background canvas}{bg=structure.fg}\\setbeamercolor{normal text}{fg=white}\\usebeamercolor[fg]{normal text}',
|
|
90
|
+
inverse: '\\setbeamercolor{background canvas}{bg=structure.fg!90!black}\\setbeamercolor{normal text}{fg=white}\\usebeamercolor[fg]{normal text}',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Special slide types mapped to Beamer frame options
|
|
95
|
+
*/
|
|
96
|
+
const BEAMER_FRAME_OPTIONS: Record<string, string> = {
|
|
97
|
+
cover: 'plain,noframenumbering,c',
|
|
98
|
+
thanks: 'plain,noframenumbering,c',
|
|
99
|
+
section: 'plain,noframenumbering,c',
|
|
100
|
+
plain: 'plain',
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Parse a bullet list into a tree structure
|
|
105
|
+
*/
|
|
106
|
+
function parseBulletList(content: string): BulletItem[] {
|
|
107
|
+
const lines = content.split('\n');
|
|
108
|
+
const items: BulletItem[] = [];
|
|
109
|
+
const stack: Array<BulletItem & { children: BulletItem[] }> = [{ children: items, indent: -1 } as any];
|
|
110
|
+
|
|
111
|
+
for (const line of lines) {
|
|
112
|
+
// Match bullet lines: "- text" or " - text" etc.
|
|
113
|
+
const match = line.match(/^(\s*)[-*]\s+(.+)$/);
|
|
114
|
+
if (!match) continue;
|
|
115
|
+
|
|
116
|
+
const indent = (match[1] || '').length;
|
|
117
|
+
const text = (match[2] || '').trim();
|
|
118
|
+
|
|
119
|
+
const item: BulletItem = { text, indent, children: [] };
|
|
120
|
+
|
|
121
|
+
// Find parent based on indentation
|
|
122
|
+
while (stack.length > 1) {
|
|
123
|
+
const top = stack[stack.length - 1];
|
|
124
|
+
if (!top || top.indent < indent) break;
|
|
125
|
+
stack.pop();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const parent = stack[stack.length - 1];
|
|
129
|
+
if (parent) {
|
|
130
|
+
parent.children.push(item);
|
|
131
|
+
}
|
|
132
|
+
stack.push(item as any);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return items;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Flatten bullet tree into sequential reveal steps
|
|
140
|
+
* Each step is: { itemIndex, subIndex, isSubItem }
|
|
141
|
+
*/
|
|
142
|
+
function flattenBuildupSteps(items: BulletItem[]): BuildupStep[] {
|
|
143
|
+
const steps: BuildupStep[] = [];
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < items.length; i++) {
|
|
146
|
+
const item = items[i];
|
|
147
|
+
if (!item) continue;
|
|
148
|
+
|
|
149
|
+
if (item.children.length === 0) {
|
|
150
|
+
// No children - single step for this item
|
|
151
|
+
steps.push({ itemIndex: i, subIndex: null });
|
|
152
|
+
} else {
|
|
153
|
+
// Has children - first show parent, then each child
|
|
154
|
+
steps.push({ itemIndex: i, subIndex: -1 }); // Parent only
|
|
155
|
+
|
|
156
|
+
for (let j = 0; j < item.children.length; j++) {
|
|
157
|
+
steps.push({ itemIndex: i, subIndex: j });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return steps;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Render a bullet item with optional color
|
|
167
|
+
*/
|
|
168
|
+
function renderBulletItem(
|
|
169
|
+
item: BulletItem,
|
|
170
|
+
color: string | null,
|
|
171
|
+
indentLevel: number = 0,
|
|
172
|
+
showChildrenUpTo: number | null = null
|
|
173
|
+
): string {
|
|
174
|
+
const indent = ' '.repeat(indentLevel);
|
|
175
|
+
const lines: string[] = [];
|
|
176
|
+
|
|
177
|
+
// Render the main item
|
|
178
|
+
const text = color ? `[${item.text}]{color=${color}}` : item.text;
|
|
179
|
+
lines.push(`${indent}- ${text}`);
|
|
180
|
+
|
|
181
|
+
// Render children if any should be shown
|
|
182
|
+
if (showChildrenUpTo !== null && showChildrenUpTo >= 0 && item.children) {
|
|
183
|
+
for (let i = 0; i <= showChildrenUpTo && i < item.children.length; i++) {
|
|
184
|
+
const child = item.children[i];
|
|
185
|
+
if (!child) continue;
|
|
186
|
+
const childColor = color; // Children inherit parent's color state
|
|
187
|
+
const childText = childColor ? `[${child.text}]{color=${childColor}}` : child.text;
|
|
188
|
+
lines.push(`${indent} - ${childText}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return lines.join('\n');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Generate buildup slides from a bullet list
|
|
197
|
+
*/
|
|
198
|
+
function expandBuildup(content: string, format: string): string[] {
|
|
199
|
+
const items = parseBulletList(content);
|
|
200
|
+
if (items.length === 0) return [content];
|
|
201
|
+
|
|
202
|
+
const steps = flattenBuildupSteps(items);
|
|
203
|
+
const slideContents: string[] = [];
|
|
204
|
+
|
|
205
|
+
for (const step of steps) {
|
|
206
|
+
const lines: string[] = [];
|
|
207
|
+
|
|
208
|
+
for (let i = 0; i < items.length; i++) {
|
|
209
|
+
const item = items[i];
|
|
210
|
+
if (!item) continue;
|
|
211
|
+
|
|
212
|
+
if (i < step.itemIndex) {
|
|
213
|
+
// Previous top-level item - greyed out with all children
|
|
214
|
+
const allChildrenIndex = item.children.length > 0 ? item.children.length - 1 : null;
|
|
215
|
+
lines.push(renderBulletItem(item, GREY_COLOR, 0, allChildrenIndex));
|
|
216
|
+
} else if (i === step.itemIndex) {
|
|
217
|
+
// Current top-level item - colored
|
|
218
|
+
if (step.subIndex === null) {
|
|
219
|
+
// No children case - just show item
|
|
220
|
+
lines.push(renderBulletItem(item, null, 0, null));
|
|
221
|
+
} else if (step.subIndex === -1) {
|
|
222
|
+
// Has children but showing parent only first
|
|
223
|
+
lines.push(renderBulletItem(item, null, 0, null));
|
|
224
|
+
} else {
|
|
225
|
+
// Showing parent + children up to subIndex
|
|
226
|
+
lines.push(renderBulletItem(item, null, 0, step.subIndex));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Future items (i > step.itemIndex) - not shown yet
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
slideContents.push(lines.join('\n'));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return slideContents;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Process ::: buildup blocks in content and expand to steps
|
|
240
|
+
*/
|
|
241
|
+
function processBuildupBlocks(content: string, format: string): BuildupResult {
|
|
242
|
+
const buildupMatch = content.match(/^:::\s*buildup\s*\n([\s\S]*?)\n:::\s*$/m);
|
|
243
|
+
|
|
244
|
+
if (!buildupMatch || buildupMatch.index === undefined) {
|
|
245
|
+
return { content, buildupSteps: null };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const buildupContent = buildupMatch[1] || '';
|
|
249
|
+
const buildupSteps = expandBuildup(buildupContent, format);
|
|
250
|
+
|
|
251
|
+
// Remove the buildup block from content (will be replaced by steps)
|
|
252
|
+
const beforeBuildup = content.slice(0, buildupMatch.index).trim();
|
|
253
|
+
const afterBuildup = content.slice(buildupMatch.index + buildupMatch[0].length).trim();
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
content: beforeBuildup,
|
|
257
|
+
afterContent: afterBuildup,
|
|
258
|
+
buildupSteps,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Parse slide style attributes from heading
|
|
264
|
+
*/
|
|
265
|
+
function parseSlideStyle(heading: string): { title: string; style: SlideStyle } {
|
|
266
|
+
const style: SlideStyle = {
|
|
267
|
+
background: null,
|
|
268
|
+
type: null,
|
|
269
|
+
nonumber: false,
|
|
270
|
+
center: false,
|
|
271
|
+
classes: [],
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// Match {.class1 .class2} at end of heading
|
|
275
|
+
const attrMatch = heading.match(/\s*\{([^}]+)\}\s*$/);
|
|
276
|
+
if (!attrMatch || attrMatch.index === undefined) {
|
|
277
|
+
return { title: heading.trim(), style };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const title = heading.slice(0, attrMatch.index).trim();
|
|
281
|
+
const attrs = attrMatch[1] || '';
|
|
282
|
+
|
|
283
|
+
// Parse each .class
|
|
284
|
+
const classMatches = attrs.matchAll(/\.(\w+)/g);
|
|
285
|
+
for (const match of classMatches) {
|
|
286
|
+
if (!match[1]) continue;
|
|
287
|
+
const cls = match[1].toLowerCase();
|
|
288
|
+
style.classes.push(cls);
|
|
289
|
+
|
|
290
|
+
// Background styles
|
|
291
|
+
if (['dark', 'light', 'accent', 'inverse'].includes(cls)) {
|
|
292
|
+
style.background = cls;
|
|
293
|
+
}
|
|
294
|
+
// Special slide types
|
|
295
|
+
else if (['cover', 'thanks', 'section', 'plain'].includes(cls)) {
|
|
296
|
+
style.type = cls;
|
|
297
|
+
// Cover, thanks, section slides default to no numbering
|
|
298
|
+
if (['cover', 'thanks', 'section'].includes(cls)) {
|
|
299
|
+
style.nonumber = true;
|
|
300
|
+
style.center = true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Explicit options
|
|
304
|
+
else if (cls === 'nonumber' || cls === 'unnumbered') {
|
|
305
|
+
style.nonumber = true;
|
|
306
|
+
} else if (cls === 'center' || cls === 'centered') {
|
|
307
|
+
style.center = true;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return { title, style };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Parse a single slide's content into steps and notes
|
|
316
|
+
*/
|
|
317
|
+
export function parseSlide(slideContent: string): Slide {
|
|
318
|
+
const lines = slideContent.split('\n');
|
|
319
|
+
|
|
320
|
+
// Extract title (first heading)
|
|
321
|
+
let title = '';
|
|
322
|
+
let titleLevel = 2;
|
|
323
|
+
let titleLineIndex = -1;
|
|
324
|
+
let style: SlideStyle = {
|
|
325
|
+
background: null,
|
|
326
|
+
type: null,
|
|
327
|
+
nonumber: false,
|
|
328
|
+
center: false,
|
|
329
|
+
classes: [],
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
for (let i = 0; i < lines.length; i++) {
|
|
333
|
+
const line = lines[i];
|
|
334
|
+
if (!line) continue;
|
|
335
|
+
const match = line.match(/^(#{1,6})\s+(.+)$/);
|
|
336
|
+
if (match && match[1] && match[2]) {
|
|
337
|
+
titleLevel = match[1].length;
|
|
338
|
+
// Parse style from heading
|
|
339
|
+
const parsed = parseSlideStyle(match[2]);
|
|
340
|
+
title = parsed.title;
|
|
341
|
+
style = parsed.style;
|
|
342
|
+
titleLineIndex = i;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Content after title
|
|
348
|
+
const contentStart = titleLineIndex >= 0 ? titleLineIndex + 1 : 0;
|
|
349
|
+
const bodyContent = lines.slice(contentStart).join('\n');
|
|
350
|
+
|
|
351
|
+
// Parse ::: step and ::: notes blocks
|
|
352
|
+
const steps: Step[] = [];
|
|
353
|
+
let notes: string | null = null;
|
|
354
|
+
let preamble = '';
|
|
355
|
+
|
|
356
|
+
// Regex to match fenced div blocks
|
|
357
|
+
const bodyLines = bodyContent.split('\n');
|
|
358
|
+
let inBlock = false;
|
|
359
|
+
let blockType: string | null = null;
|
|
360
|
+
let currentBlockContent: string[] = [];
|
|
361
|
+
let beforeFirstStep: string[] = [];
|
|
362
|
+
let foundFirstStep = false;
|
|
363
|
+
|
|
364
|
+
for (let i = 0; i < bodyLines.length; i++) {
|
|
365
|
+
const line = bodyLines[i];
|
|
366
|
+
if (!line && line !== '') continue;
|
|
367
|
+
|
|
368
|
+
if (!inBlock) {
|
|
369
|
+
// Check for block start
|
|
370
|
+
const stepMatch = line.match(/^:::\s*step\s*$/);
|
|
371
|
+
const notesMatch = line.match(/^:::\s*notes\s*$/);
|
|
372
|
+
|
|
373
|
+
if (stepMatch) {
|
|
374
|
+
inBlock = true;
|
|
375
|
+
blockType = 'step';
|
|
376
|
+
currentBlockContent = [];
|
|
377
|
+
foundFirstStep = true;
|
|
378
|
+
} else if (notesMatch) {
|
|
379
|
+
inBlock = true;
|
|
380
|
+
blockType = 'notes';
|
|
381
|
+
currentBlockContent = [];
|
|
382
|
+
} else if (!foundFirstStep) {
|
|
383
|
+
beforeFirstStep.push(line);
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
386
|
+
// Check for block end
|
|
387
|
+
if (line.match(/^:::\s*$/)) {
|
|
388
|
+
// End of block
|
|
389
|
+
if (blockType === 'step') {
|
|
390
|
+
steps.push({
|
|
391
|
+
index: steps.length + 1,
|
|
392
|
+
content: currentBlockContent.join('\n').trim(),
|
|
393
|
+
});
|
|
394
|
+
} else if (blockType === 'notes') {
|
|
395
|
+
notes = currentBlockContent.join('\n').trim();
|
|
396
|
+
}
|
|
397
|
+
inBlock = false;
|
|
398
|
+
blockType = null;
|
|
399
|
+
currentBlockContent = [];
|
|
400
|
+
} else {
|
|
401
|
+
currentBlockContent.push(line);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Handle content before first step as preamble
|
|
407
|
+
preamble = beforeFirstStep.join('\n').trim();
|
|
408
|
+
|
|
409
|
+
// If no explicit steps, treat entire body as single step
|
|
410
|
+
if (steps.length === 0) {
|
|
411
|
+
// Remove notes from body if present
|
|
412
|
+
let bodyWithoutNotes = bodyContent;
|
|
413
|
+
const notesBlockMatch = bodyContent.match(/^:::\s*notes\s*$[\s\S]*?^:::\s*$/m);
|
|
414
|
+
if (notesBlockMatch) {
|
|
415
|
+
bodyWithoutNotes = bodyContent.replace(notesBlockMatch[0], '').trim();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
steps.push({
|
|
419
|
+
index: 1,
|
|
420
|
+
content: bodyWithoutNotes.trim(),
|
|
421
|
+
});
|
|
422
|
+
preamble = '';
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
title,
|
|
427
|
+
titleLevel,
|
|
428
|
+
steps,
|
|
429
|
+
notes,
|
|
430
|
+
preamble,
|
|
431
|
+
style,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Parse markdown document into slides
|
|
437
|
+
*/
|
|
438
|
+
export function parseSlides(markdown: string): Slide[] {
|
|
439
|
+
// Normalize line endings to \n
|
|
440
|
+
const normalized = markdown.replace(/\r\n/g, '\n');
|
|
441
|
+
|
|
442
|
+
// Split by --- (horizontal rule / slide delimiter)
|
|
443
|
+
// Handle YAML frontmatter by checking for --- at start
|
|
444
|
+
let content = normalized;
|
|
445
|
+
let frontmatter: string | null = null;
|
|
446
|
+
|
|
447
|
+
// Extract YAML frontmatter if present
|
|
448
|
+
if (normalized.startsWith('---')) {
|
|
449
|
+
const endMatch = normalized.slice(3).indexOf('\n---');
|
|
450
|
+
if (endMatch !== -1) {
|
|
451
|
+
frontmatter = normalized.slice(0, endMatch + 7); // Include both ---
|
|
452
|
+
content = normalized.slice(endMatch + 7).trim();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Split remaining content by ---
|
|
457
|
+
const parts = content.split(/\n---\n/);
|
|
458
|
+
|
|
459
|
+
const slides: Slide[] = [];
|
|
460
|
+
for (const part of parts) {
|
|
461
|
+
if (!part) continue;
|
|
462
|
+
const trimmed = part.trim();
|
|
463
|
+
if (trimmed) {
|
|
464
|
+
slides.push(parseSlide(trimmed));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Attach frontmatter to first slide's preamble if exists
|
|
469
|
+
if (frontmatter && slides.length > 0 && slides[0]) {
|
|
470
|
+
slides[0]._frontmatter = frontmatter;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return slides;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Build Beamer frame options string
|
|
478
|
+
*/
|
|
479
|
+
function buildBeamerFrameOptions(style: SlideStyle | null): string {
|
|
480
|
+
if (!style) return '';
|
|
481
|
+
|
|
482
|
+
const options: string[] = [];
|
|
483
|
+
|
|
484
|
+
// Special slide type options
|
|
485
|
+
if (style.type && BEAMER_FRAME_OPTIONS[style.type]) {
|
|
486
|
+
const opts = BEAMER_FRAME_OPTIONS[style.type];
|
|
487
|
+
if (opts) options.push(...opts.split(','));
|
|
488
|
+
} else {
|
|
489
|
+
// Individual options
|
|
490
|
+
if (style.nonumber) {
|
|
491
|
+
options.push('noframenumbering');
|
|
492
|
+
}
|
|
493
|
+
if (style.center) {
|
|
494
|
+
options.push('c');
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Deduplicate
|
|
499
|
+
const unique = [...new Set(options)];
|
|
500
|
+
return unique.length > 0 ? `[${unique.join(',')}]` : '';
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Generate Beamer markdown using pandoc's native slide structure
|
|
505
|
+
* Works WITH pandoc, not against it - pandoc creates frames, we add overlays
|
|
506
|
+
*/
|
|
507
|
+
export function generateBeamerMarkdown(slides: Slide[]): string {
|
|
508
|
+
const output: string[] = [];
|
|
509
|
+
|
|
510
|
+
// Check if first slide is a cover slide - if so, skip pandoc's auto title page
|
|
511
|
+
const hasExplicitCover = slides.length > 0 && slides[0] && slides[0].style && slides[0].style.type === 'cover';
|
|
512
|
+
|
|
513
|
+
for (const slide of slides) {
|
|
514
|
+
// Include frontmatter if present
|
|
515
|
+
if (slide._frontmatter) {
|
|
516
|
+
let frontmatter = slide._frontmatter;
|
|
517
|
+
|
|
518
|
+
// If we have an explicit cover slide, remove title/author/date to prevent
|
|
519
|
+
// pandoc from generating a duplicate title frame
|
|
520
|
+
if (hasExplicitCover) {
|
|
521
|
+
// Remove title, author, date lines but keep other frontmatter
|
|
522
|
+
frontmatter = frontmatter
|
|
523
|
+
.replace(/^title:.*\n?/m, '')
|
|
524
|
+
.replace(/^author:.*\n?/m, '')
|
|
525
|
+
.replace(/^date:.*\n?/m, '')
|
|
526
|
+
.replace(/\n{2,}/g, '\n'); // Clean up extra blank lines
|
|
527
|
+
|
|
528
|
+
// Check if frontmatter is now empty (just --- and ---)
|
|
529
|
+
const content = frontmatter.replace(/---/g, '').trim();
|
|
530
|
+
if (!content) {
|
|
531
|
+
// Skip empty frontmatter entirely - don't output anything
|
|
532
|
+
// The slide content will follow directly
|
|
533
|
+
} else {
|
|
534
|
+
output.push(frontmatter);
|
|
535
|
+
output.push('');
|
|
536
|
+
}
|
|
537
|
+
} else {
|
|
538
|
+
output.push(frontmatter);
|
|
539
|
+
output.push('');
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Build pandoc heading with beamer attributes
|
|
544
|
+
let headingAttrs = '';
|
|
545
|
+
const attrList: string[] = [];
|
|
546
|
+
|
|
547
|
+
if (slide.style) {
|
|
548
|
+
if (slide.style.type === 'cover' || slide.style.type === 'thanks' || slide.style.type === 'section') {
|
|
549
|
+
attrList.push('.plain');
|
|
550
|
+
attrList.push('.noframenumbering');
|
|
551
|
+
attrList.push('.c');
|
|
552
|
+
} else if (slide.style.type === 'plain') {
|
|
553
|
+
attrList.push('.plain');
|
|
554
|
+
}
|
|
555
|
+
if (slide.style.nonumber && !attrList.includes('.noframenumbering')) {
|
|
556
|
+
attrList.push('.noframenumbering');
|
|
557
|
+
}
|
|
558
|
+
if (slide.style.center && !attrList.includes('.c')) {
|
|
559
|
+
attrList.push('.c');
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (attrList.length > 0) {
|
|
564
|
+
headingAttrs = ' {' + attrList.join(' ') + '}';
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Frame heading - pandoc creates the frame from this
|
|
568
|
+
const heading = '#'.repeat(slide.titleLevel) + ' ' + slide.title + headingAttrs;
|
|
569
|
+
output.push(heading);
|
|
570
|
+
output.push('');
|
|
571
|
+
|
|
572
|
+
// Note: Per-frame background colors (.dark, .accent) are not yet supported
|
|
573
|
+
// in the pandoc-based beamer output. The classes are preserved for PPTX.
|
|
574
|
+
|
|
575
|
+
// Add preamble if present (visible on all overlays)
|
|
576
|
+
if (slide.preamble) {
|
|
577
|
+
output.push(slide.preamble);
|
|
578
|
+
output.push('');
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Generate content
|
|
582
|
+
if (slide.steps.length === 1 && slide.steps[0]) {
|
|
583
|
+
// Single step - just output content, no overlays needed
|
|
584
|
+
output.push(slide.steps[0].content);
|
|
585
|
+
} else {
|
|
586
|
+
// Multiple steps - use \pause between steps
|
|
587
|
+
// This is the pandoc-friendly way to do incremental reveals
|
|
588
|
+
for (let i = 0; i < slide.steps.length; i++) {
|
|
589
|
+
const step = slide.steps[i];
|
|
590
|
+
if (!step) continue;
|
|
591
|
+
output.push(step.content);
|
|
592
|
+
output.push('');
|
|
593
|
+
// Add pause after each step except the last
|
|
594
|
+
if (i < slide.steps.length - 1) {
|
|
595
|
+
output.push('. . .');
|
|
596
|
+
output.push('');
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Add speaker notes as LaTeX \note{} command
|
|
602
|
+
if (slide.notes) {
|
|
603
|
+
output.push('');
|
|
604
|
+
// Escape special LaTeX characters in notes
|
|
605
|
+
const notes = slide.notes || '';
|
|
606
|
+
const escapedNotes = notes
|
|
607
|
+
.replace(/\\/g, '\\textbackslash{}')
|
|
608
|
+
.replace(/[&%$#_{}]/g, '\\$&')
|
|
609
|
+
.replace(/\n/g, '\\\\');
|
|
610
|
+
output.push('\\note{' + escapedNotes + '}');
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
output.push('');
|
|
614
|
+
output.push('---');
|
|
615
|
+
output.push('');
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Remove trailing ---
|
|
619
|
+
while (output.length > 0) {
|
|
620
|
+
const last = output[output.length - 1];
|
|
621
|
+
if (!last || last.trim() !== '') break;
|
|
622
|
+
output.pop();
|
|
623
|
+
}
|
|
624
|
+
if (output.length > 0 && output[output.length - 1] === '---') {
|
|
625
|
+
output.pop();
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return output.join('\n');
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Build PPTX slide class attribute string
|
|
633
|
+
*/
|
|
634
|
+
function buildPptxSlideClasses(style: SlideStyle | null): string {
|
|
635
|
+
if (!style || !style.classes || style.classes.length === 0) {
|
|
636
|
+
return '';
|
|
637
|
+
}
|
|
638
|
+
return ' {.' + style.classes.join(' .') + '}';
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Generate PPTX markdown with duplicated slides for steps
|
|
643
|
+
* Each step becomes a separate physical slide
|
|
644
|
+
* Handles ::: buildup blocks by expanding them into multiple slides
|
|
645
|
+
*/
|
|
646
|
+
export function generatePptxMarkdown(slides: Slide[]): string {
|
|
647
|
+
const output: string[] = [];
|
|
648
|
+
|
|
649
|
+
for (const slide of slides) {
|
|
650
|
+
// Include frontmatter if present (only on first slide)
|
|
651
|
+
if (slide._frontmatter) {
|
|
652
|
+
output.push(slide._frontmatter);
|
|
653
|
+
output.push('');
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Check if any step contains a buildup block
|
|
657
|
+
const stepsWithBuildup: StepWithBuildup[] = slide.steps.map((step) => {
|
|
658
|
+
const result = processBuildupBlocks(step.content, 'pptx');
|
|
659
|
+
return {
|
|
660
|
+
...step,
|
|
661
|
+
beforeBuildup: result.content,
|
|
662
|
+
afterBuildup: result.afterContent || '',
|
|
663
|
+
buildupSteps: result.buildupSteps,
|
|
664
|
+
};
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
// Generate slides - handle buildup expansion
|
|
668
|
+
for (let i = 0; i < stepsWithBuildup.length; i++) {
|
|
669
|
+
const step = stepsWithBuildup[i];
|
|
670
|
+
if (!step) continue;
|
|
671
|
+
|
|
672
|
+
if (step.buildupSteps && step.buildupSteps.length > 0) {
|
|
673
|
+
// This step has a buildup block - generate one slide per buildup step
|
|
674
|
+
for (const buildupContent of step.buildupSteps) {
|
|
675
|
+
const classes = buildPptxSlideClasses(slide.style);
|
|
676
|
+
const heading = '#'.repeat(slide.titleLevel) + ' ' + slide.title + classes;
|
|
677
|
+
output.push(heading);
|
|
678
|
+
output.push('');
|
|
679
|
+
|
|
680
|
+
// Add preamble if present
|
|
681
|
+
if (slide.preamble) {
|
|
682
|
+
output.push(slide.preamble);
|
|
683
|
+
output.push('');
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Add content before buildup block
|
|
687
|
+
if (step.beforeBuildup) {
|
|
688
|
+
output.push(step.beforeBuildup);
|
|
689
|
+
output.push('');
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Add this buildup step content
|
|
693
|
+
output.push(buildupContent);
|
|
694
|
+
output.push('');
|
|
695
|
+
|
|
696
|
+
// Add content after buildup block
|
|
697
|
+
if (step.afterBuildup) {
|
|
698
|
+
output.push(step.afterBuildup);
|
|
699
|
+
output.push('');
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Add speaker notes
|
|
703
|
+
if (slide.notes) {
|
|
704
|
+
output.push('::: notes');
|
|
705
|
+
output.push(slide.notes);
|
|
706
|
+
output.push(':::');
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
output.push('');
|
|
710
|
+
output.push('---');
|
|
711
|
+
output.push('');
|
|
712
|
+
}
|
|
713
|
+
} else {
|
|
714
|
+
// Regular step - no buildup
|
|
715
|
+
const classes = buildPptxSlideClasses(slide.style);
|
|
716
|
+
const heading = '#'.repeat(slide.titleLevel) + ' ' + slide.title + classes;
|
|
717
|
+
output.push(heading);
|
|
718
|
+
output.push('');
|
|
719
|
+
|
|
720
|
+
// Add preamble if present
|
|
721
|
+
if (slide.preamble) {
|
|
722
|
+
output.push(slide.preamble);
|
|
723
|
+
output.push('');
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Add cumulative steps up to current
|
|
727
|
+
for (let j = 0; j <= i; j++) {
|
|
728
|
+
const s = stepsWithBuildup[j];
|
|
729
|
+
if (s) {
|
|
730
|
+
output.push(s.content);
|
|
731
|
+
output.push('');
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Add speaker notes
|
|
736
|
+
if (slide.notes) {
|
|
737
|
+
output.push('::: notes');
|
|
738
|
+
output.push(slide.notes);
|
|
739
|
+
output.push(':::');
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
output.push('');
|
|
743
|
+
output.push('---');
|
|
744
|
+
output.push('');
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Remove trailing ---
|
|
750
|
+
while (output.length > 0) {
|
|
751
|
+
const last = output[output.length - 1];
|
|
752
|
+
if (!last || last.trim() !== '') break;
|
|
753
|
+
output.pop();
|
|
754
|
+
}
|
|
755
|
+
if (output.length > 0 && output[output.length - 1] === '---') {
|
|
756
|
+
output.pop();
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return output.join('\n');
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Process markdown for slide output format
|
|
764
|
+
*/
|
|
765
|
+
export function processSlideMarkdown(markdown: string, format: 'beamer' | 'pptx'): string {
|
|
766
|
+
const slides = parseSlides(markdown);
|
|
767
|
+
|
|
768
|
+
if (format === 'beamer') {
|
|
769
|
+
return generateBeamerMarkdown(slides);
|
|
770
|
+
} else if (format === 'pptx') {
|
|
771
|
+
return generatePptxMarkdown(slides);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
return markdown;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Check if markdown contains slide syntax (steps, notes, buildup, or slide styles)
|
|
779
|
+
*/
|
|
780
|
+
export function hasSlideSyntax(markdown: string): boolean {
|
|
781
|
+
// Check for ::: step, ::: notes, or ::: buildup
|
|
782
|
+
if (/^:::\s*(step|notes|buildup)\s*$/m.test(markdown)) {
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
785
|
+
// Check for slide style attributes on headings: ## Title {.dark}
|
|
786
|
+
if (/^#{1,6}\s+.+\{[^}]*\.(dark|light|accent|inverse|cover|thanks|section|plain|nonumber|center)/m.test(markdown)) {
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Export style parser for testing
|
|
793
|
+
export { parseSlideStyle };
|