mnfst 0.5.79 → 0.5.81
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/lib/manifest.appwrite.auth.js +66 -33
- package/lib/manifest.code.css +19 -7
- package/lib/manifest.code.min.css +1 -1
- package/lib/manifest.components.js +25 -155
- package/lib/manifest.css +47 -45
- package/lib/manifest.data.js +42 -1
- package/lib/manifest.integrity.json +26 -0
- package/lib/manifest.js +60 -5
- package/lib/manifest.markdown.js +95 -3
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.router.js +49 -76
- package/lib/manifest.schema.json +1 -1
- package/lib/manifest.sidebar.css +3 -3
- package/lib/manifest.svg.js +68 -5
- package/lib/manifest.typography.css +37 -41
- package/lib/manifest.utilities.css +7 -1
- package/lib/manifest.utilities.js +9 -29
- package/package.json +4 -7
- package/lib/manifest.export.js +0 -509
- package/lib/manifest.virtual.js +0 -319
package/lib/manifest.css
CHANGED
|
@@ -2416,7 +2416,7 @@
|
|
|
2416
2416
|
|
|
2417
2417
|
@layer components {
|
|
2418
2418
|
|
|
2419
|
-
:where(
|
|
2419
|
+
:where(nav[popover]) {
|
|
2420
2420
|
inset-inline-start: auto;
|
|
2421
2421
|
inset-inline-end: 0;
|
|
2422
2422
|
width: fit-content;
|
|
@@ -2456,7 +2456,7 @@
|
|
|
2456
2456
|
}
|
|
2457
2457
|
}
|
|
2458
2458
|
|
|
2459
|
-
:where(
|
|
2459
|
+
:where(nav[popover].appear-start) {
|
|
2460
2460
|
inset-inline-start: 0;
|
|
2461
2461
|
inset-inline-end: auto;
|
|
2462
2462
|
|
|
@@ -2481,7 +2481,7 @@
|
|
|
2481
2481
|
}
|
|
2482
2482
|
|
|
2483
2483
|
/* Dark mode override */
|
|
2484
|
-
.dark :where(
|
|
2484
|
+
.dark :where(nav[popover]) {
|
|
2485
2485
|
background-color: var(--color-surface-1, oklch(98.17% 0.0005 95.87))
|
|
2486
2486
|
}
|
|
2487
2487
|
}
|
|
@@ -2951,18 +2951,10 @@
|
|
|
2951
2951
|
.h4,
|
|
2952
2952
|
.h5,
|
|
2953
2953
|
.h6 {
|
|
2954
|
-
font-weight:
|
|
2954
|
+
font-weight: 550;
|
|
2955
2955
|
word-wrap: break-word
|
|
2956
2956
|
}
|
|
2957
2957
|
|
|
2958
|
-
:where(h1, h2, h3, h4):not(.unstyle),
|
|
2959
|
-
.h1,
|
|
2960
|
-
.h2,
|
|
2961
|
-
.h3,
|
|
2962
|
-
.h4 {
|
|
2963
|
-
font-weight: 700;
|
|
2964
|
-
}
|
|
2965
|
-
|
|
2966
2958
|
:where(h1, h2, h3):not(.unstyle),
|
|
2967
2959
|
.h1,
|
|
2968
2960
|
.h2,
|
|
@@ -2972,18 +2964,18 @@
|
|
|
2972
2964
|
|
|
2973
2965
|
:where(h1):not(.unstyle),
|
|
2974
2966
|
.h1 {
|
|
2975
|
-
font-size:
|
|
2967
|
+
font-size: 3rem;
|
|
2976
2968
|
line-height: 1.25
|
|
2977
2969
|
}
|
|
2978
2970
|
|
|
2979
2971
|
:where(h2):not(.unstyle),
|
|
2980
2972
|
.h2 {
|
|
2981
|
-
font-size:
|
|
2973
|
+
font-size: 2.25rem
|
|
2982
2974
|
}
|
|
2983
2975
|
|
|
2984
2976
|
:where(h3):not(.unstyle),
|
|
2985
2977
|
.h3 {
|
|
2986
|
-
font-size: 1.
|
|
2978
|
+
font-size: 1.75rem;
|
|
2987
2979
|
line-height: 1.4
|
|
2988
2980
|
}
|
|
2989
2981
|
|
|
@@ -2994,18 +2986,21 @@
|
|
|
2994
2986
|
|
|
2995
2987
|
:where(h5):not(.unstyle),
|
|
2996
2988
|
.h5 {
|
|
2997
|
-
font-
|
|
2998
|
-
font-size: .875rem;
|
|
2989
|
+
font-size: 1rem;
|
|
2999
2990
|
line-height: 1rem;
|
|
3000
|
-
color: var(--color-content-neutral,
|
|
2991
|
+
color: var(--color-content-neutral, black);
|
|
2992
|
+
|
|
2993
|
+
& a:hover {
|
|
2994
|
+
color: var(--color-content-stark, black)
|
|
2995
|
+
}
|
|
3001
2996
|
}
|
|
3002
2997
|
|
|
3003
2998
|
:where(h6):not(.unstyle),
|
|
3004
2999
|
.h6 {
|
|
3005
|
-
font-
|
|
3000
|
+
font-family: var(--font-mono);
|
|
3006
3001
|
font-size: 0.8125rem;
|
|
3007
3002
|
line-height: 1.4;
|
|
3008
|
-
|
|
3003
|
+
color: var(--color-brand-content, black)
|
|
3009
3004
|
}
|
|
3010
3005
|
|
|
3011
3006
|
/* Paragraphs */
|
|
@@ -3016,18 +3011,15 @@
|
|
|
3016
3011
|
|
|
3017
3012
|
/* Links */
|
|
3018
3013
|
:where(a:not([role=button]), button.link):not(.unstyle) {
|
|
3019
|
-
text-
|
|
3020
|
-
text-
|
|
3014
|
+
text-align: unset;
|
|
3015
|
+
text-decoration: none;
|
|
3021
3016
|
cursor: pointer;
|
|
3022
|
-
transition: var(--transition, all .05s ease-in-out)
|
|
3023
|
-
|
|
3024
|
-
&:hover {
|
|
3025
|
-
color: var(--color-content-neutral, oklch(48.26% 0.0365 255.09))
|
|
3026
|
-
}
|
|
3017
|
+
transition: var(--transition, all .05s ease-in-out)
|
|
3018
|
+
}
|
|
3027
3019
|
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3020
|
+
:where(abbr, address, blockquote, code, del, figcaption, h1, h2, h3, h4, h5, h6, ins, legend, p, small, cite, q, .h1, .h2, .h3, .h4, .h5, .h6, .paragraph, .small, .caption, .label):not(.unstyle)>a {
|
|
3021
|
+
text-decoration: underline;
|
|
3022
|
+
text-underline-offset: 2px
|
|
3031
3023
|
}
|
|
3032
3024
|
|
|
3033
3025
|
/* Blockquotes */
|
|
@@ -3037,8 +3029,8 @@
|
|
|
3037
3029
|
max-width: 100%;
|
|
3038
3030
|
margin: calc(var(--spacing, 0.25rem) * 4) 0;
|
|
3039
3031
|
padding: 0 calc(var(--spacing, 0.25rem) * 4);
|
|
3040
|
-
color: var(--color-content-stark,
|
|
3041
|
-
border-inline-start: 0.25rem solid color-mix(in oklch, var(--color-content-stark,
|
|
3032
|
+
color: var(--color-content-stark, black);
|
|
3033
|
+
border-inline-start: 0.25rem solid color-mix(in oklch, var(--color-content-stark, black) 25%, transparent);
|
|
3042
3034
|
border-inline-end: none;
|
|
3043
3035
|
|
|
3044
3036
|
& * {
|
|
@@ -3051,12 +3043,13 @@
|
|
|
3051
3043
|
display: inline-block;
|
|
3052
3044
|
width: fit-content;
|
|
3053
3045
|
height: fit-content;
|
|
3054
|
-
padding: 0
|
|
3046
|
+
padding: 0 0.7ch;
|
|
3055
3047
|
font-size: 82.5%;
|
|
3048
|
+
line-height: 1.4;
|
|
3056
3049
|
word-wrap: break-word;
|
|
3057
|
-
color: var(--color-content-neutral,
|
|
3058
|
-
background-color: color-mix(in oklch, var(--color-content-neutral,
|
|
3059
|
-
border: 1px solid color-mix(in oklch, var(--color-content-subtle,
|
|
3050
|
+
color: var(--color-content-neutral, grey);
|
|
3051
|
+
background-color: color-mix(in oklch, var(--color-content-neutral, grey) 10%, transparent);
|
|
3052
|
+
border: 1px solid color-mix(in oklch, var(--color-content-subtle, silver) 10%, transparent);
|
|
3060
3053
|
border-radius: var(--radius, 0.5rem)
|
|
3061
3054
|
}
|
|
3062
3055
|
|
|
@@ -3064,11 +3057,10 @@
|
|
|
3064
3057
|
:where(figcaption):not(.unstyle),
|
|
3065
3058
|
.caption {
|
|
3066
3059
|
font-size: 0.8125rem;
|
|
3067
|
-
color: var(--color-content-neutral,
|
|
3060
|
+
color: var(--color-content-neutral, grey);
|
|
3068
3061
|
|
|
3069
|
-
& a {
|
|
3070
|
-
|
|
3071
|
-
color: inherit
|
|
3062
|
+
& a:hover {
|
|
3063
|
+
color: var(--color-content-stark, black)
|
|
3072
3064
|
}
|
|
3073
3065
|
}
|
|
3074
3066
|
|
|
@@ -3081,7 +3073,11 @@
|
|
|
3081
3073
|
:where(small):not(.unstyle),
|
|
3082
3074
|
.small {
|
|
3083
3075
|
font-size: 0.875rem;
|
|
3084
|
-
color: var(--color-content-neutral,
|
|
3076
|
+
color: var(--color-content-neutral, grey);
|
|
3077
|
+
|
|
3078
|
+
& a:hover {
|
|
3079
|
+
color: var(--color-content-stark, black)
|
|
3080
|
+
}
|
|
3085
3081
|
}
|
|
3086
3082
|
|
|
3087
3083
|
/* Icons */
|
|
@@ -3108,8 +3104,8 @@
|
|
|
3108
3104
|
font-weight: 600;
|
|
3109
3105
|
line-height: 1;
|
|
3110
3106
|
text-align: center;
|
|
3111
|
-
color: var(--color-content-neutral,
|
|
3112
|
-
background-color: color-mix(in oklch, var(--color-content-neutral,
|
|
3107
|
+
color: var(--color-content-neutral, grey);
|
|
3108
|
+
background-color: color-mix(in oklch, var(--color-content-neutral, grey) 10%, transparent);
|
|
3113
3109
|
border-radius: calc(var(--radius, 0.5rem) / 1.5);
|
|
3114
3110
|
|
|
3115
3111
|
&:not(:last-of-type) {
|
|
@@ -3152,7 +3148,7 @@
|
|
|
3152
3148
|
font-weight: 500;
|
|
3153
3149
|
font-size: 0.75rem;
|
|
3154
3150
|
line-height: 1;
|
|
3155
|
-
color: var(--color-field-inverse,
|
|
3151
|
+
color: var(--color-field-inverse, black);
|
|
3156
3152
|
background-color: var(--color-field-surface, oklch(91.79% 0.0029 264.26));
|
|
3157
3153
|
border-radius: 100px;
|
|
3158
3154
|
|
|
@@ -3473,7 +3469,7 @@
|
|
|
3473
3469
|
|
|
3474
3470
|
/* Prose styles for long-form content */
|
|
3475
3471
|
:where(.prose, .prose details) {
|
|
3476
|
-
width:
|
|
3472
|
+
width: 42rem;
|
|
3477
3473
|
max-width: 100%;
|
|
3478
3474
|
|
|
3479
3475
|
/* Asides inside a prose element are used as callouts */
|
|
@@ -3544,6 +3540,10 @@
|
|
|
3544
3540
|
}
|
|
3545
3541
|
}
|
|
3546
3542
|
|
|
3543
|
+
&>h1 {
|
|
3544
|
+
font-size: 2.25rem
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
3547
|
&>h1+p {
|
|
3548
3548
|
margin-top: 0.625rem;
|
|
3549
3549
|
font-size: 1.125rem;
|
|
@@ -3551,11 +3551,13 @@
|
|
|
3551
3551
|
}
|
|
3552
3552
|
|
|
3553
3553
|
&>h2 {
|
|
3554
|
+
font-size: 1.75rem;
|
|
3554
3555
|
margin-top: 1rem;
|
|
3555
3556
|
margin-bottom: calc(1rem * 0.6667)
|
|
3556
3557
|
}
|
|
3557
3558
|
|
|
3558
3559
|
&>h3 {
|
|
3560
|
+
font-size: 1.25rem;
|
|
3559
3561
|
margin-top: calc(1rem * 2.4)
|
|
3560
3562
|
}
|
|
3561
3563
|
|
package/lib/manifest.data.js
CHANGED
|
@@ -12,7 +12,9 @@ async function ensureManifest() {
|
|
|
12
12
|
try {
|
|
13
13
|
const manifestUrl = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
|
|
14
14
|
const response = await fetch(manifestUrl);
|
|
15
|
-
|
|
15
|
+
const manifest = await response.json();
|
|
16
|
+
interpolateManifest(manifest);
|
|
17
|
+
return manifest;
|
|
16
18
|
} catch (error) {
|
|
17
19
|
console.error('[Manifest Data] Failed to load manifest:', error);
|
|
18
20
|
return null;
|
|
@@ -36,6 +38,33 @@ function interpolateEnvVars(str) {
|
|
|
36
38
|
});
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
// Recursively walk a manifest object and interpolate every string value in
|
|
42
|
+
// place. Object keys are left untouched. Called once at manifest-load time so
|
|
43
|
+
// downstream consumers (auth, data, appwrite) read already-resolved values.
|
|
44
|
+
function interpolateManifest(obj) {
|
|
45
|
+
if (obj === null || typeof obj !== 'object') return obj;
|
|
46
|
+
if (Array.isArray(obj)) {
|
|
47
|
+
for (let i = 0; i < obj.length; i++) {
|
|
48
|
+
const v = obj[i];
|
|
49
|
+
if (typeof v === 'string') {
|
|
50
|
+
obj[i] = interpolateEnvVars(v);
|
|
51
|
+
} else if (v !== null && typeof v === 'object') {
|
|
52
|
+
interpolateManifest(v);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return obj;
|
|
56
|
+
}
|
|
57
|
+
for (const key of Object.keys(obj)) {
|
|
58
|
+
const v = obj[key];
|
|
59
|
+
if (typeof v === 'string') {
|
|
60
|
+
obj[key] = interpolateEnvVars(v);
|
|
61
|
+
} else if (v !== null && typeof v === 'object') {
|
|
62
|
+
interpolateManifest(v);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return obj;
|
|
66
|
+
}
|
|
67
|
+
|
|
39
68
|
// Helper to get nested value from object
|
|
40
69
|
function getNestedValue(obj, path) {
|
|
41
70
|
return path.split('.').reduce((current, key) => {
|
|
@@ -179,6 +208,7 @@ function getQueries(dataSource) {
|
|
|
179
208
|
window.ManifestDataConfig = {
|
|
180
209
|
ensureManifest,
|
|
181
210
|
interpolateEnvVars,
|
|
211
|
+
interpolateManifest,
|
|
182
212
|
getNestedValue,
|
|
183
213
|
getDefaultLocale,
|
|
184
214
|
parseContentPath,
|
|
@@ -1202,6 +1232,13 @@ window.ManifestDataStore = {
|
|
|
1202
1232
|
|
|
1203
1233
|
/* Manifest Data Sources - File Loaders */
|
|
1204
1234
|
|
|
1235
|
+
// Key names that would walk into Object's prototype chain if used as nested-
|
|
1236
|
+
// path segments. Rejecting them in setNestedValue and deepMergeWithFallback
|
|
1237
|
+
// prevents a CSV row like `__proto__.polluted, true` (or a malicious JSON
|
|
1238
|
+
// locale file containing `{"__proto__": {...}}`) from polluting
|
|
1239
|
+
// Object.prototype and silently affecting every plain object on the page.
|
|
1240
|
+
const POLLUTING_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
1241
|
+
|
|
1205
1242
|
// Dynamic js-yaml loader
|
|
1206
1243
|
let jsyaml = null;
|
|
1207
1244
|
let yamlLoadingPromise = null;
|
|
@@ -1336,6 +1373,7 @@ function deepMergeWithFallback(currentData, fallbackData) {
|
|
|
1336
1373
|
!Array.isArray(currentData) && !Array.isArray(fallbackData)) {
|
|
1337
1374
|
const merged = { ...fallbackData };
|
|
1338
1375
|
for (const key in currentData) {
|
|
1376
|
+
if (POLLUTING_KEYS.has(key)) continue;
|
|
1339
1377
|
if (key.startsWith('_')) {
|
|
1340
1378
|
// Preserve metadata from current locale
|
|
1341
1379
|
merged[key] = currentData[key];
|
|
@@ -1363,6 +1401,9 @@ function deepMergeWithFallback(currentData, fallbackData) {
|
|
|
1363
1401
|
// Numeric path segments (e.g. cards.0.title) create real arrays so x-for="card in $x....cards" works.
|
|
1364
1402
|
function setNestedValue(obj, path, value) {
|
|
1365
1403
|
const keys = path.split('.');
|
|
1404
|
+
// Drop the whole row if any segment would walk into the prototype chain.
|
|
1405
|
+
// `foo.constructor.prototype.polluted` is just as dangerous as `__proto__.polluted`.
|
|
1406
|
+
if (keys.some(k => POLLUTING_KEYS.has(k))) return;
|
|
1366
1407
|
let current = obj;
|
|
1367
1408
|
|
|
1368
1409
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest.appwrite.auth.js": "sha384-to37ssZJXGeOS6+rf2VI47ox2mEqgsi5oQ1E5vv8XU/lDspbDFE1KHEMm8TxBhxW",
|
|
3
|
+
"manifest.appwrite.data.js": "sha384-00ulLT+GAIuPHA/rRT9p98vYlsyDzkyKXtg86BDQ6FGQa5vVVN+W6kuforniBAsz",
|
|
4
|
+
"manifest.appwrite.presence.js": "sha384-uxRpx9/Jj0kGtklH5QmUlAzD3zdSvFRfK6bcJQqxl+Bsf5tOo4zgwqJTQgtZoHQP",
|
|
5
|
+
"manifest.code.js": "sha384-e6s5v78qYi7GgrXqXRAtWerh7g5f2m9k8bPZnxk+CeBWXOImWvZFWpT7uZUcWHqT",
|
|
6
|
+
"manifest.color.js": "sha384-Z9G/lzt0vVMxjz4wkPuGG1X9mmQAJR15aOoGX3ephf7r2wnlUWet5GLgkUMtT4vt",
|
|
7
|
+
"manifest.colorpicker.js": "sha384-0EVn+Ha06h7FIvOxc6WjZYnKYXzi+zba08yKvczSEGTRkWRxyKN2TFrZHI1SDCXu",
|
|
8
|
+
"manifest.colors.js": "sha384-u8iD6kapVj4OjeCILxBkYQKgXtDQ7LdEodILkQuknzPMwzSMBmDHN25UuzxepHby",
|
|
9
|
+
"manifest.components.js": "sha384-3dCTD5EwCZTiX+1obYtDNM3WWwPh2JDQUQQsdRUUK3gs6FXjse1ShkKaT/2jsNaI",
|
|
10
|
+
"manifest.data.js": "sha384-dLvmRs4rC/s3tg4lePwJgKb1Q/cAKc7uFTjHzpusnQgcnfXahmGdAW4g5MOF2V8M",
|
|
11
|
+
"manifest.dropdowns.js": "sha384-WMrFoSpKfJuo81dyrwhVrDO8rq+rDwh2x8x4nH01BY5ZHkvjE+/SaT2gWCI0zOn+",
|
|
12
|
+
"manifest.icons.js": "sha384-uOkboYrovjCpl22eey3Jaxpey+pOnot5NDnRRumcRxiR7IOVaRh1i20gYnWXR5dW",
|
|
13
|
+
"manifest.localization.js": "sha384-eKdBIMEAwsugPP2p2fuPzQUkU44f1+Y0JgukMJ1KXLQY1/AYvpcGsEiritVDElsN",
|
|
14
|
+
"manifest.markdown.js": "sha384-ykEREUXsGW26uQcPfTy05lXjaBdNPduUsiTOsr9btQ4NiOy5dMgEk2fMD669p9Pn",
|
|
15
|
+
"manifest.resize.js": "sha384-Ak5gf44ERfh9pOSAD1qZzJSysslpwBCkevIlz7R3dszTUyzUKGKGF4pn5arOtgG0",
|
|
16
|
+
"manifest.router.js": "sha384-n6xmIfWnYzd/0kkVTFuHhFzHuxiDgZ1Lg1W0yB6/w3Myw5pQ6PgE6SJBHfVsO7/D",
|
|
17
|
+
"manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
|
|
18
|
+
"manifest.svg.js": "sha384-gulBJnHAlmmBo9nIxMNO+4rjF6Xdvrv8la8PPYAC5vOA2uAou/twI6rXrp+vOKIC",
|
|
19
|
+
"manifest.tabs.js": "sha384-v6Ti0zHfdLhkFHbTMg0FH6uMrThuBvZrL2PQgVBeeXhDjuN7x4MtoNWogPbAQTaD",
|
|
20
|
+
"manifest.tailwind.js": "sha384-aHLvl2oSuUgy06VaBqhhByn5wWxqvnqxw6KCwehakKUS00F/s/Nb62umeASS6Y4P",
|
|
21
|
+
"manifest.toasts.js": "sha384-ytd5rDbax/Ou9z23uedFXPZbxDPsk2E/pxCTq4WLvfv+os1qTI6kELp0kPp07g24",
|
|
22
|
+
"manifest.tooltips.js": "sha384-ppK+/G3UH8/J1Dn48comIwXcPjVVKiCNEJr/L1B+6eZQr2DLoBohwbIUR7w40jZf",
|
|
23
|
+
"manifest.url.parameters.js": "sha384-FIufiClqDx1rJpU/QUc9z/D43qClQ6Qm8rBahipbJl9BDHUvhrOsUDegmTWW7Tuf",
|
|
24
|
+
"manifest.utilities.js": "sha384-Q98oZClq/iRKFmuwHolisLgEitsTZiEPHxUW29liKlnL1Gx+YGq8MMivYbDlGDD6",
|
|
25
|
+
"manifest.js": "sha384-KyKrxpjCNpSBv30BPYliAWOonAQ6cKW+IHF6+twjmyiGGx6UNV9YVDqGZmjtr/i/"
|
|
26
|
+
}
|
package/lib/manifest.js
CHANGED
|
@@ -175,6 +175,41 @@
|
|
|
175
175
|
const DEFAULT_VERSION = 'latest';
|
|
176
176
|
const ALPINE_CDN_URL = 'https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js';
|
|
177
177
|
|
|
178
|
+
// SRI integrity map: { 'manifest.foo.min.js': 'sha384-...', ... }
|
|
179
|
+
// Inlined by build.mjs's emitIntegrityMap() step. Empty in source so the
|
|
180
|
+
// unbuilt loader works in dev (where files are served from the local
|
|
181
|
+
// project, same-origin, no SRI needed). Built artifact in lib/ carries
|
|
182
|
+
// the populated map. Looked up by the filename suffix of every script URL
|
|
183
|
+
// addScript() injects — when the file is in the map, the matching
|
|
184
|
+
// integrity + crossorigin attributes are set, and the browser refuses to
|
|
185
|
+
// execute the script if the bytes don't match (defends against CDN
|
|
186
|
+
// poisoning / npm hijack).
|
|
187
|
+
const INTEGRITY = {
|
|
188
|
+
"manifest.appwrite.auth.js": "sha384-to37ssZJXGeOS6+rf2VI47ox2mEqgsi5oQ1E5vv8XU/lDspbDFE1KHEMm8TxBhxW",
|
|
189
|
+
"manifest.appwrite.data.js": "sha384-00ulLT+GAIuPHA/rRT9p98vYlsyDzkyKXtg86BDQ6FGQa5vVVN+W6kuforniBAsz",
|
|
190
|
+
"manifest.appwrite.presence.js": "sha384-uxRpx9/Jj0kGtklH5QmUlAzD3zdSvFRfK6bcJQqxl+Bsf5tOo4zgwqJTQgtZoHQP",
|
|
191
|
+
"manifest.code.js": "sha384-e6s5v78qYi7GgrXqXRAtWerh7g5f2m9k8bPZnxk+CeBWXOImWvZFWpT7uZUcWHqT",
|
|
192
|
+
"manifest.color.js": "sha384-Z9G/lzt0vVMxjz4wkPuGG1X9mmQAJR15aOoGX3ephf7r2wnlUWet5GLgkUMtT4vt",
|
|
193
|
+
"manifest.colorpicker.js": "sha384-0EVn+Ha06h7FIvOxc6WjZYnKYXzi+zba08yKvczSEGTRkWRxyKN2TFrZHI1SDCXu",
|
|
194
|
+
"manifest.colors.js": "sha384-u8iD6kapVj4OjeCILxBkYQKgXtDQ7LdEodILkQuknzPMwzSMBmDHN25UuzxepHby",
|
|
195
|
+
"manifest.components.js": "sha384-3dCTD5EwCZTiX+1obYtDNM3WWwPh2JDQUQQsdRUUK3gs6FXjse1ShkKaT/2jsNaI",
|
|
196
|
+
"manifest.data.js": "sha384-dLvmRs4rC/s3tg4lePwJgKb1Q/cAKc7uFTjHzpusnQgcnfXahmGdAW4g5MOF2V8M",
|
|
197
|
+
"manifest.dropdowns.js": "sha384-WMrFoSpKfJuo81dyrwhVrDO8rq+rDwh2x8x4nH01BY5ZHkvjE+/SaT2gWCI0zOn+",
|
|
198
|
+
"manifest.icons.js": "sha384-uOkboYrovjCpl22eey3Jaxpey+pOnot5NDnRRumcRxiR7IOVaRh1i20gYnWXR5dW",
|
|
199
|
+
"manifest.localization.js": "sha384-eKdBIMEAwsugPP2p2fuPzQUkU44f1+Y0JgukMJ1KXLQY1/AYvpcGsEiritVDElsN",
|
|
200
|
+
"manifest.markdown.js": "sha384-ykEREUXsGW26uQcPfTy05lXjaBdNPduUsiTOsr9btQ4NiOy5dMgEk2fMD669p9Pn",
|
|
201
|
+
"manifest.resize.js": "sha384-Ak5gf44ERfh9pOSAD1qZzJSysslpwBCkevIlz7R3dszTUyzUKGKGF4pn5arOtgG0",
|
|
202
|
+
"manifest.router.js": "sha384-n6xmIfWnYzd/0kkVTFuHhFzHuxiDgZ1Lg1W0yB6/w3Myw5pQ6PgE6SJBHfVsO7/D",
|
|
203
|
+
"manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
|
|
204
|
+
"manifest.svg.js": "sha384-gulBJnHAlmmBo9nIxMNO+4rjF6Xdvrv8la8PPYAC5vOA2uAou/twI6rXrp+vOKIC",
|
|
205
|
+
"manifest.tabs.js": "sha384-v6Ti0zHfdLhkFHbTMg0FH6uMrThuBvZrL2PQgVBeeXhDjuN7x4MtoNWogPbAQTaD",
|
|
206
|
+
"manifest.tailwind.js": "sha384-aHLvl2oSuUgy06VaBqhhByn5wWxqvnqxw6KCwehakKUS00F/s/Nb62umeASS6Y4P",
|
|
207
|
+
"manifest.toasts.js": "sha384-ytd5rDbax/Ou9z23uedFXPZbxDPsk2E/pxCTq4WLvfv+os1qTI6kELp0kPp07g24",
|
|
208
|
+
"manifest.tooltips.js": "sha384-ppK+/G3UH8/J1Dn48comIwXcPjVVKiCNEJr/L1B+6eZQr2DLoBohwbIUR7w40jZf",
|
|
209
|
+
"manifest.url.parameters.js": "sha384-FIufiClqDx1rJpU/QUc9z/D43qClQ6Qm8rBahipbJl9BDHUvhrOsUDegmTWW7Tuf",
|
|
210
|
+
"manifest.utilities.js": "sha384-Q98oZClq/iRKFmuwHolisLgEitsTZiEPHxUW29liKlnL1Gx+YGq8MMivYbDlGDD6"
|
|
211
|
+
};
|
|
212
|
+
|
|
178
213
|
// Get base URL for a given version
|
|
179
214
|
function getBaseUrl(version = DEFAULT_VERSION) {
|
|
180
215
|
return `https://cdn.jsdelivr.net/npm/mnfst@${version}/lib`;
|
|
@@ -199,9 +234,7 @@
|
|
|
199
234
|
'slides',
|
|
200
235
|
'resize',
|
|
201
236
|
'colorpicker',
|
|
202
|
-
'url-parameters'
|
|
203
|
-
'virtual',
|
|
204
|
-
'export'
|
|
237
|
+
'url-parameters'
|
|
205
238
|
];
|
|
206
239
|
|
|
207
240
|
// Appwrite integration plugins (opt-in only, never auto-loaded)
|
|
@@ -211,10 +244,16 @@
|
|
|
211
244
|
'appwrite-presence'
|
|
212
245
|
];
|
|
213
246
|
|
|
214
|
-
// Plugin dependencies: plugins that require other plugins to be loaded first
|
|
247
|
+
// Plugin dependencies: plugins that require other plugins to be loaded first.
|
|
248
|
+
// All Appwrite plugins depend on `data` for env-var interpolation at
|
|
249
|
+
// manifest-load time (window.ManifestDataConfig.interpolateManifest); auth
|
|
250
|
+
// and presence also share the data plugin's manifest-fetch plumbing.
|
|
251
|
+
// Localization reads data-source URLs that may carry ${VAR} references.
|
|
215
252
|
const PLUGIN_DEPENDENCIES = {
|
|
253
|
+
'appwrite-auth': ['data'],
|
|
216
254
|
'appwrite-data': ['data'],
|
|
217
|
-
'appwrite-presence': ['data']
|
|
255
|
+
'appwrite-presence': ['data'],
|
|
256
|
+
'localization': ['data']
|
|
218
257
|
};
|
|
219
258
|
|
|
220
259
|
// Derive default plugin list from manifest (only load data/localization/components when manifest needs them)
|
|
@@ -315,6 +354,15 @@
|
|
|
315
354
|
const script = document.createElement('script');
|
|
316
355
|
script.src = url;
|
|
317
356
|
script.async = false; // Ensure scripts execute in order
|
|
357
|
+
// Apply SRI when the file is in the inlined integrity map. The map
|
|
358
|
+
// keys files by basename, so it works whether the URL points at
|
|
359
|
+
// jsDelivr or a user-configured pluginBase mirror — as long as the
|
|
360
|
+
// served bytes match what this loader version was built against.
|
|
361
|
+
const fileName = url.split('/').pop().split('?')[0];
|
|
362
|
+
if (INTEGRITY[fileName]) {
|
|
363
|
+
script.integrity = INTEGRITY[fileName];
|
|
364
|
+
script.crossOrigin = 'anonymous';
|
|
365
|
+
}
|
|
318
366
|
script.onload = () => resolve();
|
|
319
367
|
script.onerror = () => reject(new Error(`Failed to load ${pluginName} from ${url}`));
|
|
320
368
|
document.head.appendChild(script);
|
|
@@ -522,6 +570,13 @@
|
|
|
522
570
|
manifest = await manifestPromise;
|
|
523
571
|
}
|
|
524
572
|
if (manifest && typeof window !== 'undefined') {
|
|
573
|
+
// Resolve ${VAR} env-var references once, at the canonical load
|
|
574
|
+
// point, so downstream plugins (auth, data, appwrite) read
|
|
575
|
+
// already-interpolated values instead of each handling env-var
|
|
576
|
+
// substitution themselves.
|
|
577
|
+
if (window.ManifestDataConfig?.interpolateManifest) {
|
|
578
|
+
window.ManifestDataConfig.interpolateManifest(manifest);
|
|
579
|
+
}
|
|
525
580
|
window.__manifestLoaded = manifest;
|
|
526
581
|
if (window.ManifestComponentsRegistry) {
|
|
527
582
|
window.ManifestComponentsRegistry.manifest = manifest;
|
package/lib/manifest.markdown.js
CHANGED
|
@@ -17,6 +17,68 @@ if (typeof window !== 'undefined') {
|
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// Cache for DOMPurify loading (only fetched when the .safe modifier is used)
|
|
21
|
+
let purifyPromise = null;
|
|
22
|
+
|
|
23
|
+
// DOMPurify config tuned for Manifest's markdown output. The markdown
|
|
24
|
+
// extensions emit <x-code>, <x-icon>, and other x-* custom elements that must
|
|
25
|
+
// survive sanitization, so custom-element handling is enabled with a
|
|
26
|
+
// tag-name allowlist (x-*) and an attribute filter that rejects event
|
|
27
|
+
// handlers (on*). DOMPurify's defaults handle <script>, javascript: URLs,
|
|
28
|
+
// srcdoc, and the usual XSS vectors for standard HTML tags.
|
|
29
|
+
const MARKDOWN_PURIFY_CONFIG = {
|
|
30
|
+
CUSTOM_ELEMENT_HANDLING: {
|
|
31
|
+
tagNameCheck: /^x-[a-z][\w-]*$/,
|
|
32
|
+
attributeNameCheck: /^(?!on)[a-z][\w\-:]*$/i,
|
|
33
|
+
allowCustomizedBuiltInElements: false
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
async function loadDOMPurify() {
|
|
38
|
+
if (typeof window.DOMPurify !== 'undefined') return window.DOMPurify;
|
|
39
|
+
if (purifyPromise) return purifyPromise;
|
|
40
|
+
purifyPromise = new Promise((resolve, reject) => {
|
|
41
|
+
const script = document.createElement('script');
|
|
42
|
+
script.src = 'https://cdn.jsdelivr.net/npm/dompurify@latest/dist/purify.min.js';
|
|
43
|
+
script.onload = () => {
|
|
44
|
+
if (typeof window.DOMPurify !== 'undefined') {
|
|
45
|
+
resolve(window.DOMPurify);
|
|
46
|
+
} else {
|
|
47
|
+
console.error('[Manifest Markdown] DOMPurify failed to load — DOMPurify is undefined');
|
|
48
|
+
purifyPromise = null;
|
|
49
|
+
reject(new Error('DOMPurify failed to load'));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
script.onerror = (err) => {
|
|
53
|
+
console.error('[Manifest Markdown] DOMPurify script failed to load:', err);
|
|
54
|
+
purifyPromise = null;
|
|
55
|
+
reject(err);
|
|
56
|
+
};
|
|
57
|
+
document.head.appendChild(script);
|
|
58
|
+
});
|
|
59
|
+
return purifyPromise;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Sanitize HTML if the .safe modifier was used; pass-through otherwise.
|
|
63
|
+
// Manifest's default is unsanitized so authors can render arbitrary HTML and
|
|
64
|
+
// the markdown custom-element extensions work — but the .safe opt-in lets
|
|
65
|
+
// authors render data-source content (e.g. user-submitted markdown from
|
|
66
|
+
// Appwrite) without an XSS sink.
|
|
67
|
+
async function maybeSanitizeMarkdownHtml(html, safe) {
|
|
68
|
+
if (!safe) return html;
|
|
69
|
+
try {
|
|
70
|
+
const DOMPurify = await loadDOMPurify();
|
|
71
|
+
return DOMPurify.sanitize(html, MARKDOWN_PURIFY_CONFIG);
|
|
72
|
+
} catch {
|
|
73
|
+
// Loader failure — fall back to escaping rather than silently emitting
|
|
74
|
+
// un-sanitized HTML. The author asked for safe; honour that.
|
|
75
|
+
const escaped = String(html)
|
|
76
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
77
|
+
console.warn('[Manifest Markdown] x-markdown.safe: DOMPurify unavailable — emitting escaped text.');
|
|
78
|
+
return escaped;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
20
82
|
// Load marked.js from CDN
|
|
21
83
|
async function loadMarkedJS() {
|
|
22
84
|
if (typeof marked !== 'undefined') {
|
|
@@ -52,6 +114,18 @@ async function loadMarkedJS() {
|
|
|
52
114
|
return markedPromise;
|
|
53
115
|
}
|
|
54
116
|
|
|
117
|
+
// HTML-escape a string for safe interpolation inside an attribute value.
|
|
118
|
+
// Used by the code-fence renderer below — title/language strings come from
|
|
119
|
+
// the markdown source, so without escaping a fence like ```js " onclick=alert(1) x="
|
|
120
|
+
// could inject arbitrary attributes onto the <x-code> element.
|
|
121
|
+
function escapeForAttribute(s) {
|
|
122
|
+
return String(s)
|
|
123
|
+
.replace(/&/g, '&')
|
|
124
|
+
.replace(/"/g, '"')
|
|
125
|
+
.replace(/</g, '<')
|
|
126
|
+
.replace(/>/g, '>');
|
|
127
|
+
}
|
|
128
|
+
|
|
55
129
|
// Configure marked to preserve full language strings
|
|
56
130
|
async function configureMarked(marked) {
|
|
57
131
|
marked.use({
|
|
@@ -67,10 +141,10 @@ async function configureMarked(marked) {
|
|
|
67
141
|
// Build attributes for the x-code element
|
|
68
142
|
let xCodeAttributes = '';
|
|
69
143
|
if (attributes.title) {
|
|
70
|
-
xCodeAttributes += ` name="${attributes.title}"`;
|
|
144
|
+
xCodeAttributes += ` name="${escapeForAttribute(attributes.title)}"`;
|
|
71
145
|
}
|
|
72
146
|
if (attributes.language) {
|
|
73
|
-
xCodeAttributes += ` language="${attributes.language}"`;
|
|
147
|
+
xCodeAttributes += ` language="${escapeForAttribute(attributes.language)}"`;
|
|
74
148
|
}
|
|
75
149
|
if (attributes.numbers) {
|
|
76
150
|
xCodeAttributes += ' numbers';
|
|
@@ -158,7 +232,7 @@ async function configureMarked(marked) {
|
|
|
158
232
|
parsedContent = marked.parse(token.text);
|
|
159
233
|
}
|
|
160
234
|
|
|
161
|
-
const iconHtml = iconValue ? `<span x-icon="${iconValue}"></span>` : '';
|
|
235
|
+
const iconHtml = iconValue ? `<span x-icon="${escapeForAttribute(iconValue)}"></span>` : '';
|
|
162
236
|
|
|
163
237
|
// Create a temporary div to count top-level elements
|
|
164
238
|
const temp = document.createElement('div');
|
|
@@ -337,6 +411,15 @@ async function initializeMarkdownPlugin() {
|
|
|
337
411
|
return;
|
|
338
412
|
}
|
|
339
413
|
|
|
414
|
+
// Opt-in sanitization. When `.safe` is on the directive
|
|
415
|
+
// (`x-markdown.safe="$x.user.bio"`), parsed HTML is run through
|
|
416
|
+
// DOMPurify before injection. Default is unsanitized — Manifest's
|
|
417
|
+
// design lets authors render raw HTML and custom-element extensions
|
|
418
|
+
// (x-code, x-icon, callouts) freely. Use .safe when the markdown
|
|
419
|
+
// source can contain content from untrusted parties (Appwrite
|
|
420
|
+
// collections, API responses, crowdsourced translations, etc.).
|
|
421
|
+
const safe = Array.isArray(modifiers) && modifiers.includes('safe');
|
|
422
|
+
|
|
340
423
|
// Prerender idempotency: if the page is a prerendered MPA and this
|
|
341
424
|
// element already has rendered HTML children, the content was baked
|
|
342
425
|
// at build time and is authoritative for SEO + no-JS users. Skip
|
|
@@ -394,6 +477,9 @@ async function initializeMarkdownPlugin() {
|
|
|
394
477
|
// Post-process HTML to enable checkboxes (remove disabled attribute)
|
|
395
478
|
html = enableCheckboxes(html);
|
|
396
479
|
|
|
480
|
+
// Apply opt-in DOMPurify sanitization for x-markdown.safe
|
|
481
|
+
html = await maybeSanitizeMarkdownHtml(html, safe);
|
|
482
|
+
|
|
397
483
|
// Only update if content has changed and isn't empty
|
|
398
484
|
if (element.innerHTML !== html && html.trim() !== '') {
|
|
399
485
|
// Create a temporary container to hold the HTML
|
|
@@ -567,6 +653,9 @@ async function initializeMarkdownPlugin() {
|
|
|
567
653
|
// Post-process HTML to enable checkboxes (remove disabled attribute)
|
|
568
654
|
html = enableCheckboxes(html);
|
|
569
655
|
|
|
656
|
+
// Apply opt-in DOMPurify sanitization for x-markdown.safe
|
|
657
|
+
html = await maybeSanitizeMarkdownHtml(html, safe);
|
|
658
|
+
|
|
570
659
|
// Only update DOM if HTML actually changed
|
|
571
660
|
if (el.innerHTML !== html) {
|
|
572
661
|
// Create temporary container
|
|
@@ -649,6 +738,9 @@ async function initializeMarkdownPlugin() {
|
|
|
649
738
|
// Post-process HTML to enable checkboxes (remove disabled attribute)
|
|
650
739
|
html = html.replace(/<input type="checkbox"([^>]*?)disabled([^>]*?)>/g, '<input type="checkbox"$1$2>');
|
|
651
740
|
|
|
741
|
+
// Apply opt-in DOMPurify sanitization for x-markdown.safe
|
|
742
|
+
html = await maybeSanitizeMarkdownHtml(html, safe);
|
|
743
|
+
|
|
652
744
|
// Create temporary container
|
|
653
745
|
const temp = document.createElement('div');
|
|
654
746
|
temp.innerHTML = html;
|