@streamscloud/kit 0.80.0 → 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -12,7 +12,11 @@ export declare class HtmlHelper {
|
|
|
12
12
|
*/
|
|
13
13
|
static trimElementValue(element: HTMLInputElement | HTMLTextAreaElement): boolean;
|
|
14
14
|
static pasteIntoInput(value: string, element: HTMLInputElement | HTMLTextAreaElement): void;
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Sanitizes SVG markup and serializes it as XML, valid both inline in HTML and as a standalone `image/svg+xml` file.
|
|
17
|
+
* Returns null when the markup isn't a well-formed SVG document, or when there is no window.
|
|
18
|
+
*/
|
|
19
|
+
static sanitizeSvg(svg: string): string | null;
|
|
16
20
|
static sanitizeHtml(html: string): string;
|
|
17
21
|
static stripHtml(html: string, lineBreaksReplacer?: string | null, trimResult?: boolean): string;
|
|
18
22
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import dp from 'dompurify';
|
|
2
|
+
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
|
3
|
+
const isSvgRoot = (element) => element?.localName === 'svg' && element.namespaceURI === SVG_NAMESPACE;
|
|
2
4
|
export class HtmlHelper {
|
|
3
5
|
static clearSelection() {
|
|
4
6
|
if (window.getSelection) {
|
|
@@ -66,11 +68,28 @@ export class HtmlHelper {
|
|
|
66
68
|
}));
|
|
67
69
|
element.focus();
|
|
68
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Sanitizes SVG markup and serializes it as XML, valid both inline in HTML and as a standalone `image/svg+xml` file.
|
|
73
|
+
* Returns null when the markup isn't a well-formed SVG document, or when there is no window.
|
|
74
|
+
*/
|
|
69
75
|
static sanitizeSvg(svg) {
|
|
70
76
|
if (typeof window === 'undefined') {
|
|
71
|
-
return
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const parsed = new DOMParser().parseFromString(svg, 'image/svg+xml');
|
|
80
|
+
if (!isSvgRoot(parsed.documentElement) || parsed.getElementsByTagName('parsererror').length) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const sanitized = dp.sanitize(parsed.documentElement, {
|
|
84
|
+
USE_PROFILES: { svg: true, svgFilters: true },
|
|
85
|
+
ADD_ATTR: ['dominant-baseline'],
|
|
86
|
+
RETURN_DOM_FRAGMENT: true
|
|
87
|
+
});
|
|
88
|
+
const root = sanitized.firstElementChild;
|
|
89
|
+
if (!isSvgRoot(root)) {
|
|
90
|
+
return null;
|
|
72
91
|
}
|
|
73
|
-
return
|
|
92
|
+
return new XMLSerializer().serializeToString(root);
|
|
74
93
|
}
|
|
75
94
|
static sanitizeHtml(html) {
|
|
76
95
|
return HtmlSanitizer.sanitize(html);
|
|
@@ -5,9 +5,12 @@ let mounted = $state(false);
|
|
|
5
5
|
onMount(() => {
|
|
6
6
|
mounted = true;
|
|
7
7
|
});
|
|
8
|
+
const sanitizedSvg = $derived(mounted ? HtmlHelper.sanitizeSvg(src) : null);
|
|
8
9
|
</script>
|
|
9
10
|
|
|
10
|
-
{#if
|
|
11
|
+
{#if mounted && sanitizedSvg === null}
|
|
12
|
+
<i class="invalid-svg">Invalid SVG</i>
|
|
13
|
+
{:else}
|
|
11
14
|
<div
|
|
12
15
|
class="icon"
|
|
13
16
|
class:icon--accent={color === 'accent'}
|
|
@@ -26,12 +29,10 @@ onMount(() => {
|
|
|
26
29
|
class:icon--control-md={size === 'control-md'}
|
|
27
30
|
class:icon--control-lg={size === 'control-lg'}
|
|
28
31
|
class:icon--control-xl={size === 'control-xl'}>
|
|
29
|
-
{#if
|
|
30
|
-
{@html
|
|
32
|
+
{#if sanitizedSvg}
|
|
33
|
+
{@html sanitizedSvg}
|
|
31
34
|
{/if}
|
|
32
35
|
</div>
|
|
33
|
-
{:else}
|
|
34
|
-
<i class="invalid-svg">Invalid SVG</i>
|
|
35
36
|
{/if}
|
|
36
37
|
|
|
37
38
|
<!--
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IconColor, IconSize } from './types';
|
|
2
2
|
interface Props {
|
|
3
|
-
/**
|
|
3
|
+
/** SVG markup, a well-formed `image/svg+xml` document (an XML declaration and DOCTYPE are allowed) */
|
|
4
4
|
src: string;
|
|
5
5
|
/** Predefined color preset applied to the icon */
|
|
6
6
|
color?: IconColor | null;
|