slides-grab 1.0.0 → 1.1.2
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/README.md +35 -5
- package/bin/ppt-agent.js +46 -2
- package/convert.cjs +47 -13
- package/package.json +21 -4
- package/scripts/build-viewer.js +349 -0
- package/scripts/editor-server.js +7 -1
- package/scripts/figma-export.js +148 -0
- package/scripts/html2pdf.js +419 -32
- package/scripts/html2pptx.js +135 -0
- package/scripts/install-codex-skills.js +119 -0
- package/scripts/validate-slides.js +159 -371
- package/skills/{ppt-presentation-skill → slides-grab}/SKILL.md +16 -13
- package/skills/{ppt-design-skill → slides-grab-design}/SKILL.md +12 -5
- package/skills/{ppt-pptx-skill → slides-grab-export}/SKILL.md +7 -6
- package/skills/{ppt-plan-skill → slides-grab-plan}/SKILL.md +2 -2
- package/src/editor/codex-edit.js +136 -1
- package/src/editor/js/editor-init.js +10 -3
- package/src/figma.js +63 -0
- package/src/html2pptx.cjs +1166 -0
- package/src/image-contract.js +222 -0
- package/src/validation/cli.js +97 -0
- package/src/validation/core.js +688 -0
- package/templates/split-layout.html +3 -1
- package/AGENTS.md +0 -80
- package/PROGRESS.md +0 -39
- package/SETUP.md +0 -51
- package/prd.json +0 -135
- package/prd.md +0 -104
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description: End-to-end presentation workflow for Codex. Use when making a full presentation from scratch — planning, designing slides, editing, and exporting.
|
|
2
|
+
name: slides-grab
|
|
3
|
+
description: End-to-end presentation workflow for Codex. Use when making a full presentation from scratch — planning, designing slides, editing, and exporting. PDF is preferred; PPTX/Figma export is experimental / unstable.
|
|
4
4
|
metadata:
|
|
5
|
-
short-description: Full pipeline from topic to PPTX/
|
|
5
|
+
short-description: Full pipeline from topic to PDF + experimental / unstable PPTX/Figma export
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# slides-grab Skill (Codex) - Full Workflow Orchestrator
|
|
9
9
|
|
|
10
10
|
Guides you through the complete presentation pipeline from topic to exported file.
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@ Guides you through the complete presentation pipeline from topic to exported fil
|
|
|
15
15
|
|
|
16
16
|
### Stage 1 — Plan
|
|
17
17
|
|
|
18
|
-
Use **
|
|
18
|
+
Use **slides-grab-plan** (`skills/slides-grab-plan/SKILL.md`).
|
|
19
19
|
|
|
20
20
|
1. Take user's topic, audience, and tone.
|
|
21
21
|
2. Create `slide-outline.md`.
|
|
@@ -26,23 +26,25 @@ Use **ppt-plan-skill** (`skills/ppt-plan-skill/SKILL.md`).
|
|
|
26
26
|
|
|
27
27
|
### Stage 2 — Design
|
|
28
28
|
|
|
29
|
-
Use **
|
|
29
|
+
Use **slides-grab-design** (`skills/slides-grab-design/SKILL.md`).
|
|
30
30
|
|
|
31
31
|
1. Read approved `slide-outline.md`.
|
|
32
32
|
2. Generate `slide-*.html` files in the slides workspace (default: `slides/`).
|
|
33
|
-
3.
|
|
34
|
-
4.
|
|
35
|
-
5.
|
|
36
|
-
6.
|
|
33
|
+
3. Run validation: `slides-grab validate --slides-dir <path>`
|
|
34
|
+
4. If validation fails, automatically fix the slide HTML/CSS until validation passes.
|
|
35
|
+
5. Build the viewer: `node scripts/build-viewer.js --slides-dir <path>`
|
|
36
|
+
6. Present viewer to user for review.
|
|
37
|
+
7. Revise individual slides based on feedback, then re-run validation and rebuild the viewer.
|
|
38
|
+
8. Optionally launch the visual editor: `slides-grab edit --slides-dir <path>`
|
|
37
39
|
|
|
38
40
|
**Do not proceed to Stage 3 without approval.**
|
|
39
41
|
|
|
40
42
|
### Stage 3 — Export
|
|
41
43
|
|
|
42
|
-
Use **
|
|
44
|
+
Use **slides-grab-export** (`skills/slides-grab-export/SKILL.md`).
|
|
43
45
|
|
|
44
46
|
1. Confirm user wants conversion.
|
|
45
|
-
2. Export to PPTX: `slides-grab convert --slides-dir <path> --output <name>.pptx`
|
|
47
|
+
2. Export to PPTX: `slides-grab convert --slides-dir <path> --output <name>.pptx` (**experimental / unstable**)
|
|
46
48
|
3. Export to PDF (if requested): `slides-grab pdf --slides-dir <path> --output <name>.pdf`
|
|
47
49
|
4. Report results.
|
|
48
50
|
|
|
@@ -54,4 +56,5 @@ Use **ppt-pptx-skill** (`skills/ppt-pptx-skill/SKILL.md`).
|
|
|
54
56
|
2. **Get explicit user approval** before advancing to the next stage.
|
|
55
57
|
3. **Read each stage's SKILL.md** for detailed rules — this skill only orchestrates.
|
|
56
58
|
4. **Use `decks/<deck-name>/`** as the slides workspace for multi-deck projects.
|
|
57
|
-
5.
|
|
59
|
+
5. **Call out export risk clearly**: PPTX and Figma export are experimental / unstable and must be described as best-effort output.
|
|
60
|
+
6. For full design constraints, refer to `.claude/skills/design-skill/SKILL.md`.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: slides-grab-design
|
|
3
3
|
description: Stage 2 design skill for Codex. Generate and iterate slide-XX.html files in the selected slides workspace.
|
|
4
4
|
metadata:
|
|
5
5
|
short-description: Build HTML slides and viewer for review loop
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# slides-grab Design Skill (Codex)
|
|
9
9
|
|
|
10
10
|
Use this after `slide-outline.md` is approved.
|
|
11
11
|
|
|
@@ -24,13 +24,20 @@ Generate high-quality `slide-XX.html` files in the selected slides workspace (`s
|
|
|
24
24
|
## Workflow
|
|
25
25
|
1. Read approved `slide-outline.md`.
|
|
26
26
|
2. Generate slide HTML files with 2-digit numbering in selected `--slides-dir`.
|
|
27
|
-
3. Run `
|
|
28
|
-
4.
|
|
29
|
-
5.
|
|
27
|
+
3. Run `slides-grab validate --slides-dir <path>` after generation or edits.
|
|
28
|
+
4. If validation fails, automatically fix the source slide HTML/CSS and re-run validation until it passes.
|
|
29
|
+
5. Run `node scripts/build-viewer.js --slides-dir <path>` only after validation passes.
|
|
30
|
+
6. Iterate on user feedback by editing only requested slide files, then re-run validation and rebuild the viewer.
|
|
31
|
+
7. Keep revising until user approves conversion stage.
|
|
30
32
|
|
|
31
33
|
## Rules
|
|
32
34
|
- Keep slide size 720pt x 405pt.
|
|
33
35
|
- Keep semantic text tags (`p`, `h1-h6`, `ul`, `ol`, `li`).
|
|
36
|
+
- Put local images under `<slides-dir>/assets/` and reference them as `./assets/<file>`.
|
|
37
|
+
- Allow `data:` URLs when the slide must be fully self-contained.
|
|
38
|
+
- Treat remote `https://` images as best-effort only, and never use absolute filesystem paths.
|
|
39
|
+
- Prefer `<img>` for slide imagery and `data-image-placeholder` when no final asset exists.
|
|
40
|
+
- Do not present slides for review until `slides-grab validate --slides-dir <path>` passes.
|
|
34
41
|
- Do not start conversion before approval.
|
|
35
42
|
|
|
36
43
|
## Reference
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description: Stage 3 conversion skill for Codex. Convert approved HTML slides to
|
|
2
|
+
name: slides-grab-export
|
|
3
|
+
description: Stage 3 conversion skill for Codex. Convert approved HTML slides to PDF and to experimental / unstable PPTX/Figma outputs, then validate artifacts.
|
|
4
4
|
metadata:
|
|
5
5
|
short-description: Convert slides and run conversion checks
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# slides-grab Export Skill (Codex)
|
|
9
9
|
|
|
10
10
|
Use this only after the user approves design output.
|
|
11
11
|
|
|
12
12
|
## Goal
|
|
13
|
-
Convert reviewed slide HTML into
|
|
13
|
+
Convert reviewed slide HTML into PDF reliably, and into experimental / unstable PPTX/Figma outputs on a best-effort basis.
|
|
14
14
|
|
|
15
15
|
## Inputs
|
|
16
16
|
- Approved `<slides-dir>/slide-*.html`
|
|
@@ -22,8 +22,8 @@ Convert reviewed slide HTML into PPTX (and optional PDF) reliably.
|
|
|
22
22
|
## Workflow
|
|
23
23
|
1. Confirm user approval for conversion.
|
|
24
24
|
2. Run conversion command:
|
|
25
|
-
- `node .claude/skills/pptx-skill/scripts/html2pptx.js`
|
|
26
|
-
- or `slides-grab convert --slides-dir <path>`
|
|
25
|
+
- `node .claude/skills/pptx-skill/scripts/html2pptx.js` (**experimental / unstable**)
|
|
26
|
+
- or `slides-grab convert --slides-dir <path>` (**experimental / unstable**)
|
|
27
27
|
3. If requested, run PDF conversion:
|
|
28
28
|
- `slides-grab pdf --slides-dir <path>`
|
|
29
29
|
4. Report success/failure with actionable errors.
|
|
@@ -31,6 +31,7 @@ Convert reviewed slide HTML into PPTX (and optional PDF) reliably.
|
|
|
31
31
|
## Rules
|
|
32
32
|
- Do not modify slide content during conversion stage unless explicitly requested.
|
|
33
33
|
- If conversion fails, diagnose and fix root causes in source HTML/CSS.
|
|
34
|
+
- Always tell the user that PPTX and Figma export are experimental / unstable and may require manual cleanup.
|
|
34
35
|
|
|
35
36
|
## Reference
|
|
36
37
|
For detailed conversion behavior and tools, use:
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: slides-grab-plan
|
|
3
3
|
description: Stage 1 planning skill for Codex. Build and iterate slide-outline.md until explicit user approval.
|
|
4
4
|
metadata:
|
|
5
5
|
short-description: Create and revise slide outline before design stage
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# slides-grab Plan Skill (Codex)
|
|
9
9
|
|
|
10
10
|
Use this when the user asks to start a new presentation from scratch.
|
|
11
11
|
|
package/src/editor/codex-edit.js
CHANGED
|
@@ -1,9 +1,60 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
1
2
|
import { mkdir } from 'node:fs/promises';
|
|
2
|
-
import { dirname } from 'node:path';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
3
4
|
import sharp from 'sharp';
|
|
4
5
|
|
|
6
|
+
import { getPackageRoot } from '../resolve.js';
|
|
7
|
+
|
|
5
8
|
export const SLIDE_SIZE = { width: 960, height: 540 };
|
|
6
9
|
|
|
10
|
+
const PPT_DESIGN_SKILL_PATH = join(getPackageRoot(), 'skills', 'slides-grab-design', 'SKILL.md');
|
|
11
|
+
const DETAILED_DESIGN_SKILL_PATH = join(getPackageRoot(), '.claude', 'skills', 'design-skill', 'SKILL.md');
|
|
12
|
+
const DETAILED_DESIGN_SECTION_HEADINGS = [
|
|
13
|
+
'## Base Settings',
|
|
14
|
+
'### 4. Image Usage Rules (Local Asset / Data URL / Remote URL / Placeholder)',
|
|
15
|
+
'## Text Usage Rules',
|
|
16
|
+
'## Workflow (Stage 2: Design + Human Review)',
|
|
17
|
+
'## Important Notes',
|
|
18
|
+
];
|
|
19
|
+
const DETAILED_DESIGN_SKILL_FALLBACK = [
|
|
20
|
+
'## Base Settings',
|
|
21
|
+
'',
|
|
22
|
+
'### Slide Size (16:9 default)',
|
|
23
|
+
'- Keep slide body at 720pt x 405pt.',
|
|
24
|
+
'- Use Pretendard as the default font stack.',
|
|
25
|
+
'- Include the Pretendard webfont CDN link when needed.',
|
|
26
|
+
'',
|
|
27
|
+
'### 4. Image Usage Rules (Local Asset / Data URL / Remote URL / Placeholder)',
|
|
28
|
+
'- Always include alt on img tags.',
|
|
29
|
+
'- Use ./assets/<file> as the default image contract for slide HTML.',
|
|
30
|
+
'- Keep slide assets in <slides-dir>/assets/.',
|
|
31
|
+
'- data: URLs are allowed for fully self-contained slides.',
|
|
32
|
+
'- Remote https:// URLs are allowed but non-deterministic and fallback only.',
|
|
33
|
+
'- Do not use absolute filesystem paths in slide HTML.',
|
|
34
|
+
'- Do not use non-body background-image for content imagery; use <img> instead.',
|
|
35
|
+
'- Use data-image-placeholder to reserve space when no image is available yet.',
|
|
36
|
+
'',
|
|
37
|
+
'## Text Usage Rules',
|
|
38
|
+
'- All text must be inside <p>, <h1>-<h6>, <ul>, <ol>, or <li>.',
|
|
39
|
+
'- Never place text directly in <div> or <span>.',
|
|
40
|
+
'',
|
|
41
|
+
'## Workflow (Stage 2: Design + Human Review)',
|
|
42
|
+
'- After slide generation or edits, run node scripts/build-viewer.js --slides-dir <path>.',
|
|
43
|
+
'- Edit only the relevant HTML file during revision loops.',
|
|
44
|
+
'- Never start PPTX conversion without explicit approval.',
|
|
45
|
+
'- Never forget to rebuild the viewer after slide changes.',
|
|
46
|
+
'',
|
|
47
|
+
'## Important Notes',
|
|
48
|
+
'- CSS gradients are not supported in PowerPoint conversion; replace them with background images.',
|
|
49
|
+
'- Always include the Pretendard CDN link.',
|
|
50
|
+
'- Use ./assets/<file> from each slide-XX.html and avoid absolute filesystem paths.',
|
|
51
|
+
'- Always include # prefix in CSS colors.',
|
|
52
|
+
'- Never place text directly in div/span.',
|
|
53
|
+
].join('\n');
|
|
54
|
+
|
|
55
|
+
let cachedPptDesignSkillPrompt = null;
|
|
56
|
+
let cachedDetailedDesignSkillPrompt = null;
|
|
57
|
+
|
|
7
58
|
function clamp(value, min, max) {
|
|
8
59
|
return Math.min(max, Math.max(min, value));
|
|
9
60
|
}
|
|
@@ -77,6 +128,67 @@ function formatTargets(targets) {
|
|
|
77
128
|
});
|
|
78
129
|
}
|
|
79
130
|
|
|
131
|
+
export function getPptDesignSkillPrompt() {
|
|
132
|
+
if (cachedPptDesignSkillPrompt !== null) {
|
|
133
|
+
return cachedPptDesignSkillPrompt;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
cachedPptDesignSkillPrompt = readFileSync(PPT_DESIGN_SKILL_PATH, 'utf8').trim();
|
|
138
|
+
} catch {
|
|
139
|
+
cachedPptDesignSkillPrompt = '';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return cachedPptDesignSkillPrompt;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function extractMarkdownSection(markdown, heading) {
|
|
146
|
+
const lines = markdown.split('\n');
|
|
147
|
+
const startIndex = lines.findIndex((line) => line.trim() === heading.trim());
|
|
148
|
+
if (startIndex === -1) {
|
|
149
|
+
return '';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const levelMatch = heading.match(/^(#+)\s/);
|
|
153
|
+
const headingLevel = levelMatch ? levelMatch[1].length : null;
|
|
154
|
+
if (!headingLevel) {
|
|
155
|
+
return '';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const extracted = [lines[startIndex]];
|
|
159
|
+
for (let i = startIndex + 1; i < lines.length; i += 1) {
|
|
160
|
+
const line = lines[i];
|
|
161
|
+
const nextHeadingMatch = line.match(/^(#+)\s/);
|
|
162
|
+
if (nextHeadingMatch && nextHeadingMatch[1].length <= headingLevel) {
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
extracted.push(line);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return extracted.join('\n').trim();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function getDetailedDesignSkillPrompt() {
|
|
172
|
+
if (cachedDetailedDesignSkillPrompt !== null) {
|
|
173
|
+
return cachedDetailedDesignSkillPrompt;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const markdown = readFileSync(DETAILED_DESIGN_SKILL_PATH, 'utf8');
|
|
178
|
+
const sections = DETAILED_DESIGN_SECTION_HEADINGS
|
|
179
|
+
.map((heading) => extractMarkdownSection(markdown, heading))
|
|
180
|
+
.filter(Boolean);
|
|
181
|
+
|
|
182
|
+
cachedDetailedDesignSkillPrompt = sections.length > 0
|
|
183
|
+
? sections.join('\n\n')
|
|
184
|
+
: DETAILED_DESIGN_SKILL_FALLBACK;
|
|
185
|
+
} catch {
|
|
186
|
+
cachedDetailedDesignSkillPrompt = DETAILED_DESIGN_SKILL_FALLBACK;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return cachedDetailedDesignSkillPrompt;
|
|
190
|
+
}
|
|
191
|
+
|
|
80
192
|
export function buildCodexEditPrompt({ slideFile, slidePath, userPrompt, selections = [] }) {
|
|
81
193
|
const sanitizedPrompt = typeof userPrompt === 'string' ? userPrompt.trim() : '';
|
|
82
194
|
if (!sanitizedPrompt) {
|
|
@@ -103,9 +215,30 @@ export function buildCodexEditPrompt({ slideFile, slidePath, userPrompt, selecti
|
|
|
103
215
|
];
|
|
104
216
|
});
|
|
105
217
|
|
|
218
|
+
const pptDesignSkillPrompt = getPptDesignSkillPrompt();
|
|
219
|
+
const skillLines = pptDesignSkillPrompt
|
|
220
|
+
? [
|
|
221
|
+
'Project skill guidance (follow strictly):',
|
|
222
|
+
`Source: ${PPT_DESIGN_SKILL_PATH}`,
|
|
223
|
+
pptDesignSkillPrompt,
|
|
224
|
+
'',
|
|
225
|
+
]
|
|
226
|
+
: [];
|
|
227
|
+
const detailedDesignSkillPrompt = getDetailedDesignSkillPrompt();
|
|
228
|
+
const detailedSkillLines = detailedDesignSkillPrompt
|
|
229
|
+
? [
|
|
230
|
+
'Detailed design/export guardrails (selected from the full design system):',
|
|
231
|
+
`Primary source: ${DETAILED_DESIGN_SKILL_PATH}`,
|
|
232
|
+
detailedDesignSkillPrompt,
|
|
233
|
+
'',
|
|
234
|
+
]
|
|
235
|
+
: [];
|
|
236
|
+
|
|
106
237
|
return [
|
|
107
238
|
`Edit ${normalizedSlidePath} only.`,
|
|
108
239
|
'',
|
|
240
|
+
...skillLines,
|
|
241
|
+
...detailedSkillLines,
|
|
109
242
|
'User edit request:',
|
|
110
243
|
sanitizedPrompt,
|
|
111
244
|
'',
|
|
@@ -116,6 +249,8 @@ export function buildCodexEditPrompt({ slideFile, slidePath, userPrompt, selecti
|
|
|
116
249
|
'- Keep existing structure/content unless the request requires a change.',
|
|
117
250
|
'- Keep slide dimensions at 720pt x 405pt.',
|
|
118
251
|
'- Keep text in semantic tags (<p>, <h1>-<h6>, <ul>, <ol>, <li>).',
|
|
252
|
+
'- Keep local assets under ./assets/ and preserve portable relative paths.',
|
|
253
|
+
'- Do not persist runtime-only editor/viewer injections such as <base>, debug scripts, or viewer wrapper markup into the slide file.',
|
|
119
254
|
'- Return after applying the change.',
|
|
120
255
|
].join('\n');
|
|
121
256
|
}
|
|
@@ -149,6 +149,13 @@ popoverBgColorInput.addEventListener('input', () => {
|
|
|
149
149
|
}, 'Background color updated.', { delay: 300 });
|
|
150
150
|
});
|
|
151
151
|
|
|
152
|
+
function hasEditableFocus() {
|
|
153
|
+
const activeElement = document.activeElement;
|
|
154
|
+
if (!(activeElement instanceof HTMLElement)) return false;
|
|
155
|
+
if (activeElement.matches('input, textarea, select')) return true;
|
|
156
|
+
return activeElement.isContentEditable;
|
|
157
|
+
}
|
|
158
|
+
|
|
152
159
|
// Style toggles
|
|
153
160
|
toggleBold.addEventListener('click', () => {
|
|
154
161
|
mutateSelectedObject((el) => {
|
|
@@ -199,9 +206,9 @@ alignRight.addEventListener('click', () => {
|
|
|
199
206
|
|
|
200
207
|
// Global keyboard
|
|
201
208
|
document.addEventListener('keydown', (event) => {
|
|
202
|
-
const
|
|
209
|
+
const inEditableField = hasEditableFocus();
|
|
203
210
|
|
|
204
|
-
if (state.toolMode === TOOL_MODE_SELECT && (event.ctrlKey || event.metaKey) && !
|
|
211
|
+
if (state.toolMode === TOOL_MODE_SELECT && (event.ctrlKey || event.metaKey) && !inEditableField) {
|
|
205
212
|
const key = event.key.toLowerCase();
|
|
206
213
|
if (key === 'b') { event.preventDefault(); if (!toggleBold.disabled) toggleBold.click(); return; }
|
|
207
214
|
if (key === 'i') { event.preventDefault(); if (!toggleItalic.disabled) toggleItalic.click(); return; }
|
|
@@ -219,7 +226,7 @@ document.addEventListener('keydown', (event) => {
|
|
|
219
226
|
return;
|
|
220
227
|
}
|
|
221
228
|
|
|
222
|
-
if (
|
|
229
|
+
if (inEditableField) return;
|
|
223
230
|
|
|
224
231
|
if (event.key === 'ArrowLeft') {
|
|
225
232
|
event.preventDefault();
|
package/src/figma.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { mkdir } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, extname, join, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_FIGMA_SUFFIX = '-figma.pptx';
|
|
5
|
+
export const SLIDE_FILE_PATTERN = /^slide-.*\.html$/i;
|
|
6
|
+
export const FIGMA_EXPORT_LAYOUT_NAME = 'SLIDES_GRAB_STANDARD';
|
|
7
|
+
export const SLIDE_WIDTH_INCHES = 10;
|
|
8
|
+
export const SLIDE_HEIGHT_INCHES = 5.625;
|
|
9
|
+
|
|
10
|
+
export function buildDefaultFigmaOutput(slidesDir) {
|
|
11
|
+
const absoluteSlidesDir = resolve(slidesDir);
|
|
12
|
+
const deckName = basename(absoluteSlidesDir);
|
|
13
|
+
const parentDir = dirname(absoluteSlidesDir);
|
|
14
|
+
return join(parentDir, `${deckName}${DEFAULT_FIGMA_SUFFIX}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function normalizeFigmaOutput(slidesDir, output) {
|
|
18
|
+
if (typeof output === 'string' && output.trim() !== '') {
|
|
19
|
+
const trimmed = output.trim();
|
|
20
|
+
return extname(trimmed).toLowerCase() === '.pptx' ? trimmed : `${trimmed}.pptx`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return buildDefaultFigmaOutput(slidesDir);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getFigmaImportCaveats() {
|
|
27
|
+
return [
|
|
28
|
+
'Figma export is experimental / unstable. Figma imports PPTX best-effort, and complex layouts, shadows, and grouped elements can shift or flatten.',
|
|
29
|
+
'Fonts are resolved inside Figma. If Pretendard is unavailable there, expect substitution and reflow.',
|
|
30
|
+
'Import is one-way. Re-importing creates a new Figma Slides file instead of updating the existing one.',
|
|
31
|
+
'Review every imported slide, especially chart-heavy slides and text near slide edges.',
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getFigmaManualImportInstructions() {
|
|
36
|
+
return 'Figma Slides -> Import -> select the generated .pptx file.';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function configureFigmaExportPresentation(pres) {
|
|
40
|
+
pres.defineLayout({
|
|
41
|
+
name: FIGMA_EXPORT_LAYOUT_NAME,
|
|
42
|
+
width: SLIDE_WIDTH_INCHES,
|
|
43
|
+
height: SLIDE_HEIGHT_INCHES,
|
|
44
|
+
});
|
|
45
|
+
pres.layout = FIGMA_EXPORT_LAYOUT_NAME;
|
|
46
|
+
return pres;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function ensureOutputDirectory(outputFile) {
|
|
50
|
+
await mkdir(dirname(resolve(outputFile)), { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function toSlideOrder(fileName) {
|
|
54
|
+
const match = fileName.match(/\d+/);
|
|
55
|
+
return match ? Number.parseInt(match[0], 10) : Number.POSITIVE_INFINITY;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function sortFigmaSlideFiles(a, b) {
|
|
59
|
+
const orderA = toSlideOrder(a);
|
|
60
|
+
const orderB = toSlideOrder(b);
|
|
61
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
62
|
+
return a.localeCompare(b);
|
|
63
|
+
}
|