mnfst 0.5.165 → 0.5.167
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 +69 -17
- package/lib/manifest.chat.js +270 -7
- package/lib/manifest.combobox.css +18 -1
- package/lib/manifest.css +231 -3
- package/lib/manifest.input.css +2 -2
- package/lib/manifest.integrity.json +3 -3
- package/lib/manifest.js +98 -49
- package/lib/manifest.min.css +1 -1
- package/package.json +2 -2
package/lib/manifest.css
CHANGED
|
@@ -945,6 +945,217 @@
|
|
|
945
945
|
}
|
|
946
946
|
}
|
|
947
947
|
|
|
948
|
+
/* Manifest Chat */
|
|
949
|
+
|
|
950
|
+
@layer utilities {
|
|
951
|
+
|
|
952
|
+
:where(.chat-wrapper) {
|
|
953
|
+
display: flex;
|
|
954
|
+
flex-direction: column;
|
|
955
|
+
gap: 1rem;
|
|
956
|
+
width: 100%;
|
|
957
|
+
height: 100%;
|
|
958
|
+
box-shadow: var(--shadow);
|
|
959
|
+
overflow: hidden;
|
|
960
|
+
|
|
961
|
+
/* Chat log scroll container. column-reverse pins the view to the
|
|
962
|
+
bottom natively: new messages and a growing composer never hide
|
|
963
|
+
the latest message, and reading history isn't yanked on updates. */
|
|
964
|
+
:where([role="log"]) {
|
|
965
|
+
display: flex;
|
|
966
|
+
flex-direction: column-reverse;
|
|
967
|
+
flex: 1;
|
|
968
|
+
font-size: 0.875rem;
|
|
969
|
+
overflow-y: auto;
|
|
970
|
+
|
|
971
|
+
/* Message column */
|
|
972
|
+
> :where(div) {
|
|
973
|
+
display: flex;
|
|
974
|
+
flex-direction: column;
|
|
975
|
+
justify-content: flex-end;
|
|
976
|
+
gap: 2rem;
|
|
977
|
+
min-height: 100%;
|
|
978
|
+
width: calc(var(--spacing-content-width, 74rem) - 4rem);
|
|
979
|
+
max-width: 100%;
|
|
980
|
+
margin-inline-start: auto;
|
|
981
|
+
margin-inline-end: auto;
|
|
982
|
+
padding-inline-start: 1rem;
|
|
983
|
+
padding-inline-end: 1rem;
|
|
984
|
+
padding-block-start: 1.5rem;
|
|
985
|
+
padding-block-end: 5rem;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/* Messages */
|
|
989
|
+
:where(div div) {
|
|
990
|
+
|
|
991
|
+
/* Markdown spacing */
|
|
992
|
+
:where(p) {
|
|
993
|
+
margin-block: 0.5rem;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
:where(p:first-child) {
|
|
997
|
+
margin-block-start: 0;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
:where(p:last-child) {
|
|
1001
|
+
margin-block-end: 0;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/* Own messages */
|
|
1006
|
+
:where([data-own]) {
|
|
1007
|
+
width: fit-content;
|
|
1008
|
+
max-width: calc(var(--spacing-content-width, 74rem) - 10rem);
|
|
1009
|
+
margin-inline-start: auto;
|
|
1010
|
+
padding-block: 0.5rem;
|
|
1011
|
+
padding-inline: 0.75rem;
|
|
1012
|
+
background: color-mix(in oklch, var(--color-surface-2) 50%, transparent);
|
|
1013
|
+
border-radius: var(--radius);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/* Attachments */
|
|
1017
|
+
:where(img, video) {
|
|
1018
|
+
display: block;
|
|
1019
|
+
max-width: 20rem;
|
|
1020
|
+
max-height: 15rem;
|
|
1021
|
+
margin-block: 0.5rem;
|
|
1022
|
+
border-radius: var(--radius);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
:where(audio) {
|
|
1026
|
+
display: block;
|
|
1027
|
+
width: 18rem;
|
|
1028
|
+
max-width: 100%;
|
|
1029
|
+
margin-block: 0.5rem;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
:where(a[download]) {
|
|
1033
|
+
display: inline-flex;
|
|
1034
|
+
align-items: center;
|
|
1035
|
+
gap: 0.4rem;
|
|
1036
|
+
margin-block: 0.5rem;
|
|
1037
|
+
padding-block: 0.35rem;
|
|
1038
|
+
padding-inline: 0.6rem;
|
|
1039
|
+
background: var(--color-surface-2);
|
|
1040
|
+
border-radius: var(--radius);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/* Chat input */
|
|
1045
|
+
:where(form) {
|
|
1046
|
+
position: relative;
|
|
1047
|
+
width: var(--spacing-content-width, 74rem);
|
|
1048
|
+
max-width: 100%;
|
|
1049
|
+
margin-inline-start: auto;
|
|
1050
|
+
margin-inline-end: auto;
|
|
1051
|
+
|
|
1052
|
+
:where(textarea) {
|
|
1053
|
+
padding-inline-end: 2.5rem;
|
|
1054
|
+
field-sizing: content;
|
|
1055
|
+
resize: none;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
&:has(button[data-action="attach"]) :where(textarea) {
|
|
1059
|
+
padding-inline-start: 2.5rem;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/* Submit button */
|
|
1063
|
+
:where(button[type="submit"]) {
|
|
1064
|
+
position: absolute;
|
|
1065
|
+
bottom: var(--spacing-field-padding, 0.625rem);
|
|
1066
|
+
inset-inline-end: calc(var(--spacing-field-padding, 0.625rem) * 1.3);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/* Toolbar buttons (data-action) — attach, formatting, voice, … */
|
|
1070
|
+
:where(button[data-action="attach"]) {
|
|
1071
|
+
position: absolute;
|
|
1072
|
+
bottom: var(--spacing-field-padding, 0.625rem);
|
|
1073
|
+
inset-inline-start: calc(var(--spacing-field-padding, 0.625rem) * 1.3);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
/* Composer variant — the form itself is the field (.textarea):
|
|
1077
|
+
[ attachment thumbnails ]
|
|
1078
|
+
[ attach | growing textarea | send ] */
|
|
1079
|
+
&:where(.textarea) {
|
|
1080
|
+
display: grid;
|
|
1081
|
+
grid-template-columns: auto 1fr auto;
|
|
1082
|
+
align-items: end;
|
|
1083
|
+
gap: 0.25rem;
|
|
1084
|
+
padding: 0.375rem;
|
|
1085
|
+
|
|
1086
|
+
/* Buttons sit in-flow at the row's bottom corners */
|
|
1087
|
+
:where(button[type="submit"], button[data-action]) {
|
|
1088
|
+
position: static;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/* The inner textarea is a bare well inside the field */
|
|
1092
|
+
:where(textarea) {
|
|
1093
|
+
padding-inline: 0.25rem;
|
|
1094
|
+
padding-block: 0.45rem;
|
|
1095
|
+
|
|
1096
|
+
&,
|
|
1097
|
+
&:hover,
|
|
1098
|
+
&:active,
|
|
1099
|
+
&:focus-visible {
|
|
1100
|
+
background: transparent;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/* Attachment thumbnails — removable, X-scrollable */
|
|
1105
|
+
:where(menu) {
|
|
1106
|
+
grid-column: 1 / -1;
|
|
1107
|
+
display: flex;
|
|
1108
|
+
gap: 0.5rem;
|
|
1109
|
+
margin: 0;
|
|
1110
|
+
padding: 0.375rem 0.25rem 0.125rem;
|
|
1111
|
+
list-style: none;
|
|
1112
|
+
overflow-x: auto;
|
|
1113
|
+
|
|
1114
|
+
:where(li) {
|
|
1115
|
+
position: relative;
|
|
1116
|
+
flex: 0 0 auto;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
:where(img) {
|
|
1120
|
+
display: block;
|
|
1121
|
+
width: 3.5rem;
|
|
1122
|
+
height: 3.5rem;
|
|
1123
|
+
object-fit: cover;
|
|
1124
|
+
border-radius: calc(var(--radius, 0.5rem) * 0.75);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/* Non-image attachments — file chip */
|
|
1128
|
+
:where(li > span) {
|
|
1129
|
+
display: flex;
|
|
1130
|
+
align-items: center;
|
|
1131
|
+
gap: 0.3rem;
|
|
1132
|
+
height: 3.5rem;
|
|
1133
|
+
padding-inline: 0.6rem;
|
|
1134
|
+
font-size: 0.75rem;
|
|
1135
|
+
white-space: nowrap;
|
|
1136
|
+
background: var(--color-surface-2);
|
|
1137
|
+
border-radius: calc(var(--radius, 0.5rem) * 0.75);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/* Remove button — revealed on hover/focus */
|
|
1141
|
+
:where(button) {
|
|
1142
|
+
position: absolute;
|
|
1143
|
+
inset-block-start: -0.35rem;
|
|
1144
|
+
inset-inline-end: -0.35rem;
|
|
1145
|
+
opacity: 0;
|
|
1146
|
+
transition: var(--transition, all .05s ease-in-out);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
:where(li:hover, li:focus-within) :where(button) {
|
|
1150
|
+
opacity: 1;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
}
|
|
1158
|
+
|
|
948
1159
|
/* Manifest Checkboxes */
|
|
949
1160
|
|
|
950
1161
|
@layer components {
|
|
@@ -1582,16 +1793,25 @@
|
|
|
1582
1793
|
gap: var(--spacing, 0.25rem);
|
|
1583
1794
|
max-width: 100%;
|
|
1584
1795
|
height: calc(var(--spacing-field-height, 2.25rem) - var(--spacing, 0.25rem) * 2.5);
|
|
1796
|
+
margin: 0;
|
|
1797
|
+
padding: 0;
|
|
1585
1798
|
padding-inline-start: calc(var(--spacing, 0.25rem) * 2);
|
|
1586
1799
|
font-size: 0.875rem;
|
|
1800
|
+
line-height: normal;
|
|
1587
1801
|
color: var(--color-content-stark, oklch(43.9% 0 0));
|
|
1588
1802
|
background-color: var(--color-popover-surface, oklch(100% 0 0));
|
|
1803
|
+
border: 0;
|
|
1589
1804
|
border-radius: calc(var(--radius, 0.5rem) * 0.7);
|
|
1590
1805
|
overflow: hidden;
|
|
1591
1806
|
user-select: none;
|
|
1592
1807
|
|
|
1593
1808
|
/* Label */
|
|
1594
1809
|
&>span {
|
|
1810
|
+
min-width: 0;
|
|
1811
|
+
margin: 0;
|
|
1812
|
+
padding: 0;
|
|
1813
|
+
border: 0;
|
|
1814
|
+
font: inherit;
|
|
1595
1815
|
overflow: hidden;
|
|
1596
1816
|
text-overflow: ellipsis;
|
|
1597
1817
|
white-space: nowrap
|
|
@@ -1602,15 +1822,23 @@
|
|
|
1602
1822
|
display: inline-flex;
|
|
1603
1823
|
align-items: center;
|
|
1604
1824
|
justify-content: center;
|
|
1825
|
+
box-sizing: border-box;
|
|
1826
|
+
flex: none;
|
|
1827
|
+
width: auto !important;
|
|
1605
1828
|
min-width: 0;
|
|
1829
|
+
max-width: none;
|
|
1606
1830
|
height: 100%;
|
|
1831
|
+
min-height: 0;
|
|
1607
1832
|
aspect-ratio: 1 / 1;
|
|
1833
|
+
margin: 0;
|
|
1608
1834
|
padding: 0;
|
|
1835
|
+
border: 0;
|
|
1836
|
+
border-radius: 0;
|
|
1609
1837
|
font-size: 1em;
|
|
1610
1838
|
line-height: 1;
|
|
1611
1839
|
color: var(--color-content-neutral, oklch(43.9% 0 0));
|
|
1612
1840
|
background: transparent;
|
|
1613
|
-
|
|
1841
|
+
box-shadow: none;
|
|
1614
1842
|
cursor: pointer;
|
|
1615
1843
|
|
|
1616
1844
|
&:hover {
|
|
@@ -3107,7 +3335,7 @@
|
|
|
3107
3335
|
|
|
3108
3336
|
@layer components {
|
|
3109
3337
|
|
|
3110
|
-
:where(input:not([type=range], [type=color]), textarea, label:has([type=search], [type=file]), .label:has([type=search], [type=file])):not(.unstyle) {
|
|
3338
|
+
:where(input:not([type=range], [type=color]), textarea, .textarea, label:has([type=search], [type=file]), .label:has([type=search], [type=file])):not(.unstyle) {
|
|
3111
3339
|
width: 100%;
|
|
3112
3340
|
max-width: 100%;
|
|
3113
3341
|
color: var(--color-field-inverse, oklch(43.9% 0 0));
|
|
@@ -3226,7 +3454,7 @@
|
|
|
3226
3454
|
}
|
|
3227
3455
|
|
|
3228
3456
|
/* Textarea-specific styles */
|
|
3229
|
-
:where(textarea):not(.unstyle) {
|
|
3457
|
+
:where(textarea, .textarea):not(.unstyle) {
|
|
3230
3458
|
display: block;
|
|
3231
3459
|
height: auto;
|
|
3232
3460
|
padding: var(--spacing-field-padding, 0.625rem) calc(var(--spacing-field-padding, 0.625rem) * 1.3)
|
package/lib/manifest.input.css
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
@layer components {
|
|
4
4
|
|
|
5
|
-
:where(input:not([type=range], [type=color]), textarea, label:has([type=search], [type=file]), .label:has([type=search], [type=file])):not(.unstyle) {
|
|
5
|
+
:where(input:not([type=range], [type=color]), textarea, .textarea, label:has([type=search], [type=file]), .label:has([type=search], [type=file])):not(.unstyle) {
|
|
6
6
|
width: 100%;
|
|
7
7
|
max-width: 100%;
|
|
8
8
|
color: var(--color-field-inverse, oklch(43.9% 0 0));
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
/* Textarea-specific styles */
|
|
124
|
-
:where(textarea):not(.unstyle) {
|
|
124
|
+
:where(textarea, .textarea):not(.unstyle) {
|
|
125
125
|
display: block;
|
|
126
126
|
height: auto;
|
|
127
127
|
padding: var(--spacing-field-padding, 0.625rem) calc(var(--spacing-field-padding, 0.625rem) * 1.3)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"manifest.appwrite.auth.js": "sha384
|
|
2
|
+
"manifest.appwrite.auth.js": "sha384-IFEn8+AMicdf5DkS+ytkPls8CpW2kDelRWrL3M61cu1HqaTJvHmABQUPgIMJC/tc",
|
|
3
3
|
"manifest.appwrite.data.js": "sha384-KFKVo5TQESLJB7wgk+C8Zb/sAl43QAMdioWW3tQRWYq5umfdpGCZ5Hld9y9f6/Np",
|
|
4
4
|
"manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
|
|
5
5
|
"manifest.charts.js": "sha384-wTQlB3hJ9V9lmisCgm8l6YkpJV+nA4+Vci3dI0x1nBtWnS9TFr+eXaJelP7AusyD",
|
|
6
|
-
"manifest.chat.js": "sha384-
|
|
6
|
+
"manifest.chat.js": "sha384-3+eva9YYz5DrdS3DeRGw9oXH/CreuYX+ZHHtYP25vmnYDHiEpFf9nEGLvwSHBXjz",
|
|
7
7
|
"manifest.code.js": "sha384-G3MNfvQRWWY+gm4SGGrSsgQbDeil9MP+ON2DTk6Rcq9Nsex8woHMDt5EJHrFH0Nj",
|
|
8
8
|
"manifest.color.js": "sha384-6Rv3LxyTcZNjrhtayQfqRdCx0uSZ4BiEbgEI98I62eTvp8Aw7LBIoNJ0Je1oktwL",
|
|
9
9
|
"manifest.colorpicker.js": "sha384-N5ezEwgKrDodV4YdURxhdyZKmg1n/QhqvmAC4SEG03mw1pEAZAA8Gphp+NLAOxts",
|
|
@@ -29,5 +29,5 @@
|
|
|
29
29
|
"manifest.url.parameters.js": "sha384-FIufiClqDx1rJpU/QUc9z/D43qClQ6Qm8rBahipbJl9BDHUvhrOsUDegmTWW7Tuf",
|
|
30
30
|
"manifest.utilities.js": "sha384-MtzNaVOrgyepjlY5nz38pAJk67yiFPU1H5WoPNfcNbUZTXy+oin1lrtKCIcobEOJ",
|
|
31
31
|
"manifest.virtual.js": "sha384-c194pXD0Ld48QJ+HPVNibvbn54/ETJRuYhUOWesEBjtOIQcQMIKo0DnCyQMvDlLg",
|
|
32
|
-
"manifest.js": "sha384-
|
|
32
|
+
"manifest.js": "sha384-RusFaByZu31LkLZCAhjgtJC8iWksvmjTKS8D5Xj9/4N7B/Xr4DDyFGmiL23kajgK"
|
|
33
33
|
}
|
package/lib/manifest.js
CHANGED
|
@@ -202,11 +202,29 @@
|
|
|
202
202
|
|
|
203
203
|
// Configuration
|
|
204
204
|
const DEFAULT_VERSION = 'latest';
|
|
205
|
-
|
|
205
|
+
|
|
206
|
+
// CDN fallback chain (first-party first). Each origin serves the npm scheme
|
|
207
|
+
// `<origin>/<pkg>@<version>/<path>`. Override order with `data-cdn`
|
|
208
|
+
// (comma-separated origins).
|
|
209
|
+
let CDN_HOSTS = [
|
|
210
|
+
'https://cdn.manifestx.dev/npm',
|
|
211
|
+
'https://cdn.jsdelivr.net/npm',
|
|
212
|
+
'https://unpkg.com'
|
|
213
|
+
];
|
|
214
|
+
function setCdnHosts(value) {
|
|
215
|
+
if (!value) return;
|
|
216
|
+
const hosts = value.split(',').map(s => s.trim().replace(/\/$/, '')).filter(Boolean);
|
|
217
|
+
if (hosts.length) CDN_HOSTS = hosts;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// unpkg serves packages as published (no auto-minify); mnfst ships unminified .js.
|
|
221
|
+
function hostFile(host, file) {
|
|
222
|
+
return host.includes('unpkg.com') ? file.replace(/\.min\.js$/, '.js') : file;
|
|
223
|
+
}
|
|
206
224
|
|
|
207
225
|
// Get base URL for a given version
|
|
208
|
-
function getBaseUrl(version = DEFAULT_VERSION) {
|
|
209
|
-
return
|
|
226
|
+
function getBaseUrl(version = DEFAULT_VERSION, host = CDN_HOSTS[0]) {
|
|
227
|
+
return `${host}/mnfst@${version}/lib`;
|
|
210
228
|
}
|
|
211
229
|
|
|
212
230
|
// Available core plugins (auto-loaded if no data-plugins specified)
|
|
@@ -279,26 +297,29 @@
|
|
|
279
297
|
});
|
|
280
298
|
}
|
|
281
299
|
|
|
282
|
-
// Plugin
|
|
283
|
-
//
|
|
300
|
+
// Plugin URLs: `data-plugin-base` override → single unminified `.js` (no
|
|
301
|
+
// fallback — an explicit base, e.g. local dev, must fail loudly); else one
|
|
302
|
+
// candidate per CDN host, tried in order.
|
|
284
303
|
let _pluginBase = null;
|
|
285
304
|
function setPluginBase(b) { _pluginBase = b || null; }
|
|
286
|
-
function
|
|
305
|
+
function getPluginUrlCandidates(pluginName, version = DEFAULT_VERSION) {
|
|
287
306
|
// Hyphenated API name → dotted file name (`appwrite-auth` → appwrite.auth).
|
|
288
307
|
const fileName = pluginName.replace(/-/g, '.');
|
|
289
308
|
if (_pluginBase) {
|
|
290
309
|
const base = _pluginBase.replace(/\/$/, '');
|
|
291
|
-
return `${base}/manifest.${fileName}.js
|
|
310
|
+
return [`${base}/manifest.${fileName}.js`];
|
|
292
311
|
}
|
|
293
|
-
|
|
294
|
-
|
|
312
|
+
return CDN_HOSTS.map(h => `${getBaseUrl(version, h)}/${hostFile(h, `manifest.${fileName}.min.js`)}`);
|
|
313
|
+
}
|
|
314
|
+
function getPluginUrl(pluginName, version = DEFAULT_VERSION) {
|
|
315
|
+
return getPluginUrlCandidates(pluginName, version)[0];
|
|
295
316
|
}
|
|
296
317
|
|
|
297
|
-
//
|
|
298
|
-
function
|
|
299
|
-
if (
|
|
300
|
-
|
|
301
|
-
return
|
|
318
|
+
// Alpine URL candidates from a data-alpine value (version tag or full URL)
|
|
319
|
+
function alpineUrlCandidates(dataAlpine) {
|
|
320
|
+
if (dataAlpine && dataAlpine.startsWith('http')) return [dataAlpine];
|
|
321
|
+
const v = dataAlpine || '3';
|
|
322
|
+
return CDN_HOSTS.map(h => `${h}/alpinejs@${v}/dist/cdn.min.js`);
|
|
302
323
|
}
|
|
303
324
|
|
|
304
325
|
// Has DOMContentLoaded fired? readyState can't tell ('interactive' spans
|
|
@@ -331,7 +352,7 @@
|
|
|
331
352
|
// fire `alpine:init` before the page's registrations exist. DCL fires only
|
|
332
353
|
// after every deferred script runs, making the order deterministic (and cold
|
|
333
354
|
// cache is already past DCL, so no added delay).
|
|
334
|
-
function loadAlpine(
|
|
355
|
+
function loadAlpine(alpineUrls) {
|
|
335
356
|
whenDomReady(() => {
|
|
336
357
|
if (window.Alpine) {
|
|
337
358
|
return;
|
|
@@ -343,24 +364,30 @@
|
|
|
343
364
|
return;
|
|
344
365
|
}
|
|
345
366
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
367
|
+
// Past DCL, so each candidate executes as soon as it arrives; fall
|
|
368
|
+
// through the CDN chain on error.
|
|
369
|
+
(async () => {
|
|
370
|
+
for (const url of alpineUrls) {
|
|
371
|
+
try {
|
|
372
|
+
return await injectScript(url);
|
|
373
|
+
} catch (_) {
|
|
374
|
+
console.warn(`[Manifest Loader] Alpine failed from ${url} — trying fallback CDN`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
console.error('[Manifest Loader] Alpine.js failed to load from all CDNs.');
|
|
378
|
+
})();
|
|
350
379
|
});
|
|
351
380
|
}
|
|
352
381
|
|
|
353
|
-
//
|
|
354
|
-
function
|
|
382
|
+
// Inject one script URL and wait for it to load and execute
|
|
383
|
+
function injectScript(url) {
|
|
355
384
|
return new Promise((resolve, reject) => {
|
|
356
|
-
const url = getPluginUrl(pluginName, version);
|
|
357
|
-
|
|
358
385
|
// Skip if script with same src already in DOM (e.g. prerendered HTML or second loader run)
|
|
359
386
|
const existing = document.querySelector(`script[src="${url}"]`);
|
|
360
387
|
if (existing) {
|
|
361
388
|
if (existing.complete) return resolve();
|
|
362
389
|
existing.addEventListener('load', () => resolve());
|
|
363
|
-
existing.addEventListener('error', () => reject(new Error(`Failed to load ${
|
|
390
|
+
existing.addEventListener('error', () => reject(new Error(`Failed to load ${url}`)));
|
|
364
391
|
return;
|
|
365
392
|
}
|
|
366
393
|
|
|
@@ -368,11 +395,28 @@
|
|
|
368
395
|
script.src = url;
|
|
369
396
|
script.async = false; // Ensure scripts execute in order
|
|
370
397
|
script.onload = () => resolve();
|
|
371
|
-
script.onerror = () => reject(new Error(`Failed to load ${
|
|
398
|
+
script.onerror = () => { script.remove(); reject(new Error(`Failed to load ${url}`)); };
|
|
372
399
|
document.head.appendChild(script);
|
|
373
400
|
});
|
|
374
401
|
}
|
|
375
402
|
|
|
403
|
+
// Load a plugin, falling through the CDN chain on error. A fallback insert
|
|
404
|
+
// lands after already-inserted scripts, so strict cross-plugin execution
|
|
405
|
+
// order is traded for availability in the (already degraded) fallback case.
|
|
406
|
+
async function addScript(pluginName, version = DEFAULT_VERSION) {
|
|
407
|
+
const urls = getPluginUrlCandidates(pluginName, version);
|
|
408
|
+
let lastErr = null;
|
|
409
|
+
for (const url of urls) {
|
|
410
|
+
try {
|
|
411
|
+
return await injectScript(url);
|
|
412
|
+
} catch (e) {
|
|
413
|
+
lastErr = e;
|
|
414
|
+
if (urls.length > 1) console.warn(`[Manifest Loader] ${url} failed — trying fallback CDN`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
throw lastErr || new Error(`Failed to load ${pluginName}`);
|
|
418
|
+
}
|
|
419
|
+
|
|
376
420
|
// Resolve plugin dependencies (auto-inject required dependencies)
|
|
377
421
|
function resolveDependencies(pluginList) {
|
|
378
422
|
const resolved = [];
|
|
@@ -435,6 +479,15 @@
|
|
|
435
479
|
return [];
|
|
436
480
|
}
|
|
437
481
|
|
|
482
|
+
// Detect the chat plugin from manifest.json content.
|
|
483
|
+
// Opt-in / auto-loaded when an `ai` (or `chat`) config block is present.
|
|
484
|
+
function detectChatPlugins(manifest) {
|
|
485
|
+
if (!manifest || typeof manifest !== 'object') return [];
|
|
486
|
+
if ((manifest.ai && typeof manifest.ai === 'object') ||
|
|
487
|
+
(manifest.chat && typeof manifest.chat === 'object')) return ['chat'];
|
|
488
|
+
return [];
|
|
489
|
+
}
|
|
490
|
+
|
|
438
491
|
// Parse data attributes
|
|
439
492
|
function parseDataAttributes() {
|
|
440
493
|
// Try to get current script first, then fall back to querySelector
|
|
@@ -455,6 +508,8 @@
|
|
|
455
508
|
// Override: resolve plugin URLs against this base (dir serving
|
|
456
509
|
// `manifest.<name>.js`, relative or absolute) instead of the CDN.
|
|
457
510
|
const pluginBase = script.getAttribute('data-plugin-base');
|
|
511
|
+
// Override: custom CDN fallback chain (comma-separated origins).
|
|
512
|
+
const cdn = script.getAttribute('data-cdn');
|
|
458
513
|
|
|
459
514
|
let pluginList = [];
|
|
460
515
|
const deriveFromManifest = !plugins;
|
|
@@ -483,37 +538,29 @@
|
|
|
483
538
|
version,
|
|
484
539
|
alpine,
|
|
485
540
|
pluginBase,
|
|
541
|
+
cdn,
|
|
486
542
|
};
|
|
487
543
|
}
|
|
488
544
|
|
|
489
|
-
// Load custom Tailwind CDN script
|
|
490
|
-
function loadTailwind(version = DEFAULT_VERSION) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
return resolve();
|
|
545
|
+
// Load custom Tailwind CDN script, falling through the CDN chain on error
|
|
546
|
+
async function loadTailwind(version = DEFAULT_VERSION) {
|
|
547
|
+
const urls = CDN_HOSTS.map(h => `${getBaseUrl(version, h)}/${hostFile(h, 'manifest.tailwind.min.js')}`);
|
|
548
|
+
let lastErr = null;
|
|
549
|
+
for (const url of urls) {
|
|
550
|
+
try {
|
|
551
|
+
return await injectScript(url);
|
|
552
|
+
} catch (e) {
|
|
553
|
+
lastErr = e;
|
|
499
554
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
script.async = false;
|
|
504
|
-
script.onload = () => resolve();
|
|
505
|
-
script.onerror = () => {
|
|
506
|
-
console.warn(`[Manifest Loader] Tailwind plugin not yet published to CDN. Load it directly: <script src="/scripts/tailwind.v4.3.1.js"></script>`);
|
|
507
|
-
reject(new Error(`Tailwind plugin not available from CDN. Load it directly from your project.`));
|
|
508
|
-
};
|
|
509
|
-
document.head.appendChild(script);
|
|
510
|
-
});
|
|
555
|
+
}
|
|
556
|
+
console.warn(`[Manifest Loader] Tailwind plugin not available from any CDN. Load it directly: <script src="/scripts/tailwind.v4.3.1.js"></script>`);
|
|
557
|
+
throw lastErr || new Error(`Tailwind plugin not available from CDN.`);
|
|
511
558
|
}
|
|
512
559
|
|
|
513
560
|
// Expose API
|
|
514
561
|
window.Manifest = {
|
|
515
562
|
loadPlugin: function (pluginName, version = DEFAULT_VERSION) {
|
|
516
|
-
const allPlugins = [...AVAILABLE_PLUGINS, ...APPWRITE_PLUGINS, 'payments'];
|
|
563
|
+
const allPlugins = [...AVAILABLE_PLUGINS, ...APPWRITE_PLUGINS, 'payments', 'chat'];
|
|
517
564
|
if (!allPlugins.includes(pluginName)) {
|
|
518
565
|
console.warn(`[Manifest Loader] Unknown plugin: ${pluginName}`);
|
|
519
566
|
return Promise.reject(new Error(`Unknown plugin: ${pluginName}`));
|
|
@@ -532,6 +579,7 @@
|
|
|
532
579
|
// Parse config and load plugins
|
|
533
580
|
const config = parseDataAttributes();
|
|
534
581
|
if (config && config.pluginBase) setPluginBase(config.pluginBase);
|
|
582
|
+
if (config && config.cdn) setCdnHosts(config.cdn);
|
|
535
583
|
|
|
536
584
|
if (config && config.plugins.length > 0) {
|
|
537
585
|
if (window.__manifestLoaderStarted) {
|
|
@@ -605,7 +653,8 @@
|
|
|
605
653
|
const corePlugins = getDefaultPluginsFromManifest(manifest);
|
|
606
654
|
const appwritePlugins = detectAppwritePlugins(manifest);
|
|
607
655
|
const paymentsPlugins = detectPaymentsPlugins(manifest);
|
|
608
|
-
|
|
656
|
+
const chatPlugins = detectChatPlugins(manifest);
|
|
657
|
+
pluginsToLoad = resolveDependencies([...corePlugins, ...appwritePlugins, ...paymentsPlugins, ...chatPlugins]);
|
|
609
658
|
} else {
|
|
610
659
|
const needsManifest = config.plugins.some(p => MANIFEST_DEPENDENT_PLUGINS.includes(p));
|
|
611
660
|
if (needsManifest) {
|
|
@@ -634,7 +683,7 @@
|
|
|
634
683
|
window.ManifestComponentsRegistry.manifest = manifest;
|
|
635
684
|
}
|
|
636
685
|
}
|
|
637
|
-
loadAlpine(
|
|
686
|
+
loadAlpine(alpineUrlCandidates(config.alpine));
|
|
638
687
|
};
|
|
639
688
|
|
|
640
689
|
loadPlugins();
|