@christophervr/pptx-viewer 1.1.45 → 1.2.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/CHANGELOG.md +17 -0
- package/README.md +10 -7
- package/dist/index.mjs +347 -81
- package/package.json +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
This file is generated from [Conventional Commits](https://www.conventionalcommits.org)
|
|
5
|
+
by [git-cliff](https://git-cliff.org); do not edit it by hand.
|
|
6
|
+
|
|
7
|
+
## [1.1.46](https://github.com/ChristopherVR/pptx-viewer/releases/tag/@christophervr/pptx-viewer@1.1.46) - 2026-07-03
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **cli:** Arrow-key colour prompts and PowerPoint-ready scaffolds (by @ChristopherVR) ([8de03c9](https://github.com/ChristopherVR/pptx-viewer/commit/8de03c9da8c8d20e28cca253ff6d7083de65a0d8))
|
|
12
|
+
|
|
13
|
+
## [1.1.45](https://github.com/ChristopherVR/pptx-viewer/releases/tag/@christophervr/pptx-viewer@1.1.45) - 2026-07-02
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- **cli:** Add interactive @christophervr/pptx-viewer installer (by @ChristopherVR) ([4df680d](https://github.com/ChristopherVR/pptx-viewer/commit/4df680d9791d18e38c0f413420e8e1e5f9f2907e))
|
package/README.md
CHANGED
|
@@ -19,18 +19,21 @@ This is what you get one `npx` away: a working `.pptx` viewer/editor, wired up i
|
|
|
19
19
|
npx @christophervr/pptx-viewer@latest
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
It first asks what you're building (
|
|
22
|
+
It first asks what you're building with an arrow-key checklist (`↑`/`↓` to move, `space` to toggle, `a` to select all, `enter` to confirm):
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
What are you building with pptx-viewer? (you can pick more than one)
|
|
26
|
+
(↑/↓ move, space toggle, a select all, enter confirm)
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
❯ ◉ React - pptx-react-viewer, a viewer/editor component for a React 19 app
|
|
29
|
+
◯ Vue - pptx-vue-viewer, a viewer/editor component for a Vue 3.5+ app
|
|
30
|
+
◯ Angular - pptx-angular-viewer, a viewer/editor component for an Angular 22+ app
|
|
31
|
+
◯ Core engine only - pptx-viewer-core, the framework-agnostic SDK, no UI
|
|
32
|
+
◯ MCP server - pptx-viewer-mcp, PowerPoint editing tools for AI agents
|
|
32
33
|
```
|
|
33
34
|
|
|
35
|
+
The whole flow is colour-highlighted (current row, confirmations, warnings, errors) and falls back to a plain numbered prompt in shells without raw keyboard input (piped stdin, some CI runners) or when `NO_COLOR`/a non-TTY output disables colour.
|
|
36
|
+
|
|
34
37
|
Picking more than one is fine, for example React plus the MCP server to get both a viewer and AI-agent tooling in the same repo. `pptx-viewer-mcp` never gets installed as a dependency: since it's meant to be launched by an MCP client via `npx`, this just prints the client config to paste in.
|
|
35
38
|
|
|
36
39
|
### Compatibility check
|
|
@@ -42,7 +45,7 @@ If you picked React, Vue, or Angular and a `package.json` already exists in the
|
|
|
42
45
|
When exactly one UI framework is selected, you're asked how to set it up:
|
|
43
46
|
|
|
44
47
|
- **Install here** adds the package(s) to the project in the current directory (a `package.json` must already exist; run `npm init -y` first if not).
|
|
45
|
-
- **Scaffold a new project** bootstraps a brand-new starter app in its own folder, using the framework's own official scaffolding tool ([`create-vite`](https://www.npmjs.com/package/create-vite) for React/Vue, [`@angular/cli`](https://www.npmjs.com/package/@angular/cli) for Angular), then wires in a
|
|
48
|
+
- **Scaffold a new project** bootstraps a brand-new starter app in its own folder, using the framework's own official scaffolding tool ([`create-vite`](https://www.npmjs.com/package/create-vite) for React/Vue, [`@angular/cli`](https://www.npmjs.com/package/@angular/cli) for Angular), then wires in a working `PowerPointViewer` example (same pattern as the [live demos](https://christophervr.github.io/pptx-viewer/demo/): open an existing `.pptx`, or click "New Presentation" to build a blank deck with `PptxHandler.createBlank` and start editing right away) and installs the viewer package plus `pptx-viewer-core` on top.
|
|
46
49
|
|
|
47
50
|
Scaffolding is only offered for a single framework at a time; if you select more than one UI framework together, the CLI installs into the current project instead.
|
|
48
51
|
|
package/dist/index.mjs
CHANGED
|
@@ -52,6 +52,28 @@ function parseArgs(args) {
|
|
|
52
52
|
return parsed;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// src/colors.ts
|
|
56
|
+
var isColorEnabled = process.env.NO_COLOR === void 0 && (process.env.FORCE_COLOR !== void 0 || Boolean(process.stdout.isTTY));
|
|
57
|
+
function wrap(open, close) {
|
|
58
|
+
return (text) => isColorEnabled ? `\x1B[${open}m${text}\x1B[${close}m` : text;
|
|
59
|
+
}
|
|
60
|
+
var bold = wrap(1, 22);
|
|
61
|
+
var dim = wrap(2, 22);
|
|
62
|
+
var red = wrap(31, 39);
|
|
63
|
+
var green = wrap(32, 39);
|
|
64
|
+
var yellow = wrap(33, 39);
|
|
65
|
+
var blue = wrap(34, 39);
|
|
66
|
+
var magenta = wrap(35, 39);
|
|
67
|
+
var cyan = wrap(36, 39);
|
|
68
|
+
var gray = wrap(90, 39);
|
|
69
|
+
function isUnicodeSupported() {
|
|
70
|
+
if (process.platform !== "win32") {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return Boolean(process.env.CI) || Boolean(process.env.WT_SESSION) || Boolean(process.env.ConEmuTask) || process.env.TERM_PROGRAM === "vscode" || process.env.TERM === "xterm-256color";
|
|
74
|
+
}
|
|
75
|
+
var symbols = isUnicodeSupported() ? { pointer: "\u276F", check: "\u2714", cross: "\u2718", radioOn: "\u25C9", radioOff: "\u25EF", bullet: "\xB7" } : { pointer: ">", check: "\u221A", cross: "\xD7", radioOn: "(*)", radioOff: "( )", bullet: "*" };
|
|
76
|
+
|
|
55
77
|
// src/project-deps.ts
|
|
56
78
|
import { existsSync, readFileSync } from "fs";
|
|
57
79
|
import { join } from "path";
|
|
@@ -150,6 +172,140 @@ function installCommand(pm, packages) {
|
|
|
150
172
|
|
|
151
173
|
// src/prompt.ts
|
|
152
174
|
import { createInterface } from "readline/promises";
|
|
175
|
+
|
|
176
|
+
// src/interactive-menu.ts
|
|
177
|
+
import { emitKeypressEvents } from "readline";
|
|
178
|
+
var HIDE_CURSOR = "\x1B[?25l";
|
|
179
|
+
var SHOW_CURSOR = "\x1B[?25h";
|
|
180
|
+
var CLEAR_LINE = "\x1B[2K";
|
|
181
|
+
var ERASE_DOWN = "\x1B[0J";
|
|
182
|
+
function moveUp(lines) {
|
|
183
|
+
return lines > 0 ? `\x1B[${lines}A` : "";
|
|
184
|
+
}
|
|
185
|
+
function isEnterKey(str, key) {
|
|
186
|
+
return key?.name === "return" || key?.name === "enter" || str === "\r" || str === "\n";
|
|
187
|
+
}
|
|
188
|
+
function renderChoice(choice, isCursor, checked) {
|
|
189
|
+
const pointer = isCursor ? cyan(symbols.pointer) : " ";
|
|
190
|
+
const box = checked === null ? "" : `${checked ? green(symbols.radioOn) : gray(symbols.radioOff)} `;
|
|
191
|
+
const label = isCursor ? bold(choice.label) : choice.label;
|
|
192
|
+
return `${pointer} ${box}${label} ${dim(`- ${choice.description}`)}`;
|
|
193
|
+
}
|
|
194
|
+
function groupMatesOf(choices, index) {
|
|
195
|
+
const group = choices[index].group;
|
|
196
|
+
if (!group) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
return choices.flatMap((c, i) => i !== index && c.group === group ? [i] : []);
|
|
200
|
+
}
|
|
201
|
+
function runMenu(choices, multi) {
|
|
202
|
+
return new Promise((resolve) => {
|
|
203
|
+
if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== "function") {
|
|
204
|
+
resolve(null);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
let cursor = 0;
|
|
208
|
+
const checked = /* @__PURE__ */ new Set();
|
|
209
|
+
let statusMessage = "";
|
|
210
|
+
let settled = false;
|
|
211
|
+
const hint = multi ? dim("(\u2191/\u2193 move, space toggle, a select all, enter confirm)") : dim("(\u2191/\u2193 move, enter confirm)");
|
|
212
|
+
const totalLines = choices.length + 2;
|
|
213
|
+
process.stdout.write(HIDE_CURSOR);
|
|
214
|
+
function draw(first) {
|
|
215
|
+
if (!first) {
|
|
216
|
+
process.stdout.write(moveUp(totalLines));
|
|
217
|
+
}
|
|
218
|
+
process.stdout.write(`${CLEAR_LINE}${hint}
|
|
219
|
+
`);
|
|
220
|
+
for (const [i, choice] of choices.entries()) {
|
|
221
|
+
const checkedState = multi ? checked.has(i) : null;
|
|
222
|
+
process.stdout.write(`${CLEAR_LINE}${renderChoice(choice, i === cursor, checkedState)}
|
|
223
|
+
`);
|
|
224
|
+
}
|
|
225
|
+
process.stdout.write(`${CLEAR_LINE}${statusMessage}
|
|
226
|
+
`);
|
|
227
|
+
}
|
|
228
|
+
function eraseWidget() {
|
|
229
|
+
process.stdout.write(moveUp(totalLines));
|
|
230
|
+
process.stdout.write(ERASE_DOWN);
|
|
231
|
+
}
|
|
232
|
+
function cleanup() {
|
|
233
|
+
process.stdin.setRawMode?.(false);
|
|
234
|
+
process.stdin.removeListener("keypress", onKeypress);
|
|
235
|
+
process.stdin.pause();
|
|
236
|
+
eraseWidget();
|
|
237
|
+
process.stdout.write(SHOW_CURSOR);
|
|
238
|
+
}
|
|
239
|
+
function finish(result) {
|
|
240
|
+
if (settled) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
settled = true;
|
|
244
|
+
cleanup();
|
|
245
|
+
resolve(result);
|
|
246
|
+
}
|
|
247
|
+
function check(index) {
|
|
248
|
+
for (const mate of groupMatesOf(choices, index)) {
|
|
249
|
+
checked.delete(mate);
|
|
250
|
+
}
|
|
251
|
+
checked.add(index);
|
|
252
|
+
}
|
|
253
|
+
function toggleSelectAll() {
|
|
254
|
+
const selectable = choices.flatMap((c, i) => c.group ? [] : [i]);
|
|
255
|
+
const allSelected = selectable.every((i) => checked.has(i));
|
|
256
|
+
for (const i of selectable) {
|
|
257
|
+
if (allSelected) {
|
|
258
|
+
checked.delete(i);
|
|
259
|
+
} else {
|
|
260
|
+
checked.add(i);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function onKeypress(str, key) {
|
|
265
|
+
if (key?.ctrl && key.name === "c") {
|
|
266
|
+
finish(null);
|
|
267
|
+
process.exit(130);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
statusMessage = "";
|
|
271
|
+
if (key?.name === "up") {
|
|
272
|
+
cursor = (cursor - 1 + choices.length) % choices.length;
|
|
273
|
+
draw(false);
|
|
274
|
+
} else if (key?.name === "down") {
|
|
275
|
+
cursor = (cursor + 1) % choices.length;
|
|
276
|
+
draw(false);
|
|
277
|
+
} else if (multi && key?.name === "space") {
|
|
278
|
+
if (checked.has(cursor)) {
|
|
279
|
+
checked.delete(cursor);
|
|
280
|
+
} else {
|
|
281
|
+
check(cursor);
|
|
282
|
+
}
|
|
283
|
+
draw(false);
|
|
284
|
+
} else if (multi && key?.name === "a") {
|
|
285
|
+
toggleSelectAll();
|
|
286
|
+
draw(false);
|
|
287
|
+
} else if (isEnterKey(str, key)) {
|
|
288
|
+
if (multi) {
|
|
289
|
+
if (checked.size > 0) {
|
|
290
|
+
finish([...checked].sort((a, b) => a - b));
|
|
291
|
+
} else {
|
|
292
|
+
statusMessage = dim("Select at least one option with space, then press enter.");
|
|
293
|
+
draw(false);
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
finish([cursor]);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
emitKeypressEvents(process.stdin);
|
|
301
|
+
process.stdin.setRawMode(true);
|
|
302
|
+
process.stdin.on("keypress", onKeypress);
|
|
303
|
+
process.stdin.resume();
|
|
304
|
+
draw(true);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/prompt.ts
|
|
153
309
|
function parseSelection(answer, count) {
|
|
154
310
|
const trimmed = answer.trim().toLowerCase();
|
|
155
311
|
if (trimmed === "all" || trimmed === "a") {
|
|
@@ -171,15 +327,12 @@ function parseSelection(answer, count) {
|
|
|
171
327
|
}
|
|
172
328
|
function printOptions(options) {
|
|
173
329
|
options.forEach((opt, i) => {
|
|
174
|
-
console.log(` ${i + 1}) ${opt.label}
|
|
330
|
+
console.log(` ${cyan(`${i + 1})`)} ${bold(opt.label)} ${dim(`- ${opt.description}`)}`);
|
|
175
331
|
});
|
|
176
332
|
}
|
|
177
|
-
async function
|
|
333
|
+
async function selectByNumber(options) {
|
|
178
334
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
179
335
|
try {
|
|
180
|
-
console.log(`
|
|
181
|
-
${question}
|
|
182
|
-
`);
|
|
183
336
|
printOptions(options);
|
|
184
337
|
for (; ; ) {
|
|
185
338
|
const answer = (await rl.question(`
|
|
@@ -194,12 +347,9 @@ Enter a number (1-${options.length}): `)).trim();
|
|
|
194
347
|
rl.close();
|
|
195
348
|
}
|
|
196
349
|
}
|
|
197
|
-
async function
|
|
350
|
+
async function selectManyByNumber(options) {
|
|
198
351
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
199
352
|
try {
|
|
200
|
-
console.log(`
|
|
201
|
-
${question}
|
|
202
|
-
`);
|
|
203
353
|
printOptions(options);
|
|
204
354
|
for (; ; ) {
|
|
205
355
|
const answer = await rl.question(
|
|
@@ -216,10 +366,32 @@ Enter one or more numbers, comma-separated (e.g. "1,3"), or "all": `
|
|
|
216
366
|
rl.close();
|
|
217
367
|
}
|
|
218
368
|
}
|
|
369
|
+
async function selectOption(question, options) {
|
|
370
|
+
console.log(`
|
|
371
|
+
${bold(question)}`);
|
|
372
|
+
const picked = await runMenu(options, false);
|
|
373
|
+
if (!picked) {
|
|
374
|
+
return selectByNumber(options);
|
|
375
|
+
}
|
|
376
|
+
const choice = options[picked[0]];
|
|
377
|
+
console.log(`${green("\u2714")} ${choice.label}`);
|
|
378
|
+
return choice;
|
|
379
|
+
}
|
|
380
|
+
async function multiSelect(question, options) {
|
|
381
|
+
console.log(`
|
|
382
|
+
${bold(question)}`);
|
|
383
|
+
const picked = await runMenu(options, true);
|
|
384
|
+
if (!picked) {
|
|
385
|
+
return selectManyByNumber(options);
|
|
386
|
+
}
|
|
387
|
+
const choices = picked.map((i) => options[i]);
|
|
388
|
+
console.log(`${green("\u2714")} ${choices.map((c) => c.label).join(", ")}`);
|
|
389
|
+
return choices;
|
|
390
|
+
}
|
|
219
391
|
async function confirm(question) {
|
|
220
392
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
221
393
|
try {
|
|
222
|
-
const answer = (await rl.question(`${question} (Y/n)
|
|
394
|
+
const answer = (await rl.question(`${bold(question)} ${dim("(Y/n)")} `)).trim().toLowerCase();
|
|
223
395
|
return answer === "" || answer === "y" || answer === "yes";
|
|
224
396
|
} finally {
|
|
225
397
|
rl.close();
|
|
@@ -228,7 +400,7 @@ async function confirm(question) {
|
|
|
228
400
|
async function input(question, defaultValue) {
|
|
229
401
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
230
402
|
try {
|
|
231
|
-
const answer = (await rl.question(`${question} (${defaultValue})
|
|
403
|
+
const answer = (await rl.question(`${bold(question)} ${dim(`(${defaultValue})`)} `)).trim();
|
|
232
404
|
return answer === "" ? defaultValue : answer;
|
|
233
405
|
} finally {
|
|
234
406
|
rl.close();
|
|
@@ -236,48 +408,91 @@ async function input(question, defaultValue) {
|
|
|
236
408
|
}
|
|
237
409
|
|
|
238
410
|
// src/targets.ts
|
|
239
|
-
var REACT_APP_TSX = `import { useState } from 'react';
|
|
411
|
+
var REACT_APP_TSX = `import { useCallback, useState } from 'react';
|
|
412
|
+
import { PptxHandler } from 'pptx-viewer-core';
|
|
240
413
|
import { PowerPointViewer } from 'pptx-react-viewer';
|
|
241
|
-
import 'pptx-react-viewer/styles';
|
|
414
|
+
import 'pptx-react-viewer/styles.css';
|
|
242
415
|
|
|
243
416
|
export default function App() {
|
|
244
|
-
const [content, setContent] = useState<
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
417
|
+
const [content, setContent] = useState<Uint8Array | null>(null);
|
|
418
|
+
|
|
419
|
+
const loadFile = useCallback((file: File) => {
|
|
420
|
+
const reader = new FileReader();
|
|
421
|
+
reader.onload = () => setContent(new Uint8Array(reader.result as ArrayBuffer));
|
|
422
|
+
reader.readAsArrayBuffer(file);
|
|
423
|
+
}, []);
|
|
424
|
+
|
|
425
|
+
const newPresentation = useCallback(async () => {
|
|
426
|
+
const { handler, data } = await PptxHandler.createBlank({
|
|
427
|
+
title: 'Untitled Presentation',
|
|
428
|
+
initialSlideCount: 1,
|
|
429
|
+
});
|
|
430
|
+
setContent(await handler.save(data.slides));
|
|
431
|
+
}, []);
|
|
432
|
+
|
|
433
|
+
if (content) {
|
|
434
|
+
return (
|
|
435
|
+
<div style={{ height: '100vh' }}>
|
|
436
|
+
<PowerPointViewer content={content} canEdit />
|
|
437
|
+
</div>
|
|
438
|
+
);
|
|
439
|
+
}
|
|
248
440
|
|
|
249
441
|
return (
|
|
250
|
-
<div style={{ height: '100vh' }}>
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
442
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 12, height: '100vh' }}>
|
|
443
|
+
<input
|
|
444
|
+
type="file"
|
|
445
|
+
accept=".pptx"
|
|
446
|
+
onChange={(e) => {
|
|
447
|
+
const file = e.target.files?.[0];
|
|
448
|
+
if (file) loadFile(file);
|
|
449
|
+
}}
|
|
450
|
+
/>
|
|
451
|
+
<button onClick={() => void newPresentation()}>or create a New Presentation</button>
|
|
256
452
|
</div>
|
|
257
453
|
);
|
|
258
454
|
}
|
|
259
455
|
`;
|
|
260
456
|
var VUE_APP_VUE = `<script setup lang="ts">
|
|
261
457
|
import { ref } from 'vue';
|
|
458
|
+
import { PptxHandler } from 'pptx-viewer-core';
|
|
262
459
|
import { PowerPointViewer } from 'pptx-vue-viewer';
|
|
263
|
-
import 'pptx-vue-viewer/styles';
|
|
460
|
+
import 'pptx-vue-viewer/styles.css';
|
|
264
461
|
|
|
265
462
|
const content = ref<Uint8Array>();
|
|
266
463
|
|
|
464
|
+
function loadFile(file: File) {
|
|
465
|
+
const reader = new FileReader();
|
|
466
|
+
reader.onload = () => (content.value = new Uint8Array(reader.result as ArrayBuffer));
|
|
467
|
+
reader.readAsArrayBuffer(file);
|
|
468
|
+
}
|
|
469
|
+
|
|
267
470
|
function onPick(e: Event) {
|
|
268
471
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
269
|
-
file
|
|
472
|
+
if (file) loadFile(file);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async function newPresentation() {
|
|
476
|
+
const { handler, data } = await PptxHandler.createBlank({
|
|
477
|
+
title: 'Untitled Presentation',
|
|
478
|
+
initialSlideCount: 1,
|
|
479
|
+
});
|
|
480
|
+
content.value = await handler.save(data.slides);
|
|
270
481
|
}
|
|
271
482
|
</script>
|
|
272
483
|
|
|
273
484
|
<template>
|
|
274
|
-
<div style="height: 100vh">
|
|
275
|
-
<PowerPointViewer
|
|
276
|
-
|
|
485
|
+
<div v-if="content" style="height: 100vh">
|
|
486
|
+
<PowerPointViewer :content="content" can-edit style="height: 100%" />
|
|
487
|
+
</div>
|
|
488
|
+
<div v-else style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; height: 100vh">
|
|
489
|
+
<input type="file" accept=".pptx" @change="onPick" />
|
|
490
|
+
<button @click="newPresentation">or create a New Presentation</button>
|
|
277
491
|
</div>
|
|
278
492
|
</template>
|
|
279
493
|
`;
|
|
280
494
|
var ANGULAR_APP_TS = `import { Component, signal } from '@angular/core';
|
|
495
|
+
import { PptxHandler } from 'pptx-viewer-core';
|
|
281
496
|
import { PowerPointViewerComponent } from 'pptx-angular-viewer';
|
|
282
497
|
|
|
283
498
|
@Component({
|
|
@@ -285,17 +500,20 @@ import { PowerPointViewerComponent } from 'pptx-angular-viewer';
|
|
|
285
500
|
standalone: true,
|
|
286
501
|
imports: [PowerPointViewerComponent],
|
|
287
502
|
template: \`
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
<pptx-power-point-viewer [content]="c" style="height: 100%" />
|
|
291
|
-
|
|
503
|
+
@if (content(); as c) {
|
|
504
|
+
<div style="height: 100vh">
|
|
505
|
+
<pptx-power-point-viewer [content]="c" [canEdit]="true" style="height: 100%" />
|
|
506
|
+
</div>
|
|
507
|
+
} @else {
|
|
508
|
+
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; height: 100vh">
|
|
292
509
|
<input type="file" accept=".pptx" (change)="onPick($event)" />
|
|
293
|
-
|
|
294
|
-
|
|
510
|
+
<button (click)="newPresentation()">or create a New Presentation</button>
|
|
511
|
+
</div>
|
|
512
|
+
}
|
|
295
513
|
\`,
|
|
296
514
|
})
|
|
297
515
|
export class App {
|
|
298
|
-
content = signal<ArrayBuffer | null>(null);
|
|
516
|
+
content = signal<ArrayBuffer | Uint8Array | null>(null);
|
|
299
517
|
|
|
300
518
|
async onPick(e: Event) {
|
|
301
519
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
@@ -303,6 +521,14 @@ export class App {
|
|
|
303
521
|
this.content.set(await file.arrayBuffer());
|
|
304
522
|
}
|
|
305
523
|
}
|
|
524
|
+
|
|
525
|
+
async newPresentation() {
|
|
526
|
+
const { handler, data } = await PptxHandler.createBlank({
|
|
527
|
+
title: 'Untitled Presentation',
|
|
528
|
+
initialSlideCount: 1,
|
|
529
|
+
});
|
|
530
|
+
this.content.set(await handler.save(data.slides));
|
|
531
|
+
}
|
|
306
532
|
}
|
|
307
533
|
`;
|
|
308
534
|
var TARGETS = [
|
|
@@ -311,6 +537,7 @@ var TARGETS = [
|
|
|
311
537
|
label: "React",
|
|
312
538
|
description: "pptx-react-viewer - viewer/editor component for a React 19 app",
|
|
313
539
|
mode: "install",
|
|
540
|
+
group: "framework",
|
|
314
541
|
packages: [
|
|
315
542
|
"pptx-react-viewer",
|
|
316
543
|
"react",
|
|
@@ -325,7 +552,7 @@ var TARGETS = [
|
|
|
325
552
|
"react-i18next"
|
|
326
553
|
],
|
|
327
554
|
nextSteps: `import { PowerPointViewer } from 'pptx-react-viewer';
|
|
328
|
-
import 'pptx-react-viewer/styles';
|
|
555
|
+
import 'pptx-react-viewer/styles.css';
|
|
329
556
|
|
|
330
557
|
<PowerPointViewer content={arrayBuffer} canEdit />
|
|
331
558
|
|
|
@@ -333,9 +560,14 @@ Docs: https://www.npmjs.com/package/pptx-react-viewer`,
|
|
|
333
560
|
compat: { peerPackage: "react", requiredMajor: 19 },
|
|
334
561
|
scaffold: {
|
|
335
562
|
command: "create-vite@latest",
|
|
336
|
-
|
|
563
|
+
// --no-interactive/--no-immediate stop create-vite from prompting for a linter
|
|
564
|
+
// choice and then auto-installing + auto-starting its own dev server; if it did,
|
|
565
|
+
// that dev server would block forever and our own entry-file patch + extra
|
|
566
|
+
// package install below would never run, leaving the default Vite template in place.
|
|
567
|
+
args: (dir) => [dir, "--template", "react-ts", "--no-interactive", "--no-immediate"],
|
|
337
568
|
extraPackages: [
|
|
338
569
|
"pptx-react-viewer",
|
|
570
|
+
"pptx-viewer-core",
|
|
339
571
|
"framer-motion",
|
|
340
572
|
"lucide-react",
|
|
341
573
|
"react-icons",
|
|
@@ -354,10 +586,11 @@ Docs: https://www.npmjs.com/package/pptx-react-viewer`,
|
|
|
354
586
|
label: "Vue",
|
|
355
587
|
description: "pptx-vue-viewer - viewer/editor component for a Vue 3.5+ app",
|
|
356
588
|
mode: "install",
|
|
589
|
+
group: "framework",
|
|
357
590
|
packages: ["pptx-vue-viewer", "vue", "jszip", "fast-xml-parser"],
|
|
358
591
|
nextSteps: `<script setup lang="ts">
|
|
359
592
|
import { PowerPointViewer } from 'pptx-vue-viewer';
|
|
360
|
-
import 'pptx-vue-viewer/styles';
|
|
593
|
+
import 'pptx-vue-viewer/styles.css';
|
|
361
594
|
</script>
|
|
362
595
|
|
|
363
596
|
<template>
|
|
@@ -368,8 +601,8 @@ Docs: https://www.npmjs.com/package/pptx-vue-viewer`,
|
|
|
368
601
|
compat: { peerPackage: "vue", requiredMajor: 3 },
|
|
369
602
|
scaffold: {
|
|
370
603
|
command: "create-vite@latest",
|
|
371
|
-
args: (dir) => [dir, "--template", "vue-ts"],
|
|
372
|
-
extraPackages: ["pptx-vue-viewer", "jszip", "fast-xml-parser"],
|
|
604
|
+
args: (dir) => [dir, "--template", "vue-ts", "--no-interactive", "--no-immediate"],
|
|
605
|
+
extraPackages: ["pptx-vue-viewer", "pptx-viewer-core", "jszip", "fast-xml-parser"],
|
|
373
606
|
entryCandidates: ["src/App.vue"],
|
|
374
607
|
entryContent: VUE_APP_VUE
|
|
375
608
|
}
|
|
@@ -379,9 +612,10 @@ Docs: https://www.npmjs.com/package/pptx-vue-viewer`,
|
|
|
379
612
|
label: "Angular",
|
|
380
613
|
description: "pptx-angular-viewer - viewer/editor component for an Angular 22+ app",
|
|
381
614
|
mode: "install",
|
|
615
|
+
group: "framework",
|
|
382
616
|
packages: ["pptx-angular-viewer", "@angular/core", "@angular/common", "rxjs"],
|
|
383
617
|
nextSteps: `import { PowerPointViewerComponent } from 'pptx-angular-viewer';
|
|
384
|
-
import 'pptx-angular-viewer/styles';
|
|
618
|
+
import 'pptx-angular-viewer/styles.css';
|
|
385
619
|
|
|
386
620
|
<pptx-power-point-viewer [content]="content" />
|
|
387
621
|
|
|
@@ -389,8 +623,20 @@ Docs: https://www.npmjs.com/package/pptx-angular-viewer`,
|
|
|
389
623
|
compat: { peerPackage: "@angular/core", requiredMajor: 22 },
|
|
390
624
|
scaffold: {
|
|
391
625
|
command: "@angular/cli@latest",
|
|
392
|
-
|
|
393
|
-
|
|
626
|
+
// --no-interactive matters even with the flags above supplied: the
|
|
627
|
+
// `application` schematic's `ssr` option has an `x-prompt`, and `ng new`
|
|
628
|
+
// prompts for it (plus anything else not already given a value) whenever
|
|
629
|
+
// stdin is a TTY, which ours is (we inherit the real user's terminal).
|
|
630
|
+
args: (dir) => [
|
|
631
|
+
"new",
|
|
632
|
+
dir,
|
|
633
|
+
"--standalone",
|
|
634
|
+
"--skip-git",
|
|
635
|
+
"--style=css",
|
|
636
|
+
"--skip-install",
|
|
637
|
+
"--no-interactive"
|
|
638
|
+
],
|
|
639
|
+
extraPackages: ["pptx-angular-viewer", "pptx-viewer-core"],
|
|
394
640
|
// Angular v20+ generates `app.ts`; older schematics generate `app.component.ts`.
|
|
395
641
|
entryCandidates: ["src/app/app.ts", "src/app/app.component.ts"],
|
|
396
642
|
entryContent: ANGULAR_APP_TS
|
|
@@ -455,6 +701,24 @@ function findTargetsByIds(ids) {
|
|
|
455
701
|
return match;
|
|
456
702
|
});
|
|
457
703
|
}
|
|
704
|
+
function assertSingleFramework(targets) {
|
|
705
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
706
|
+
for (const target of targets) {
|
|
707
|
+
if (!target.group) {
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
const mates = grouped.get(target.group) ?? [];
|
|
711
|
+
mates.push(target);
|
|
712
|
+
grouped.set(target.group, mates);
|
|
713
|
+
}
|
|
714
|
+
for (const mates of grouped.values()) {
|
|
715
|
+
if (mates.length > 1) {
|
|
716
|
+
throw new Error(
|
|
717
|
+
`${mates.map((t) => t.label).join(", ")} can't be selected together; pick a single UI framework.`
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
458
722
|
function mergePackages(targets) {
|
|
459
723
|
const seen = /* @__PURE__ */ new Set();
|
|
460
724
|
const merged = [];
|
|
@@ -520,29 +784,35 @@ async function scaffoldProject(recipe, projectName, pm, cwd) {
|
|
|
520
784
|
}
|
|
521
785
|
|
|
522
786
|
// src/index.ts
|
|
787
|
+
function printBanner() {
|
|
788
|
+
console.log(`
|
|
789
|
+
${bold(cyan("pptx-viewer"))} ${dim("\xB7 interactive installer")}`);
|
|
790
|
+
}
|
|
523
791
|
function printUsage() {
|
|
792
|
+
printBanner();
|
|
524
793
|
console.log(`
|
|
525
|
-
@christophervr/pptx-viewer
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
--
|
|
531
|
-
--
|
|
532
|
-
--
|
|
533
|
-
--
|
|
534
|
-
--
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
npx @christophervr/pptx-viewer
|
|
539
|
-
npx @christophervr/pptx-viewer --target react
|
|
540
|
-
npx @christophervr/pptx-viewer --target react --scaffold --dir my-app --yes
|
|
794
|
+
${bold("Usage:")} npx @christophervr/pptx-viewer [options]
|
|
795
|
+
|
|
796
|
+
${bold("Options:")}
|
|
797
|
+
${cyan("--target <ids>")} Skip the picker; comma-separated, any of: ${TARGETS.map((t) => t.id).join(", ")}
|
|
798
|
+
(react, vue, and angular are mutually exclusive; pick at most one)
|
|
799
|
+
${cyan("--scaffold")} Bootstrap a brand-new starter project instead of installing here
|
|
800
|
+
${cyan("--dir <name>")} Project directory name for --scaffold
|
|
801
|
+
${cyan("--pm <manager>")} Package manager to use: bun, pnpm, yarn, npm (default: auto-detected)
|
|
802
|
+
${cyan("--yes, -y")} Skip confirmation prompts
|
|
803
|
+
${cyan("--help, -h")} Show this help
|
|
804
|
+
|
|
805
|
+
${bold("Examples:")}
|
|
806
|
+
${gray("npx @christophervr/pptx-viewer")}
|
|
807
|
+
${gray("npx @christophervr/pptx-viewer --target react,mcp --yes")}
|
|
808
|
+
${gray("npx @christophervr/pptx-viewer --target react --scaffold --dir my-app --yes")}
|
|
541
809
|
`);
|
|
542
810
|
}
|
|
543
811
|
async function resolveTargets(requested) {
|
|
544
812
|
if (requested) {
|
|
545
|
-
|
|
813
|
+
const targets = findTargetsByIds(parseTargetIds(requested));
|
|
814
|
+
console.log(`${green("\u2714")} ${targets.map((t) => t.label).join(", ")}`);
|
|
815
|
+
return targets;
|
|
546
816
|
}
|
|
547
817
|
if (!process.stdin.isTTY) {
|
|
548
818
|
throw new Error("Not running in a terminal: pass --target explicitly (see --help).");
|
|
@@ -559,7 +829,7 @@ async function confirmCompat(cwd, targets) {
|
|
|
559
829
|
continue;
|
|
560
830
|
}
|
|
561
831
|
console.log(`
|
|
562
|
-
Warning: ${result.message}`);
|
|
832
|
+
${yellow("Warning:")} ${result.message}`);
|
|
563
833
|
if (process.stdin.isTTY) {
|
|
564
834
|
const proceed = await confirm("Continue anyway?");
|
|
565
835
|
if (!proceed) {
|
|
@@ -577,12 +847,6 @@ async function resolveScaffoldChoice(installTargets, args) {
|
|
|
577
847
|
}
|
|
578
848
|
return { useScaffold: true, scaffoldTarget: scaffoldable[0] };
|
|
579
849
|
}
|
|
580
|
-
if (scaffoldable.length > 1) {
|
|
581
|
-
console.log(
|
|
582
|
-
"\nScaffolding a new project only supports one UI framework at a time; installing instead."
|
|
583
|
-
);
|
|
584
|
-
return { useScaffold: false };
|
|
585
|
-
}
|
|
586
850
|
if (scaffoldable.length === 1 && process.stdin.isTTY) {
|
|
587
851
|
const choice = await selectOption("Install into the current project, or scaffold a new one?", [
|
|
588
852
|
{ label: "Install here", description: "Add the package(s) to the project in this directory" },
|
|
@@ -609,13 +873,14 @@ async function runScaffoldMode(target, args, configTargets, cwd) {
|
|
|
609
873
|
const pm = args.pm ?? detectPackageManager(cwd);
|
|
610
874
|
console.log(
|
|
611
875
|
`
|
|
612
|
-
About to scaffold "${projectName}" with ${recipe.command} (${target.label}), then install with ${pm}.
|
|
876
|
+
${bold("About to scaffold")} "${cyan(projectName)}" with ${recipe.command} (${target.label}), then install with ${pm}.
|
|
613
877
|
`
|
|
614
878
|
);
|
|
615
879
|
if (!args.yes && process.stdin.isTTY) {
|
|
616
880
|
const proceed = await confirm("Continue?");
|
|
617
881
|
if (!proceed) {
|
|
618
|
-
console.log(
|
|
882
|
+
console.log(`
|
|
883
|
+
${dim("Skipped.")}`);
|
|
619
884
|
return;
|
|
620
885
|
}
|
|
621
886
|
}
|
|
@@ -623,15 +888,15 @@ About to scaffold "${projectName}" with ${recipe.command} (${target.label}), the
|
|
|
623
888
|
if (!result.patchedFile) {
|
|
624
889
|
console.log(
|
|
625
890
|
`
|
|
626
|
-
Scaffolded the project, but could not find an entry file to wire up automatically. See the quick-start snippet below and add it yourself.`
|
|
891
|
+
${yellow("Scaffolded the project, but could not find an entry file to wire up automatically.")} See the quick-start snippet below and add it yourself.`
|
|
627
892
|
);
|
|
628
893
|
}
|
|
629
894
|
console.log(
|
|
630
895
|
`
|
|
631
|
-
Done. Next steps:
|
|
896
|
+
${green("\u2714")} ${bold("Done.")} Next steps:
|
|
632
897
|
|
|
633
|
-
cd ${projectName}
|
|
634
|
-
${pm} run dev
|
|
898
|
+
${cyan(`cd ${projectName}`)}
|
|
899
|
+
${cyan(`${pm} run dev`)}
|
|
635
900
|
|
|
636
901
|
${target.nextSteps}
|
|
637
902
|
`
|
|
@@ -650,22 +915,23 @@ async function runInstallMode(installTargets, configTargets, args, cwd) {
|
|
|
650
915
|
}
|
|
651
916
|
const proceedPastCompat = await confirmCompat(cwd, installTargets);
|
|
652
917
|
if (!proceedPastCompat) {
|
|
653
|
-
console.log(
|
|
918
|
+
console.log(`
|
|
919
|
+
${red("Aborted.")}`);
|
|
654
920
|
return;
|
|
655
921
|
}
|
|
656
922
|
const packages = mergePackages(installTargets);
|
|
657
923
|
const pm = args.pm ?? detectPackageManager(cwd);
|
|
658
924
|
const [command, cmdArgs] = installCommand(pm, packages);
|
|
659
925
|
console.log(`
|
|
660
|
-
About to run: ${command} ${cmdArgs.join(" ")}
|
|
926
|
+
${bold("About to run:")} ${cyan(`${command} ${cmdArgs.join(" ")}`)}
|
|
661
927
|
`);
|
|
662
928
|
if (!args.yes && process.stdin.isTTY) {
|
|
663
929
|
const proceed = await confirm("Install now?");
|
|
664
930
|
if (!proceed) {
|
|
665
931
|
console.log(
|
|
666
932
|
`
|
|
667
|
-
Skipped. Run this yourself when ready:
|
|
668
|
-
${command} ${cmdArgs.join(" ")}
|
|
933
|
+
${dim("Skipped.")} Run this yourself when ready:
|
|
934
|
+
${cyan(`${command} ${cmdArgs.join(" ")}`)}
|
|
669
935
|
`
|
|
670
936
|
);
|
|
671
937
|
return;
|
|
@@ -676,7 +942,7 @@ Skipped. Run this yourself when ready:
|
|
|
676
942
|
throw new Error(`${command} exited with code ${exitCode}`);
|
|
677
943
|
}
|
|
678
944
|
console.log(`
|
|
679
|
-
Done. Next steps:`);
|
|
945
|
+
${green("\u2714")} ${bold("Done.")} Next steps:`);
|
|
680
946
|
for (const target of installTargets) {
|
|
681
947
|
console.log(`
|
|
682
948
|
${target.nextSteps}
|
|
@@ -695,9 +961,9 @@ async function main() {
|
|
|
695
961
|
printUsage();
|
|
696
962
|
return;
|
|
697
963
|
}
|
|
964
|
+
printBanner();
|
|
698
965
|
const targets = await resolveTargets(args.target);
|
|
699
|
-
|
|
700
|
-
Selected: ${targets.map((t) => t.label).join(", ")}`);
|
|
966
|
+
assertSingleFramework(targets);
|
|
701
967
|
const installTargets = targets.filter((t) => t.mode === "install");
|
|
702
968
|
const configTargets = targets.filter((t) => t.mode === "print-config");
|
|
703
969
|
const cwd = process.cwd();
|
|
@@ -710,6 +976,6 @@ Selected: ${targets.map((t) => t.label).join(", ")}`);
|
|
|
710
976
|
}
|
|
711
977
|
main().catch((err) => {
|
|
712
978
|
const message = err instanceof Error ? err.message : String(err);
|
|
713
|
-
console.error(
|
|
979
|
+
console.error(`${red("\u2718 Error:")} ${message}`);
|
|
714
980
|
process.exit(1);
|
|
715
981
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@christophervr/pptx-viewer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Interactive installer for the pptx-viewer family of packages: React, Vue, and Angular viewer components, the framework-agnostic core engine, and the MCP server.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|