md-slides 1.0.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 +219 -0
- package/demo/slides.md +184 -0
- package/docs/index.html +718 -0
- package/package.json +39 -0
- package/src/cli.js +55 -0
- package/src/commands/build.js +42 -0
- package/src/commands/export.js +18 -0
- package/src/commands/init.js +100 -0
- package/src/commands/preview.js +85 -0
- package/src/parser.js +178 -0
- package/src/renderer.js +237 -0
- package/src/themes.js +452 -0
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "md-slides",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Convert Markdown to beautiful presentation slides. Zero config, developer-friendly.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"slides": "src/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node test/test.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"markdown",
|
|
15
|
+
"slides",
|
|
16
|
+
"presentation",
|
|
17
|
+
"cli",
|
|
18
|
+
"reveal",
|
|
19
|
+
"deck"
|
|
20
|
+
],
|
|
21
|
+
"author": "Deepankar Rawat <dprrwt>",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/dprrwt/md-slides"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"chalk": "^5.3.0",
|
|
29
|
+
"chokidar": "^3.6.0",
|
|
30
|
+
"commander": "^12.1.0",
|
|
31
|
+
"highlight.js": "^11.10.0",
|
|
32
|
+
"marked": "^14.1.0",
|
|
33
|
+
"open": "^10.1.0",
|
|
34
|
+
"ws": "^8.18.0"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { initProject } from './commands/init.js';
|
|
5
|
+
import { buildSlides } from './commands/build.js';
|
|
6
|
+
import { previewSlides } from './commands/preview.js';
|
|
7
|
+
import { exportSlides } from './commands/export.js';
|
|
8
|
+
import { createRequire } from 'module';
|
|
9
|
+
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const pkg = require('../package.json');
|
|
12
|
+
|
|
13
|
+
const program = new Command();
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.name('slides')
|
|
17
|
+
.description('Convert Markdown to beautiful presentation slides')
|
|
18
|
+
.version(pkg.version);
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.command('init [name]')
|
|
22
|
+
.description('Create a new presentation from template')
|
|
23
|
+
.option('-t, --theme <theme>', 'Theme: dark, light, minimal, neon', 'dark')
|
|
24
|
+
.action((name, options) => {
|
|
25
|
+
initProject(name || 'presentation', options);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
program
|
|
29
|
+
.command('build [file]')
|
|
30
|
+
.description('Build slides to static HTML')
|
|
31
|
+
.option('-o, --output <dir>', 'Output directory', 'dist')
|
|
32
|
+
.option('-t, --theme <theme>', 'Theme override')
|
|
33
|
+
.action((file, options) => {
|
|
34
|
+
buildSlides(file || 'slides.md', options);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
program
|
|
38
|
+
.command('preview [file]')
|
|
39
|
+
.alias('dev')
|
|
40
|
+
.description('Live preview with hot reload')
|
|
41
|
+
.option('-p, --port <port>', 'Port number', '3000')
|
|
42
|
+
.option('-t, --theme <theme>', 'Theme override')
|
|
43
|
+
.action((file, options) => {
|
|
44
|
+
previewSlides(file || 'slides.md', options);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
program
|
|
48
|
+
.command('export [file]')
|
|
49
|
+
.description('Export to PDF')
|
|
50
|
+
.option('-o, --output <file>', 'Output file', 'slides.pdf')
|
|
51
|
+
.action((file, options) => {
|
|
52
|
+
exportSlides(file || 'slides.md', options);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
program.parse();
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
2
|
+
import { join, resolve, basename } from 'path';
|
|
3
|
+
import { parseSlides } from '../parser.js';
|
|
4
|
+
import { renderHTML } from '../renderer.js';
|
|
5
|
+
|
|
6
|
+
export function buildSlides(file, options = {}) {
|
|
7
|
+
const { output = 'dist', theme } = options;
|
|
8
|
+
const filePath = resolve(file);
|
|
9
|
+
|
|
10
|
+
if (!existsSync(filePath)) {
|
|
11
|
+
console.log(`\n ❌ File not found: ${file}\n`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
16
|
+
const slidesData = parseSlides(content);
|
|
17
|
+
|
|
18
|
+
const buildOptions = {
|
|
19
|
+
theme: theme || slidesData.metadata.theme || 'dark',
|
|
20
|
+
title: slidesData.metadata.title || basename(file, '.md'),
|
|
21
|
+
author: slidesData.metadata.author || '',
|
|
22
|
+
liveReload: false,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const html = renderHTML(slidesData, buildOptions);
|
|
26
|
+
|
|
27
|
+
// Create output directory
|
|
28
|
+
const outputDir = resolve(output);
|
|
29
|
+
mkdirSync(outputDir, { recursive: true });
|
|
30
|
+
|
|
31
|
+
const outputFile = join(outputDir, 'index.html');
|
|
32
|
+
writeFileSync(outputFile, html);
|
|
33
|
+
|
|
34
|
+
console.log(`
|
|
35
|
+
✅ Built ${slidesData.slides.length} slides → ${outputFile}
|
|
36
|
+
|
|
37
|
+
Theme: ${buildOptions.theme}
|
|
38
|
+
Title: ${buildOptions.title}
|
|
39
|
+
|
|
40
|
+
Open in browser or deploy to GitHub Pages.
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { resolve, basename } from 'path';
|
|
3
|
+
|
|
4
|
+
export function exportSlides(file, options = {}) {
|
|
5
|
+
console.log(`
|
|
6
|
+
📄 PDF Export
|
|
7
|
+
|
|
8
|
+
For now, use the browser's Print to PDF:
|
|
9
|
+
|
|
10
|
+
1. Run: slides preview ${file}
|
|
11
|
+
2. Open the URL in Chrome
|
|
12
|
+
3. Press Ctrl+P (or Cmd+P)
|
|
13
|
+
4. Select "Save as PDF"
|
|
14
|
+
5. Set Layout to "Landscape"
|
|
15
|
+
|
|
16
|
+
Native PDF export coming in a future version.
|
|
17
|
+
`);
|
|
18
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
function getSampleSlides(theme) {
|
|
5
|
+
return [
|
|
6
|
+
'---',
|
|
7
|
+
'title: My Presentation',
|
|
8
|
+
'author: Your Name',
|
|
9
|
+
'theme: ' + theme,
|
|
10
|
+
'---',
|
|
11
|
+
'',
|
|
12
|
+
'# Hello, World 👋',
|
|
13
|
+
'',
|
|
14
|
+
'Welcome to **md-slides**',
|
|
15
|
+
'',
|
|
16
|
+
'---',
|
|
17
|
+
'',
|
|
18
|
+
'## What is md-slides?',
|
|
19
|
+
'',
|
|
20
|
+
'A Markdown-to-slides converter that just works.',
|
|
21
|
+
'',
|
|
22
|
+
'- Write in **Markdown**',
|
|
23
|
+
'- Present in the **browser**',
|
|
24
|
+
'- Deploy anywhere as **static HTML**',
|
|
25
|
+
'',
|
|
26
|
+
'<!-- notes: This is the intro slide. Mention how easy it is to get started. -->',
|
|
27
|
+
'',
|
|
28
|
+
'---',
|
|
29
|
+
'',
|
|
30
|
+
'## Code Highlighting',
|
|
31
|
+
'',
|
|
32
|
+
'```javascript',
|
|
33
|
+
'// Syntax highlighting works out of the box',
|
|
34
|
+
'function fibonacci(n) {',
|
|
35
|
+
' if (n <= 1) return n;',
|
|
36
|
+
' return fibonacci(n - 1) + fibonacci(n - 2);',
|
|
37
|
+
'}',
|
|
38
|
+
'',
|
|
39
|
+
'console.log(fibonacci(10)); // 55',
|
|
40
|
+
'```',
|
|
41
|
+
'',
|
|
42
|
+
'---',
|
|
43
|
+
'',
|
|
44
|
+
'## Keyboard Shortcuts',
|
|
45
|
+
'',
|
|
46
|
+
'| Key | Action |',
|
|
47
|
+
'|-----|--------|',
|
|
48
|
+
'| ← → | Navigate slides |',
|
|
49
|
+
'| S | Toggle speaker notes |',
|
|
50
|
+
'| F | Fullscreen |',
|
|
51
|
+
'| Home / End | First / Last slide |',
|
|
52
|
+
'',
|
|
53
|
+
'---',
|
|
54
|
+
'',
|
|
55
|
+
'> "The best presentations are the ones you barely have to think about making."',
|
|
56
|
+
'',
|
|
57
|
+
'---',
|
|
58
|
+
'',
|
|
59
|
+
'<!-- layout: center -->',
|
|
60
|
+
'',
|
|
61
|
+
'# Thank You! 🎉',
|
|
62
|
+
'',
|
|
63
|
+
'**@yourhandle** · yourwebsite.com',
|
|
64
|
+
].join('\n');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function initProject(name, options = {}) {
|
|
68
|
+
const dir = join(process.cwd(), name);
|
|
69
|
+
const theme = options.theme || 'dark';
|
|
70
|
+
|
|
71
|
+
if (existsSync(dir)) {
|
|
72
|
+
console.log('\n Warning: Directory "' + name + '" already exists.\n');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
mkdirSync(dir, { recursive: true });
|
|
77
|
+
|
|
78
|
+
// Create slides.md
|
|
79
|
+
writeFileSync(join(dir, 'slides.md'), getSampleSlides(theme));
|
|
80
|
+
|
|
81
|
+
// Create .gitignore
|
|
82
|
+
writeFileSync(join(dir, '.gitignore'), 'dist/\nnode_modules/\n');
|
|
83
|
+
|
|
84
|
+
console.log([
|
|
85
|
+
'',
|
|
86
|
+
' ✨ Created presentation: ' + name + '/',
|
|
87
|
+
'',
|
|
88
|
+
' Files:',
|
|
89
|
+
' slides.md Your presentation',
|
|
90
|
+
' .gitignore Ignores dist/',
|
|
91
|
+
'',
|
|
92
|
+
' Next steps:',
|
|
93
|
+
' cd ' + name,
|
|
94
|
+
' slides preview Live preview with hot reload',
|
|
95
|
+
' slides build Build to dist/',
|
|
96
|
+
'',
|
|
97
|
+
' Edit slides.md and separate slides with ---',
|
|
98
|
+
'',
|
|
99
|
+
].join('\n'));
|
|
100
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { readFileSync, existsSync, watch } from 'fs';
|
|
2
|
+
import { resolve, basename } from 'path';
|
|
3
|
+
import { createServer } from 'http';
|
|
4
|
+
import { WebSocketServer } from 'ws';
|
|
5
|
+
import { parseSlides } from '../parser.js';
|
|
6
|
+
import { renderHTML } from '../renderer.js';
|
|
7
|
+
|
|
8
|
+
export function previewSlides(file, options = {}) {
|
|
9
|
+
const { port = '3000', theme } = options;
|
|
10
|
+
const filePath = resolve(file);
|
|
11
|
+
const httpPort = parseInt(port);
|
|
12
|
+
const wsPort = httpPort + 1;
|
|
13
|
+
|
|
14
|
+
if (!existsSync(filePath)) {
|
|
15
|
+
console.log(`\n ❌ File not found: ${file}\n`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function buildHTML() {
|
|
20
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
21
|
+
const slidesData = parseSlides(content);
|
|
22
|
+
return renderHTML(slidesData, {
|
|
23
|
+
theme: theme || slidesData.metadata.theme || 'dark',
|
|
24
|
+
title: slidesData.metadata.title || basename(file, '.md'),
|
|
25
|
+
author: slidesData.metadata.author || '',
|
|
26
|
+
liveReload: true,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let html = buildHTML();
|
|
31
|
+
|
|
32
|
+
// HTTP server
|
|
33
|
+
const server = createServer((req, res) => {
|
|
34
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
35
|
+
res.end(html);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// WebSocket for live reload
|
|
39
|
+
const wss = new WebSocketServer({ port: wsPort });
|
|
40
|
+
|
|
41
|
+
// Watch for file changes
|
|
42
|
+
let debounce = null;
|
|
43
|
+
watch(filePath, () => {
|
|
44
|
+
if (debounce) clearTimeout(debounce);
|
|
45
|
+
debounce = setTimeout(() => {
|
|
46
|
+
try {
|
|
47
|
+
html = buildHTML();
|
|
48
|
+
wss.clients.forEach(client => {
|
|
49
|
+
if (client.readyState === 1) {
|
|
50
|
+
client.send('reload');
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
console.log(' 🔄 Rebuilt — reloading...');
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.log(` ⚠️ Build error: ${err.message}`);
|
|
56
|
+
}
|
|
57
|
+
}, 150);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
server.listen(httpPort, () => {
|
|
61
|
+
const url = `http://localhost:${httpPort}`;
|
|
62
|
+
console.log(`
|
|
63
|
+
🎬 Live preview running
|
|
64
|
+
|
|
65
|
+
URL: ${url}
|
|
66
|
+
File: ${filePath}
|
|
67
|
+
|
|
68
|
+
Watching for changes...
|
|
69
|
+
Press Ctrl+C to stop.
|
|
70
|
+
`);
|
|
71
|
+
|
|
72
|
+
// Try to open browser
|
|
73
|
+
import('open').then(({ default: open }) => {
|
|
74
|
+
open(url).catch(() => {});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
server.on('error', (err) => {
|
|
79
|
+
if (err.code === 'EADDRINUSE') {
|
|
80
|
+
console.log(`\n ❌ Port ${httpPort} is in use. Try: slides preview -p ${httpPort + 10}\n`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
throw err;
|
|
84
|
+
});
|
|
85
|
+
}
|
package/src/parser.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown Slides Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses markdown into slide objects with frontmatter support.
|
|
5
|
+
* Slide separator: --- (horizontal rule)
|
|
6
|
+
* Speaker notes: <!--notes: ... -->
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { marked } from 'marked';
|
|
10
|
+
import hljs from 'highlight.js';
|
|
11
|
+
|
|
12
|
+
// Configure marked with syntax highlighting
|
|
13
|
+
marked.setOptions({
|
|
14
|
+
highlight: (code, lang) => {
|
|
15
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
16
|
+
return hljs.highlight(code, { language: lang }).value;
|
|
17
|
+
}
|
|
18
|
+
return hljs.highlightAuto(code).value;
|
|
19
|
+
},
|
|
20
|
+
gfm: true,
|
|
21
|
+
breaks: false,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parse frontmatter from markdown content
|
|
26
|
+
* Supports YAML-like key: value pairs between --- delimiters at the start
|
|
27
|
+
*/
|
|
28
|
+
export function parseFrontmatter(content) {
|
|
29
|
+
const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n/;
|
|
30
|
+
const match = content.match(frontmatterRegex);
|
|
31
|
+
|
|
32
|
+
if (!match) {
|
|
33
|
+
return { metadata: {}, body: content };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const metadata = {};
|
|
37
|
+
const lines = match[1].split('\n');
|
|
38
|
+
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
const colonIndex = line.indexOf(':');
|
|
41
|
+
if (colonIndex > 0) {
|
|
42
|
+
const key = line.slice(0, colonIndex).trim();
|
|
43
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
44
|
+
metadata[key] = value;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
metadata,
|
|
50
|
+
body: content.slice(match[0].length),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parse slide-level directives from comment syntax
|
|
56
|
+
* <!-- class: classname -->
|
|
57
|
+
* <!-- background: #hex or url -->
|
|
58
|
+
* <!-- transition: fade|slide|zoom -->
|
|
59
|
+
*/
|
|
60
|
+
function parseSlideDirectives(slideContent) {
|
|
61
|
+
const directives = {};
|
|
62
|
+
const directiveRegex = /<!--\s*(class|background|transition|layout|align):\s*(.*?)\s*-->/g;
|
|
63
|
+
let match;
|
|
64
|
+
|
|
65
|
+
while ((match = directiveRegex.exec(slideContent)) !== null) {
|
|
66
|
+
directives[match[1]] = match[2];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Remove directive comments from content
|
|
70
|
+
const cleanContent = slideContent.replace(directiveRegex, '').trim();
|
|
71
|
+
|
|
72
|
+
return { directives, cleanContent };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Extract speaker notes from <!-- notes: ... --> blocks
|
|
77
|
+
*/
|
|
78
|
+
function extractNotes(slideContent) {
|
|
79
|
+
const notesRegex = /<!--\s*notes:\s*([\s\S]*?)\s*-->/;
|
|
80
|
+
const match = slideContent.match(notesRegex);
|
|
81
|
+
|
|
82
|
+
if (!match) {
|
|
83
|
+
return { notes: '', content: slideContent };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
notes: match[1].trim(),
|
|
88
|
+
content: slideContent.replace(notesRegex, '').trim(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Detect slide layout from content structure
|
|
94
|
+
*/
|
|
95
|
+
function detectLayout(htmlContent) {
|
|
96
|
+
const hasH1 = /<h1[^>]*>/.test(htmlContent);
|
|
97
|
+
const hasH2 = /<h2[^>]*>/.test(htmlContent);
|
|
98
|
+
const hasOnlyHeading = /^<h[12][^>]*>.*<\/h[12]>\s*$/s.test(htmlContent.trim());
|
|
99
|
+
const hasImage = /<img[^>]*>/.test(htmlContent);
|
|
100
|
+
const hasCode = /<pre><code/.test(htmlContent);
|
|
101
|
+
const hasList = /<[uo]l>/.test(htmlContent);
|
|
102
|
+
const hasBlockquote = /<blockquote>/.test(htmlContent);
|
|
103
|
+
|
|
104
|
+
if (hasOnlyHeading) return 'center';
|
|
105
|
+
if (hasH1 && !hasH2) return 'title';
|
|
106
|
+
if (hasBlockquote && !hasCode && !hasList) return 'quote';
|
|
107
|
+
if (hasCode) return 'code';
|
|
108
|
+
if (hasImage) return 'image';
|
|
109
|
+
return 'default';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Parse markdown content into an array of slide objects
|
|
114
|
+
*/
|
|
115
|
+
export function parseSlides(content) {
|
|
116
|
+
const { metadata, body } = parseFrontmatter(content);
|
|
117
|
+
|
|
118
|
+
// Split on --- that's on its own line (not inside code blocks)
|
|
119
|
+
const rawSlides = splitSlides(body);
|
|
120
|
+
|
|
121
|
+
const slides = rawSlides.map((raw, index) => {
|
|
122
|
+
const { notes, content: withoutNotes } = extractNotes(raw);
|
|
123
|
+
const { directives, cleanContent } = parseSlideDirectives(withoutNotes);
|
|
124
|
+
const html = marked.parse(cleanContent);
|
|
125
|
+
const layout = directives.layout || detectLayout(html);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
index,
|
|
129
|
+
raw: cleanContent,
|
|
130
|
+
html,
|
|
131
|
+
notes,
|
|
132
|
+
layout,
|
|
133
|
+
directives,
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return { metadata, slides };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Split markdown into slides by --- separator
|
|
142
|
+
* Respects code blocks (doesn't split inside ```)
|
|
143
|
+
*/
|
|
144
|
+
function splitSlides(content) {
|
|
145
|
+
const lines = content.split('\n');
|
|
146
|
+
const slides = [];
|
|
147
|
+
let current = [];
|
|
148
|
+
let inCodeBlock = false;
|
|
149
|
+
|
|
150
|
+
for (const line of lines) {
|
|
151
|
+
// Track code block state
|
|
152
|
+
if (line.trim().startsWith('```')) {
|
|
153
|
+
inCodeBlock = !inCodeBlock;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check for slide separator (--- on its own line, not in code block)
|
|
157
|
+
if (!inCodeBlock && /^---\s*$/.test(line.trim()) && current.length > 0) {
|
|
158
|
+
const slideContent = current.join('\n').trim();
|
|
159
|
+
if (slideContent) {
|
|
160
|
+
slides.push(slideContent);
|
|
161
|
+
}
|
|
162
|
+
current = [];
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
current.push(line);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Don't forget the last slide
|
|
170
|
+
const lastSlide = current.join('\n').trim();
|
|
171
|
+
if (lastSlide) {
|
|
172
|
+
slides.push(lastSlide);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return slides;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export default { parseSlides, parseFrontmatter };
|