mnfst 0.5.62 → 0.5.63
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.js +8 -11
- package/lib/manifest.markdown.js +21 -1
- package/lib/manifest.url.parameters.js +231 -0
- package/package.json +2 -2
package/lib/manifest.js
CHANGED
|
@@ -198,7 +198,8 @@
|
|
|
198
198
|
'tabs',
|
|
199
199
|
'slides',
|
|
200
200
|
'resize',
|
|
201
|
-
'colorpicker'
|
|
201
|
+
'colorpicker',
|
|
202
|
+
'url-parameters'
|
|
202
203
|
];
|
|
203
204
|
|
|
204
205
|
// Appwrite integration plugins (opt-in only, never auto-loaded)
|
|
@@ -248,20 +249,16 @@
|
|
|
248
249
|
let _pluginBase = null;
|
|
249
250
|
function setPluginBase(b) { _pluginBase = b || null; }
|
|
250
251
|
function getPluginUrl(pluginName, version = DEFAULT_VERSION) {
|
|
252
|
+
// Map hyphenated plugin API names to their dotted file names.
|
|
253
|
+
// `appwrite-auth` → `manifest.appwrite.auth.js`
|
|
254
|
+
// `url-parameters` → `manifest.url.parameters.js`
|
|
255
|
+
const fileName = pluginName.replace(/-/g, '.');
|
|
251
256
|
if (_pluginBase) {
|
|
252
257
|
const base = _pluginBase.replace(/\/$/, '');
|
|
253
|
-
|
|
254
|
-
const appwriteName = pluginName.replace('appwrite-', 'appwrite.');
|
|
255
|
-
return `${base}/manifest.${appwriteName}.js`;
|
|
256
|
-
}
|
|
257
|
-
return `${base}/manifest.${pluginName}.js`;
|
|
258
|
+
return `${base}/manifest.${fileName}.js`;
|
|
258
259
|
}
|
|
259
260
|
const base = getBaseUrl(version);
|
|
260
|
-
|
|
261
|
-
const appwriteName = pluginName.replace('appwrite-', 'appwrite.');
|
|
262
|
-
return `${base}/manifest.${appwriteName}.min.js`;
|
|
263
|
-
}
|
|
264
|
-
return `${base}/manifest.${pluginName}.min.js`;
|
|
261
|
+
return `${base}/manifest.${fileName}.min.js`;
|
|
265
262
|
}
|
|
266
263
|
|
|
267
264
|
// Resolve Alpine CDN URL from a data-alpine value (version tag or full URL)
|
package/lib/manifest.markdown.js
CHANGED
|
@@ -6,6 +6,17 @@ let markedPromise = null;
|
|
|
6
6
|
// Cache for fetched markdown files to prevent duplicate requests
|
|
7
7
|
const markdownCache = new Map();
|
|
8
8
|
|
|
9
|
+
// Invalidate the markdown fetch cache when mnfst-run signals a data file
|
|
10
|
+
// changed on disk. Without this, a saved .md file is re-read by the data
|
|
11
|
+
// plugin but x-markdown still serves the old content from cache; combined
|
|
12
|
+
// with the lastProcessedContent short-circuit in the directive's effect,
|
|
13
|
+
// the article appears blank until the user manually reloads.
|
|
14
|
+
if (typeof window !== 'undefined') {
|
|
15
|
+
window.addEventListener('manifest:dev-reload', () => {
|
|
16
|
+
markdownCache.clear();
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
// Load marked.js from CDN
|
|
10
21
|
async function loadMarkedJS() {
|
|
11
22
|
if (typeof marked !== 'undefined') {
|
|
@@ -527,8 +538,17 @@ async function initializeMarkdownPlugin() {
|
|
|
527
538
|
}
|
|
528
539
|
}
|
|
529
540
|
|
|
530
|
-
// Skip if content hasn't changed
|
|
541
|
+
// Skip re-render if content hasn't changed, but still restore
|
|
542
|
+
// visibility — during a dev-reload the data plugin briefly
|
|
543
|
+
// clears its source cache, which makes the expression
|
|
544
|
+
// resolve to undefined and pushes opacity to 0; if we
|
|
545
|
+
// early-return here without restoring it, the article stays
|
|
546
|
+
// hidden even though innerHTML is intact.
|
|
531
547
|
if (markdownContent === lastProcessedContent) {
|
|
548
|
+
if (el.innerHTML && el.innerHTML.trim() !== '') {
|
|
549
|
+
hasContent = true;
|
|
550
|
+
el.style.opacity = '1';
|
|
551
|
+
}
|
|
532
552
|
return;
|
|
533
553
|
}
|
|
534
554
|
lastProcessedContent = markdownContent;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/* Manifest URL Parameters */
|
|
2
|
+
|
|
3
|
+
function initializeUrlParametersPlugin() {
|
|
4
|
+
// Initialize empty parameters store
|
|
5
|
+
Alpine.store('urlParams', {
|
|
6
|
+
current: {},
|
|
7
|
+
_initialized: false
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// Cache for debounced updates
|
|
11
|
+
const updateTimeouts = new Map();
|
|
12
|
+
const DEBOUNCE_DELAY = 300;
|
|
13
|
+
|
|
14
|
+
// Helper to parse query string
|
|
15
|
+
function parseQueryString(queryString) {
|
|
16
|
+
const params = new URLSearchParams(queryString);
|
|
17
|
+
const result = {};
|
|
18
|
+
|
|
19
|
+
for (const [key, value] of params.entries()) {
|
|
20
|
+
// Handle array values (comma-separated)
|
|
21
|
+
if (value.includes(',')) {
|
|
22
|
+
result[key] = value.split(',').filter(Boolean);
|
|
23
|
+
} else {
|
|
24
|
+
result[key] = value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Helper to stringify query object
|
|
32
|
+
function stringifyQueryObject(query) {
|
|
33
|
+
const params = new URLSearchParams();
|
|
34
|
+
|
|
35
|
+
for (const [key, value] of Object.entries(query)) {
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
params.set(key, value.filter(Boolean).join(','));
|
|
38
|
+
} else if (value != null && value !== '') {
|
|
39
|
+
params.set(key, value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return params.toString();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Helper to ensure value is in array format
|
|
47
|
+
function ensureArray(value) {
|
|
48
|
+
if (Array.isArray(value)) return value;
|
|
49
|
+
if (value == null || value === '') return [];
|
|
50
|
+
return [value];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Update URL with new query parameters
|
|
54
|
+
async function updateURL(updates, action = 'set') {
|
|
55
|
+
|
|
56
|
+
const url = new URL(window.location.href);
|
|
57
|
+
const currentParams = parseQueryString(url.search);
|
|
58
|
+
|
|
59
|
+
// Apply updates based on action
|
|
60
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
61
|
+
switch (action) {
|
|
62
|
+
case 'add':
|
|
63
|
+
const currentAdd = ensureArray(currentParams[key]);
|
|
64
|
+
const newValues = ensureArray(value);
|
|
65
|
+
currentParams[key] = [...new Set([...currentAdd, ...newValues])];
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case 'remove':
|
|
69
|
+
const currentRemove = ensureArray(currentParams[key]);
|
|
70
|
+
const removeValue = ensureArray(value)[0]; // Take first value to remove
|
|
71
|
+
currentParams[key] = currentRemove.filter(v => v !== removeValue);
|
|
72
|
+
if (currentParams[key].length === 0) {
|
|
73
|
+
delete currentParams[key];
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
|
|
77
|
+
case 'set':
|
|
78
|
+
default:
|
|
79
|
+
if (value == null || value === '') {
|
|
80
|
+
delete currentParams[key];
|
|
81
|
+
} else {
|
|
82
|
+
currentParams[key] = value;
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Update URL
|
|
89
|
+
const newQueryString = stringifyQueryObject(currentParams);
|
|
90
|
+
url.search = newQueryString ? `?${newQueryString}` : '';
|
|
91
|
+
|
|
92
|
+
// Update URL using pushState to ensure changes are visible
|
|
93
|
+
window.history.pushState({}, '', url.toString());
|
|
94
|
+
|
|
95
|
+
// Update store
|
|
96
|
+
Alpine.store('urlParams', {
|
|
97
|
+
current: currentParams,
|
|
98
|
+
_initialized: true
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Dispatch event
|
|
102
|
+
document.dispatchEvent(new CustomEvent('url-updated', {
|
|
103
|
+
detail: { updates, action }
|
|
104
|
+
}));
|
|
105
|
+
|
|
106
|
+
return currentParams;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Add $url magic method
|
|
110
|
+
Alpine.magic('url', () => {
|
|
111
|
+
const store = Alpine.store('urlParams');
|
|
112
|
+
|
|
113
|
+
return new Proxy({}, {
|
|
114
|
+
get(target, prop) {
|
|
115
|
+
// Handle special keys
|
|
116
|
+
if (prop === Symbol.iterator || prop === 'then' || prop === 'catch' || prop === 'finally') {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Get current value
|
|
121
|
+
const value = store.current[prop];
|
|
122
|
+
|
|
123
|
+
// Return a proxy for the value
|
|
124
|
+
return new Proxy({}, {
|
|
125
|
+
get(target, key) {
|
|
126
|
+
if (key === 'value') {
|
|
127
|
+
// Ensure arrays are returned as arrays, not strings
|
|
128
|
+
if (Array.isArray(value)) return value;
|
|
129
|
+
if (typeof value === 'string' && value.includes(',')) {
|
|
130
|
+
return value.split(',').filter(Boolean);
|
|
131
|
+
}
|
|
132
|
+
// Return undefined/null values as they are (for proper falsy checks)
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
if (key === 'set') return (newValue) => {
|
|
136
|
+
clearTimeout(updateTimeouts.get(prop));
|
|
137
|
+
const timeout = setTimeout(() => {
|
|
138
|
+
updateURL({ [prop]: newValue }, 'set');
|
|
139
|
+
}, DEBOUNCE_DELAY);
|
|
140
|
+
updateTimeouts.set(prop, timeout);
|
|
141
|
+
};
|
|
142
|
+
if (key === 'add') return (newValue) => {
|
|
143
|
+
clearTimeout(updateTimeouts.get(prop));
|
|
144
|
+
const timeout = setTimeout(() => {
|
|
145
|
+
updateURL({ [prop]: newValue }, 'add');
|
|
146
|
+
}, DEBOUNCE_DELAY);
|
|
147
|
+
updateTimeouts.set(prop, timeout);
|
|
148
|
+
};
|
|
149
|
+
if (key === 'remove') return (value) => {
|
|
150
|
+
clearTimeout(updateTimeouts.get(prop));
|
|
151
|
+
const timeout = setTimeout(() => {
|
|
152
|
+
updateURL({ [prop]: value }, 'remove');
|
|
153
|
+
}, DEBOUNCE_DELAY);
|
|
154
|
+
updateTimeouts.set(prop, timeout);
|
|
155
|
+
};
|
|
156
|
+
if (key === 'clear') return () => {
|
|
157
|
+
clearTimeout(updateTimeouts.get(prop));
|
|
158
|
+
const timeout = setTimeout(() => {
|
|
159
|
+
updateURL({ [prop]: null }, 'set');
|
|
160
|
+
}, DEBOUNCE_DELAY);
|
|
161
|
+
updateTimeouts.set(prop, timeout);
|
|
162
|
+
};
|
|
163
|
+
return undefined;
|
|
164
|
+
},
|
|
165
|
+
set(target, key, newValue) {
|
|
166
|
+
if (key === 'value') {
|
|
167
|
+
// Make value settable for x-model compatibility
|
|
168
|
+
clearTimeout(updateTimeouts.get(prop));
|
|
169
|
+
const timeout = setTimeout(() => {
|
|
170
|
+
updateURL({ [prop]: newValue }, 'set');
|
|
171
|
+
}, DEBOUNCE_DELAY);
|
|
172
|
+
updateTimeouts.set(prop, timeout);
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Initialize with current URL parameters
|
|
183
|
+
const initialParams = parseQueryString(window.location.search);
|
|
184
|
+
Alpine.store('urlParams', {
|
|
185
|
+
current: initialParams,
|
|
186
|
+
_initialized: true
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Listen for popstate events
|
|
190
|
+
window.addEventListener('popstate', () => {
|
|
191
|
+
const params = parseQueryString(window.location.search);
|
|
192
|
+
Alpine.store('urlParams', {
|
|
193
|
+
current: params,
|
|
194
|
+
_initialized: true
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Track initialization to prevent duplicates
|
|
200
|
+
let urlParametersPluginInitialized = false;
|
|
201
|
+
|
|
202
|
+
function ensureUrlParametersPluginInitialized() {
|
|
203
|
+
if (urlParametersPluginInitialized) return;
|
|
204
|
+
if (!window.Alpine || typeof window.Alpine.directive !== 'function') return;
|
|
205
|
+
|
|
206
|
+
urlParametersPluginInitialized = true;
|
|
207
|
+
initializeUrlParametersPlugin();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Expose on window for loader to call if needed
|
|
211
|
+
window.ensureUrlParametersPluginInitialized = ensureUrlParametersPluginInitialized;
|
|
212
|
+
|
|
213
|
+
// Handle both DOMContentLoaded and alpine:init
|
|
214
|
+
if (document.readyState === 'loading') {
|
|
215
|
+
document.addEventListener('DOMContentLoaded', ensureUrlParametersPluginInitialized);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
document.addEventListener('alpine:init', ensureUrlParametersPluginInitialized);
|
|
219
|
+
|
|
220
|
+
// If Alpine is already initialized when this script loads, initialize immediately
|
|
221
|
+
if (window.Alpine && typeof window.Alpine.directive === 'function') {
|
|
222
|
+
setTimeout(ensureUrlParametersPluginInitialized, 0);
|
|
223
|
+
} else if (document.readyState === 'complete') {
|
|
224
|
+
const checkAlpine = setInterval(() => {
|
|
225
|
+
if (window.Alpine && typeof window.Alpine.directive === 'function') {
|
|
226
|
+
clearInterval(checkAlpine);
|
|
227
|
+
ensureUrlParametersPluginInitialized();
|
|
228
|
+
}
|
|
229
|
+
}, 10);
|
|
230
|
+
setTimeout(() => clearInterval(checkAlpine), 5000);
|
|
231
|
+
}
|
package/package.json
CHANGED