Skip to content

Commit d222ce1

Browse files
authored
Merge pull request #73 from mainmatter/pichfl/add
Add `add` command to create new slide files
2 parents e65f70b + 764d106 commit d222ce1

File tree

9 files changed

+258
-16
lines changed

9 files changed

+258
-16
lines changed

lib/commands/add.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import fs from 'node:fs/promises';
2+
import path from 'node:path';
3+
import { globby } from 'globby';
4+
import { deburr, kebabCase } from 'lodash-es';
5+
import { ascii, printAutoRevealMessage, toNearest } from '../utils.js';
6+
7+
const DIGITS = 3;
8+
export const DEFAULT_INCREMENT = 10;
9+
10+
const filenameTemplate = (number, digits, title) =>
11+
`${number.toString().padStart(digits, '0')}${
12+
title ? `-${kebabCase(ascii(deburr(title)))}` : ''
13+
}.md`;
14+
15+
const slideTemplate = (title) =>
16+
`${title ? `# ${title}\n\n` : ''}Note:
17+
18+
This note is only visible to the presenter.
19+
`;
20+
21+
const createMessageTemplate = (filename) => `Created ./slides/${filename}
22+
Hint: Create a vertical slide by adding "---" to that file.`;
23+
24+
export async function nextSlideNumber(increment = DEFAULT_INCREMENT) {
25+
const slides = await globby('slides/*.md');
26+
27+
if (slides.length === 0) {
28+
return 0;
29+
}
30+
31+
const lastSlide = slides.at(-1);
32+
const lastSlideName = path.basename(lastSlide);
33+
const lastSlideNumber = Number.parseInt(lastSlideName, 10);
34+
35+
return toNearest(lastSlideNumber, increment) + Number.parseInt(increment, 10);
36+
}
37+
38+
export async function writeNewSlide(
39+
title,
40+
{
41+
writeTitleToFileName = true,
42+
cwd = process.cwd(),
43+
increment = DEFAULT_INCREMENT,
44+
} = {},
45+
) {
46+
const number = await nextSlideNumber(increment);
47+
const filename = filenameTemplate(
48+
number,
49+
DIGITS,
50+
writeTitleToFileName ? title : '',
51+
);
52+
53+
await fs.writeFile(path.join(cwd, filename), slideTemplate(title));
54+
55+
return { filename };
56+
}
57+
58+
export async function add(title, { increment = DEFAULT_INCREMENT } = {}) {
59+
const { filename } = await writeNewSlide(
60+
title ? title.join(' ') : undefined,
61+
{
62+
increment,
63+
cwd: path.join(process.cwd(), 'slides'),
64+
},
65+
);
66+
67+
printAutoRevealMessage(createMessageTemplate(filename));
68+
}

lib/commands/create.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ import fs from 'node:fs/promises';
22
import path from 'node:path';
33
import { deburr, kebabCase } from 'lodash-es';
44
import { mkdirp } from 'mkdirp';
5+
import { ascii, printAutoRevealMessage } from '../utils.js';
6+
import { writeNewSlide } from './add.js';
7+
8+
const createMessageTemplate = (
9+
targetDirectory,
10+
) => `Setting up your presentation in
11+
${targetDirectory}
12+
13+
Hint: Create new slides by running "auto-reveal add".
14+
Hint: Create new vertical slides by adding "---" inside your slide markdown file.`;
515

616
export async function create(target, { name }) {
717
const targetDirectory = target ? path.resolve(target) : process.cwd();
@@ -12,24 +22,21 @@ export async function create(target, { name }) {
1222
await mkdirp(targetDirectory);
1323
}
1424

15-
console.log(
16-
`\nauto-reveal\n\n Setting up your presentation in \n ${
17-
target ? targetDirectory : 'current directory'
18-
}`,
19-
);
25+
printAutoRevealMessage(createMessageTemplate(targetDirectory));
2026

2127
await mkdirp(path.join(targetDirectory, 'slides'));
2228
await mkdirp(path.join(targetDirectory, 'public'));
2329

24-
await fs.writeFile(
25-
path.join(targetDirectory, 'slides', '000.md'),
26-
`# ${name}`,
27-
);
30+
await writeNewSlide(name, {
31+
cwd: path.join(targetDirectory, 'slides'),
32+
writeTitleToFileName: false,
33+
});
34+
2835
await fs.writeFile(
2936
path.join(targetDirectory, 'package.json'),
3037
`${JSON.stringify(
3138
{
32-
name: kebabCase(deburr(name)),
39+
name: kebabCase(ascii(deburr(name))),
3340
version: '0.0.0',
3441
scripts: {
3542
'auto-reveal': 'auto-reveal',

lib/program.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Command } from 'commander';
22

3+
import { DEFAULT_INCREMENT, add } from './commands/add.js';
34
import { build } from './commands/build.js';
45
import { create } from './commands/create.js';
56
import { start } from './commands/start.js';
@@ -33,6 +34,18 @@ program
3334
)
3435
.action(create);
3536

37+
program
38+
.command('add')
39+
.alias('slide')
40+
.description('Add a new slide to your presentation.')
41+
.argument('[title...]', 'Title of the slide')
42+
.option(
43+
'-i, --increment <increment>',
44+
'Increment of the slide prefix',
45+
DEFAULT_INCREMENT,
46+
)
47+
.action(add);
48+
3649
program
3750
.command('build')
3851
.description('Build a static copy of your presentation.')

lib/utils.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@ export function getTitle(defaultTitle = 'auto-reveal') {
2020
return process.env.npm_package_name ?? defaultTitle;
2121
}
2222

23+
export function ascii(str) {
24+
return str.replace(/[^\x20-\x7E]/g, '');
25+
}
26+
27+
export const toNearest = (number, base = 10) => Math.ceil(number / base) * base;
28+
29+
export function printAutoRevealMessage(message = '') {
30+
console.log(
31+
`\nauto-reveal\n\n${message.trim().replace(/^(?!\s*$)/gm, ' ')}\n`,
32+
);
33+
}
34+
2335
const { getTheme, getThemePackage, getRevealJsTheme, readJsonSync } = utils;
2436

2537
export { getTheme, getThemePackage, getRevealJsTheme, readJsonSync };

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"dependencies": {
3131
"commander": "^13.1.0",
3232
"fixturify": "^3.0.0",
33+
"globby": "^14.1.0",
3334
"lodash-es": "^4.17.21",
3435
"mkdirp": "^3.0.1",
3536
"reveal.js": "5.2.0",
@@ -42,7 +43,6 @@
4243
"@biomejs/biome": "1.9.4",
4344
"execa": "^9.5.2",
4445
"fixturify-project": "^7.1.3",
45-
"globby": "^14.1.0",
4646
"release-plan": "0.16.0",
4747
"vitest": "^3.0.0"
4848
},

pnpm-lock.yaml

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/commands/add.test.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { execa } from 'execa';
2+
import fixturify from 'fixturify';
3+
import { describe, expect, it } from 'vitest';
4+
5+
import { makeFolder } from '../helpers.js';
6+
7+
describe('add command tests', () => {
8+
it('adds a new slide in an empty slides folder', async () => {
9+
const { cwd } = await makeFolder({
10+
files: {
11+
slides: {},
12+
},
13+
});
14+
15+
const result = await execa({
16+
cwd,
17+
})`${process.cwd()}/bin/auto-reveal add`;
18+
19+
const fixtures = fixturify.readSync(cwd);
20+
21+
expect(result.stdout).toStrictEqual(
22+
'\nauto-reveal\n\n Created ./slides/000.md\n Hint: Create a vertical slide by adding "---" to that file.\n',
23+
);
24+
expect(result.exitCode).to.equal(0);
25+
expect(fixtures).toStrictEqual({
26+
slides: {
27+
'000.md': 'Note:\n\nThis note is only visible to the presenter.\n',
28+
},
29+
});
30+
});
31+
32+
it('adds a new slide without a title', async () => {
33+
const { cwd } = await makeFolder({
34+
files: {
35+
slides: {
36+
'000.md': '# Slide 1',
37+
},
38+
},
39+
});
40+
41+
const result = await execa({
42+
cwd,
43+
})`${process.cwd()}/bin/auto-reveal add`;
44+
45+
const fixtures = fixturify.readSync(cwd);
46+
47+
expect(result.stdout).toStrictEqual(
48+
'\nauto-reveal\n\n Created ./slides/010.md\n Hint: Create a vertical slide by adding "---" to that file.\n',
49+
);
50+
expect(result.exitCode).to.equal(0);
51+
expect(fixtures).toStrictEqual({
52+
slides: {
53+
'000.md': '# Slide 1',
54+
'010.md': 'Note:\n\nThis note is only visible to the presenter.\n',
55+
},
56+
});
57+
});
58+
59+
it('adds a new slide with a custom increment', async () => {
60+
const { cwd } = await makeFolder({
61+
files: {
62+
slides: {
63+
'003.md': '# Slide 1',
64+
},
65+
},
66+
});
67+
68+
const result1 = await execa({
69+
cwd,
70+
})`${process.cwd()}/bin/auto-reveal add -i 5`;
71+
72+
expect(result1.stdout).toStrictEqual(
73+
'\nauto-reveal\n\n Created ./slides/010.md\n Hint: Create a vertical slide by adding "---" to that file.\n',
74+
);
75+
expect(result1.exitCode).to.equal(0);
76+
77+
const result2 = await execa({
78+
cwd,
79+
})`${process.cwd()}/bin/auto-reveal add -i 5`;
80+
81+
expect(result2.stdout).toStrictEqual(
82+
'\nauto-reveal\n\n Created ./slides/015.md\n Hint: Create a vertical slide by adding "---" to that file.\n',
83+
);
84+
expect(result2.exitCode).to.equal(0);
85+
86+
const fixtures = fixturify.readSync(cwd);
87+
expect(fixtures).toStrictEqual({
88+
slides: {
89+
'003.md': '# Slide 1',
90+
'010.md': 'Note:\n\nThis note is only visible to the presenter.\n',
91+
'015.md': 'Note:\n\nThis note is only visible to the presenter.\n',
92+
},
93+
});
94+
});
95+
96+
it('adds a new slide with a title', async () => {
97+
const { cwd } = await makeFolder({
98+
files: {
99+
slides: {
100+
'000.md': '# Slide 1',
101+
},
102+
},
103+
});
104+
105+
const result = await execa({
106+
cwd,
107+
})`${process.cwd()}/bin/auto-reveal add 👋 Hallöle`;
108+
109+
const fixtures = fixturify.readSync(cwd);
110+
111+
expect(result.stdout).toStrictEqual(
112+
'\nauto-reveal\n\n Created ./slides/010-hallole.md\n Hint: Create a vertical slide by adding "---" to that file.\n',
113+
);
114+
expect(result.exitCode).to.equal(0);
115+
expect(fixtures).toStrictEqual({
116+
slides: {
117+
'000.md': '# Slide 1',
118+
'010-hallole.md':
119+
'# 👋 Hallöle\n\nNote:\n\nThis note is only visible to the presenter.\n',
120+
},
121+
});
122+
});
123+
});

tests/commands/create.test.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path from 'node:path';
12
import { execa } from 'execa';
23
import fixturify from 'fixturify';
34
import { describe, expect, it } from 'vitest';
@@ -15,14 +16,19 @@ describe('create command tests', () => {
1516
const fixtures = fixturify.readSync(cwd);
1617

1718
expect(result.exitCode).to.equal(0);
18-
expect(result.stdout).to.include('\nauto-reveal\n');
19+
expect(result.stdout).toStrictEqual(
20+
`\nauto-reveal\n\n Setting up your presentation in\n ${cwd}\n\n Hint: Create new slides by running "auto-reveal add".\n Hint: Create new vertical slides by adding "---" inside your slide markdown file.\n`,
21+
);
1922
expect(fixtures).toStrictEqual({
2023
public: {},
2124
slides: {
22-
'000.md': expect.stringMatching(/^# tmp-/),
25+
'000.md': expect.stringMatching(/.*/),
2326
},
2427
'package.json': expect.stringMatching(/.*/),
2528
});
29+
expect(fixtures.slides['000.md']).toStrictEqual(
30+
`# ${path.basename(cwd)}\n\nNote:\n\nThis note is only visible to the presenter.\n`,
31+
);
2632
expect(JSON.parse(fixtures['package.json'])).toStrictEqual({
2733
name: expect.stringMatching(/^tmp-/),
2834
version: '0.0.0',

tests/utils.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { describe, expect, test } from 'vitest';
2+
import { toNearest } from '../lib/utils';
3+
4+
describe('Utils', () => {
5+
test('toNearest', async () => {
6+
expect(toNearest(3, 5)).toBe(5);
7+
expect(toNearest(7, 10)).toBe(10);
8+
expect(toNearest(15, 15)).toBe(15);
9+
expect(toNearest(10, 20)).toBe(20);
10+
expect(toNearest(10, 5)).toBe(10);
11+
expect(toNearest(0, 10)).toBe(0);
12+
});
13+
});

0 commit comments

Comments
 (0)