@christophervr/pptx-viewer 1.5.0 → 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 +6 -0
- package/dist/index.mjs +445 -111
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ 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
|
+
|
|
7
13
|
## [1.4.2](https://github.com/ChristopherVR/pptx-viewer/releases/tag/@christophervr/pptx-viewer@1.4.2) - 2026-07-07
|
|
8
14
|
|
|
9
15
|
## [1.4.1](https://github.com/ChristopherVR/pptx-viewer/releases/tag/@christophervr/pptx-viewer@1.4.1) - 2026-07-05
|
package/dist/index.mjs
CHANGED
|
@@ -410,38 +410,76 @@ async function input(question, defaultValue) {
|
|
|
410
410
|
// src/templates/angular.ts
|
|
411
411
|
var ANGULAR_APP_TS = `import { Component, signal } from '@angular/core';
|
|
412
412
|
import { PptxHandler } from 'pptx-viewer-core';
|
|
413
|
+
import type { CollaborationConfig } from 'pptx-angular-viewer';
|
|
413
414
|
import { PowerPointViewerComponent } from 'pptx-angular-viewer';
|
|
414
415
|
|
|
415
416
|
@Component({
|
|
416
417
|
selector: 'app-root',
|
|
417
418
|
standalone: true,
|
|
418
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
|
+
\`],
|
|
419
433
|
template: \`
|
|
420
434
|
@if (content(); as c) {
|
|
421
|
-
<div style="height:
|
|
422
|
-
<pptx-power-point-viewer
|
|
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
|
+
/>
|
|
423
444
|
</div>
|
|
424
445
|
} @else {
|
|
425
|
-
<div
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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>
|
|
433
464
|
</div>
|
|
434
465
|
}
|
|
435
466
|
\`,
|
|
436
467
|
})
|
|
437
468
|
export class App {
|
|
438
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
|
+
}
|
|
439
479
|
|
|
440
480
|
async onPick(e: Event) {
|
|
441
481
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
442
|
-
if (file)
|
|
443
|
-
this.content.set(await file.arrayBuffer());
|
|
444
|
-
}
|
|
482
|
+
if (file) this.content.set(await file.arrayBuffer());
|
|
445
483
|
}
|
|
446
484
|
|
|
447
485
|
async newPresentation() {
|
|
@@ -487,17 +525,18 @@ bootstrapApplication(App, {
|
|
|
487
525
|
// src/templates/react.ts
|
|
488
526
|
var REACT_APP_TSX = `import { useCallback, useState } from 'react';
|
|
489
527
|
import { PptxHandler } from 'pptx-viewer-core';
|
|
528
|
+
import type { CollaborationConfig } from 'pptx-react-viewer';
|
|
490
529
|
import { PowerPointViewer } from 'pptx-react-viewer';
|
|
491
530
|
import 'pptx-react-viewer/styles.css';
|
|
492
531
|
import './i18n';
|
|
493
532
|
|
|
494
533
|
export default function App() {
|
|
495
534
|
const [content, setContent] = useState<Uint8Array | null>(null);
|
|
535
|
+
const [over, setOver] = useState(false);
|
|
536
|
+
const [collab, setCollab] = useState<CollaborationConfig | undefined>();
|
|
496
537
|
|
|
497
|
-
const loadFile = useCallback((file: File) => {
|
|
498
|
-
|
|
499
|
-
reader.onload = () => setContent(new Uint8Array(reader.result as ArrayBuffer));
|
|
500
|
-
reader.readAsArrayBuffer(file);
|
|
538
|
+
const loadFile = useCallback(async (file: File) => {
|
|
539
|
+
setContent(new Uint8Array(await file.arrayBuffer()));
|
|
501
540
|
}, []);
|
|
502
541
|
|
|
503
542
|
const newPresentation = useCallback(async () => {
|
|
@@ -510,34 +549,55 @@ export default function App() {
|
|
|
510
549
|
|
|
511
550
|
if (content) {
|
|
512
551
|
return (
|
|
513
|
-
<div style={{ height: '
|
|
514
|
-
<PowerPointViewer
|
|
552
|
+
<div style={{ height: '100dvh' }}>
|
|
553
|
+
<PowerPointViewer
|
|
554
|
+
content={content}
|
|
555
|
+
canEdit
|
|
556
|
+
collaboration={collab}
|
|
557
|
+
onStartCollaboration={setCollab}
|
|
558
|
+
onStopCollaboration={() => setCollab(undefined)}
|
|
559
|
+
/>
|
|
515
560
|
</div>
|
|
516
561
|
);
|
|
517
562
|
}
|
|
518
563
|
|
|
519
564
|
return (
|
|
520
|
-
<div
|
|
521
|
-
<
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
/>
|
|
533
|
-
</label>
|
|
534
|
-
<span style={{ color: '#6b7280', fontSize: 13 }}>or</span>
|
|
535
|
-
<button
|
|
536
|
-
onClick={() => void newPresentation()}
|
|
537
|
-
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()}
|
|
538
577
|
>
|
|
539
|
-
|
|
540
|
-
|
|
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>
|
|
541
601
|
</div>
|
|
542
602
|
);
|
|
543
603
|
}
|
|
@@ -564,27 +624,157 @@ export default i18n;
|
|
|
564
624
|
|
|
565
625
|
// src/templates/shared.ts
|
|
566
626
|
var MINIMAL_APP_CSS = `:root {
|
|
567
|
-
color-scheme:
|
|
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;
|
|
568
740
|
}
|
|
569
741
|
|
|
570
742
|
body {
|
|
571
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;
|
|
572
752
|
}
|
|
573
753
|
`;
|
|
574
754
|
|
|
575
755
|
// src/templates/svelte.ts
|
|
576
756
|
var SVELTE_APP_SVELTE = `<script lang="ts">
|
|
577
757
|
import { PptxHandler } from 'pptx-viewer-core';
|
|
758
|
+
import type { CollaborationConfig } from 'pptx-svelte-viewer';
|
|
578
759
|
import { PowerPointViewer } from 'pptx-svelte-viewer';
|
|
579
760
|
|
|
580
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
|
+
}
|
|
581
774
|
|
|
582
775
|
function onPick(e: Event) {
|
|
583
776
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
584
|
-
if (
|
|
585
|
-
const reader = new FileReader();
|
|
586
|
-
reader.onload = () => (content = new Uint8Array(reader.result as ArrayBuffer));
|
|
587
|
-
reader.readAsArrayBuffer(file);
|
|
777
|
+
if (file) void loadFile(file);
|
|
588
778
|
}
|
|
589
779
|
|
|
590
780
|
async function newPresentation() {
|
|
@@ -597,88 +787,154 @@ var SVELTE_APP_SVELTE = `<script lang="ts">
|
|
|
597
787
|
</script>
|
|
598
788
|
|
|
599
789
|
{#if content}
|
|
600
|
-
<div style="height:
|
|
601
|
-
<PowerPointViewer
|
|
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
|
+
/>
|
|
602
798
|
</div>
|
|
603
799
|
{:else}
|
|
604
|
-
<div
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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>
|
|
612
821
|
</div>
|
|
613
822
|
{/if}
|
|
614
823
|
`;
|
|
615
824
|
|
|
616
825
|
// src/templates/vanilla.ts
|
|
617
|
-
var VANILLA_MAIN_TS = `import {
|
|
618
|
-
import {
|
|
826
|
+
var VANILLA_MAIN_TS = `import { createPptxViewer } from 'pptx-vanilla-viewer';
|
|
827
|
+
import { PptxHandler } from 'pptx-viewer-core';
|
|
619
828
|
|
|
620
829
|
import './style.css';
|
|
621
830
|
|
|
622
831
|
const app = document.querySelector<HTMLDivElement>('#app')!;
|
|
623
832
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
833
|
+
function show(source: ArrayBuffer | Uint8Array): void {
|
|
834
|
+
app.innerHTML = '';
|
|
835
|
+
app.style.height = '100dvh';
|
|
836
|
+
createPptxViewer(app, { source, editable: true });
|
|
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
|
+
});
|
|
652
888
|
|
|
653
|
-
picker
|
|
654
|
-
|
|
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;
|
|
655
902
|
const { handler, data } = await PptxHandler.createBlank({
|
|
656
903
|
title: 'Untitled Presentation',
|
|
657
904
|
initialSlideCount: 1,
|
|
658
905
|
});
|
|
659
906
|
show(await handler.save(data.slides));
|
|
660
|
-
})
|
|
661
|
-
}
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
showLanding();
|
|
662
911
|
`;
|
|
663
912
|
|
|
664
913
|
// src/templates/vue.ts
|
|
665
914
|
var VUE_APP_VUE = `<script setup lang="ts">
|
|
666
915
|
import { ref } from 'vue';
|
|
667
916
|
import { PptxHandler } from 'pptx-viewer-core';
|
|
917
|
+
import type { CollaborationConfig } from 'pptx-vue-viewer';
|
|
668
918
|
import { PowerPointViewer } from 'pptx-vue-viewer';
|
|
669
919
|
import 'pptx-vue-viewer/styles.css';
|
|
670
920
|
|
|
671
921
|
const content = ref<Uint8Array>();
|
|
922
|
+
const over = ref(false);
|
|
923
|
+
const collab = ref<CollaborationConfig | undefined>();
|
|
672
924
|
|
|
673
|
-
function loadFile(file: File) {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
925
|
+
async function loadFile(file: File) {
|
|
926
|
+
content.value = new Uint8Array(await file.arrayBuffer());
|
|
927
|
+
}
|
|
928
|
+
|
|
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);
|
|
677
933
|
}
|
|
678
934
|
|
|
679
935
|
function onPick(e: Event) {
|
|
680
936
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
681
|
-
if (file) loadFile(file);
|
|
937
|
+
if (file) void loadFile(file);
|
|
682
938
|
}
|
|
683
939
|
|
|
684
940
|
async function newPresentation() {
|
|
@@ -691,19 +947,53 @@ async function newPresentation() {
|
|
|
691
947
|
</script>
|
|
692
948
|
|
|
693
949
|
<template>
|
|
694
|
-
<div v-if="content" style="height:
|
|
695
|
-
<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
|
+
/>
|
|
696
959
|
</div>
|
|
697
|
-
<div
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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>
|
|
705
978
|
</div>
|
|
706
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>
|
|
707
997
|
`;
|
|
708
998
|
var VUE_MAIN_TS = `import { createApp } from 'vue';
|
|
709
999
|
import { createI18n } from 'vue-i18n';
|
|
@@ -724,6 +1014,13 @@ createApp(App).use(i18n).mount('#app');
|
|
|
724
1014
|
`;
|
|
725
1015
|
|
|
726
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
|
+
];
|
|
727
1024
|
var TARGETS = [
|
|
728
1025
|
{
|
|
729
1026
|
id: "react",
|
|
@@ -775,7 +1072,8 @@ Docs: https://www.npmjs.com/package/pptx-react-viewer`,
|
|
|
775
1072
|
extraFiles: {
|
|
776
1073
|
"src/i18n.ts": REACT_I18N_TS,
|
|
777
1074
|
"src/index.css": MINIMAL_APP_CSS
|
|
778
|
-
}
|
|
1075
|
+
},
|
|
1076
|
+
optionalExtras: COLLAB_EXTRAS
|
|
779
1077
|
}
|
|
780
1078
|
},
|
|
781
1079
|
{
|
|
@@ -808,7 +1106,8 @@ Docs: https://www.npmjs.com/package/pptx-vue-viewer`,
|
|
|
808
1106
|
],
|
|
809
1107
|
entryCandidates: ["src/App.vue"],
|
|
810
1108
|
entryContent: VUE_APP_VUE,
|
|
811
|
-
extraFiles: { "src/main.ts": VUE_MAIN_TS }
|
|
1109
|
+
extraFiles: { "src/main.ts": VUE_MAIN_TS },
|
|
1110
|
+
optionalExtras: COLLAB_EXTRAS
|
|
812
1111
|
}
|
|
813
1112
|
},
|
|
814
1113
|
{
|
|
@@ -844,7 +1143,22 @@ Docs: https://www.npmjs.com/package/pptx-angular-viewer`,
|
|
|
844
1143
|
// Angular v20+ generates `app.ts`; older schematics generate `app.component.ts`.
|
|
845
1144
|
entryCandidates: ["src/app/app.ts", "src/app/app.component.ts"],
|
|
846
1145
|
entryContent: ANGULAR_APP_TS,
|
|
847
|
-
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
|
|
848
1162
|
}
|
|
849
1163
|
},
|
|
850
1164
|
{
|
|
@@ -872,7 +1186,8 @@ Docs: https://www.npmjs.com/package/pptx-svelte-viewer`,
|
|
|
872
1186
|
entryContent: SVELTE_APP_SVELTE,
|
|
873
1187
|
// The starter's main.ts imports ./app.css; replace the Vite demo
|
|
874
1188
|
// styles (centred #app with padding) with a full-viewport reset.
|
|
875
|
-
extraFiles: { "src/app.css": MINIMAL_APP_CSS }
|
|
1189
|
+
extraFiles: { "src/app.css": MINIMAL_APP_CSS },
|
|
1190
|
+
optionalExtras: COLLAB_EXTRAS
|
|
876
1191
|
}
|
|
877
1192
|
},
|
|
878
1193
|
{
|
|
@@ -895,12 +1210,19 @@ Docs: https://www.npmjs.com/package/pptx-vanilla-viewer`,
|
|
|
895
1210
|
scaffold: {
|
|
896
1211
|
command: "create-vite@latest",
|
|
897
1212
|
args: (dir) => [dir, "--template", "vanilla-ts", "--no-interactive", "--no-immediate"],
|
|
898
|
-
extraPackages: [
|
|
1213
|
+
extraPackages: [
|
|
1214
|
+
"pptx-vanilla-viewer",
|
|
1215
|
+
"pptx-viewer-core",
|
|
1216
|
+
"three",
|
|
1217
|
+
"jszip",
|
|
1218
|
+
"fast-xml-parser"
|
|
1219
|
+
],
|
|
899
1220
|
entryCandidates: ["src/main.ts"],
|
|
900
1221
|
entryContent: VANILLA_MAIN_TS,
|
|
901
1222
|
// main.ts imports ./style.css; replace the Vite demo styles with a
|
|
902
1223
|
// full-viewport reset.
|
|
903
|
-
extraFiles: { "src/style.css": MINIMAL_APP_CSS }
|
|
1224
|
+
extraFiles: { "src/style.css": MINIMAL_APP_CSS },
|
|
1225
|
+
optionalExtras: COLLAB_EXTRAS
|
|
904
1226
|
}
|
|
905
1227
|
},
|
|
906
1228
|
{
|
|
@@ -999,7 +1321,7 @@ import { spawn } from "child_process";
|
|
|
999
1321
|
function runCommand(command, args, cwd, options) {
|
|
1000
1322
|
return new Promise((resolve, reject) => {
|
|
1001
1323
|
const isWindows = process.platform === "win32";
|
|
1002
|
-
const stdio = options?.silent ? "ignore" : "inherit";
|
|
1324
|
+
const stdio = options?.silent ? ["inherit", "ignore", "inherit"] : "inherit";
|
|
1003
1325
|
const child = isWindows ? spawn([command, ...args].join(" "), { cwd, stdio, shell: true }) : spawn(command, args, { cwd, stdio });
|
|
1004
1326
|
child.on("error", reject);
|
|
1005
1327
|
child.on("close", (code) => resolve(code ?? 1));
|
|
@@ -1138,6 +1460,18 @@ async function runScaffoldMode(target, args, configTargets, cwd) {
|
|
|
1138
1460
|
if (!recipe) {
|
|
1139
1461
|
throw new Error(`${target.label} has no scaffold recipe.`);
|
|
1140
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;
|
|
1141
1475
|
const defaultName = `pptx-${target.id}-app`;
|
|
1142
1476
|
const rawName = args.dir ?? (process.stdin.isTTY ? await input("Project directory name", defaultName) : defaultName);
|
|
1143
1477
|
const projectName = sanitizeProjectName(rawName);
|
|
@@ -1155,7 +1489,7 @@ ${dim("Skipped.")}`);
|
|
|
1155
1489
|
return;
|
|
1156
1490
|
}
|
|
1157
1491
|
}
|
|
1158
|
-
const result = await scaffoldProject(
|
|
1492
|
+
const result = await scaffoldProject(effectiveRecipe, projectName, pm, cwd);
|
|
1159
1493
|
if (!result.patchedFile) {
|
|
1160
1494
|
console.log(
|
|
1161
1495
|
`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@christophervr/pptx-viewer",
|
|
3
|
-
"version": "1.5.
|
|
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",
|