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 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
- if (pluginName.startsWith('appwrite-')) {
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
- if (pluginName.startsWith('appwrite-')) {
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)
@@ -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 (prevents unnecessary re-renders)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.62",
3
+ "version": "0.5.63",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter",
@@ -66,4 +66,4 @@
66
66
  "bugs": {
67
67
  "url": "https://github.com/andrewmatlock/manifest/issues"
68
68
  }
69
- }
69
+ }