pptx-angular-viewer 1.13.0 → 1.13.2
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
CHANGED
|
@@ -4,6 +4,15 @@ 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.13.1](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.13.1) - 2026-07-09
|
|
8
|
+
|
|
9
|
+
## [1.13.0](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.13.0) - 2026-07-09
|
|
10
|
+
|
|
11
|
+
### Other
|
|
12
|
+
|
|
13
|
+
- Reconcile with origin/main before push (by @ChristopherVR) ([ef5fc85](https://github.com/ChristopherVR/pptx-viewer/commit/ef5fc85dca2e20ff3e105d622594e0f65d010fb0))
|
|
14
|
+
- Reconcile with origin/main before push (by @ChristopherVR) ([030b28b](https://github.com/ChristopherVR/pptx-viewer/commit/030b28bb21697ed681e4e59aa40db29f4b4a18d0))
|
|
15
|
+
|
|
7
16
|
## [1.12.0](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.12.0) - 2026-07-09
|
|
8
17
|
|
|
9
18
|
### Features
|
|
@@ -20400,6 +20400,39 @@ function buildRunEffectStyle(style) {
|
|
|
20400
20400
|
return css;
|
|
20401
20401
|
}
|
|
20402
20402
|
|
|
20403
|
+
/**
|
|
20404
|
+
* DOMPurify wrappers shared by every markup-sanitising call site (MathML/SVG
|
|
20405
|
+
* equation rendering, print-document/SVG assembly). DOMPurify's factory has
|
|
20406
|
+
* no working `sanitize` until handed a `window`, so in non-DOM contexts
|
|
20407
|
+
* (node-based tests, SSR without jsdom) these degrade rather than throw.
|
|
20408
|
+
*
|
|
20409
|
+
* Two degradation strategies, picked per call site by how the sanitised
|
|
20410
|
+
* output is used:
|
|
20411
|
+
* - {@link sanitizeMarkupOrRaw}: returns the untouched input outside a DOM.
|
|
20412
|
+
* Safe when the caller only ever renders the result in the browser (e.g.
|
|
20413
|
+
* `dangerouslySetInnerHTML` / `v-html`), so the fallback path never
|
|
20414
|
+
* reaches a real sink.
|
|
20415
|
+
* - {@link sanitizeMarkupOrEmpty}: returns `''` outside a DOM (fail closed).
|
|
20416
|
+
* Used where the sanitised string is unconditionally spliced into a
|
|
20417
|
+
* larger assembled document (print HTML/SVG) with no further gate, so a
|
|
20418
|
+
* silent pass-through of the raw, unsanitised input would defeat the
|
|
20419
|
+
* sanitisation entirely.
|
|
20420
|
+
*/
|
|
20421
|
+
function purifyFn() {
|
|
20422
|
+
const purify = DOMPurify;
|
|
20423
|
+
return typeof purify.sanitize === 'function' ? purify.sanitize : undefined;
|
|
20424
|
+
}
|
|
20425
|
+
/** Sanitise `markup`; outside a DOM, returns the raw input unchanged. */
|
|
20426
|
+
function sanitizeMarkupOrRaw(markup, config) {
|
|
20427
|
+
const sanitize = purifyFn();
|
|
20428
|
+
return sanitize ? sanitize(markup, config) : markup;
|
|
20429
|
+
}
|
|
20430
|
+
/** Sanitise `markup`; outside a DOM, returns `''` (fail closed). */
|
|
20431
|
+
function sanitizeMarkupOrEmpty(markup, config) {
|
|
20432
|
+
const sanitize = purifyFn();
|
|
20433
|
+
return sanitize ? sanitize(markup, config) : '';
|
|
20434
|
+
}
|
|
20435
|
+
|
|
20403
20436
|
/**
|
|
20404
20437
|
* MathML / SVG markup sanitisation, shared by every binding's equation
|
|
20405
20438
|
* renderer.
|
|
@@ -20418,20 +20451,15 @@ function buildRunEffectStyle(style) {
|
|
|
20418
20451
|
* Safely sanitise a MathML/SVG markup string.
|
|
20419
20452
|
*
|
|
20420
20453
|
* In browser environments DOMPurify ships with `sanitize` ready to go. In
|
|
20421
|
-
* non-DOM contexts (node-based tests, SSR without jsdom)
|
|
20422
|
-
*
|
|
20423
|
-
* the raw input. The XSS surface only matters in the browser, so this fallback
|
|
20454
|
+
* non-DOM contexts (node-based tests, SSR without jsdom) it falls back to the
|
|
20455
|
+
* raw input; the XSS surface only matters in the browser, so this fallback
|
|
20424
20456
|
* is safe for non-DOM consumers.
|
|
20425
20457
|
*
|
|
20426
20458
|
* @param markup - MathML (optionally with embedded SVG) markup to sanitise.
|
|
20427
20459
|
* @returns The sanitised markup, or the raw input when no DOM is available.
|
|
20428
20460
|
*/
|
|
20429
20461
|
function sanitizeMathMl(markup) {
|
|
20430
|
-
|
|
20431
|
-
if (typeof purify.sanitize !== 'function') {
|
|
20432
|
-
return markup;
|
|
20433
|
-
}
|
|
20434
|
-
return purify.sanitize(markup, { USE_PROFILES: { mathMl: true, svg: true } });
|
|
20462
|
+
return sanitizeMarkupOrRaw(markup, { USE_PROFILES: { mathMl: true, svg: true } });
|
|
20435
20463
|
}
|
|
20436
20464
|
|
|
20437
20465
|
/**
|
|
@@ -33982,13 +34010,13 @@ function buildNotesTextStream(lines, startY) {
|
|
|
33982
34010
|
}
|
|
33983
34011
|
|
|
33984
34012
|
/**
|
|
33985
|
-
* Pure SVG-print string helpers
|
|
34013
|
+
* Pure SVG-print string helpers, shared by bindings that offer a vector print
|
|
33986
34014
|
* path (currently React). These build self-contained SVG / print-HTML strings
|
|
33987
34015
|
* and escape text; they touch no DOM.
|
|
33988
34016
|
*
|
|
33989
34017
|
* The DOM-bound driver pieces (cloning the live element tree, reading
|
|
33990
34018
|
* `getComputedStyle`, fetching images to base64, `Blob` wrapping) stay in the
|
|
33991
|
-
* binding
|
|
34019
|
+
* binding; only the string assembly and escaping live here.
|
|
33992
34020
|
*/
|
|
33993
34021
|
/* ------------------------------------------------------------------ */
|
|
33994
34022
|
/* XML escaping */
|
|
@@ -34036,20 +34064,47 @@ function sanitizeCssDeclaration(value) {
|
|
|
34036
34064
|
* JS callers, so a runtime check keeps the value that reaches `@page` /
|
|
34037
34065
|
* `<style>` interpolation confined to those two known-safe strings.
|
|
34038
34066
|
*/
|
|
34039
|
-
function sanitizeOrientation(value) {
|
|
34067
|
+
function sanitizeOrientation$1(value) {
|
|
34040
34068
|
return value === 'portrait' ? 'portrait' : 'landscape';
|
|
34041
34069
|
}
|
|
34070
|
+
/** Element/attribute shapes that are never legitimate in a rendered slide SVG. */
|
|
34071
|
+
const UNSAFE_SVG_SUBSTRINGS = [
|
|
34072
|
+
'</section',
|
|
34073
|
+
'<script',
|
|
34074
|
+
'<foreignobject',
|
|
34075
|
+
'<iframe',
|
|
34076
|
+
'<embed',
|
|
34077
|
+
'<object',
|
|
34078
|
+
// eslint-disable-next-line no-script-url -- security deny-list entry: verifies the scheme is rejected, never executed.
|
|
34079
|
+
'javascript:',
|
|
34080
|
+
];
|
|
34081
|
+
/** Matches an `on<event>=` handler attribute, e.g. `onload=`, `onclick=`. */
|
|
34082
|
+
const EVENT_HANDLER_ATTR_RE$1 = /\son\w+\s*=/iu;
|
|
34042
34083
|
/**
|
|
34043
34084
|
* Guard against a malformed or tampered per-slide SVG string breaking out
|
|
34044
34085
|
* of the `<section>` wrapper it's embedded in (unescaped) by
|
|
34045
|
-
* {@link buildPrintDocument}.
|
|
34046
|
-
*
|
|
34047
|
-
*
|
|
34048
|
-
*
|
|
34086
|
+
* {@link buildPrintDocument}.
|
|
34087
|
+
*
|
|
34088
|
+
* This is deliberately an allow-list first, deny-list second check, not
|
|
34089
|
+
* just a deny-list: legitimate SVG produced by this library's own
|
|
34090
|
+
* `SvgExporter` render path is always a single well-formed `<svg>...</svg>`
|
|
34091
|
+
* root element, so anything that doesn't structurally look like that is
|
|
34092
|
+
* rejected outright. The deny-list then additionally screens for common
|
|
34093
|
+
* SVG-based script-injection vectors (`<script>`, `<foreignObject>` (which
|
|
34094
|
+
* can embed arbitrary HTML), `on*=` event handler attributes, `javascript:`
|
|
34095
|
+
* URIs) so a crafted-but-still-`<svg>...</svg>`-shaped payload is also
|
|
34096
|
+
* rejected.
|
|
34049
34097
|
*/
|
|
34050
34098
|
function isSafeSvgMarkup(svg) {
|
|
34051
|
-
const
|
|
34052
|
-
|
|
34099
|
+
const trimmed = svg.trim();
|
|
34100
|
+
if (!trimmed.toLowerCase().startsWith('<svg') || !trimmed.toLowerCase().endsWith('</svg>')) {
|
|
34101
|
+
return false;
|
|
34102
|
+
}
|
|
34103
|
+
const lower = trimmed.toLowerCase();
|
|
34104
|
+
if (UNSAFE_SVG_SUBSTRINGS.some((needle) => lower.includes(needle))) {
|
|
34105
|
+
return false;
|
|
34106
|
+
}
|
|
34107
|
+
return !EVENT_HANDLER_ATTR_RE$1.test(trimmed);
|
|
34053
34108
|
}
|
|
34054
34109
|
/* ------------------------------------------------------------------ */
|
|
34055
34110
|
/* Print stylesheet / document construction */
|
|
@@ -34091,11 +34146,18 @@ function buildPrintStyleSheet(width, height, customCss) {
|
|
|
34091
34146
|
*/
|
|
34092
34147
|
function buildPrintDocument(svgs, width, height, options = {}) {
|
|
34093
34148
|
const { title = 'Print', orientation = 'landscape', colorFilter = '' } = options;
|
|
34094
|
-
const safeOrientation = sanitizeOrientation(orientation);
|
|
34149
|
+
const safeOrientation = sanitizeOrientation$1(orientation);
|
|
34095
34150
|
const safeColorFilter = sanitizeCssDeclaration(colorFilter);
|
|
34096
34151
|
const slidePages = svgs
|
|
34097
34152
|
.map((svg, i) => {
|
|
34098
|
-
|
|
34153
|
+
// Belt-and-suspenders: the structural allow/deny-list guard runs
|
|
34154
|
+
// first, then DOMPurify's SVG profile actually transforms the
|
|
34155
|
+
// markup (stripping `<script>`/`<foreignObject>`/event handlers/
|
|
34156
|
+
// `javascript:` URIs) before it is spliced in, rather than merely
|
|
34157
|
+
// gating the raw, untransformed string behind a boolean check.
|
|
34158
|
+
const safeSvg = isSafeSvgMarkup(svg)
|
|
34159
|
+
? sanitizeMarkupOrEmpty(svg, { USE_PROFILES: { svg: true } })
|
|
34160
|
+
: '';
|
|
34099
34161
|
return `<section class="print-slide-page" aria-label="Slide ${i + 1}">
|
|
34100
34162
|
${safeSvg}
|
|
34101
34163
|
</section>`;
|
|
@@ -35558,12 +35620,59 @@ function buildHandoutsHtml(slideImages, slideIndices, slidesPerPage) {
|
|
|
35558
35620
|
}
|
|
35559
35621
|
return pages.join('');
|
|
35560
35622
|
}
|
|
35623
|
+
/**
|
|
35624
|
+
* `orientation` is typed as a union at compile time, but `buildPrintHtmlDocument`
|
|
35625
|
+
* is a public export reachable from plain JS callers, so a runtime check keeps
|
|
35626
|
+
* the value that reaches `@page` / `<style>` interpolation confined to those
|
|
35627
|
+
* two known-safe strings.
|
|
35628
|
+
*/
|
|
35629
|
+
function sanitizeOrientation(value) {
|
|
35630
|
+
return value === 'portrait' ? 'portrait' : 'landscape';
|
|
35631
|
+
}
|
|
35632
|
+
/** Element/attribute shapes that must never appear in assembled print-window body HTML. */
|
|
35633
|
+
const UNSAFE_BODY_HTML_SUBSTRINGS = [
|
|
35634
|
+
'<script',
|
|
35635
|
+
'<iframe',
|
|
35636
|
+
'<embed',
|
|
35637
|
+
'<object',
|
|
35638
|
+
'<foreignobject',
|
|
35639
|
+
// eslint-disable-next-line no-script-url -- security deny-list entry: verifies the scheme is rejected, never executed.
|
|
35640
|
+
'javascript:',
|
|
35641
|
+
];
|
|
35642
|
+
/** Matches an `on<event>=` handler attribute, e.g. `onload=`, `onclick=`. */
|
|
35643
|
+
const EVENT_HANDLER_ATTR_RE = /\son\w+\s*=/iu;
|
|
35644
|
+
/**
|
|
35645
|
+
* Defense-in-depth guard for the assembled print-window body HTML. Every
|
|
35646
|
+
* dynamic value the `build*Html` helpers above embed (titles, notes, image
|
|
35647
|
+
* `src`) is already escaped via {@link escapeHtml} / {@link safeDataImageSrc}
|
|
35648
|
+
* before it's spliced into `bodyHtml`; this additionally screens the
|
|
35649
|
+
* assembled fragment for script-injection shapes, so a future caller that
|
|
35650
|
+
* forgets to escape a new field doesn't reach the printed window unnoticed.
|
|
35651
|
+
*/
|
|
35652
|
+
function isSafePrintBodyHtml(html) {
|
|
35653
|
+
const lower = html.toLowerCase();
|
|
35654
|
+
if (UNSAFE_BODY_HTML_SUBSTRINGS.some((needle) => lower.includes(needle))) {
|
|
35655
|
+
return false;
|
|
35656
|
+
}
|
|
35657
|
+
return !EVENT_HANDLER_ATTR_RE.test(html);
|
|
35658
|
+
}
|
|
35659
|
+
/** DOMPurify config for the assembled print-window body: plain HTML, no MathML/SVG needed. */
|
|
35660
|
+
const PRINT_BODY_SANITIZE_CONFIG = { USE_PROFILES: { html: true } };
|
|
35561
35661
|
/**
|
|
35562
35662
|
* Assemble the complete printable HTML document string (doctype + head with
|
|
35563
35663
|
* print CSS + body). Pure: the caller writes it into a print window.
|
|
35564
35664
|
*/
|
|
35565
35665
|
function buildPrintHtmlDocument(options) {
|
|
35566
|
-
const { title, bodyHtml,
|
|
35666
|
+
const { title, bodyHtml, frameSlides } = options;
|
|
35667
|
+
const orientation = sanitizeOrientation(options.orientation);
|
|
35668
|
+
const colorFilter = options.colorFilter;
|
|
35669
|
+
// Belt-and-suspenders: the deny-list guard runs first, then DOMPurify
|
|
35670
|
+
// actually transforms the markup (stripping `<script>`/`<iframe>`/event
|
|
35671
|
+
// handlers/`javascript:` URIs) before it is spliced in, rather than
|
|
35672
|
+
// merely gating the raw, untransformed string behind a boolean check.
|
|
35673
|
+
const safeBodyHtml = isSafePrintBodyHtml(bodyHtml)
|
|
35674
|
+
? sanitizeMarkupOrEmpty(bodyHtml, PRINT_BODY_SANITIZE_CONFIG)
|
|
35675
|
+
: '';
|
|
35567
35676
|
const frameStyle = frameSlides
|
|
35568
35677
|
? 'img.slide-img, .notes-slide, .handout-cell img { border: 2px solid #000 !important; }'
|
|
35569
35678
|
: '';
|
|
@@ -35611,7 +35720,7 @@ function buildPrintHtmlDocument(options) {
|
|
|
35611
35720
|
${frameStyle}
|
|
35612
35721
|
</style>
|
|
35613
35722
|
</head>
|
|
35614
|
-
<body>${
|
|
35723
|
+
<body>${safeBodyHtml}</body>
|
|
35615
35724
|
</html>`;
|
|
35616
35725
|
}
|
|
35617
35726
|
|
|
@@ -75803,7 +75912,7 @@ class RibbonComponent {
|
|
|
75803
75912
|
|
|
75804
75913
|
<!-- ── Ribbon content (collapsible via the ribbon toggle) ──────────── -->
|
|
75805
75914
|
<div
|
|
75806
|
-
class="flex flex-nowrap items-
|
|
75915
|
+
class="flex flex-nowrap items-center gap-1.5 overflow-x-auto px-2 py-1"
|
|
75807
75916
|
[style.display]="ribbonExpanded() ? null : 'none'"
|
|
75808
75917
|
>
|
|
75809
75918
|
@switch (activeTab()) {
|
|
@@ -76109,7 +76218,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
76109
76218
|
|
|
76110
76219
|
<!-- ── Ribbon content (collapsible via the ribbon toggle) ──────────── -->
|
|
76111
76220
|
<div
|
|
76112
|
-
class="flex flex-nowrap items-
|
|
76221
|
+
class="flex flex-nowrap items-center gap-1.5 overflow-x-auto px-2 py-1"
|
|
76113
76222
|
[style.display]="ribbonExpanded() ? null : 'none'"
|
|
76114
76223
|
>
|
|
76115
76224
|
@switch (activeTab()) {
|