@portel/photon 1.18.0 → 1.19.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/dist/auto-ui/beam.d.ts.map +1 -1
- package/dist/auto-ui/beam.js +14 -4
- package/dist/auto-ui/beam.js.map +1 -1
- package/dist/beam-form.bundle.js +5 -3
- package/dist/beam-form.bundle.js.map +2 -2
- package/dist/beam.bundle.js +686 -30
- package/dist/beam.bundle.js.map +3 -3
- package/dist/claude-code-plugin.js +1 -1
- package/dist/cli/commands/beam.d.ts.map +1 -1
- package/dist/cli/commands/beam.js +8 -2
- package/dist/cli/commands/beam.js.map +1 -1
- package/dist/cli/commands/changelog.d.ts +9 -0
- package/dist/cli/commands/changelog.d.ts.map +1 -0
- package/dist/cli/commands/changelog.js +133 -0
- package/dist/cli/commands/changelog.js.map +1 -0
- package/dist/cli/commands/maker.d.ts.map +1 -1
- package/dist/cli/commands/maker.js +23 -2
- package/dist/cli/commands/maker.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +53 -0
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/package.d.ts.map +1 -1
- package/dist/cli/commands/package.js +18 -2
- package/dist/cli/commands/package.js.map +1 -1
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +1 -0
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/update.d.ts +3 -2
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +50 -43
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +16 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli-alias.js +1 -1
- package/dist/cli-alias.js.map +1 -1
- package/dist/context-store.d.ts +23 -33
- package/dist/context-store.d.ts.map +1 -1
- package/dist/context-store.js +147 -97
- package/dist/context-store.js.map +1 -1
- package/dist/context.d.ts +15 -10
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +37 -13
- package/dist/context.js.map +1 -1
- package/dist/daemon/server.js +4 -2
- package/dist/daemon/server.js.map +1 -1
- package/dist/data-migration.d.ts +27 -0
- package/dist/data-migration.d.ts.map +1 -0
- package/dist/data-migration.js +307 -0
- package/dist/data-migration.js.map +1 -0
- package/dist/editor-support/docblock-tag-catalog.d.ts.map +1 -1
- package/dist/editor-support/docblock-tag-catalog.js +6 -0
- package/dist/editor-support/docblock-tag-catalog.js.map +1 -1
- package/dist/loader.d.ts +10 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +97 -12
- package/dist/loader.js.map +1 -1
- package/dist/marketplace-manager.d.ts.map +1 -1
- package/dist/marketplace-manager.js +25 -5
- package/dist/marketplace-manager.js.map +1 -1
- package/dist/photon-cli-runner.d.ts.map +1 -1
- package/dist/photon-cli-runner.js +47 -21
- package/dist/photon-cli-runner.js.map +1 -1
- package/dist/photon-doc-extractor.d.ts +1 -0
- package/dist/photon-doc-extractor.d.ts.map +1 -1
- package/dist/photon-doc-extractor.js +6 -0
- package/dist/photon-doc-extractor.js.map +1 -1
- package/dist/readme-syncer.d.ts.map +1 -1
- package/dist/readme-syncer.js +6 -1
- package/dist/readme-syncer.js.map +1 -1
- package/dist/server.d.ts +40 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +143 -28
- package/dist/server.js.map +1 -1
- package/dist/shared/audit.js +4 -4
- package/dist/shared/audit.js.map +1 -1
- package/dist/tasks/store.d.ts.map +1 -1
- package/dist/tasks/store.js +6 -2
- package/dist/tasks/store.js.map +1 -1
- package/dist/version-notify.d.ts +27 -0
- package/dist/version-notify.d.ts.map +1 -0
- package/dist/version-notify.js +142 -0
- package/dist/version-notify.js.map +1 -0
- package/package.json +2 -2
- package/dist/auto-ui/bridge/openai-shim.d.ts +0 -20
- package/dist/auto-ui/bridge/openai-shim.d.ts.map +0 -1
- package/dist/auto-ui/bridge/openai-shim.js +0 -231
- package/dist/auto-ui/bridge/openai-shim.js.map +0 -1
- package/dist/auto-ui/bridge/photon-app.d.ts +0 -162
- package/dist/auto-ui/bridge/photon-app.d.ts.map +0 -1
- package/dist/auto-ui/bridge/photon-app.js +0 -460
- package/dist/auto-ui/bridge/photon-app.js.map +0 -1
- package/dist/auto-ui/daemon-tools.d.ts +0 -45
- package/dist/auto-ui/daemon-tools.d.ts.map +0 -1
- package/dist/auto-ui/daemon-tools.js +0 -581
- package/dist/auto-ui/daemon-tools.js.map +0 -1
- package/dist/auto-ui/design-system/index.d.ts +0 -21
- package/dist/auto-ui/design-system/index.d.ts.map +0 -1
- package/dist/auto-ui/design-system/index.js +0 -27
- package/dist/auto-ui/design-system/index.js.map +0 -1
- package/dist/auto-ui/design-system/transaction-ui.d.ts +0 -70
- package/dist/auto-ui/design-system/transaction-ui.d.ts.map +0 -1
- package/dist/auto-ui/design-system/transaction-ui.js +0 -982
- package/dist/auto-ui/design-system/transaction-ui.js.map +0 -1
- package/dist/auto-ui/playground-server.d.ts +0 -7
- package/dist/auto-ui/playground-server.d.ts.map +0 -1
- package/dist/auto-ui/playground-server.js +0 -840
- package/dist/auto-ui/playground-server.js.map +0 -1
- package/dist/auto-ui/rendering/components.d.ts +0 -29
- package/dist/auto-ui/rendering/components.d.ts.map +0 -1
- package/dist/auto-ui/rendering/components.js +0 -1341
- package/dist/auto-ui/rendering/components.js.map +0 -1
- package/dist/auto-ui/rendering/field-analyzer.d.ts +0 -104
- package/dist/auto-ui/rendering/field-analyzer.d.ts.map +0 -1
- package/dist/auto-ui/rendering/field-analyzer.js +0 -447
- package/dist/auto-ui/rendering/field-analyzer.js.map +0 -1
- package/dist/auto-ui/rendering/field-renderers.d.ts +0 -64
- package/dist/auto-ui/rendering/field-renderers.d.ts.map +0 -1
- package/dist/auto-ui/rendering/field-renderers.js +0 -317
- package/dist/auto-ui/rendering/field-renderers.js.map +0 -1
- package/dist/auto-ui/rendering/index.d.ts +0 -28
- package/dist/auto-ui/rendering/index.d.ts.map +0 -1
- package/dist/auto-ui/rendering/index.js +0 -60
- package/dist/auto-ui/rendering/index.js.map +0 -1
- package/dist/auto-ui/rendering/layout-selector.d.ts +0 -60
- package/dist/auto-ui/rendering/layout-selector.d.ts.map +0 -1
- package/dist/auto-ui/rendering/layout-selector.js +0 -476
- package/dist/auto-ui/rendering/layout-selector.js.map +0 -1
- package/dist/markdown-utils.d.ts +0 -8
- package/dist/markdown-utils.d.ts.map +0 -1
- package/dist/markdown-utils.js +0 -64
- package/dist/markdown-utils.js.map +0 -1
- package/dist/mcp-client.d.ts +0 -9
- package/dist/mcp-client.d.ts.map +0 -1
- package/dist/mcp-client.js +0 -11
- package/dist/mcp-client.js.map +0 -1
- package/dist/mcp-elicitation.d.ts +0 -32
- package/dist/mcp-elicitation.d.ts.map +0 -1
- package/dist/mcp-elicitation.js +0 -26
- package/dist/mcp-elicitation.js.map +0 -1
- package/dist/photons/builder-compass.photon.d.ts +0 -167
- package/dist/photons/builder-compass.photon.d.ts.map +0 -1
- package/dist/photons/builder-compass.photon.js +0 -816
- package/dist/photons/builder-compass.photon.js.map +0 -1
- package/dist/photons/builder-compass.photon.ts +0 -1129
- package/dist/photons/docs/ui/docs.html +0 -441
- package/dist/photons/docs.photon.d.ts +0 -237
- package/dist/photons/docs.photon.d.ts.map +0 -1
- package/dist/photons/docs.photon.js +0 -483
- package/dist/photons/docs.photon.js.map +0 -1
- package/dist/photons/docs.photon.ts +0 -536
- package/dist/photons/slides.photon.d.ts +0 -212
- package/dist/photons/slides.photon.d.ts.map +0 -1
- package/dist/photons/slides.photon.js +0 -355
- package/dist/photons/slides.photon.js.map +0 -1
- package/dist/photons/slides.photon.ts +0 -370
- package/dist/photons/spreadsheet/ui/spreadsheet.html +0 -779
- package/dist/photons/spreadsheet.photon.d.ts +0 -554
- package/dist/photons/spreadsheet.photon.d.ts.map +0 -1
- package/dist/photons/spreadsheet.photon.js +0 -1050
- package/dist/photons/spreadsheet.photon.js.map +0 -1
- package/dist/photons/spreadsheet.photon.ts +0 -1239
- package/dist/photons/ui/builder-compass.html +0 -1199
- package/dist/photons/ui/builder-compass.photon.html +0 -380
- package/dist/security-scanner.d.ts +0 -52
- package/dist/security-scanner.d.ts.map +0 -1
- package/dist/security-scanner.js +0 -181
- package/dist/security-scanner.js.map +0 -1
- package/dist/shared/performance.d.ts +0 -65
- package/dist/shared/performance.d.ts.map +0 -1
- package/dist/shared/performance.js +0 -136
- package/dist/shared/performance.js.map +0 -1
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Slides — AI-Native Presentation Tool
|
|
3
|
-
*
|
|
4
|
-
* Each instance is a deck: `_use('quarterly-review')` → `quarterly-review.md`.
|
|
5
|
-
* Pass a full path to open any markdown file: `_use('/path/to/deck.md')`.
|
|
6
|
-
*
|
|
7
|
-
* Slides are Marp-compatible markdown rendered natively by Beam's
|
|
8
|
-
* `@format slides` viewer — no custom UI, no external dependencies.
|
|
9
|
-
*
|
|
10
|
-
* @version 2.0.0
|
|
11
|
-
* @runtime ^1.14.0
|
|
12
|
-
* @tags presentation, slides, markdown, marp, ai-control
|
|
13
|
-
* @icon 📽️
|
|
14
|
-
* @stateful
|
|
15
|
-
*/
|
|
16
|
-
import * as fs from 'fs/promises';
|
|
17
|
-
import { existsSync, mkdirSync } from 'fs';
|
|
18
|
-
import * as path from 'path';
|
|
19
|
-
import * as os from 'os';
|
|
20
|
-
|
|
21
|
-
const DEFAULT_DECK = `---
|
|
22
|
-
marp: true
|
|
23
|
-
theme: default
|
|
24
|
-
paginate: true
|
|
25
|
-
transition: fade
|
|
26
|
-
---
|
|
27
|
-
# AI-Native Slides
|
|
28
|
-
### Powered by Marp & Photon
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
# How it works
|
|
33
|
-
|
|
34
|
-
1. **AI generates Marp markdown**
|
|
35
|
-
2. **Beam renders high-fidelity slides**
|
|
36
|
-
3. **Navigate with keyboard or buttons**
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
# Try it!
|
|
41
|
-
|
|
42
|
-
Ask me to:
|
|
43
|
-
- "Add a slide about the benefits of AI"
|
|
44
|
-
- "Change the theme to gaia"
|
|
45
|
-
- "Edit slide 2 with new content"
|
|
46
|
-
`;
|
|
47
|
-
|
|
48
|
-
export default class Slides {
|
|
49
|
-
protected settings = {
|
|
50
|
-
/** @property Directory where slide markdown files are stored */
|
|
51
|
-
folder: path.join(os.homedir(), 'Documents', 'slides'),
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
declare memory: {
|
|
55
|
-
get<T>(key: string): Promise<T | null>;
|
|
56
|
-
set(key: string, value: unknown): Promise<void>;
|
|
57
|
-
};
|
|
58
|
-
declare emit: (payload: { event: string; data: unknown }) => void;
|
|
59
|
-
declare instanceName: string;
|
|
60
|
-
|
|
61
|
-
// ── File Resolution ──────────────────────────────────────────
|
|
62
|
-
|
|
63
|
-
private get defaultFolder(): string {
|
|
64
|
-
return this.settings?.folder || path.join(os.homedir(), 'Documents', 'slides');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
private get deckPath(): string {
|
|
68
|
-
const name = this.instanceName || 'slides';
|
|
69
|
-
if (path.isAbsolute(name)) return name.endsWith('.md') ? name : name + '.md';
|
|
70
|
-
if (name.includes('/') || name.includes('\\')) {
|
|
71
|
-
const resolved = path.resolve(name);
|
|
72
|
-
return resolved.endsWith('.md') ? resolved : resolved + '.md';
|
|
73
|
-
}
|
|
74
|
-
const dir = this.defaultFolder;
|
|
75
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
76
|
-
return path.join(dir, name.endsWith('.md') ? name : name + '.md');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async onInitialize() {
|
|
80
|
-
const dir = this.defaultFolder;
|
|
81
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
82
|
-
|
|
83
|
-
if (!existsSync(this.deckPath)) {
|
|
84
|
-
await fs.writeFile(this.deckPath, DEFAULT_DECK, 'utf8');
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ── Presentation ─────────────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Open the presentation
|
|
92
|
-
* @format slides
|
|
93
|
-
* @autorun
|
|
94
|
-
* @readOnly
|
|
95
|
-
*/
|
|
96
|
-
async main() {
|
|
97
|
-
return await this.readDeck();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Move to the next slide
|
|
102
|
-
*/
|
|
103
|
-
async next() {
|
|
104
|
-
const md = await this.readDeck();
|
|
105
|
-
const total = this.countSlides(md);
|
|
106
|
-
const state = await this.getState();
|
|
107
|
-
if (state.currentSlide < total - 1) {
|
|
108
|
-
state.currentSlide++;
|
|
109
|
-
await this.memory.set('state', state);
|
|
110
|
-
}
|
|
111
|
-
this.emit({ event: 'slideChanged', data: { index: state.currentSlide } });
|
|
112
|
-
return `Slide ${state.currentSlide + 1} of ${total}`;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Move to the previous slide
|
|
117
|
-
*/
|
|
118
|
-
async previous() {
|
|
119
|
-
const state = await this.getState();
|
|
120
|
-
if (state.currentSlide > 0) {
|
|
121
|
-
state.currentSlide--;
|
|
122
|
-
await this.memory.set('state', state);
|
|
123
|
-
}
|
|
124
|
-
this.emit({ event: 'slideChanged', data: { index: state.currentSlide } });
|
|
125
|
-
return `Slide ${state.currentSlide + 1}`;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Jump to a specific slide
|
|
130
|
-
* @param index 0-based slide index
|
|
131
|
-
*/
|
|
132
|
-
async go({ index }: { index: number }) {
|
|
133
|
-
const md = await this.readDeck();
|
|
134
|
-
const total = this.countSlides(md);
|
|
135
|
-
const state = await this.getState();
|
|
136
|
-
state.currentSlide = clamp(Math.trunc(index), 0, Math.max(total - 1, 0));
|
|
137
|
-
await this.memory.set('state', state);
|
|
138
|
-
this.emit({ event: 'slideChanged', data: { index: state.currentSlide } });
|
|
139
|
-
return `Slide ${state.currentSlide + 1} of ${total}`;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// ── Deck Management ──────────────────────────────────────────
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* List saved decks in the slides folder
|
|
146
|
-
* @format table
|
|
147
|
-
* @readOnly
|
|
148
|
-
*/
|
|
149
|
-
async list() {
|
|
150
|
-
const dir = this.defaultFolder;
|
|
151
|
-
if (!existsSync(dir)) return [];
|
|
152
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
153
|
-
const decks = await Promise.all(
|
|
154
|
-
entries
|
|
155
|
-
.filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.md'))
|
|
156
|
-
.map(async (e) => {
|
|
157
|
-
const stat = await fs.stat(path.join(dir, e.name));
|
|
158
|
-
const md = await fs.readFile(path.join(dir, e.name), 'utf8');
|
|
159
|
-
return {
|
|
160
|
-
file: e.name,
|
|
161
|
-
title: firstHeading(md) || e.name.replace(/\.md$/i, ''),
|
|
162
|
-
slides: countSeparators(md),
|
|
163
|
-
updated: stat.mtime.toISOString().slice(0, 16).replace('T', ' '),
|
|
164
|
-
};
|
|
165
|
-
})
|
|
166
|
-
);
|
|
167
|
-
return decks.sort((a, b) => b.updated.localeCompare(a.updated));
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Read the current deck's raw markdown
|
|
172
|
-
* @format markdown
|
|
173
|
-
* @readOnly
|
|
174
|
-
*/
|
|
175
|
-
async read() {
|
|
176
|
-
return await this.readDeck();
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Save markdown to the current deck and show result
|
|
181
|
-
* @param markdown Full Marp markdown content
|
|
182
|
-
* @format slides
|
|
183
|
-
*/
|
|
184
|
-
async save({ markdown }: { markdown: string }) {
|
|
185
|
-
await fs.writeFile(this.deckPath, markdown, 'utf8');
|
|
186
|
-
this.emit({ event: 'deckChanged', data: { file: path.basename(this.deckPath) } });
|
|
187
|
-
return markdown;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Update the full markdown and re-render
|
|
192
|
-
* @param markdown New Marp markdown content
|
|
193
|
-
* @format slides
|
|
194
|
-
*/
|
|
195
|
-
async update({ markdown }: { markdown: string }) {
|
|
196
|
-
return this.save({ markdown });
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// ── Slide-Level Operations ───────────────────────────────────
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Insert a new slide at a position
|
|
203
|
-
* @param markdown Slide content (without --- separators)
|
|
204
|
-
* @param index Position to insert (appends if omitted)
|
|
205
|
-
* @format slides
|
|
206
|
-
*/
|
|
207
|
-
async add(params?: { markdown?: string; index?: number }) {
|
|
208
|
-
const md = await this.readDeck();
|
|
209
|
-
const { frontmatter, slides } = splitMarpMarkdown(md);
|
|
210
|
-
const content = params?.markdown ?? '# New Slide\n\nAdd your content here';
|
|
211
|
-
const index =
|
|
212
|
-
params?.index != null ? clamp(Math.trunc(params.index), 0, slides.length) : slides.length;
|
|
213
|
-
slides.splice(index, 0, content);
|
|
214
|
-
const newMd = joinMarpMarkdown(frontmatter, slides);
|
|
215
|
-
await fs.writeFile(this.deckPath, newMd, 'utf8');
|
|
216
|
-
await this.memory.set('state', { currentSlide: index });
|
|
217
|
-
this.emit({ event: 'deckChanged', data: { file: path.basename(this.deckPath) } });
|
|
218
|
-
return newMd;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Replace a slide's content
|
|
223
|
-
* @param index Slide index
|
|
224
|
-
* @param markdown New content for the slide
|
|
225
|
-
* @format slides
|
|
226
|
-
*/
|
|
227
|
-
async edit({ index, markdown }: { index: number; markdown: string }) {
|
|
228
|
-
const md = await this.readDeck();
|
|
229
|
-
const { frontmatter, slides } = splitMarpMarkdown(md);
|
|
230
|
-
const i = clamp(Math.trunc(index), 0, Math.max(slides.length - 1, 0));
|
|
231
|
-
slides[i] = markdown;
|
|
232
|
-
const newMd = joinMarpMarkdown(frontmatter, slides);
|
|
233
|
-
await fs.writeFile(this.deckPath, newMd, 'utf8');
|
|
234
|
-
this.emit({ event: 'deckChanged', data: { file: path.basename(this.deckPath) } });
|
|
235
|
-
return newMd;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Reorder a slide
|
|
240
|
-
* @param from Source index
|
|
241
|
-
* @param to Target index
|
|
242
|
-
* @format slides
|
|
243
|
-
*/
|
|
244
|
-
async move({ from, to }: { from: number; to: number }) {
|
|
245
|
-
const md = await this.readDeck();
|
|
246
|
-
const { frontmatter, slides } = splitMarpMarkdown(md);
|
|
247
|
-
const f = clamp(Math.trunc(from), 0, Math.max(slides.length - 1, 0));
|
|
248
|
-
const t = clamp(Math.trunc(to), 0, Math.max(slides.length - 1, 0));
|
|
249
|
-
if (f === t) return md;
|
|
250
|
-
const [slide] = slides.splice(f, 1);
|
|
251
|
-
slides.splice(t, 0, slide);
|
|
252
|
-
const newMd = joinMarpMarkdown(frontmatter, slides);
|
|
253
|
-
await fs.writeFile(this.deckPath, newMd, 'utf8');
|
|
254
|
-
await this.memory.set('state', { currentSlide: t });
|
|
255
|
-
this.emit({ event: 'deckChanged', data: { file: path.basename(this.deckPath) } });
|
|
256
|
-
return newMd;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Delete a slide
|
|
261
|
-
* @param index Slide index
|
|
262
|
-
* @destructive
|
|
263
|
-
* @format slides
|
|
264
|
-
*/
|
|
265
|
-
async remove({ index }: { index: number }) {
|
|
266
|
-
const md = await this.readDeck();
|
|
267
|
-
const { frontmatter, slides } = splitMarpMarkdown(md);
|
|
268
|
-
if (slides.length <= 1) return md;
|
|
269
|
-
const i = clamp(Math.trunc(index), 0, Math.max(slides.length - 1, 0));
|
|
270
|
-
slides.splice(i, 1);
|
|
271
|
-
const newMd = joinMarpMarkdown(frontmatter, slides);
|
|
272
|
-
await fs.writeFile(this.deckPath, newMd, 'utf8');
|
|
273
|
-
const cur = clamp(i, 0, Math.max(slides.length - 1, 0));
|
|
274
|
-
await this.memory.set('state', { currentSlide: cur });
|
|
275
|
-
this.emit({ event: 'deckChanged', data: { file: path.basename(this.deckPath) } });
|
|
276
|
-
return newMd;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Duplicate a slide
|
|
281
|
-
* @param index Slide index to copy
|
|
282
|
-
* @format slides
|
|
283
|
-
*/
|
|
284
|
-
async duplicate({ index }: { index: number }) {
|
|
285
|
-
const md = await this.readDeck();
|
|
286
|
-
const { frontmatter, slides } = splitMarpMarkdown(md);
|
|
287
|
-
const i = clamp(Math.trunc(index), 0, Math.max(slides.length - 1, 0));
|
|
288
|
-
slides.splice(i + 1, 0, slides[i]);
|
|
289
|
-
const newMd = joinMarpMarkdown(frontmatter, slides);
|
|
290
|
-
await fs.writeFile(this.deckPath, newMd, 'utf8');
|
|
291
|
-
await this.memory.set('state', { currentSlide: i + 1 });
|
|
292
|
-
this.emit({ event: 'deckChanged', data: { file: path.basename(this.deckPath) } });
|
|
293
|
-
return newMd;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// ── Context ──────────────────────────────────────────────────
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Current presentation state for AI context
|
|
300
|
-
* @readOnly
|
|
301
|
-
*/
|
|
302
|
-
async status() {
|
|
303
|
-
const md = await this.readDeck();
|
|
304
|
-
const { slides } = splitMarpMarkdown(md);
|
|
305
|
-
const state = await this.getState();
|
|
306
|
-
return {
|
|
307
|
-
file: path.basename(this.deckPath),
|
|
308
|
-
currentSlide: state.currentSlide,
|
|
309
|
-
totalSlides: slides.length,
|
|
310
|
-
currentContent: slides[state.currentSlide] || '',
|
|
311
|
-
nextSlidePreview: slides[state.currentSlide + 1] || null,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// ── Private ──────────────────────────────────────────────────
|
|
316
|
-
|
|
317
|
-
private async readDeck(): Promise<string> {
|
|
318
|
-
try {
|
|
319
|
-
return await fs.readFile(this.deckPath, 'utf8');
|
|
320
|
-
} catch {
|
|
321
|
-
return DEFAULT_DECK;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
private async getState() {
|
|
326
|
-
return (await this.memory.get<any>('state')) || { currentSlide: 0 };
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
private countSlides(markdown: string): number {
|
|
330
|
-
return countSeparators(markdown);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// ── Helpers ────────────────────────────────────────────────────
|
|
335
|
-
|
|
336
|
-
function clamp(v: number, min: number, max: number) {
|
|
337
|
-
return Math.min(Math.max(Number.isFinite(v) ? v : min, min), max);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function firstHeading(md: string) {
|
|
341
|
-
return md.match(/^#\s+(.+)$/m)?.[1]?.trim() || '';
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function countSeparators(md: string): number {
|
|
345
|
-
const fm = md.match(/^---\n[\s\S]*?\n---\n*/);
|
|
346
|
-
const body = fm ? md.slice(fm[0].length) : md;
|
|
347
|
-
return body.split(/\n---\s*\n/).length;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function splitMarpMarkdown(markdown: string) {
|
|
351
|
-
const fm = markdown.match(/^---\n[\s\S]*?\n---\n*/);
|
|
352
|
-
const frontmatter = fm ? fm[0].trimEnd() : '---\nmarp: true\n---';
|
|
353
|
-
const body = fm ? markdown.slice(fm[0].length) : markdown;
|
|
354
|
-
const slides: string[] = [];
|
|
355
|
-
let cur: string[] = [];
|
|
356
|
-
for (const line of body.split('\n')) {
|
|
357
|
-
if (line.trim() === '---') {
|
|
358
|
-
slides.push(cur.join('\n').trim());
|
|
359
|
-
cur = [];
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
cur.push(line);
|
|
363
|
-
}
|
|
364
|
-
slides.push(cur.join('\n').trim());
|
|
365
|
-
return { frontmatter, slides: slides.filter((s) => s.length > 0) };
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function joinMarpMarkdown(frontmatter: string, slides: string[]): string {
|
|
369
|
-
return `${frontmatter}\n\n${slides.join('\n\n---\n\n')}\n`;
|
|
370
|
-
}
|