lazyslides 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +207 -0
- package/_includes/slides/_nested-list.njk +26 -0
- package/_includes/slides/agenda.njk +17 -0
- package/_includes/slides/center.njk +16 -0
- package/_includes/slides/code.njk +15 -0
- package/_includes/slides/columns.njk +35 -0
- package/_includes/slides/comparison.njk +29 -0
- package/_includes/slides/content.njk +22 -0
- package/_includes/slides/footer.njk +58 -0
- package/_includes/slides/funnel.njk +30 -0
- package/_includes/slides/hero.njk +27 -0
- package/_includes/slides/image-overlay.njk +21 -0
- package/_includes/slides/metrics.njk +27 -0
- package/_includes/slides/quote.njk +17 -0
- package/_includes/slides/section.njk +6 -0
- package/_includes/slides/split-wide.njk +30 -0
- package/_includes/slides/split.njk +30 -0
- package/_includes/slides/table.njk +31 -0
- package/_includes/slides/timeline.njk +30 -0
- package/_includes/slides/title.njk +17 -0
- package/_layouts/default.njk +20 -0
- package/_layouts/presentation.njk +240 -0
- package/assets/css/themes/default.css +62 -0
- package/assets/css/vendor/glightbox.min.css +1 -0
- package/assets/js/vendor/glightbox.min.js +1 -0
- package/assets/reveal.js/LICENSE +19 -0
- package/assets/reveal.js/dist/reset.css +30 -0
- package/assets/reveal.js/dist/reveal.css +8 -0
- package/assets/reveal.js/dist/reveal.esm.js +9 -0
- package/assets/reveal.js/dist/reveal.esm.js.map +1 -0
- package/assets/reveal.js/dist/reveal.js +9 -0
- package/assets/reveal.js/dist/reveal.js.map +1 -0
- package/assets/reveal.js/dist/theme/beige.css +366 -0
- package/assets/reveal.js/dist/theme/black-contrast.css +362 -0
- package/assets/reveal.js/dist/theme/black.css +359 -0
- package/assets/reveal.js/dist/theme/blood.css +392 -0
- package/assets/reveal.js/dist/theme/dracula.css +385 -0
- package/assets/reveal.js/dist/theme/fonts/league-gothic/LICENSE +2 -0
- package/assets/reveal.js/dist/theme/fonts/league-gothic/league-gothic.css +10 -0
- package/assets/reveal.js/dist/theme/fonts/league-gothic/league-gothic.eot +0 -0
- package/assets/reveal.js/dist/theme/fonts/league-gothic/league-gothic.ttf +0 -0
- package/assets/reveal.js/dist/theme/fonts/league-gothic/league-gothic.woff +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/LICENSE +45 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.eot +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.ttf +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.woff +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.eot +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.ttf +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.woff +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.eot +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.ttf +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.woff +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.eot +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.ttf +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.woff +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro.css +39 -0
- package/assets/reveal.js/dist/theme/league.css +368 -0
- package/assets/reveal.js/dist/theme/moon.css +362 -0
- package/assets/reveal.js/dist/theme/night.css +360 -0
- package/assets/reveal.js/dist/theme/serif.css +363 -0
- package/assets/reveal.js/dist/theme/simple.css +362 -0
- package/assets/reveal.js/dist/theme/sky.css +370 -0
- package/assets/reveal.js/dist/theme/solarized.css +363 -0
- package/assets/reveal.js/dist/theme/white-contrast.css +362 -0
- package/assets/reveal.js/dist/theme/white.css +359 -0
- package/assets/reveal.js/dist/theme/white_contrast_compact_verbatim_headers.css +360 -0
- package/assets/reveal.js/plugin/highlight/highlight.esm.js +5 -0
- package/assets/reveal.js/plugin/highlight/highlight.js +5 -0
- package/assets/reveal.js/plugin/highlight/monokai.css +71 -0
- package/assets/reveal.js/plugin/highlight/plugin.js +439 -0
- package/assets/reveal.js/plugin/highlight/zenburn.css +80 -0
- package/assets/reveal.js/plugin/markdown/markdown.esm.js +7 -0
- package/assets/reveal.js/plugin/markdown/markdown.js +7 -0
- package/assets/reveal.js/plugin/markdown/plugin.js +491 -0
- package/assets/reveal.js/plugin/notes/notes.esm.js +1 -0
- package/assets/reveal.js/plugin/notes/notes.js +1 -0
- package/assets/reveal.js/plugin/notes/plugin.js +267 -0
- package/assets/reveal.js/plugin/notes/speaker-view.html +898 -0
- package/cli.js +80 -0
- package/data/site.json +3 -0
- package/index.js +153 -0
- package/lib/export-pdf.js +137 -0
- package/lib/init.js +154 -0
- package/lib/renumber.js +181 -0
- package/lib/validate.js +196 -0
- package/package.json +76 -0
- package/scaffold/CLAUDE.md +283 -0
- package/scaffold/README.md +29 -0
- package/scaffold/_template/index.md +78 -0
- package/scaffold/_template/outline.md +32 -0
- package/scaffold/claude-commands/add-slide.md +61 -0
- package/scaffold/claude-commands/create-outline.md +75 -0
- package/scaffold/claude-commands/new-presentation.md +218 -0
- package/scaffold/claude-commands/refine-slides.md +62 -0
- package/scaffold/claude-commands/research-topic.md +66 -0
- package/scaffold/claude-commands/validate.md +47 -0
- package/scaffold/claude-settings.json +7 -0
- package/scaffold/eleventy.config.js +11 -0
- package/scaffold/gitignore +5 -0
- package/scaffold/my-first-deck/index.md +26 -0
- package/scaffold/nvmrc +1 -0
- package/scaffold/package.json.tmpl +25 -0
- package/scaffold/presentations.11tydata.js +11 -0
- package/scaffold/styles.css +3 -0
- package/src/styles.css +2077 -0
package/lib/renumber.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
function extractSlideTitle(lines, templateIdx) {
|
|
5
|
+
let title = null;
|
|
6
|
+
let subtitle = null;
|
|
7
|
+
let templateType = null;
|
|
8
|
+
|
|
9
|
+
const templateMatch = lines[templateIdx].match(/template:\s*(\w+)/);
|
|
10
|
+
if (templateMatch) templateType = templateMatch[1];
|
|
11
|
+
|
|
12
|
+
for (let idx = templateIdx + 1; idx < lines.length; idx++) {
|
|
13
|
+
const line = lines[idx];
|
|
14
|
+
if (/^\s*-\s*template:/.test(line)) break;
|
|
15
|
+
|
|
16
|
+
const titleMatch = line.match(/^\s+title:\s*(.+)$/);
|
|
17
|
+
if (titleMatch) {
|
|
18
|
+
title = titleMatch[1].trim();
|
|
19
|
+
if ((title.startsWith('"') && title.endsWith('"')) || (title.startsWith("'") && title.endsWith("'"))) {
|
|
20
|
+
title = title.slice(1, -1);
|
|
21
|
+
}
|
|
22
|
+
title = title.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const subtitleMatch = line.match(/^\s+subtitle:\s*(.+)$/);
|
|
26
|
+
if (subtitleMatch) {
|
|
27
|
+
subtitle = subtitleMatch[1].trim();
|
|
28
|
+
if ((subtitle.startsWith('"') && subtitle.endsWith('"')) || (subtitle.startsWith("'") && subtitle.endsWith("'"))) {
|
|
29
|
+
subtitle = subtitle.slice(1, -1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (templateType === "section" && title && subtitle) {
|
|
35
|
+
return `${title} \u2014 ${subtitle}`;
|
|
36
|
+
}
|
|
37
|
+
return title || "Untitled";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function renumberFile(file) {
|
|
41
|
+
let lines = fs.readFileSync(file, "utf-8").split("\n");
|
|
42
|
+
|
|
43
|
+
// First pass: find all template line positions and their slide numbers
|
|
44
|
+
let templateLines = {};
|
|
45
|
+
let slideCounter = 0;
|
|
46
|
+
|
|
47
|
+
lines.forEach((line, idx) => {
|
|
48
|
+
if (/^\s*-\s*template:/.test(line)) {
|
|
49
|
+
slideCounter++;
|
|
50
|
+
templateLines[idx] = slideCounter;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (slideCounter === 0) {
|
|
55
|
+
console.log(` No slides found in ${file}`);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Second pass: find which slides have comments
|
|
60
|
+
const slidesWithComments = {};
|
|
61
|
+
lines.forEach((line, idx) => {
|
|
62
|
+
if (/^(\s*#\s*Slide)\s*(\d*):\s*(.*)$/.test(line)) {
|
|
63
|
+
for (let futureIdx = idx + 1; futureIdx < lines.length; futureIdx++) {
|
|
64
|
+
if (templateLines[futureIdx] !== undefined) {
|
|
65
|
+
slidesWithComments[futureIdx] = idx;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Third pass: add missing comments (work backwards to preserve line indices)
|
|
73
|
+
let insertions = 0;
|
|
74
|
+
const sortedKeys = Object.keys(templateLines).map(Number).sort((a, b) => b - a);
|
|
75
|
+
|
|
76
|
+
for (const templateIdx of sortedKeys) {
|
|
77
|
+
const slideNum = templateLines[templateIdx];
|
|
78
|
+
if (slidesWithComments[templateIdx] === undefined) {
|
|
79
|
+
const title = extractSlideTitle(lines, templateIdx);
|
|
80
|
+
const comment = `# Slide ${slideNum}: ${title}`;
|
|
81
|
+
lines.splice(templateIdx, 0, comment);
|
|
82
|
+
insertions++;
|
|
83
|
+
console.log(` Added: # Slide ${slideNum}: ${title}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Fourth pass: rebuild templateLines if insertions happened
|
|
88
|
+
if (insertions > 0) {
|
|
89
|
+
templateLines = {};
|
|
90
|
+
slideCounter = 0;
|
|
91
|
+
lines.forEach((line, idx) => {
|
|
92
|
+
if (/^\s*-\s*template:/.test(line)) {
|
|
93
|
+
slideCounter++;
|
|
94
|
+
templateLines[idx] = slideCounter;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Fifth pass: renumber all comments
|
|
100
|
+
let changes = 0;
|
|
101
|
+
lines.forEach((line, idx) => {
|
|
102
|
+
const match = line.match(/^(\s*#\s*Slide)\s*(\d*):\s*(.*)$/);
|
|
103
|
+
if (match) {
|
|
104
|
+
const prefix = match[1];
|
|
105
|
+
const oldNum = match[2];
|
|
106
|
+
const rest = match[3];
|
|
107
|
+
|
|
108
|
+
let nextSlideNum = null;
|
|
109
|
+
for (let futureIdx = idx + 1; futureIdx < lines.length; futureIdx++) {
|
|
110
|
+
if (templateLines[futureIdx] !== undefined) {
|
|
111
|
+
nextSlideNum = templateLines[futureIdx];
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (nextSlideNum !== null) {
|
|
117
|
+
const newLine = `${prefix} ${nextSlideNum}: ${rest}`;
|
|
118
|
+
if (lines[idx] !== newLine) {
|
|
119
|
+
console.log(` Line ${idx + 1}: '# Slide ${oldNum}:' -> '# Slide ${nextSlideNum}:'`);
|
|
120
|
+
lines[idx] = newLine;
|
|
121
|
+
changes++;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (changes > 0 || insertions > 0) {
|
|
128
|
+
fs.writeFileSync(file, lines.join("\n"));
|
|
129
|
+
console.log(` \u2713 Updated ${file}: ${insertions} comment(s) added, ${changes} renumbered`);
|
|
130
|
+
return true;
|
|
131
|
+
} else {
|
|
132
|
+
console.log(` No changes needed in ${file}`);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Renumber slide comments in presentation YAML files.
|
|
139
|
+
* @param {object} [opts]
|
|
140
|
+
* @param {string} [opts.cwd] - working directory (defaults to process.cwd())
|
|
141
|
+
* @param {string[]} [opts.files] - specific files to process
|
|
142
|
+
*/
|
|
143
|
+
export async function run(opts = {}) {
|
|
144
|
+
const cwd = opts.cwd || process.cwd();
|
|
145
|
+
|
|
146
|
+
if (opts.files && opts.files.length > 0) {
|
|
147
|
+
for (const file of opts.files) {
|
|
148
|
+
if (!fs.existsSync(file)) {
|
|
149
|
+
throw new Error(`File not found: ${file}`);
|
|
150
|
+
}
|
|
151
|
+
console.log(`Renumbering: ${file}`);
|
|
152
|
+
renumberFile(file);
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const dir = path.join(cwd, "presentations");
|
|
158
|
+
if (!fs.existsSync(dir)) {
|
|
159
|
+
console.log(`No presentations directory found at ${dir}`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const files = fs.readdirSync(dir, { withFileTypes: true })
|
|
164
|
+
.filter((e) => e.isDirectory() && e.name !== "_template")
|
|
165
|
+
.map((e) => path.join(dir, e.name, "index.md"))
|
|
166
|
+
.filter((f) => fs.existsSync(f));
|
|
167
|
+
|
|
168
|
+
if (files.length === 0) {
|
|
169
|
+
console.log(`No presentation files found in ${dir}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let totalUpdated = 0;
|
|
174
|
+
for (const file of files) {
|
|
175
|
+
console.log(`Renumbering: ${file}`);
|
|
176
|
+
if (renumberFile(file)) totalUpdated++;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log("\n" + "=".repeat(50));
|
|
180
|
+
console.log(`Renumbering complete: ${files.length} file(s) processed, ${totalUpdated} updated`);
|
|
181
|
+
}
|
package/lib/validate.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
|
|
5
|
+
const REQUIRED_FIELDS = ["title", "slides"];
|
|
6
|
+
const VALID_TEMPLATES = [
|
|
7
|
+
"title", "section", "content", "metrics", "comparison", "columns",
|
|
8
|
+
"quote", "center", "hero", "image-overlay", "code", "timeline",
|
|
9
|
+
"funnel", "split", "split-wide", "table", "agenda",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
function findPresentations(dir) {
|
|
13
|
+
const results = [];
|
|
14
|
+
if (!fs.existsSync(dir)) return results;
|
|
15
|
+
|
|
16
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
if (!entry.isDirectory() || entry.name === "_template") continue;
|
|
19
|
+
const indexPath = path.join(dir, entry.name, "index.md");
|
|
20
|
+
if (fs.existsSync(indexPath)) {
|
|
21
|
+
results.push(indexPath);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return results;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validate all presentation YAML files.
|
|
29
|
+
* @param {object} [opts]
|
|
30
|
+
* @param {string} [opts.cwd] - working directory (defaults to process.cwd())
|
|
31
|
+
* @param {boolean} [opts.renumber] - run renumber before validating
|
|
32
|
+
* @returns {{ errors: string[], warnings: string[], validated: number }}
|
|
33
|
+
*/
|
|
34
|
+
export async function run(opts = {}) {
|
|
35
|
+
const cwd = opts.cwd || process.cwd();
|
|
36
|
+
const presentationsDir = path.join(cwd, "presentations");
|
|
37
|
+
|
|
38
|
+
if (opts.renumber) {
|
|
39
|
+
const { run: renumber } = await import("./renumber.js");
|
|
40
|
+
await renumber({ cwd });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const errors = [];
|
|
44
|
+
const warnings = [];
|
|
45
|
+
let validated = 0;
|
|
46
|
+
|
|
47
|
+
const files = findPresentations(presentationsDir);
|
|
48
|
+
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
console.log(`Validating: ${file}`);
|
|
51
|
+
validated++;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
55
|
+
|
|
56
|
+
// Check: Comment-hidden slide detection
|
|
57
|
+
content.split("\n").forEach((line, idx) => {
|
|
58
|
+
if (/#.*-\s*template:/.test(line)) {
|
|
59
|
+
warnings.push(`${file}:${idx + 1}: Possible slide definition hidden in comment`);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Extract front matter
|
|
64
|
+
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
65
|
+
if (!match) {
|
|
66
|
+
errors.push(`${file}: No valid YAML front matter found`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const data = yaml.load(match[1]);
|
|
71
|
+
|
|
72
|
+
// Check required fields
|
|
73
|
+
for (const field of REQUIRED_FIELDS) {
|
|
74
|
+
if (!(field in data)) {
|
|
75
|
+
errors.push(`${file}: Missing required field '${field}'`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Validate slides array
|
|
80
|
+
if (data.slides) {
|
|
81
|
+
if (!Array.isArray(data.slides)) {
|
|
82
|
+
errors.push(`${file}: 'slides' must be an array`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const sections = [];
|
|
87
|
+
|
|
88
|
+
data.slides.forEach((slide, idx) => {
|
|
89
|
+
const slideNum = idx + 1;
|
|
90
|
+
|
|
91
|
+
if (!slide.template) {
|
|
92
|
+
errors.push(`${file}: Slide ${slideNum} missing 'template' field`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!VALID_TEMPLATES.includes(slide.template)) {
|
|
97
|
+
warnings.push(`${file}: Slide ${slideNum} has unknown template '${slide.template}'`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (slide.template === "section") {
|
|
101
|
+
sections.push({ title: slide.title, start: slideNum });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!slide.title && !["quote"].includes(slide.template)) {
|
|
105
|
+
warnings.push(`${file}: Slide ${slideNum} (${slide.template}) missing 'title'`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Comparison template structure validation
|
|
109
|
+
if (slide.template === "comparison") {
|
|
110
|
+
if (!slide.rows) {
|
|
111
|
+
errors.push(`${file}: Slide ${slideNum} (comparison) missing 'rows:' - comparison template requires rows format`);
|
|
112
|
+
}
|
|
113
|
+
if ((slide.left && typeof slide.left === "object") || (slide.right && typeof slide.right === "object")) {
|
|
114
|
+
errors.push(`${file}: Slide ${slideNum} (comparison) uses wrong format - use 'rows:' with left/right pairs, not nested left:/right: objects`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Image path validation
|
|
119
|
+
if (slide.image) {
|
|
120
|
+
const imagePath = slide.image;
|
|
121
|
+
if (imagePath.includes("../")) {
|
|
122
|
+
warnings.push(`${file}: Slide ${slideNum} image path contains '../'`);
|
|
123
|
+
}
|
|
124
|
+
const presentationDir = path.dirname(file);
|
|
125
|
+
const fullImagePath = path.join(presentationDir, imagePath);
|
|
126
|
+
if (!fs.existsSync(fullImagePath)) {
|
|
127
|
+
warnings.push(`${file}: Slide ${slideNum} missing image '${imagePath}'`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Expected slide count
|
|
133
|
+
if (data.expected_slides && data.slides.length !== data.expected_slides) {
|
|
134
|
+
warnings.push(`${file}: Expected ${data.expected_slides} slides, found ${data.slides.length}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Section index output
|
|
138
|
+
if (sections.length > 0) {
|
|
139
|
+
console.log("");
|
|
140
|
+
console.log(" SECTIONS:");
|
|
141
|
+
console.log(" " + "\u2500".repeat(50));
|
|
142
|
+
|
|
143
|
+
sections.forEach((section, idx) => {
|
|
144
|
+
const endSlide = idx + 1 < sections.length
|
|
145
|
+
? sections[idx + 1].start - 1
|
|
146
|
+
: data.slides.length;
|
|
147
|
+
|
|
148
|
+
const title = section.title || "(untitled section)";
|
|
149
|
+
const range = `[slides ${section.start}-${endSlide}]`;
|
|
150
|
+
console.log(` ${title.padEnd(42)} ${range}`);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Slide listing output
|
|
155
|
+
console.log("");
|
|
156
|
+
console.log(" SLIDES:");
|
|
157
|
+
console.log(" " + "\u2500".repeat(50));
|
|
158
|
+
|
|
159
|
+
data.slides.forEach((slide, idx) => {
|
|
160
|
+
const slideNum = idx + 1;
|
|
161
|
+
const template = slide.template || "???";
|
|
162
|
+
let title = slide.title || slide.text || slide.headline || "(no title)";
|
|
163
|
+
if (title.length > 48) title = title.substring(0, 45) + "...";
|
|
164
|
+
console.log(` ${String(slideNum).padStart(3)}. [${template}] ${title}`);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
console.log("");
|
|
168
|
+
console.log(` \u2713 ${data.slides.length} slides validated`);
|
|
169
|
+
}
|
|
170
|
+
} catch (e) {
|
|
171
|
+
if (e.name === "YAMLException") {
|
|
172
|
+
errors.push(`${file}: YAML syntax error - ${e.message}`);
|
|
173
|
+
} else {
|
|
174
|
+
errors.push(`${file}: ${e.constructor.name} - ${e.message}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log("\n" + "=".repeat(50));
|
|
180
|
+
console.log(`Validation complete: ${validated} presentation(s)`);
|
|
181
|
+
|
|
182
|
+
if (warnings.length > 0) {
|
|
183
|
+
console.log(`\n\u26A0\uFE0F Warnings (${warnings.length}):`);
|
|
184
|
+
warnings.forEach((w) => console.log(` ${w}`));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (errors.length > 0) {
|
|
188
|
+
console.log(`\n\u274C Errors (${errors.length}):`);
|
|
189
|
+
errors.forEach((e) => console.log(` ${e}`));
|
|
190
|
+
console.log("\nBuild may fail or skip files with errors!");
|
|
191
|
+
} else {
|
|
192
|
+
console.log("\n\u2705 All presentations valid");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { errors, warnings, validated };
|
|
196
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lazyslides",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-native presentations from YAML — Eleventy plugin with Reveal.js",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Chris Tietz",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"presentations",
|
|
9
|
+
"slides",
|
|
10
|
+
"reveal.js",
|
|
11
|
+
"eleventy",
|
|
12
|
+
"yaml",
|
|
13
|
+
"tailwind",
|
|
14
|
+
"ai",
|
|
15
|
+
"claude",
|
|
16
|
+
"slide-deck"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Chrisissorry/lazyslides.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/Chrisissorry/lazyslides",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/Chrisissorry/lazyslides/issues"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22"
|
|
28
|
+
},
|
|
29
|
+
"type": "module",
|
|
30
|
+
"main": "index.js",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": "./index.js",
|
|
33
|
+
"./styles.css": "./src/styles.css"
|
|
34
|
+
},
|
|
35
|
+
"bin": {
|
|
36
|
+
"lazyslides": "./cli.js"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"index.js",
|
|
40
|
+
"cli.js",
|
|
41
|
+
"lib/",
|
|
42
|
+
"_layouts/",
|
|
43
|
+
"_includes/",
|
|
44
|
+
"assets/",
|
|
45
|
+
"src/",
|
|
46
|
+
"scaffold/",
|
|
47
|
+
"data/"
|
|
48
|
+
],
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@11ty/eleventy": "^3.0.0"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"js-yaml": "^4.1.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@11ty/eleventy": "^3.0.0",
|
|
57
|
+
"@tailwindcss/cli": "^4.0.0",
|
|
58
|
+
"concurrently": "^9.0.0",
|
|
59
|
+
"decktape": "^3.14.0",
|
|
60
|
+
"tailwindcss": "^4.1.18",
|
|
61
|
+
"vitest": "^4.0.18"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"dev": "node cli.js validate && concurrently \"pnpm:watch:*\"",
|
|
65
|
+
"watch:css": "pnpm exec tailwindcss -i src/styles.css -o _site/assets/css/styles.css --watch",
|
|
66
|
+
"watch:11ty": "pnpm exec eleventy --serve",
|
|
67
|
+
"build": "node cli.js validate && pnpm run build:css && pnpm run build:11ty",
|
|
68
|
+
"build:css": "pnpm exec tailwindcss -i src/styles.css -o _site/assets/css/styles.css",
|
|
69
|
+
"build:11ty": "pnpm exec eleventy",
|
|
70
|
+
"test": "vitest run",
|
|
71
|
+
"test:watch": "vitest",
|
|
72
|
+
"validate": "node cli.js validate",
|
|
73
|
+
"renumber": "node cli.js renumber",
|
|
74
|
+
"pdf": "node cli.js pdf"
|
|
75
|
+
}
|
|
76
|
+
}
|