@christophervr/pptx-viewer 1.4.2 → 1.5.1
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 +8 -0
- package/README.md +15 -11
- package/dist/index.mjs +614 -126
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ All notable changes to this project are documented here.
|
|
|
4
4
|
This file is generated from [Conventional Commits](https://www.conventionalcommits.org)
|
|
5
5
|
by [git-cliff](https://git-cliff.org); do not edit it by hand.
|
|
6
6
|
|
|
7
|
+
## [1.5.0](https://github.com/ChristopherVR/pptx-viewer/releases/tag/@christophervr/pptx-viewer@1.5.0) - 2026-07-11
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **cli:** Add svelte and vanilla js install/scaffold targets (by @ChristopherVR) ([768aafe](https://github.com/ChristopherVR/pptx-viewer/commit/768aafe14f57b75cc3d91a00c62be261c4044789))
|
|
12
|
+
|
|
13
|
+
## [1.4.2](https://github.com/ChristopherVR/pptx-viewer/releases/tag/@christophervr/pptx-viewer@1.4.2) - 2026-07-07
|
|
14
|
+
|
|
7
15
|
## [1.4.1](https://github.com/ChristopherVR/pptx-viewer/releases/tag/@christophervr/pptx-viewer@1.4.1) - 2026-07-05
|
|
8
16
|
|
|
9
17
|
## [1.4.0](https://github.com/ChristopherVR/pptx-viewer/releases/tag/@christophervr/pptx-viewer@1.4.0) - 2026-07-05
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|

|
|
9
9
|
|
|
10
|
-
This is what you get one `npx` away: a working `.pptx` viewer/editor, wired up in a React, Vue, or
|
|
10
|
+
This is what you get one `npx` away: a working `.pptx` viewer/editor, wired up in a React, Vue, Angular, Svelte, or vanilla JavaScript app, or just the framework-agnostic engine on its own.
|
|
11
11
|
|
|
12
12
|
<samp>**[▶️ Try the live demo](https://christophervr.github.io/pptx-viewer/demo/)** · **[📦 npm](https://www.npmjs.com/package/@christophervr/pptx-viewer)** · **[📖 Full docs](https://christophervr.github.io/pptx-viewer/)** · **[🧩 Core SDK](https://www.npmjs.com/package/pptx-viewer-core)**</samp>
|
|
13
13
|
|
|
@@ -28,6 +28,8 @@ What are you building with pptx-viewer? (you can pick more than one)
|
|
|
28
28
|
❯ ◉ React - pptx-react-viewer, a viewer/editor component for a React 19 app
|
|
29
29
|
◯ Vue - pptx-vue-viewer, a viewer/editor component for a Vue 3.5+ app
|
|
30
30
|
◯ Angular - pptx-angular-viewer, a viewer/editor component for an Angular 22+ app
|
|
31
|
+
◯ Svelte - pptx-svelte-viewer, a viewer/editor component for a Svelte 5 app
|
|
32
|
+
◯ Vanilla JS - pptx-vanilla-viewer, a zero-framework viewer/editor, plain DOM
|
|
31
33
|
◯ Core engine only - pptx-viewer-core, the framework-agnostic SDK, no UI
|
|
32
34
|
◯ MCP server - pptx-viewer-mcp, PowerPoint editing tools for AI agents
|
|
33
35
|
```
|
|
@@ -38,14 +40,14 @@ Picking more than one is fine, for example React plus the MCP server to get both
|
|
|
38
40
|
|
|
39
41
|
### Compatibility check
|
|
40
42
|
|
|
41
|
-
If you picked React, Vue, or
|
|
43
|
+
If you picked React, Vue, Angular, or Svelte and a `package.json` already exists in the current directory, the CLI looks at what's actually installed (or declared) for `react`, `vue`, `@angular/core`, or `svelte` and compares it against the major version each viewer package requires. If they don't match (say, `react@18` in a project but `pptx-react-viewer` needs `react@^19`), it warns you and asks whether to continue before touching anything. (Vanilla JS has no framework peer, so there is nothing to check.)
|
|
42
44
|
|
|
43
45
|
### Install here, or scaffold a new project?
|
|
44
46
|
|
|
45
47
|
When exactly one UI framework is selected, you're asked how to set it up:
|
|
46
48
|
|
|
47
49
|
- **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).
|
|
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.
|
|
50
|
+
- **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/Svelte/Vanilla JS, [`@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.
|
|
49
51
|
|
|
50
52
|
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.
|
|
51
53
|
|
|
@@ -60,14 +62,14 @@ npx @christophervr/pptx-viewer --target react --scaffold --dir my-app --yes
|
|
|
60
62
|
npx @christophervr/pptx-viewer --pm pnpm # force a package manager instead of auto-detecting
|
|
61
63
|
```
|
|
62
64
|
|
|
63
|
-
| Flag | Meaning
|
|
64
|
-
| ---------------- |
|
|
65
|
-
| `--target <ids>` | Comma-separated: `react`, `vue`, `angular`, `core`, `mcp`. Skips the picker.
|
|
66
|
-
| `--scaffold` | Bootstrap a new project instead of installing here. Needs exactly one
|
|
67
|
-
| `--dir <name>` | Project directory name for `--scaffold` (default: `pptx-<target>-app`).
|
|
68
|
-
| `--pm <manager>` | `bun`, `pnpm`, `yarn`, or `npm`. Skips auto-detection.
|
|
69
|
-
| `--yes`, `-y` | Skip confirmation prompts (including the compatibility warning).
|
|
70
|
-
| `--help`, `-h` | Print usage.
|
|
65
|
+
| Flag | Meaning |
|
|
66
|
+
| ---------------- | ------------------------------------------------------------------------------------------------- |
|
|
67
|
+
| `--target <ids>` | Comma-separated: `react`, `vue`, `angular`, `svelte`, `vanilla`, `core`, `mcp`. Skips the picker. |
|
|
68
|
+
| `--scaffold` | Bootstrap a new project instead of installing here. Needs exactly one UI binding target selected. |
|
|
69
|
+
| `--dir <name>` | Project directory name for `--scaffold` (default: `pptx-<target>-app`). |
|
|
70
|
+
| `--pm <manager>` | `bun`, `pnpm`, `yarn`, or `npm`. Skips auto-detection. |
|
|
71
|
+
| `--yes`, `-y` | Skip confirmation prompts (including the compatibility warning). |
|
|
72
|
+
| `--help`, `-h` | Print usage. |
|
|
71
73
|
|
|
72
74
|
## What it installs
|
|
73
75
|
|
|
@@ -76,6 +78,8 @@ npx @christophervr/pptx-viewer --pm pnpm # force a pac
|
|
|
76
78
|
| **React** | [`pptx-react-viewer`](https://www.npmjs.com/package/pptx-react-viewer) | View, edit, present, collaborate, and export `.pptx` files in React 19. |
|
|
77
79
|
| **Vue** | [`pptx-vue-viewer`](https://www.npmjs.com/package/pptx-vue-viewer) | The Vue 3.5+ counterpart, feature-equivalent to the React package. |
|
|
78
80
|
| **Angular** | [`pptx-angular-viewer`](https://www.npmjs.com/package/pptx-angular-viewer) | The Angular 22+ counterpart, feature-equivalent to the React package. |
|
|
81
|
+
| **Svelte** | [`pptx-svelte-viewer`](https://www.npmjs.com/package/pptx-svelte-viewer) | The Svelte 5 counterpart, built on the same shared engine. |
|
|
82
|
+
| **Vanilla JS** | [`pptx-vanilla-viewer`](https://www.npmjs.com/package/pptx-vanilla-viewer) | The zero-framework binding: plain DOM, one factory function. |
|
|
79
83
|
| **Core only** | [`pptx-viewer-core`](https://www.npmjs.com/package/pptx-viewer-core) | The framework-agnostic parse/edit/save/convert SDK, no UI. |
|
|
80
84
|
| **MCP server** | [`pptx-viewer-mcp`](https://www.npmjs.com/package/pptx-viewer-mcp) | 25 PowerPoint editing tools exposed to AI agents (Claude, Cursor, ...). |
|
|
81
85
|
|
package/dist/index.mjs
CHANGED
|
@@ -407,20 +407,136 @@ async function input(question, defaultValue) {
|
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
-
// src/
|
|
410
|
+
// src/templates/angular.ts
|
|
411
|
+
var ANGULAR_APP_TS = `import { Component, signal } from '@angular/core';
|
|
412
|
+
import { PptxHandler } from 'pptx-viewer-core';
|
|
413
|
+
import type { CollaborationConfig } from 'pptx-angular-viewer';
|
|
414
|
+
import { PowerPointViewerComponent } from 'pptx-angular-viewer';
|
|
415
|
+
|
|
416
|
+
@Component({
|
|
417
|
+
selector: 'app-root',
|
|
418
|
+
standalone: true,
|
|
419
|
+
imports: [PowerPointViewerComponent],
|
|
420
|
+
styles: [\`
|
|
421
|
+
:host { display: block; height: 100dvh; }
|
|
422
|
+
.stage { display: flex; align-items: center; justify-content: center; height: 100dvh; padding: 2rem; cursor: default; }
|
|
423
|
+
.dropzone { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.75rem; max-width: 520px; width: 100%; padding: 3rem; text-align: center; border: 2px dashed var(--pptx-border, #374151); border-radius: 0.75rem; cursor: pointer; transition: border-color 0.15s, background 0.15s; }
|
|
424
|
+
.dropzone.over, .dropzone:hover { border-color: var(--pptx-primary, #6366f1); background: var(--pptx-muted, rgba(255,255,255,0.04)); }
|
|
425
|
+
h1 { margin: 0; font-size: 1.5rem; font-weight: 500; }
|
|
426
|
+
p { margin: 0; font-size: 0.875rem; color: var(--pptx-muted-foreground, #9ca3af); }
|
|
427
|
+
.pick-label { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1.25rem; border-radius: 0.5rem; border: 1px solid var(--pptx-border, #374151); background: var(--pptx-muted, #1f2937); color: var(--pptx-foreground, #f3f4f6); cursor: pointer; font-size: 0.875rem; transition: background 0.15s; }
|
|
428
|
+
.pick-label:hover { background: var(--pptx-accent, #374151); }
|
|
429
|
+
.or-sep { font-size: 0.8rem; color: var(--pptx-muted-foreground, #6b7280); }
|
|
430
|
+
.new-btn { padding: 0.5rem 1.25rem; border-radius: 0.5rem; border: none; background: var(--pptx-primary, #6366f1); color: #fff; cursor: pointer; font-size: 0.875rem; font-weight: 500; transition: opacity 0.15s; }
|
|
431
|
+
.new-btn:hover { opacity: 0.9; }
|
|
432
|
+
\`],
|
|
433
|
+
template: \`
|
|
434
|
+
@if (content(); as c) {
|
|
435
|
+
<div style="height: 100dvh">
|
|
436
|
+
<pptx-power-point-viewer
|
|
437
|
+
[content]="c"
|
|
438
|
+
[canEdit]="true"
|
|
439
|
+
style="height: 100%"
|
|
440
|
+
[collaboration]="collab()"
|
|
441
|
+
(startCollaboration)="collab.set($event)"
|
|
442
|
+
(stopCollaboration)="collab.set(undefined)"
|
|
443
|
+
/>
|
|
444
|
+
</div>
|
|
445
|
+
} @else {
|
|
446
|
+
<div
|
|
447
|
+
class="stage"
|
|
448
|
+
[class.over]="over()"
|
|
449
|
+
(dragover)="$event.preventDefault(); over.set(true)"
|
|
450
|
+
(dragleave)="over.set(false)"
|
|
451
|
+
(drop)="onDrop($event)"
|
|
452
|
+
(click)="fileInput.click()"
|
|
453
|
+
>
|
|
454
|
+
<div class="dropzone">
|
|
455
|
+
<h1>Open a Presentation</h1>
|
|
456
|
+
<p>Drag & drop a .pptx file here, or</p>
|
|
457
|
+
<label class="pick-label" (click)="$event.stopPropagation()">
|
|
458
|
+
Choose .pptx file
|
|
459
|
+
<input #fileInput type="file" accept=".pptx" style="display: none" (change)="onPick($event)" />
|
|
460
|
+
</label>
|
|
461
|
+
<span class="or-sep">or</span>
|
|
462
|
+
<button class="new-btn" (click)="$event.stopPropagation(); newPresentation()">New Presentation</button>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
}
|
|
466
|
+
\`,
|
|
467
|
+
})
|
|
468
|
+
export class App {
|
|
469
|
+
content = signal<ArrayBuffer | Uint8Array | null>(null);
|
|
470
|
+
collab = signal<CollaborationConfig | undefined>(undefined);
|
|
471
|
+
over = signal(false);
|
|
472
|
+
|
|
473
|
+
async onDrop(e: DragEvent) {
|
|
474
|
+
e.preventDefault();
|
|
475
|
+
this.over.set(false);
|
|
476
|
+
const file = e.dataTransfer?.files?.[0];
|
|
477
|
+
if (file?.name.endsWith('.pptx')) this.content.set(await file.arrayBuffer());
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async onPick(e: Event) {
|
|
481
|
+
const file = (e.target as HTMLInputElement).files?.[0];
|
|
482
|
+
if (file) this.content.set(await file.arrayBuffer());
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async newPresentation() {
|
|
486
|
+
const { handler, data } = await PptxHandler.createBlank({
|
|
487
|
+
title: 'Untitled Presentation',
|
|
488
|
+
initialSlideCount: 1,
|
|
489
|
+
});
|
|
490
|
+
this.content.set(await handler.save(data.slides));
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
`;
|
|
494
|
+
var ANGULAR_MAIN_TS = `import 'zone.js';
|
|
495
|
+
import '@angular/compiler';
|
|
496
|
+
import { bootstrapApplication } from '@angular/platform-browser';
|
|
497
|
+
import { Injectable } from '@angular/core';
|
|
498
|
+
import type { MissingTranslationHandlerParams } from '@ngx-translate/core';
|
|
499
|
+
import { MissingTranslationHandler, provideTranslateService } from '@ngx-translate/core';
|
|
500
|
+
import { keyToLabel } from 'pptx-angular-viewer';
|
|
501
|
+
|
|
502
|
+
import { App } from './app/app.ts';
|
|
503
|
+
|
|
504
|
+
@Injectable()
|
|
505
|
+
class LabelFallbackHandler implements MissingTranslationHandler {
|
|
506
|
+
handle(params: MissingTranslationHandlerParams): string {
|
|
507
|
+
return keyToLabel(params.key);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
bootstrapApplication(App, {
|
|
512
|
+
providers: [
|
|
513
|
+
provideTranslateService({
|
|
514
|
+
lang: 'en',
|
|
515
|
+
fallbackLang: 'en',
|
|
516
|
+
missingTranslationHandler: {
|
|
517
|
+
provide: MissingTranslationHandler,
|
|
518
|
+
useClass: LabelFallbackHandler,
|
|
519
|
+
},
|
|
520
|
+
}),
|
|
521
|
+
],
|
|
522
|
+
}).catch((err) => console.error(err));
|
|
523
|
+
`;
|
|
524
|
+
|
|
525
|
+
// src/templates/react.ts
|
|
411
526
|
var REACT_APP_TSX = `import { useCallback, useState } from 'react';
|
|
412
527
|
import { PptxHandler } from 'pptx-viewer-core';
|
|
528
|
+
import type { CollaborationConfig } from 'pptx-react-viewer';
|
|
413
529
|
import { PowerPointViewer } from 'pptx-react-viewer';
|
|
414
530
|
import 'pptx-react-viewer/styles.css';
|
|
415
531
|
import './i18n';
|
|
416
532
|
|
|
417
533
|
export default function App() {
|
|
418
534
|
const [content, setContent] = useState<Uint8Array | null>(null);
|
|
535
|
+
const [over, setOver] = useState(false);
|
|
536
|
+
const [collab, setCollab] = useState<CollaborationConfig | undefined>();
|
|
419
537
|
|
|
420
|
-
const loadFile = useCallback((file: File) => {
|
|
421
|
-
|
|
422
|
-
reader.onload = () => setContent(new Uint8Array(reader.result as ArrayBuffer));
|
|
423
|
-
reader.readAsArrayBuffer(file);
|
|
538
|
+
const loadFile = useCallback(async (file: File) => {
|
|
539
|
+
setContent(new Uint8Array(await file.arrayBuffer()));
|
|
424
540
|
}, []);
|
|
425
541
|
|
|
426
542
|
const newPresentation = useCallback(async () => {
|
|
@@ -433,34 +549,55 @@ export default function App() {
|
|
|
433
549
|
|
|
434
550
|
if (content) {
|
|
435
551
|
return (
|
|
436
|
-
<div style={{ height: '
|
|
437
|
-
<PowerPointViewer
|
|
552
|
+
<div style={{ height: '100dvh' }}>
|
|
553
|
+
<PowerPointViewer
|
|
554
|
+
content={content}
|
|
555
|
+
canEdit
|
|
556
|
+
collaboration={collab}
|
|
557
|
+
onStartCollaboration={setCollab}
|
|
558
|
+
onStopCollaboration={() => setCollab(undefined)}
|
|
559
|
+
/>
|
|
438
560
|
</div>
|
|
439
561
|
);
|
|
440
562
|
}
|
|
441
563
|
|
|
442
564
|
return (
|
|
443
|
-
<div
|
|
444
|
-
<
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
/>
|
|
456
|
-
</label>
|
|
457
|
-
<span style={{ color: '#6b7280', fontSize: 13 }}>or</span>
|
|
458
|
-
<button
|
|
459
|
-
onClick={() => void newPresentation()}
|
|
460
|
-
style={{ padding: '10px 20px', borderRadius: 8, border: 'none', background: '#2563eb', color: '#fff', cursor: 'pointer', fontSize: 14, fontWeight: 500 }}
|
|
565
|
+
<div className="stage">
|
|
566
|
+
<div
|
|
567
|
+
className={\`dropzone\${over ? ' over' : ''}\`}
|
|
568
|
+
onDragOver={(e) => { e.preventDefault(); setOver(true); }}
|
|
569
|
+
onDragLeave={() => setOver(false)}
|
|
570
|
+
onDrop={(e) => {
|
|
571
|
+
e.preventDefault();
|
|
572
|
+
setOver(false);
|
|
573
|
+
const file = e.dataTransfer.files[0];
|
|
574
|
+
if (file?.name.endsWith('.pptx')) void loadFile(file);
|
|
575
|
+
}}
|
|
576
|
+
onClick={() => document.getElementById('file-input')?.click()}
|
|
461
577
|
>
|
|
462
|
-
|
|
463
|
-
|
|
578
|
+
<h1>Open a Presentation</h1>
|
|
579
|
+
<p>Drag & drop a .pptx file here, or</p>
|
|
580
|
+
<label className="pick-label" onClick={(e) => e.stopPropagation()}>
|
|
581
|
+
Choose .pptx file
|
|
582
|
+
<input
|
|
583
|
+
id="file-input"
|
|
584
|
+
type="file"
|
|
585
|
+
accept=".pptx"
|
|
586
|
+
style={{ display: 'none' }}
|
|
587
|
+
onChange={(e) => {
|
|
588
|
+
const file = e.target.files?.[0];
|
|
589
|
+
if (file) void loadFile(file);
|
|
590
|
+
}}
|
|
591
|
+
/>
|
|
592
|
+
</label>
|
|
593
|
+
<span className="or-sep">or</span>
|
|
594
|
+
<button
|
|
595
|
+
className="new-btn"
|
|
596
|
+
onClick={(e) => { e.stopPropagation(); void newPresentation(); }}
|
|
597
|
+
>
|
|
598
|
+
New Presentation
|
|
599
|
+
</button>
|
|
600
|
+
</div>
|
|
464
601
|
</div>
|
|
465
602
|
);
|
|
466
603
|
}
|
|
@@ -484,31 +621,320 @@ i18n.use(initReactI18next).init({
|
|
|
484
621
|
|
|
485
622
|
export default i18n;
|
|
486
623
|
`;
|
|
487
|
-
|
|
488
|
-
|
|
624
|
+
|
|
625
|
+
// src/templates/shared.ts
|
|
626
|
+
var MINIMAL_APP_CSS = `:root {
|
|
627
|
+
color-scheme: dark;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
*,
|
|
631
|
+
*::before,
|
|
632
|
+
*::after {
|
|
633
|
+
box-sizing: border-box;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
body {
|
|
637
|
+
margin: 0;
|
|
638
|
+
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
639
|
+
overflow-x: hidden;
|
|
640
|
+
background: var(--pptx-background, #030712);
|
|
641
|
+
color: var(--pptx-foreground, #f3f4f6);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
#app,
|
|
645
|
+
#root {
|
|
646
|
+
height: 100dvh;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.stage {
|
|
650
|
+
display: flex;
|
|
651
|
+
align-items: center;
|
|
652
|
+
justify-content: center;
|
|
653
|
+
height: 100dvh;
|
|
654
|
+
padding: 2rem;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.dropzone {
|
|
658
|
+
display: flex;
|
|
659
|
+
flex-direction: column;
|
|
660
|
+
align-items: center;
|
|
661
|
+
justify-content: center;
|
|
662
|
+
gap: 0.75rem;
|
|
663
|
+
max-width: 520px;
|
|
664
|
+
width: 100%;
|
|
665
|
+
padding: 3rem;
|
|
666
|
+
text-align: center;
|
|
667
|
+
border: 2px dashed var(--pptx-border, #374151);
|
|
668
|
+
border-radius: 0.75rem;
|
|
669
|
+
cursor: pointer;
|
|
670
|
+
transition:
|
|
671
|
+
border-color 0.15s,
|
|
672
|
+
background 0.15s;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.dropzone.over,
|
|
676
|
+
.dropzone:hover {
|
|
677
|
+
border-color: var(--pptx-primary, #6366f1);
|
|
678
|
+
background: var(--pptx-muted, rgba(255, 255, 255, 0.04));
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
.dropzone h1 {
|
|
682
|
+
margin: 0;
|
|
683
|
+
font-size: 1.5rem;
|
|
684
|
+
font-weight: 500;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.dropzone p {
|
|
688
|
+
margin: 0;
|
|
689
|
+
font-size: 0.875rem;
|
|
690
|
+
color: var(--pptx-muted-foreground, #9ca3af);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.pick-label {
|
|
694
|
+
display: inline-flex;
|
|
695
|
+
align-items: center;
|
|
696
|
+
gap: 0.5rem;
|
|
697
|
+
padding: 0.5rem 1.25rem;
|
|
698
|
+
border-radius: 0.5rem;
|
|
699
|
+
border: 1px solid var(--pptx-border, #374151);
|
|
700
|
+
background: var(--pptx-muted, #1f2937);
|
|
701
|
+
color: var(--pptx-foreground, #f3f4f6);
|
|
702
|
+
cursor: pointer;
|
|
703
|
+
font-size: 0.875rem;
|
|
704
|
+
transition: background 0.15s;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.pick-label:hover {
|
|
708
|
+
background: var(--pptx-accent, #374151);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.or-sep {
|
|
712
|
+
font-size: 0.8rem;
|
|
713
|
+
color: var(--pptx-muted-foreground, #6b7280);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
.new-btn {
|
|
717
|
+
padding: 0.5rem 1.25rem;
|
|
718
|
+
border-radius: 0.5rem;
|
|
719
|
+
border: none;
|
|
720
|
+
background: var(--pptx-primary, #6366f1);
|
|
721
|
+
color: #fff;
|
|
722
|
+
cursor: pointer;
|
|
723
|
+
font-size: 0.875rem;
|
|
724
|
+
font-weight: 500;
|
|
725
|
+
transition: opacity 0.15s;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
.new-btn:hover {
|
|
729
|
+
opacity: 0.9;
|
|
730
|
+
}
|
|
731
|
+
`;
|
|
732
|
+
var ANGULAR_GLOBAL_CSS = `:root {
|
|
733
|
+
color-scheme: dark;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
*,
|
|
737
|
+
*::before,
|
|
738
|
+
*::after {
|
|
739
|
+
box-sizing: border-box;
|
|
489
740
|
}
|
|
490
741
|
|
|
491
742
|
body {
|
|
492
743
|
margin: 0;
|
|
744
|
+
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
745
|
+
background: var(--pptx-background, #030712);
|
|
746
|
+
color: var(--pptx-foreground, #f3f4f6);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
app-root {
|
|
750
|
+
display: block;
|
|
751
|
+
height: 100dvh;
|
|
752
|
+
}
|
|
753
|
+
`;
|
|
754
|
+
|
|
755
|
+
// src/templates/svelte.ts
|
|
756
|
+
var SVELTE_APP_SVELTE = `<script lang="ts">
|
|
757
|
+
import { PptxHandler } from 'pptx-viewer-core';
|
|
758
|
+
import type { CollaborationConfig } from 'pptx-svelte-viewer';
|
|
759
|
+
import { PowerPointViewer } from 'pptx-svelte-viewer';
|
|
760
|
+
|
|
761
|
+
let content = $state<Uint8Array | null>(null);
|
|
762
|
+
let over = $state(false);
|
|
763
|
+
let collab = $state<CollaborationConfig | undefined>();
|
|
764
|
+
|
|
765
|
+
async function loadFile(file: File) {
|
|
766
|
+
content = new Uint8Array(await file.arrayBuffer());
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function onDrop(e: DragEvent) {
|
|
770
|
+
over = false;
|
|
771
|
+
const file = e.dataTransfer?.files?.[0];
|
|
772
|
+
if (file?.name.endsWith('.pptx')) void loadFile(file);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function onPick(e: Event) {
|
|
776
|
+
const file = (e.target as HTMLInputElement).files?.[0];
|
|
777
|
+
if (file) void loadFile(file);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
async function newPresentation() {
|
|
781
|
+
const { handler, data } = await PptxHandler.createBlank({
|
|
782
|
+
title: 'Untitled Presentation',
|
|
783
|
+
initialSlideCount: 1,
|
|
784
|
+
});
|
|
785
|
+
content = await handler.save(data.slides);
|
|
786
|
+
}
|
|
787
|
+
</script>
|
|
788
|
+
|
|
789
|
+
{#if content}
|
|
790
|
+
<div style="height: 100dvh">
|
|
791
|
+
<PowerPointViewer
|
|
792
|
+
source={content}
|
|
793
|
+
editable
|
|
794
|
+
collaboration={collab}
|
|
795
|
+
onstartcollaboration={(cfg) => { collab = cfg; }}
|
|
796
|
+
onstopcollaboration={() => { collab = undefined; }}
|
|
797
|
+
/>
|
|
798
|
+
</div>
|
|
799
|
+
{:else}
|
|
800
|
+
<div
|
|
801
|
+
class="stage"
|
|
802
|
+
ondragover={(e) => { e.preventDefault(); over = true; }}
|
|
803
|
+
ondragleave={() => { over = false; }}
|
|
804
|
+
ondrop={(e) => { e.preventDefault(); onDrop(e); }}
|
|
805
|
+
onclick={() => document.getElementById('file-input')?.click()}
|
|
806
|
+
role="button"
|
|
807
|
+
tabindex="0"
|
|
808
|
+
>
|
|
809
|
+
<div class="dropzone" class:over>
|
|
810
|
+
<h1>Open a Presentation</h1>
|
|
811
|
+
<p>Drag & drop a .pptx file here, or</p>
|
|
812
|
+
<label class="pick-label" onclick={(e) => e.stopPropagation()}>
|
|
813
|
+
Choose .pptx file
|
|
814
|
+
<input id="file-input" type="file" accept=".pptx" style="display: none" onchange={onPick} />
|
|
815
|
+
</label>
|
|
816
|
+
<span class="or-sep">or</span>
|
|
817
|
+
<button class="new-btn" onclick={(e) => { e.stopPropagation(); void newPresentation(); }}>
|
|
818
|
+
New Presentation
|
|
819
|
+
</button>
|
|
820
|
+
</div>
|
|
821
|
+
</div>
|
|
822
|
+
{/if}
|
|
823
|
+
`;
|
|
824
|
+
|
|
825
|
+
// src/templates/vanilla.ts
|
|
826
|
+
var VANILLA_MAIN_TS = `import { createPptxViewer } from 'pptx-vanilla-viewer';
|
|
827
|
+
import { PptxHandler } from 'pptx-viewer-core';
|
|
828
|
+
|
|
829
|
+
import './style.css';
|
|
830
|
+
|
|
831
|
+
const app = document.querySelector<HTMLDivElement>('#app')!;
|
|
832
|
+
|
|
833
|
+
function show(source: ArrayBuffer | Uint8Array): void {
|
|
834
|
+
app.innerHTML = '';
|
|
835
|
+
app.style.height = '100dvh';
|
|
836
|
+
createPptxViewer(app, { source, editable: true });
|
|
493
837
|
}
|
|
838
|
+
|
|
839
|
+
function showLanding(): void {
|
|
840
|
+
app.style.height = '';
|
|
841
|
+
app.innerHTML = '';
|
|
842
|
+
|
|
843
|
+
const stage = document.createElement('div');
|
|
844
|
+
stage.className = 'stage';
|
|
845
|
+
|
|
846
|
+
const zone = document.createElement('div');
|
|
847
|
+
zone.className = 'dropzone';
|
|
848
|
+
|
|
849
|
+
const h1 = document.createElement('h1');
|
|
850
|
+
h1.textContent = 'Open a Presentation';
|
|
851
|
+
|
|
852
|
+
const hint = document.createElement('p');
|
|
853
|
+
hint.textContent = 'Drag & drop a .pptx file here, or';
|
|
854
|
+
|
|
855
|
+
const label = document.createElement('label');
|
|
856
|
+
label.className = 'pick-label';
|
|
857
|
+
label.textContent = 'Choose .pptx file';
|
|
858
|
+
|
|
859
|
+
const input = document.createElement('input');
|
|
860
|
+
input.type = 'file';
|
|
861
|
+
input.accept = '.pptx';
|
|
862
|
+
input.style.display = 'none';
|
|
863
|
+
label.append(input);
|
|
864
|
+
|
|
865
|
+
const orSep = document.createElement('span');
|
|
866
|
+
orSep.className = 'or-sep';
|
|
867
|
+
orSep.textContent = 'or';
|
|
868
|
+
|
|
869
|
+
const newBtn = document.createElement('button');
|
|
870
|
+
newBtn.className = 'new-btn';
|
|
871
|
+
newBtn.textContent = 'New Presentation';
|
|
872
|
+
|
|
873
|
+
zone.append(h1, hint, label, orSep, newBtn);
|
|
874
|
+
stage.append(zone);
|
|
875
|
+
app.append(stage);
|
|
876
|
+
|
|
877
|
+
zone.addEventListener('dragover', (e) => {
|
|
878
|
+
e.preventDefault();
|
|
879
|
+
zone.classList.add('over');
|
|
880
|
+
});
|
|
881
|
+
zone.addEventListener('dragleave', () => zone.classList.remove('over'));
|
|
882
|
+
zone.addEventListener('drop', (e) => {
|
|
883
|
+
e.preventDefault();
|
|
884
|
+
zone.classList.remove('over');
|
|
885
|
+
const file = e.dataTransfer?.files?.[0];
|
|
886
|
+
if (file?.name.endsWith('.pptx')) void file.arrayBuffer().then(show);
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
// Click the zone to open the file picker (but not if the button was clicked).
|
|
890
|
+
zone.addEventListener('click', () => input.click());
|
|
891
|
+
label.addEventListener('click', (e) => e.stopPropagation());
|
|
892
|
+
input.addEventListener('click', (e) => e.stopPropagation());
|
|
893
|
+
input.addEventListener('change', () => {
|
|
894
|
+
const file = input.files?.[0];
|
|
895
|
+
if (file) void file.arrayBuffer().then(show);
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
newBtn.addEventListener('click', async (e) => {
|
|
899
|
+
e.stopPropagation();
|
|
900
|
+
newBtn.textContent = 'Creating...';
|
|
901
|
+
newBtn.disabled = true;
|
|
902
|
+
const { handler, data } = await PptxHandler.createBlank({
|
|
903
|
+
title: 'Untitled Presentation',
|
|
904
|
+
initialSlideCount: 1,
|
|
905
|
+
});
|
|
906
|
+
show(await handler.save(data.slides));
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
showLanding();
|
|
494
911
|
`;
|
|
912
|
+
|
|
913
|
+
// src/templates/vue.ts
|
|
495
914
|
var VUE_APP_VUE = `<script setup lang="ts">
|
|
496
915
|
import { ref } from 'vue';
|
|
497
916
|
import { PptxHandler } from 'pptx-viewer-core';
|
|
917
|
+
import type { CollaborationConfig } from 'pptx-vue-viewer';
|
|
498
918
|
import { PowerPointViewer } from 'pptx-vue-viewer';
|
|
499
919
|
import 'pptx-vue-viewer/styles.css';
|
|
500
920
|
|
|
501
921
|
const content = ref<Uint8Array>();
|
|
922
|
+
const over = ref(false);
|
|
923
|
+
const collab = ref<CollaborationConfig | undefined>();
|
|
924
|
+
|
|
925
|
+
async function loadFile(file: File) {
|
|
926
|
+
content.value = new Uint8Array(await file.arrayBuffer());
|
|
927
|
+
}
|
|
502
928
|
|
|
503
|
-
function
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
929
|
+
function onDrop(e: DragEvent) {
|
|
930
|
+
over.value = false;
|
|
931
|
+
const file = e.dataTransfer?.files?.[0];
|
|
932
|
+
if (file?.name.endsWith('.pptx')) void loadFile(file);
|
|
507
933
|
}
|
|
508
934
|
|
|
509
935
|
function onPick(e: Event) {
|
|
510
936
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
511
|
-
if (file) loadFile(file);
|
|
937
|
+
if (file) void loadFile(file);
|
|
512
938
|
}
|
|
513
939
|
|
|
514
940
|
async function newPresentation() {
|
|
@@ -521,19 +947,53 @@ async function newPresentation() {
|
|
|
521
947
|
</script>
|
|
522
948
|
|
|
523
949
|
<template>
|
|
524
|
-
<div v-if="content" style="height:
|
|
525
|
-
<PowerPointViewer
|
|
950
|
+
<div v-if="content" style="height: 100dvh">
|
|
951
|
+
<PowerPointViewer
|
|
952
|
+
:content="content"
|
|
953
|
+
can-edit
|
|
954
|
+
style="height: 100%"
|
|
955
|
+
:collaboration="collab"
|
|
956
|
+
@start-collaboration="collab = $event"
|
|
957
|
+
@stop-collaboration="collab = undefined"
|
|
958
|
+
/>
|
|
526
959
|
</div>
|
|
527
|
-
<div
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
960
|
+
<div
|
|
961
|
+
v-else
|
|
962
|
+
class="stage"
|
|
963
|
+
@dragover.prevent="over = true"
|
|
964
|
+
@dragleave="over = false"
|
|
965
|
+
@drop.prevent="onDrop($event as DragEvent)"
|
|
966
|
+
@click="($refs.input as HTMLInputElement).click()"
|
|
967
|
+
>
|
|
968
|
+
<div :class="['dropzone', { over }]">
|
|
969
|
+
<h1>Open a Presentation</h1>
|
|
970
|
+
<p>Drag & drop a .pptx file here, or</p>
|
|
971
|
+
<label class="pick-label" @click.stop>
|
|
972
|
+
Choose .pptx file
|
|
973
|
+
<input ref="input" type="file" accept=".pptx" style="display: none" @change="onPick" />
|
|
974
|
+
</label>
|
|
975
|
+
<span class="or-sep">or</span>
|
|
976
|
+
<button class="new-btn" @click.stop="newPresentation">New Presentation</button>
|
|
977
|
+
</div>
|
|
535
978
|
</div>
|
|
536
979
|
</template>
|
|
980
|
+
|
|
981
|
+
<style>
|
|
982
|
+
:root { color-scheme: dark; }
|
|
983
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
984
|
+
body { margin: 0; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; background: var(--pptx-background, #030712); color: var(--pptx-foreground, #f3f4f6); }
|
|
985
|
+
#app { height: 100dvh; }
|
|
986
|
+
.stage { display: flex; align-items: center; justify-content: center; height: 100dvh; padding: 2rem; }
|
|
987
|
+
.dropzone { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.75rem; max-width: 520px; width: 100%; padding: 3rem; text-align: center; border: 2px dashed var(--pptx-border, #374151); border-radius: 0.75rem; cursor: pointer; transition: border-color 0.15s, background 0.15s; }
|
|
988
|
+
.dropzone.over, .dropzone:hover { border-color: var(--pptx-primary, #6366f1); background: var(--pptx-muted, rgba(255, 255, 255, 0.04)); }
|
|
989
|
+
.dropzone h1 { margin: 0; font-size: 1.5rem; font-weight: 500; }
|
|
990
|
+
.dropzone p { margin: 0; font-size: 0.875rem; color: var(--pptx-muted-foreground, #9ca3af); }
|
|
991
|
+
.pick-label { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1.25rem; border-radius: 0.5rem; border: 1px solid var(--pptx-border, #374151); background: var(--pptx-muted, #1f2937); color: var(--pptx-foreground, #f3f4f6); cursor: pointer; font-size: 0.875rem; transition: background 0.15s; }
|
|
992
|
+
.pick-label:hover { background: var(--pptx-accent, #374151); }
|
|
993
|
+
.or-sep { font-size: 0.8rem; color: var(--pptx-muted-foreground, #6b7280); }
|
|
994
|
+
.new-btn { padding: 0.5rem 1.25rem; border-radius: 0.5rem; border: none; background: var(--pptx-primary, #6366f1); color: #fff; cursor: pointer; font-size: 0.875rem; font-weight: 500; transition: opacity 0.15s; }
|
|
995
|
+
.new-btn:hover { opacity: 0.9; }
|
|
996
|
+
</style>
|
|
537
997
|
`;
|
|
538
998
|
var VUE_MAIN_TS = `import { createApp } from 'vue';
|
|
539
999
|
import { createI18n } from 'vue-i18n';
|
|
@@ -552,81 +1012,15 @@ const i18n = createI18n({
|
|
|
552
1012
|
|
|
553
1013
|
createApp(App).use(i18n).mount('#app');
|
|
554
1014
|
`;
|
|
555
|
-
var ANGULAR_APP_TS = `import { Component, signal } from '@angular/core';
|
|
556
|
-
import { PptxHandler } from 'pptx-viewer-core';
|
|
557
|
-
import { PowerPointViewerComponent } from 'pptx-angular-viewer';
|
|
558
|
-
|
|
559
|
-
@Component({
|
|
560
|
-
selector: 'app-root',
|
|
561
|
-
standalone: true,
|
|
562
|
-
imports: [PowerPointViewerComponent],
|
|
563
|
-
template: \`
|
|
564
|
-
@if (content(); as c) {
|
|
565
|
-
<div style="height: 100vh">
|
|
566
|
-
<pptx-power-point-viewer [content]="c" [canEdit]="true" style="height: 100%" />
|
|
567
|
-
</div>
|
|
568
|
-
} @else {
|
|
569
|
-
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 24px; height: 100vh; font-family: system-ui, sans-serif">
|
|
570
|
-
<h1 style="margin: 0; font-size: 24px; font-weight: 500; color: #e5e7eb">Open a Presentation</h1>
|
|
571
|
-
<label style="display: inline-flex; align-items: center; gap: 8px; padding: 10px 20px; border-radius: 8px; border: 1px solid #4b5563; background: #1f2937; color: #f3f4f6; cursor: pointer; font-size: 14px">
|
|
572
|
-
Choose .pptx file
|
|
573
|
-
<input type="file" accept=".pptx" style="display: none" (change)="onPick($event)" />
|
|
574
|
-
</label>
|
|
575
|
-
<span style="color: #6b7280; font-size: 13px">or</span>
|
|
576
|
-
<button style="padding: 10px 20px; border-radius: 8px; border: none; background: #2563eb; color: #fff; cursor: pointer; font-size: 14px; font-weight: 500" (click)="newPresentation()">New Presentation</button>
|
|
577
|
-
</div>
|
|
578
|
-
}
|
|
579
|
-
\`,
|
|
580
|
-
})
|
|
581
|
-
export class App {
|
|
582
|
-
content = signal<ArrayBuffer | Uint8Array | null>(null);
|
|
583
1015
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
const { handler, data } = await PptxHandler.createBlank({
|
|
593
|
-
title: 'Untitled Presentation',
|
|
594
|
-
initialSlideCount: 1,
|
|
595
|
-
});
|
|
596
|
-
this.content.set(await handler.save(data.slides));
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
`;
|
|
600
|
-
var ANGULAR_MAIN_TS = `import 'zone.js';
|
|
601
|
-
import '@angular/compiler';
|
|
602
|
-
import { bootstrapApplication } from '@angular/platform-browser';
|
|
603
|
-
import { Injectable } from '@angular/core';
|
|
604
|
-
import type { MissingTranslationHandlerParams } from '@ngx-translate/core';
|
|
605
|
-
import { MissingTranslationHandler, provideTranslateService } from '@ngx-translate/core';
|
|
606
|
-
import { keyToLabel } from 'pptx-angular-viewer';
|
|
607
|
-
|
|
608
|
-
import { App } from './app/app.ts';
|
|
609
|
-
|
|
610
|
-
@Injectable()
|
|
611
|
-
class LabelFallbackHandler implements MissingTranslationHandler {
|
|
612
|
-
handle(params: MissingTranslationHandlerParams): string {
|
|
613
|
-
return keyToLabel(params.key);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
bootstrapApplication(App, {
|
|
618
|
-
providers: [
|
|
619
|
-
provideTranslateService({
|
|
620
|
-
lang: 'en',
|
|
621
|
-
fallbackLang: 'en',
|
|
622
|
-
missingTranslationHandler: {
|
|
623
|
-
provide: MissingTranslationHandler,
|
|
624
|
-
useClass: LabelFallbackHandler,
|
|
625
|
-
},
|
|
626
|
-
}),
|
|
627
|
-
],
|
|
628
|
-
}).catch((err) => console.error(err));
|
|
629
|
-
`;
|
|
1016
|
+
// src/targets.ts
|
|
1017
|
+
var COLLAB_EXTRAS = [
|
|
1018
|
+
{
|
|
1019
|
+
prompt: "Include real-time collaboration? (adds yjs, y-websocket, y-webrtc)",
|
|
1020
|
+
packages: ["yjs", "y-websocket", "y-webrtc"]
|
|
1021
|
+
// defaultInclude is true (the default) - demo apps ship with collab packages.
|
|
1022
|
+
}
|
|
1023
|
+
];
|
|
630
1024
|
var TARGETS = [
|
|
631
1025
|
{
|
|
632
1026
|
id: "react",
|
|
@@ -677,8 +1071,9 @@ Docs: https://www.npmjs.com/package/pptx-react-viewer`,
|
|
|
677
1071
|
entryContent: REACT_APP_TSX,
|
|
678
1072
|
extraFiles: {
|
|
679
1073
|
"src/i18n.ts": REACT_I18N_TS,
|
|
680
|
-
"src/index.css":
|
|
681
|
-
}
|
|
1074
|
+
"src/index.css": MINIMAL_APP_CSS
|
|
1075
|
+
},
|
|
1076
|
+
optionalExtras: COLLAB_EXTRAS
|
|
682
1077
|
}
|
|
683
1078
|
},
|
|
684
1079
|
{
|
|
@@ -711,7 +1106,8 @@ Docs: https://www.npmjs.com/package/pptx-vue-viewer`,
|
|
|
711
1106
|
],
|
|
712
1107
|
entryCandidates: ["src/App.vue"],
|
|
713
1108
|
entryContent: VUE_APP_VUE,
|
|
714
|
-
extraFiles: { "src/main.ts": VUE_MAIN_TS }
|
|
1109
|
+
extraFiles: { "src/main.ts": VUE_MAIN_TS },
|
|
1110
|
+
optionalExtras: COLLAB_EXTRAS
|
|
715
1111
|
}
|
|
716
1112
|
},
|
|
717
1113
|
{
|
|
@@ -747,7 +1143,86 @@ Docs: https://www.npmjs.com/package/pptx-angular-viewer`,
|
|
|
747
1143
|
// Angular v20+ generates `app.ts`; older schematics generate `app.component.ts`.
|
|
748
1144
|
entryCandidates: ["src/app/app.ts", "src/app/app.component.ts"],
|
|
749
1145
|
entryContent: ANGULAR_APP_TS,
|
|
750
|
-
extraFiles: { "src/main.ts": ANGULAR_MAIN_TS }
|
|
1146
|
+
extraFiles: { "src/main.ts": ANGULAR_MAIN_TS, "src/styles.css": ANGULAR_GLOBAL_CSS },
|
|
1147
|
+
// @angular/cli@22 requires Node.js >=22.22.0, >=24.13.1, or >=26.0.0. Check
|
|
1148
|
+
// BEFORE the project-name prompt so the user sees the real reason immediately.
|
|
1149
|
+
preflight: () => {
|
|
1150
|
+
const node = process.versions.node;
|
|
1151
|
+
const [maj, min, pat] = node.split(".").map(Number);
|
|
1152
|
+
const ok = maj === 22 && min >= 22 || maj === 24 && (min > 13 || min === 13 && pat >= 1) || maj >= 26;
|
|
1153
|
+
if (!ok) {
|
|
1154
|
+
throw new Error(
|
|
1155
|
+
`@angular/cli@latest requires Node.js v22.22.0+, v24.13.1+, or v26.0.0+.
|
|
1156
|
+
You are running Node.js v${node}.
|
|
1157
|
+
Update Node.js at: https://nodejs.org`
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1161
|
+
optionalExtras: COLLAB_EXTRAS
|
|
1162
|
+
}
|
|
1163
|
+
},
|
|
1164
|
+
{
|
|
1165
|
+
id: "svelte",
|
|
1166
|
+
label: "Svelte",
|
|
1167
|
+
description: "pptx-svelte-viewer - viewer/editor component for a Svelte 5 app",
|
|
1168
|
+
mode: "install",
|
|
1169
|
+
group: "framework",
|
|
1170
|
+
packages: ["pptx-svelte-viewer", "svelte", "jszip", "fast-xml-parser"],
|
|
1171
|
+
// The Svelte binding compiles its styles into the components, so there
|
|
1172
|
+
// is no `/styles.css` subpath to import.
|
|
1173
|
+
nextSteps: `<script lang="ts">
|
|
1174
|
+
import { PowerPointViewer } from 'pptx-svelte-viewer';
|
|
1175
|
+
</script>
|
|
1176
|
+
|
|
1177
|
+
<PowerPointViewer source={bytes} editable />
|
|
1178
|
+
|
|
1179
|
+
Docs: https://www.npmjs.com/package/pptx-svelte-viewer`,
|
|
1180
|
+
compat: { peerPackage: "svelte", requiredMajor: 5 },
|
|
1181
|
+
scaffold: {
|
|
1182
|
+
command: "create-vite@latest",
|
|
1183
|
+
args: (dir) => [dir, "--template", "svelte-ts", "--no-interactive", "--no-immediate"],
|
|
1184
|
+
extraPackages: ["pptx-svelte-viewer", "pptx-viewer-core", "jszip", "fast-xml-parser"],
|
|
1185
|
+
entryCandidates: ["src/App.svelte"],
|
|
1186
|
+
entryContent: SVELTE_APP_SVELTE,
|
|
1187
|
+
// The starter's main.ts imports ./app.css; replace the Vite demo
|
|
1188
|
+
// styles (centred #app with padding) with a full-viewport reset.
|
|
1189
|
+
extraFiles: { "src/app.css": MINIMAL_APP_CSS },
|
|
1190
|
+
optionalExtras: COLLAB_EXTRAS
|
|
1191
|
+
}
|
|
1192
|
+
},
|
|
1193
|
+
{
|
|
1194
|
+
id: "vanilla",
|
|
1195
|
+
label: "Vanilla JS",
|
|
1196
|
+
description: "pptx-vanilla-viewer - zero-framework viewer/editor, plain DOM, no framework at all",
|
|
1197
|
+
mode: "install",
|
|
1198
|
+
group: "framework",
|
|
1199
|
+
// The vanilla binding injects its own stylesheet at runtime; jszip and
|
|
1200
|
+
// fast-xml-parser are its only peers.
|
|
1201
|
+
packages: ["pptx-vanilla-viewer", "jszip", "fast-xml-parser"],
|
|
1202
|
+
nextSteps: `import { createPptxViewer } from 'pptx-vanilla-viewer';
|
|
1203
|
+
|
|
1204
|
+
const viewer = createPptxViewer(document.getElementById('host')!, {
|
|
1205
|
+
source: '/deck.pptx', // URL, ArrayBuffer, Uint8Array, Blob, or File
|
|
1206
|
+
editable: true,
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
Docs: https://www.npmjs.com/package/pptx-vanilla-viewer`,
|
|
1210
|
+
scaffold: {
|
|
1211
|
+
command: "create-vite@latest",
|
|
1212
|
+
args: (dir) => [dir, "--template", "vanilla-ts", "--no-interactive", "--no-immediate"],
|
|
1213
|
+
extraPackages: [
|
|
1214
|
+
"pptx-vanilla-viewer",
|
|
1215
|
+
"pptx-viewer-core",
|
|
1216
|
+
"three",
|
|
1217
|
+
"jszip",
|
|
1218
|
+
"fast-xml-parser"
|
|
1219
|
+
],
|
|
1220
|
+
entryCandidates: ["src/main.ts"],
|
|
1221
|
+
entryContent: VANILLA_MAIN_TS,
|
|
1222
|
+
// main.ts imports ./style.css; replace the Vite demo styles with a
|
|
1223
|
+
// full-viewport reset.
|
|
1224
|
+
extraFiles: { "src/style.css": MINIMAL_APP_CSS },
|
|
1225
|
+
optionalExtras: COLLAB_EXTRAS
|
|
751
1226
|
}
|
|
752
1227
|
},
|
|
753
1228
|
{
|
|
@@ -846,7 +1321,7 @@ import { spawn } from "child_process";
|
|
|
846
1321
|
function runCommand(command, args, cwd, options) {
|
|
847
1322
|
return new Promise((resolve, reject) => {
|
|
848
1323
|
const isWindows = process.platform === "win32";
|
|
849
|
-
const stdio = options?.silent ? "ignore" : "inherit";
|
|
1324
|
+
const stdio = options?.silent ? ["inherit", "ignore", "inherit"] : "inherit";
|
|
850
1325
|
const child = isWindows ? spawn([command, ...args].join(" "), { cwd, stdio, shell: true }) : spawn(command, args, { cwd, stdio });
|
|
851
1326
|
child.on("error", reject);
|
|
852
1327
|
child.on("close", (code) => resolve(code ?? 1));
|
|
@@ -912,7 +1387,7 @@ ${bold("Usage:")} npx @christophervr/pptx-viewer [options]
|
|
|
912
1387
|
|
|
913
1388
|
${bold("Options:")}
|
|
914
1389
|
${cyan("--target <ids>")} Skip the picker; comma-separated, any of: ${TARGETS.map((t) => t.id).join(", ")}
|
|
915
|
-
(react, vue,
|
|
1390
|
+
(the UI bindings - react, vue, angular, svelte, vanilla - are mutually exclusive; pick at most one)
|
|
916
1391
|
${cyan("--scaffold")} Bootstrap a brand-new starter project instead of installing here
|
|
917
1392
|
${cyan("--dir <name>")} Project directory name for --scaffold
|
|
918
1393
|
${cyan("--pm <manager>")} Package manager to use: bun, pnpm, yarn, npm (default: auto-detected)
|
|
@@ -960,7 +1435,8 @@ async function resolveScaffoldChoice(installTargets, args) {
|
|
|
960
1435
|
const scaffoldable = installTargets.filter((t) => t.scaffold);
|
|
961
1436
|
if (args.scaffold) {
|
|
962
1437
|
if (scaffoldable.length !== 1) {
|
|
963
|
-
|
|
1438
|
+
const scaffoldIds = TARGETS.filter((t) => t.scaffold).map((t) => t.id).join(", ");
|
|
1439
|
+
throw new Error(`--scaffold requires exactly one of: ${scaffoldIds} to be selected.`);
|
|
964
1440
|
}
|
|
965
1441
|
return { useScaffold: true, scaffoldTarget: scaffoldable[0] };
|
|
966
1442
|
}
|
|
@@ -984,6 +1460,18 @@ async function runScaffoldMode(target, args, configTargets, cwd) {
|
|
|
984
1460
|
if (!recipe) {
|
|
985
1461
|
throw new Error(`${target.label} has no scaffold recipe.`);
|
|
986
1462
|
}
|
|
1463
|
+
recipe.preflight?.();
|
|
1464
|
+
const optionalPkgs = [];
|
|
1465
|
+
if (recipe.optionalExtras) {
|
|
1466
|
+
for (const extra of recipe.optionalExtras) {
|
|
1467
|
+
const defaultChoice = extra.defaultInclude !== false;
|
|
1468
|
+
const include = args.yes || !process.stdin.isTTY ? defaultChoice : await confirm(extra.prompt);
|
|
1469
|
+
if (include) {
|
|
1470
|
+
optionalPkgs.push(...extra.packages);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
const effectiveRecipe = optionalPkgs.length > 0 ? { ...recipe, extraPackages: [...recipe.extraPackages, ...optionalPkgs] } : recipe;
|
|
987
1475
|
const defaultName = `pptx-${target.id}-app`;
|
|
988
1476
|
const rawName = args.dir ?? (process.stdin.isTTY ? await input("Project directory name", defaultName) : defaultName);
|
|
989
1477
|
const projectName = sanitizeProjectName(rawName);
|
|
@@ -1001,7 +1489,7 @@ ${dim("Skipped.")}`);
|
|
|
1001
1489
|
return;
|
|
1002
1490
|
}
|
|
1003
1491
|
}
|
|
1004
|
-
const result = await scaffoldProject(
|
|
1492
|
+
const result = await scaffoldProject(effectiveRecipe, projectName, pm, cwd);
|
|
1005
1493
|
if (!result.patchedFile) {
|
|
1006
1494
|
console.log(
|
|
1007
1495
|
`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@christophervr/pptx-viewer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
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
|
"angular",
|