@christophervr/pptx-viewer 1.1.45 → 1.1.46
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 +11 -0
- package/README.md +10 -7
- package/dist/index.mjs +265 -68
- package/package.json +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
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.45](https://github.com/ChristopherVR/pptx-viewer/releases/tag/@christophervr/pptx-viewer@1.1.45) - 2026-07-02
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **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,29 @@ 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
|
+
var symbols = {
|
|
70
|
+
pointer: "\u276F",
|
|
71
|
+
check: "\u2714",
|
|
72
|
+
cross: "\u2718",
|
|
73
|
+
radioOn: "\u25C9",
|
|
74
|
+
radioOff: "\u25EF",
|
|
75
|
+
bullet: "\xB7"
|
|
76
|
+
};
|
|
77
|
+
|
|
55
78
|
// src/project-deps.ts
|
|
56
79
|
import { existsSync, readFileSync } from "fs";
|
|
57
80
|
import { join } from "path";
|
|
@@ -150,6 +173,102 @@ function installCommand(pm, packages) {
|
|
|
150
173
|
|
|
151
174
|
// src/prompt.ts
|
|
152
175
|
import { createInterface } from "readline/promises";
|
|
176
|
+
|
|
177
|
+
// src/interactive-menu.ts
|
|
178
|
+
import { emitKeypressEvents } from "readline";
|
|
179
|
+
var HIDE_CURSOR = "\x1B[?25l";
|
|
180
|
+
var SHOW_CURSOR = "\x1B[?25h";
|
|
181
|
+
var CLEAR_LINE = "\x1B[2K";
|
|
182
|
+
function moveUp(lines) {
|
|
183
|
+
return lines > 0 ? `\x1B[${lines}A` : "";
|
|
184
|
+
}
|
|
185
|
+
function renderChoice(choice, isCursor, checked) {
|
|
186
|
+
const pointer = isCursor ? cyan(symbols.pointer) : " ";
|
|
187
|
+
const box = checked === null ? "" : `${checked ? green(symbols.radioOn) : gray(symbols.radioOff)} `;
|
|
188
|
+
const label = isCursor ? bold(choice.label) : choice.label;
|
|
189
|
+
return `${pointer} ${box}${label} ${dim(`- ${choice.description}`)}`;
|
|
190
|
+
}
|
|
191
|
+
function runMenu(choices, multi) {
|
|
192
|
+
return new Promise((resolve) => {
|
|
193
|
+
if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== "function") {
|
|
194
|
+
resolve(null);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
let cursor = 0;
|
|
198
|
+
const checked = /* @__PURE__ */ new Set();
|
|
199
|
+
let settled = false;
|
|
200
|
+
const hint = multi ? dim("(\u2191/\u2193 move, space toggle, a select all, enter confirm)") : dim("(\u2191/\u2193 move, enter confirm)");
|
|
201
|
+
console.log(hint);
|
|
202
|
+
process.stdout.write(HIDE_CURSOR);
|
|
203
|
+
function draw(first) {
|
|
204
|
+
if (!first) {
|
|
205
|
+
process.stdout.write(moveUp(choices.length));
|
|
206
|
+
}
|
|
207
|
+
for (const [i, choice] of choices.entries()) {
|
|
208
|
+
const checkedState = multi ? checked.has(i) : null;
|
|
209
|
+
process.stdout.write(`${CLEAR_LINE}${renderChoice(choice, i === cursor, checkedState)}
|
|
210
|
+
`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function cleanup() {
|
|
214
|
+
process.stdin.setRawMode?.(false);
|
|
215
|
+
process.stdin.removeListener("keypress", onKeypress);
|
|
216
|
+
process.stdin.pause();
|
|
217
|
+
process.stdout.write(SHOW_CURSOR);
|
|
218
|
+
}
|
|
219
|
+
function finish(result) {
|
|
220
|
+
if (settled) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
settled = true;
|
|
224
|
+
cleanup();
|
|
225
|
+
resolve(result);
|
|
226
|
+
}
|
|
227
|
+
function onKeypress(_str, key) {
|
|
228
|
+
if (key?.ctrl && key.name === "c") {
|
|
229
|
+
finish(null);
|
|
230
|
+
process.exit(130);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (key?.name === "up") {
|
|
234
|
+
cursor = (cursor - 1 + choices.length) % choices.length;
|
|
235
|
+
draw(false);
|
|
236
|
+
} else if (key?.name === "down") {
|
|
237
|
+
cursor = (cursor + 1) % choices.length;
|
|
238
|
+
draw(false);
|
|
239
|
+
} else if (multi && key?.name === "space") {
|
|
240
|
+
if (checked.has(cursor)) {
|
|
241
|
+
checked.delete(cursor);
|
|
242
|
+
} else {
|
|
243
|
+
checked.add(cursor);
|
|
244
|
+
}
|
|
245
|
+
draw(false);
|
|
246
|
+
} else if (multi && key?.name === "a") {
|
|
247
|
+
if (checked.size === choices.length) {
|
|
248
|
+
checked.clear();
|
|
249
|
+
} else {
|
|
250
|
+
choices.forEach((_, i) => checked.add(i));
|
|
251
|
+
}
|
|
252
|
+
draw(false);
|
|
253
|
+
} else if (key?.name === "return") {
|
|
254
|
+
if (multi) {
|
|
255
|
+
if (checked.size > 0) {
|
|
256
|
+
finish([...checked].sort((a, b) => a - b));
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
finish([cursor]);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
emitKeypressEvents(process.stdin);
|
|
264
|
+
process.stdin.setRawMode(true);
|
|
265
|
+
process.stdin.on("keypress", onKeypress);
|
|
266
|
+
process.stdin.resume();
|
|
267
|
+
draw(true);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/prompt.ts
|
|
153
272
|
function parseSelection(answer, count) {
|
|
154
273
|
const trimmed = answer.trim().toLowerCase();
|
|
155
274
|
if (trimmed === "all" || trimmed === "a") {
|
|
@@ -171,15 +290,12 @@ function parseSelection(answer, count) {
|
|
|
171
290
|
}
|
|
172
291
|
function printOptions(options) {
|
|
173
292
|
options.forEach((opt, i) => {
|
|
174
|
-
console.log(` ${i + 1}) ${opt.label}
|
|
293
|
+
console.log(` ${cyan(`${i + 1})`)} ${bold(opt.label)} ${dim(`- ${opt.description}`)}`);
|
|
175
294
|
});
|
|
176
295
|
}
|
|
177
|
-
async function
|
|
296
|
+
async function selectByNumber(options) {
|
|
178
297
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
179
298
|
try {
|
|
180
|
-
console.log(`
|
|
181
|
-
${question}
|
|
182
|
-
`);
|
|
183
299
|
printOptions(options);
|
|
184
300
|
for (; ; ) {
|
|
185
301
|
const answer = (await rl.question(`
|
|
@@ -194,12 +310,9 @@ Enter a number (1-${options.length}): `)).trim();
|
|
|
194
310
|
rl.close();
|
|
195
311
|
}
|
|
196
312
|
}
|
|
197
|
-
async function
|
|
313
|
+
async function selectManyByNumber(options) {
|
|
198
314
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
199
315
|
try {
|
|
200
|
-
console.log(`
|
|
201
|
-
${question}
|
|
202
|
-
`);
|
|
203
316
|
printOptions(options);
|
|
204
317
|
for (; ; ) {
|
|
205
318
|
const answer = await rl.question(
|
|
@@ -216,10 +329,32 @@ Enter one or more numbers, comma-separated (e.g. "1,3"), or "all": `
|
|
|
216
329
|
rl.close();
|
|
217
330
|
}
|
|
218
331
|
}
|
|
332
|
+
async function selectOption(question, options) {
|
|
333
|
+
console.log(`
|
|
334
|
+
${bold(question)}`);
|
|
335
|
+
const picked = await runMenu(options, false);
|
|
336
|
+
if (!picked) {
|
|
337
|
+
return selectByNumber(options);
|
|
338
|
+
}
|
|
339
|
+
const choice = options[picked[0]];
|
|
340
|
+
console.log(`${green("\u2714")} ${choice.label}`);
|
|
341
|
+
return choice;
|
|
342
|
+
}
|
|
343
|
+
async function multiSelect(question, options) {
|
|
344
|
+
console.log(`
|
|
345
|
+
${bold(question)}`);
|
|
346
|
+
const picked = await runMenu(options, true);
|
|
347
|
+
if (!picked) {
|
|
348
|
+
return selectManyByNumber(options);
|
|
349
|
+
}
|
|
350
|
+
const choices = picked.map((i) => options[i]);
|
|
351
|
+
console.log(`${green("\u2714")} ${choices.map((c) => c.label).join(", ")}`);
|
|
352
|
+
return choices;
|
|
353
|
+
}
|
|
219
354
|
async function confirm(question) {
|
|
220
355
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
221
356
|
try {
|
|
222
|
-
const answer = (await rl.question(`${question} (Y/n)
|
|
357
|
+
const answer = (await rl.question(`${bold(question)} ${dim("(Y/n)")} `)).trim().toLowerCase();
|
|
223
358
|
return answer === "" || answer === "y" || answer === "yes";
|
|
224
359
|
} finally {
|
|
225
360
|
rl.close();
|
|
@@ -228,7 +363,7 @@ async function confirm(question) {
|
|
|
228
363
|
async function input(question, defaultValue) {
|
|
229
364
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
230
365
|
try {
|
|
231
|
-
const answer = (await rl.question(`${question} (${defaultValue})
|
|
366
|
+
const answer = (await rl.question(`${bold(question)} ${dim(`(${defaultValue})`)} `)).trim();
|
|
232
367
|
return answer === "" ? defaultValue : answer;
|
|
233
368
|
} finally {
|
|
234
369
|
rl.close();
|
|
@@ -236,48 +371,91 @@ async function input(question, defaultValue) {
|
|
|
236
371
|
}
|
|
237
372
|
|
|
238
373
|
// src/targets.ts
|
|
239
|
-
var REACT_APP_TSX = `import { useState } from 'react';
|
|
374
|
+
var REACT_APP_TSX = `import { useCallback, useState } from 'react';
|
|
375
|
+
import { PptxHandler } from 'pptx-viewer-core';
|
|
240
376
|
import { PowerPointViewer } from 'pptx-react-viewer';
|
|
241
377
|
import 'pptx-react-viewer/styles';
|
|
242
378
|
|
|
243
379
|
export default function App() {
|
|
244
|
-
const [content, setContent] = useState<
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
380
|
+
const [content, setContent] = useState<Uint8Array | null>(null);
|
|
381
|
+
|
|
382
|
+
const loadFile = useCallback((file: File) => {
|
|
383
|
+
const reader = new FileReader();
|
|
384
|
+
reader.onload = () => setContent(new Uint8Array(reader.result as ArrayBuffer));
|
|
385
|
+
reader.readAsArrayBuffer(file);
|
|
386
|
+
}, []);
|
|
387
|
+
|
|
388
|
+
const newPresentation = useCallback(async () => {
|
|
389
|
+
const { handler, data } = await PptxHandler.createBlank({
|
|
390
|
+
title: 'Untitled Presentation',
|
|
391
|
+
initialSlideCount: 1,
|
|
392
|
+
});
|
|
393
|
+
setContent(await handler.save(data.slides));
|
|
394
|
+
}, []);
|
|
395
|
+
|
|
396
|
+
if (content) {
|
|
397
|
+
return (
|
|
398
|
+
<div style={{ height: '100vh' }}>
|
|
399
|
+
<PowerPointViewer content={content} canEdit />
|
|
400
|
+
</div>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
248
403
|
|
|
249
404
|
return (
|
|
250
|
-
<div style={{ height: '100vh' }}>
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
405
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 12, height: '100vh' }}>
|
|
406
|
+
<input
|
|
407
|
+
type="file"
|
|
408
|
+
accept=".pptx"
|
|
409
|
+
onChange={(e) => {
|
|
410
|
+
const file = e.target.files?.[0];
|
|
411
|
+
if (file) loadFile(file);
|
|
412
|
+
}}
|
|
413
|
+
/>
|
|
414
|
+
<button onClick={() => void newPresentation()}>or create a New Presentation</button>
|
|
256
415
|
</div>
|
|
257
416
|
);
|
|
258
417
|
}
|
|
259
418
|
`;
|
|
260
419
|
var VUE_APP_VUE = `<script setup lang="ts">
|
|
261
420
|
import { ref } from 'vue';
|
|
421
|
+
import { PptxHandler } from 'pptx-viewer-core';
|
|
262
422
|
import { PowerPointViewer } from 'pptx-vue-viewer';
|
|
263
423
|
import 'pptx-vue-viewer/styles';
|
|
264
424
|
|
|
265
425
|
const content = ref<Uint8Array>();
|
|
266
426
|
|
|
427
|
+
function loadFile(file: File) {
|
|
428
|
+
const reader = new FileReader();
|
|
429
|
+
reader.onload = () => (content.value = new Uint8Array(reader.result as ArrayBuffer));
|
|
430
|
+
reader.readAsArrayBuffer(file);
|
|
431
|
+
}
|
|
432
|
+
|
|
267
433
|
function onPick(e: Event) {
|
|
268
434
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
269
|
-
file
|
|
435
|
+
if (file) loadFile(file);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function newPresentation() {
|
|
439
|
+
const { handler, data } = await PptxHandler.createBlank({
|
|
440
|
+
title: 'Untitled Presentation',
|
|
441
|
+
initialSlideCount: 1,
|
|
442
|
+
});
|
|
443
|
+
content.value = await handler.save(data.slides);
|
|
270
444
|
}
|
|
271
445
|
</script>
|
|
272
446
|
|
|
273
447
|
<template>
|
|
274
|
-
<div style="height: 100vh">
|
|
275
|
-
<PowerPointViewer
|
|
276
|
-
|
|
448
|
+
<div v-if="content" style="height: 100vh">
|
|
449
|
+
<PowerPointViewer :content="content" can-edit style="height: 100%" />
|
|
450
|
+
</div>
|
|
451
|
+
<div v-else style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; height: 100vh">
|
|
452
|
+
<input type="file" accept=".pptx" @change="onPick" />
|
|
453
|
+
<button @click="newPresentation">or create a New Presentation</button>
|
|
277
454
|
</div>
|
|
278
455
|
</template>
|
|
279
456
|
`;
|
|
280
457
|
var ANGULAR_APP_TS = `import { Component, signal } from '@angular/core';
|
|
458
|
+
import { PptxHandler } from 'pptx-viewer-core';
|
|
281
459
|
import { PowerPointViewerComponent } from 'pptx-angular-viewer';
|
|
282
460
|
|
|
283
461
|
@Component({
|
|
@@ -285,17 +463,20 @@ import { PowerPointViewerComponent } from 'pptx-angular-viewer';
|
|
|
285
463
|
standalone: true,
|
|
286
464
|
imports: [PowerPointViewerComponent],
|
|
287
465
|
template: \`
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
<pptx-power-point-viewer [content]="c" style="height: 100%" />
|
|
291
|
-
|
|
466
|
+
@if (content(); as c) {
|
|
467
|
+
<div style="height: 100vh">
|
|
468
|
+
<pptx-power-point-viewer [content]="c" [canEdit]="true" style="height: 100%" />
|
|
469
|
+
</div>
|
|
470
|
+
} @else {
|
|
471
|
+
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; height: 100vh">
|
|
292
472
|
<input type="file" accept=".pptx" (change)="onPick($event)" />
|
|
293
|
-
|
|
294
|
-
|
|
473
|
+
<button (click)="newPresentation()">or create a New Presentation</button>
|
|
474
|
+
</div>
|
|
475
|
+
}
|
|
295
476
|
\`,
|
|
296
477
|
})
|
|
297
478
|
export class App {
|
|
298
|
-
content = signal<ArrayBuffer | null>(null);
|
|
479
|
+
content = signal<ArrayBuffer | Uint8Array | null>(null);
|
|
299
480
|
|
|
300
481
|
async onPick(e: Event) {
|
|
301
482
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
@@ -303,6 +484,14 @@ export class App {
|
|
|
303
484
|
this.content.set(await file.arrayBuffer());
|
|
304
485
|
}
|
|
305
486
|
}
|
|
487
|
+
|
|
488
|
+
async newPresentation() {
|
|
489
|
+
const { handler, data } = await PptxHandler.createBlank({
|
|
490
|
+
title: 'Untitled Presentation',
|
|
491
|
+
initialSlideCount: 1,
|
|
492
|
+
});
|
|
493
|
+
this.content.set(await handler.save(data.slides));
|
|
494
|
+
}
|
|
306
495
|
}
|
|
307
496
|
`;
|
|
308
497
|
var TARGETS = [
|
|
@@ -336,6 +525,7 @@ Docs: https://www.npmjs.com/package/pptx-react-viewer`,
|
|
|
336
525
|
args: (dir) => [dir, "--template", "react-ts"],
|
|
337
526
|
extraPackages: [
|
|
338
527
|
"pptx-react-viewer",
|
|
528
|
+
"pptx-viewer-core",
|
|
339
529
|
"framer-motion",
|
|
340
530
|
"lucide-react",
|
|
341
531
|
"react-icons",
|
|
@@ -369,7 +559,7 @@ Docs: https://www.npmjs.com/package/pptx-vue-viewer`,
|
|
|
369
559
|
scaffold: {
|
|
370
560
|
command: "create-vite@latest",
|
|
371
561
|
args: (dir) => [dir, "--template", "vue-ts"],
|
|
372
|
-
extraPackages: ["pptx-vue-viewer", "jszip", "fast-xml-parser"],
|
|
562
|
+
extraPackages: ["pptx-vue-viewer", "pptx-viewer-core", "jszip", "fast-xml-parser"],
|
|
373
563
|
entryCandidates: ["src/App.vue"],
|
|
374
564
|
entryContent: VUE_APP_VUE
|
|
375
565
|
}
|
|
@@ -390,7 +580,7 @@ Docs: https://www.npmjs.com/package/pptx-angular-viewer`,
|
|
|
390
580
|
scaffold: {
|
|
391
581
|
command: "@angular/cli@latest",
|
|
392
582
|
args: (dir) => ["new", dir, "--standalone", "--skip-git", "--style=css", "--skip-install"],
|
|
393
|
-
extraPackages: ["pptx-angular-viewer"],
|
|
583
|
+
extraPackages: ["pptx-angular-viewer", "pptx-viewer-core"],
|
|
394
584
|
// Angular v20+ generates `app.ts`; older schematics generate `app.component.ts`.
|
|
395
585
|
entryCandidates: ["src/app/app.ts", "src/app/app.component.ts"],
|
|
396
586
|
entryContent: ANGULAR_APP_TS
|
|
@@ -520,29 +710,34 @@ async function scaffoldProject(recipe, projectName, pm, cwd) {
|
|
|
520
710
|
}
|
|
521
711
|
|
|
522
712
|
// src/index.ts
|
|
713
|
+
function printBanner() {
|
|
714
|
+
console.log(`
|
|
715
|
+
${bold(cyan("pptx-viewer"))} ${dim("\xB7 interactive installer")}`);
|
|
716
|
+
}
|
|
523
717
|
function printUsage() {
|
|
718
|
+
printBanner();
|
|
524
719
|
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,mcp --yes
|
|
540
|
-
npx @christophervr/pptx-viewer --target react --scaffold --dir my-app --yes
|
|
720
|
+
${bold("Usage:")} npx @christophervr/pptx-viewer [options]
|
|
721
|
+
|
|
722
|
+
${bold("Options:")}
|
|
723
|
+
${cyan("--target <ids>")} Skip the picker; comma-separated, any of: ${TARGETS.map((t) => t.id).join(", ")}
|
|
724
|
+
${cyan("--scaffold")} Bootstrap a brand-new starter project instead of installing here
|
|
725
|
+
${cyan("--dir <name>")} Project directory name for --scaffold
|
|
726
|
+
${cyan("--pm <manager>")} Package manager to use: bun, pnpm, yarn, npm (default: auto-detected)
|
|
727
|
+
${cyan("--yes, -y")} Skip confirmation prompts
|
|
728
|
+
${cyan("--help, -h")} Show this help
|
|
729
|
+
|
|
730
|
+
${bold("Examples:")}
|
|
731
|
+
${gray("npx @christophervr/pptx-viewer")}
|
|
732
|
+
${gray("npx @christophervr/pptx-viewer --target react,mcp --yes")}
|
|
733
|
+
${gray("npx @christophervr/pptx-viewer --target react --scaffold --dir my-app --yes")}
|
|
541
734
|
`);
|
|
542
735
|
}
|
|
543
736
|
async function resolveTargets(requested) {
|
|
544
737
|
if (requested) {
|
|
545
|
-
|
|
738
|
+
const targets = findTargetsByIds(parseTargetIds(requested));
|
|
739
|
+
console.log(`${green("\u2714")} ${targets.map((t) => t.label).join(", ")}`);
|
|
740
|
+
return targets;
|
|
546
741
|
}
|
|
547
742
|
if (!process.stdin.isTTY) {
|
|
548
743
|
throw new Error("Not running in a terminal: pass --target explicitly (see --help).");
|
|
@@ -559,7 +754,7 @@ async function confirmCompat(cwd, targets) {
|
|
|
559
754
|
continue;
|
|
560
755
|
}
|
|
561
756
|
console.log(`
|
|
562
|
-
Warning: ${result.message}`);
|
|
757
|
+
${yellow("Warning:")} ${result.message}`);
|
|
563
758
|
if (process.stdin.isTTY) {
|
|
564
759
|
const proceed = await confirm("Continue anyway?");
|
|
565
760
|
if (!proceed) {
|
|
@@ -579,7 +774,8 @@ async function resolveScaffoldChoice(installTargets, args) {
|
|
|
579
774
|
}
|
|
580
775
|
if (scaffoldable.length > 1) {
|
|
581
776
|
console.log(
|
|
582
|
-
|
|
777
|
+
`
|
|
778
|
+
${dim("Scaffolding a new project only supports one UI framework at a time; installing instead.")}`
|
|
583
779
|
);
|
|
584
780
|
return { useScaffold: false };
|
|
585
781
|
}
|
|
@@ -609,13 +805,14 @@ async function runScaffoldMode(target, args, configTargets, cwd) {
|
|
|
609
805
|
const pm = args.pm ?? detectPackageManager(cwd);
|
|
610
806
|
console.log(
|
|
611
807
|
`
|
|
612
|
-
About to scaffold "${projectName}" with ${recipe.command} (${target.label}), then install with ${pm}.
|
|
808
|
+
${bold("About to scaffold")} "${cyan(projectName)}" with ${recipe.command} (${target.label}), then install with ${pm}.
|
|
613
809
|
`
|
|
614
810
|
);
|
|
615
811
|
if (!args.yes && process.stdin.isTTY) {
|
|
616
812
|
const proceed = await confirm("Continue?");
|
|
617
813
|
if (!proceed) {
|
|
618
|
-
console.log(
|
|
814
|
+
console.log(`
|
|
815
|
+
${dim("Skipped.")}`);
|
|
619
816
|
return;
|
|
620
817
|
}
|
|
621
818
|
}
|
|
@@ -623,15 +820,15 @@ About to scaffold "${projectName}" with ${recipe.command} (${target.label}), the
|
|
|
623
820
|
if (!result.patchedFile) {
|
|
624
821
|
console.log(
|
|
625
822
|
`
|
|
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.`
|
|
823
|
+
${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
824
|
);
|
|
628
825
|
}
|
|
629
826
|
console.log(
|
|
630
827
|
`
|
|
631
|
-
Done. Next steps:
|
|
828
|
+
${green("\u2714")} ${bold("Done.")} Next steps:
|
|
632
829
|
|
|
633
|
-
cd ${projectName}
|
|
634
|
-
${pm} run dev
|
|
830
|
+
${cyan(`cd ${projectName}`)}
|
|
831
|
+
${cyan(`${pm} run dev`)}
|
|
635
832
|
|
|
636
833
|
${target.nextSteps}
|
|
637
834
|
`
|
|
@@ -650,22 +847,23 @@ async function runInstallMode(installTargets, configTargets, args, cwd) {
|
|
|
650
847
|
}
|
|
651
848
|
const proceedPastCompat = await confirmCompat(cwd, installTargets);
|
|
652
849
|
if (!proceedPastCompat) {
|
|
653
|
-
console.log(
|
|
850
|
+
console.log(`
|
|
851
|
+
${red("Aborted.")}`);
|
|
654
852
|
return;
|
|
655
853
|
}
|
|
656
854
|
const packages = mergePackages(installTargets);
|
|
657
855
|
const pm = args.pm ?? detectPackageManager(cwd);
|
|
658
856
|
const [command, cmdArgs] = installCommand(pm, packages);
|
|
659
857
|
console.log(`
|
|
660
|
-
About to run: ${command} ${cmdArgs.join(" ")}
|
|
858
|
+
${bold("About to run:")} ${cyan(`${command} ${cmdArgs.join(" ")}`)}
|
|
661
859
|
`);
|
|
662
860
|
if (!args.yes && process.stdin.isTTY) {
|
|
663
861
|
const proceed = await confirm("Install now?");
|
|
664
862
|
if (!proceed) {
|
|
665
863
|
console.log(
|
|
666
864
|
`
|
|
667
|
-
Skipped. Run this yourself when ready:
|
|
668
|
-
${command} ${cmdArgs.join(" ")}
|
|
865
|
+
${dim("Skipped.")} Run this yourself when ready:
|
|
866
|
+
${cyan(`${command} ${cmdArgs.join(" ")}`)}
|
|
669
867
|
`
|
|
670
868
|
);
|
|
671
869
|
return;
|
|
@@ -676,7 +874,7 @@ Skipped. Run this yourself when ready:
|
|
|
676
874
|
throw new Error(`${command} exited with code ${exitCode}`);
|
|
677
875
|
}
|
|
678
876
|
console.log(`
|
|
679
|
-
Done. Next steps:`);
|
|
877
|
+
${green("\u2714")} ${bold("Done.")} Next steps:`);
|
|
680
878
|
for (const target of installTargets) {
|
|
681
879
|
console.log(`
|
|
682
880
|
${target.nextSteps}
|
|
@@ -695,9 +893,8 @@ async function main() {
|
|
|
695
893
|
printUsage();
|
|
696
894
|
return;
|
|
697
895
|
}
|
|
896
|
+
printBanner();
|
|
698
897
|
const targets = await resolveTargets(args.target);
|
|
699
|
-
console.log(`
|
|
700
|
-
Selected: ${targets.map((t) => t.label).join(", ")}`);
|
|
701
898
|
const installTargets = targets.filter((t) => t.mode === "install");
|
|
702
899
|
const configTargets = targets.filter((t) => t.mode === "print-config");
|
|
703
900
|
const cwd = process.cwd();
|
|
@@ -710,6 +907,6 @@ Selected: ${targets.map((t) => t.label).join(", ")}`);
|
|
|
710
907
|
}
|
|
711
908
|
main().catch((err) => {
|
|
712
909
|
const message = err instanceof Error ? err.message : String(err);
|
|
713
|
-
console.error(
|
|
910
|
+
console.error(`${red("\u2718 Error:")} ${message}`);
|
|
714
911
|
process.exit(1);
|
|
715
912
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@christophervr/pptx-viewer",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.46",
|
|
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",
|