mnfst 0.5.176 → 0.5.177
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.chat.js +26 -8
- package/lib/manifest.integrity.json +3 -3
- package/lib/manifest.markdown.js +28 -9
- package/lib/manifest.url.parameters.js +93 -136
- package/package.json +1 -1
package/lib/manifest.chat.js
CHANGED
|
@@ -58,6 +58,18 @@
|
|
|
58
58
|
return out;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// ---- shared revision ----------------------------------------------------
|
|
62
|
+
// Bumped on every commit of every handle; list getters touch it so any
|
|
63
|
+
// effect that read chat state — even a stale/empty handle mid-transition —
|
|
64
|
+
// re-arms on the next commit anywhere. Guards against directive effects
|
|
65
|
+
// whose dependency edge to a specific handle's state is lost during a
|
|
66
|
+
// key-flip flush (x-for dying on first load while a sibling x-effect lives).
|
|
67
|
+
let _rev = null;
|
|
68
|
+
function rev() {
|
|
69
|
+
if (!_rev) _rev = window.Alpine.reactive({ n: 0 });
|
|
70
|
+
return _rev;
|
|
71
|
+
}
|
|
72
|
+
|
|
61
73
|
// ---- handle -------------------------------------------------------------
|
|
62
74
|
function createHandle(adapter, conversationId, opts) {
|
|
63
75
|
opts = opts || {};
|
|
@@ -80,9 +92,9 @@
|
|
|
80
92
|
function ordered() { return _msgs.slice().sort(byKey); }
|
|
81
93
|
// Fresh per-message snapshots each commit so a keyed x-for re-renders
|
|
82
94
|
// in-place mutations (streaming appends, status) that identity wouldn't trip.
|
|
83
|
-
function commit() { state.messages = ordered().map(m => Object.assign({}, m, { body: Object.assign({}, m.body) })); }
|
|
84
|
-
function commitParticipants() { state.participants = [..._participants.values()]; }
|
|
85
|
-
function commitTyping() { state.typing = [..._typing.values()]; }
|
|
95
|
+
function commit() { state.messages = ordered().map(m => Object.assign({}, m, { body: Object.assign({}, m.body) })); rev().n++; }
|
|
96
|
+
function commitParticipants() { state.participants = [..._participants.values()]; rev().n++; }
|
|
97
|
+
function commitTyping() { state.typing = [..._typing.values()]; rev().n++; }
|
|
86
98
|
|
|
87
99
|
function upsert(raw) {
|
|
88
100
|
const incoming = normalize(raw, ++seq);
|
|
@@ -146,6 +158,10 @@
|
|
|
146
158
|
ingestLoad(await adapter.load(conversationId, opts.around ? { around: opts.around } : undefined));
|
|
147
159
|
if (adapter.subscribe) { unsub = adapter.subscribe(conversationId, handlers); state.live = true; }
|
|
148
160
|
state.status = 'ready';
|
|
161
|
+
// Settled tick: the seed load resolves amid the consumer's own
|
|
162
|
+
// transition flush; one macrotask commit re-triggers any effect
|
|
163
|
+
// whose mid-transition run raced it.
|
|
164
|
+
setTimeout(() => commit(), 0);
|
|
149
165
|
} catch (e) { state.status = 'error'; state.error = String(e && e.message || e); }
|
|
150
166
|
}
|
|
151
167
|
|
|
@@ -179,10 +195,11 @@
|
|
|
179
195
|
__v_skip: true, // keep Alpine from re-proxying the handle when stored in x-data
|
|
180
196
|
id: 'h_' + Math.round(performance.now()) + '_' + (++seq),
|
|
181
197
|
conversationId, isAggregate,
|
|
182
|
-
get messages() { return state.messages; },
|
|
183
|
-
get participants() { return state.participants; },
|
|
198
|
+
get messages() { void rev().n; return state.messages; },
|
|
199
|
+
get participants() { void rev().n; return state.participants; },
|
|
184
200
|
get me() { return state.me; },
|
|
185
|
-
get typing() { return state.typing; },
|
|
201
|
+
get typing() { void rev().n; return state.typing; },
|
|
202
|
+
get version() { return rev().n; }, // guaranteed-trackable scalar; bumps on every commit
|
|
186
203
|
get status() { return state.status; },
|
|
187
204
|
get live() { return state.live; },
|
|
188
205
|
get atStart() { return state.atStart; },
|
|
@@ -198,8 +215,8 @@
|
|
|
198
215
|
loadReplies: !!adapter.loadReplies, loadReactions: !!adapter.loadReactions
|
|
199
216
|
};
|
|
200
217
|
},
|
|
201
|
-
tree(o) { void state.messages; return buildTree(state.messages, o); },
|
|
202
|
-
flatTree(o) { void state.messages; return flattenTree(buildTree(state.messages, o)); },
|
|
218
|
+
tree(o) { void rev().n; void state.messages; return buildTree(state.messages, o); },
|
|
219
|
+
flatTree(o) { void rev().n; void state.messages; return flattenTree(buildTree(state.messages, o)); },
|
|
203
220
|
send,
|
|
204
221
|
edit: (id, body) => adapter.edit && adapter.edit(conversationId, id, body),
|
|
205
222
|
retract: (id) => adapter.retract && adapter.retract(conversationId, id),
|
|
@@ -234,6 +251,7 @@
|
|
|
234
251
|
return order === 'ts' ? all.sort(byKey) : all;
|
|
235
252
|
},
|
|
236
253
|
get participants() { const seen = new Map(); for (const h of handles) for (const p of h.participants) seen.set(p.id, p); return [...seen.values()]; },
|
|
254
|
+
get version() { return rev().n; },
|
|
237
255
|
get status() { return handles.some(h => h.status === 'loading') ? 'loading' : (handles.every(h => h.status === 'ready') ? 'ready' : 'idle'); },
|
|
238
256
|
get live() { return handles.some(h => h.live); },
|
|
239
257
|
can: { send: handles.some(h => h.can.send) },
|
|
@@ -3,7 +3,7 @@
|
|
|
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-qQrjhWkIDi4oU5uQrpy8DdlMjY+gfiivBuX6mssNiFdkawgkVBvKneGgp+nd3PYJ",
|
|
6
|
-
"manifest.chat.js": "sha384-
|
|
6
|
+
"manifest.chat.js": "sha384-6UF2khlkJCyHR+LthQNdY0YMXg+MU5BCBKA7LZENGWQK2M3Dt9k1IJYGVWS4h8FR",
|
|
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",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"manifest.export.js": "sha384-NkqOydcrdQ6qFEcpBKc80JCbeSCuybASrTUGajuTMBrHMzt1OIe8b3ndNn9VfqSh",
|
|
16
16
|
"manifest.icons.js": "sha384-kKOINpX70YzNO3FvkV6umrmO3f0KIu0DNbwB+ej55a7y/lhU7v/myQtXL0bwCTbt",
|
|
17
17
|
"manifest.localization.js": "sha384-MUetuW8PllIM+9JDMw/tx+qGem8t+ZUN5WZpycsTDKtz+LaX4DJehsfh/YZt9J7L",
|
|
18
|
-
"manifest.markdown.js": "sha384-
|
|
18
|
+
"manifest.markdown.js": "sha384-zdN4TgS3A92DGxSCcnlwtoroiQeCzyH1RayLJN0pDlQ9R0trodWCXJda8+awFGlB",
|
|
19
19
|
"manifest.native.js": "sha384-4WfuciRjqrfDLpHXp91AKQUTt8FDHaupSuVErRgdeaeoHf5d1CfHWqBd/aCOduDk",
|
|
20
20
|
"manifest.payments.js": "sha384-vhG1ZL3wGzb0vt0ygJJQF3jVQumMuvc2smGjYezuyQ+auN+MN9HY2k5NW2bdClxl",
|
|
21
21
|
"manifest.resize.js": "sha384-S3v4RAJoA77LUH6MddYG9bx//dZX//up7XWeH69Ql2ge3bVHgC6mR/CJBe6y4nQI",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"manifest.tailwind.js": "sha384-r/Jvqb+Piz21jUts10BbVAJoKkShOdJRVFpywyi4HPIi/OfOJq5CtPyWAPbdkMgt",
|
|
28
28
|
"manifest.toasts.js": "sha384-ytd5rDbax/Ou9z23uedFXPZbxDPsk2E/pxCTq4WLvfv+os1qTI6kELp0kPp07g24",
|
|
29
29
|
"manifest.tooltips.js": "sha384-HeJ0BGK3ebut1t8lm+MGeUUxd1pOZDyX8e6HFLsa4kAiRL+LwGDr67ckkRqv7to1",
|
|
30
|
-
"manifest.url.parameters.js": "sha384
|
|
30
|
+
"manifest.url.parameters.js": "sha384-+ZOsgPgbFmKOZsReC1EiH8PsZwvr2nC/duhzIzFf70xrbRrLsr926sLFW0M+BXtN",
|
|
31
31
|
"manifest.utilities.js": "sha384-yLEqEbd1FncqswrtrcTO7KrIEK4iP0ZvqCi64XLI7D77/L0YiJbjLdLGGg6l7rgN",
|
|
32
32
|
"manifest.virtual.js": "sha384-c194pXD0Ld48QJ+HPVNibvbn54/ETJRuYhUOWesEBjtOIQcQMIKo0DnCyQMvDlLg",
|
|
33
33
|
"manifest.js": "sha384-FLz68HEE8hEVLlAsoAX2+5cWBrhS5+B24rdJy5obXtP5U5bJKCswjbIQL8MpDUFZ"
|
package/lib/manifest.markdown.js
CHANGED
|
@@ -596,11 +596,29 @@ async function initializeMarkdownPlugin() {
|
|
|
596
596
|
return;
|
|
597
597
|
}
|
|
598
598
|
|
|
599
|
-
//
|
|
599
|
+
// Classify the source: real expressions evaluate; bare markdown/paths
|
|
600
|
+
// are quoted so Alpine doesn't choke on them.
|
|
601
|
+
const trimmed = expression.trim();
|
|
602
|
+
const isExplicitExpression = trimmed.includes('+') || trimmed.includes('`') || trimmed.includes('${') ||
|
|
603
|
+
trimmed.startsWith('$') || trimmed.startsWith("'") || trimmed.startsWith('"');
|
|
604
|
+
const looksLikePath = /\.(md|markdown)([?#]|$)/i.test(trimmed) || /^[\w./-]*\/[\w./-]*$/.test(trimmed);
|
|
605
|
+
const isMemberChain = !looksLikePath &&
|
|
606
|
+
/^[A-Za-z_$][\w$]*(\s*(\?\.|\.)\s*[A-Za-z_$][\w$]*|\s*\[[^\]]*\]|\s*\([^()]*\))+$/.test(trimmed);
|
|
607
|
+
const isBareIdentifier = !looksLikePath && /^[A-Za-z_$][\w$]*$/.test(trimmed);
|
|
608
|
+
|
|
600
609
|
let processedExpression = expression;
|
|
601
|
-
if (!
|
|
602
|
-
|
|
603
|
-
|
|
610
|
+
if (!isExplicitExpression) {
|
|
611
|
+
if (isMemberChain) {
|
|
612
|
+
// e.g. m.body.text — evaluate; undefined mid-chain (data still
|
|
613
|
+
// loading) resolves to undefined instead of throwing.
|
|
614
|
+
processedExpression = `(() => { try { return ${trimmed} } catch (_) { return undefined } })()`;
|
|
615
|
+
} else if (isBareIdentifier) {
|
|
616
|
+
// e.g. t — scope variable if defined, else literal one-word markdown
|
|
617
|
+
processedExpression = `(typeof ${trimmed} === 'undefined' ? '${trimmed}' : ${trimmed})`;
|
|
618
|
+
} else {
|
|
619
|
+
// inline markdown or file path — quote as literal
|
|
620
|
+
processedExpression = `'${expression.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n')}'`;
|
|
621
|
+
}
|
|
604
622
|
}
|
|
605
623
|
const getMarkdownContent = evaluateLater(processedExpression);
|
|
606
624
|
|
|
@@ -620,12 +638,13 @@ async function initializeMarkdownPlugin() {
|
|
|
620
638
|
pathOrContent = expression;
|
|
621
639
|
}
|
|
622
640
|
|
|
623
|
-
//
|
|
641
|
+
// Path = single whitespace-free token: .md/.markdown file, or a
|
|
642
|
+
// slash path of plain word chars. Evaluated content (chat
|
|
643
|
+
// messages etc.) with slashes or extensions inside prose stays prose.
|
|
624
644
|
const isFilePath = typeof pathOrContent === 'string' &&
|
|
625
|
-
(pathOrContent
|
|
626
|
-
|
|
627
|
-
pathOrContent
|
|
628
|
-
pathOrContent.includes('/'));
|
|
645
|
+
!/\s/.test(pathOrContent) &&
|
|
646
|
+
(/\.(md|markdown)([?#]|$)/i.test(pathOrContent) ||
|
|
647
|
+
/^[\w./-]*\/[\w./-]*$/.test(pathOrContent));
|
|
629
648
|
|
|
630
649
|
let markdownContent = pathOrContent;
|
|
631
650
|
|
|
@@ -1,199 +1,156 @@
|
|
|
1
1
|
/* Manifest URL Parameters */
|
|
2
2
|
|
|
3
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
4
|
const DEBOUNCE_DELAY = 300;
|
|
13
5
|
|
|
14
|
-
//
|
|
6
|
+
// Parse query string; comma-separated values become arrays
|
|
15
7
|
function parseQueryString(queryString) {
|
|
16
8
|
const params = new URLSearchParams(queryString);
|
|
17
9
|
const result = {};
|
|
18
|
-
|
|
19
10
|
for (const [key, value] of params.entries()) {
|
|
20
|
-
// Handle array values (comma-separated)
|
|
21
11
|
if (value.includes(',')) {
|
|
22
12
|
result[key] = value.split(',').filter(Boolean);
|
|
23
13
|
} else {
|
|
24
14
|
result[key] = value;
|
|
25
15
|
}
|
|
26
16
|
}
|
|
27
|
-
|
|
28
17
|
return result;
|
|
29
18
|
}
|
|
30
19
|
|
|
31
|
-
// Helper to stringify query object
|
|
32
20
|
function stringifyQueryObject(query) {
|
|
33
21
|
const params = new URLSearchParams();
|
|
34
|
-
|
|
35
22
|
for (const [key, value] of Object.entries(query)) {
|
|
36
23
|
if (Array.isArray(value)) {
|
|
37
|
-
params.set(key, value.filter(Boolean).join(','));
|
|
24
|
+
if (value.filter(Boolean).length) params.set(key, value.filter(Boolean).join(','));
|
|
38
25
|
} else if (value != null && value !== '') {
|
|
39
26
|
params.set(key, value);
|
|
40
27
|
}
|
|
41
28
|
}
|
|
42
|
-
|
|
43
29
|
return params.toString();
|
|
44
30
|
}
|
|
45
31
|
|
|
46
|
-
// Helper to ensure value is in array format
|
|
47
32
|
function ensureArray(value) {
|
|
48
33
|
if (Array.isArray(value)) return value;
|
|
49
34
|
if (value == null || value === '') return [];
|
|
35
|
+
if (typeof value === 'string' && value.includes(',')) return value.split(',').filter(Boolean);
|
|
50
36
|
return [value];
|
|
51
37
|
}
|
|
52
38
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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;
|
|
39
|
+
// Single reactive source of truth. Reads through $url register as Alpine
|
|
40
|
+
// dependencies; location is a debounced mirror of this state, not the store.
|
|
41
|
+
const params = Alpine.reactive({
|
|
42
|
+
current: parseQueryString(window.location.search),
|
|
43
|
+
_initialized: true
|
|
44
|
+
});
|
|
45
|
+
Alpine.store('urlParams', params);
|
|
46
|
+
|
|
47
|
+
// ---- location mirror (debounced, coalesces multi-key writes) ------------
|
|
48
|
+
let mirrorTimer = null;
|
|
49
|
+
function mirror() {
|
|
50
|
+
clearTimeout(mirrorTimer);
|
|
51
|
+
mirrorTimer = setTimeout(() => {
|
|
52
|
+
mirrorTimer = null;
|
|
53
|
+
const url = new URL(window.location.href);
|
|
54
|
+
const qs = stringifyQueryObject(params.current);
|
|
55
|
+
url.search = qs ? `?${qs}` : '';
|
|
56
|
+
if (url.toString() !== window.location.href) {
|
|
57
|
+
history.pushState({}, '', url.toString());
|
|
85
58
|
}
|
|
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());
|
|
59
|
+
}, DEBOUNCE_DELAY);
|
|
60
|
+
}
|
|
94
61
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
62
|
+
// ---- rehydrate from location (back/forward, router go/replace) ----------
|
|
63
|
+
function rehydrate() {
|
|
64
|
+
if (mirrorTimer) { clearTimeout(mirrorTimer); mirrorTimer = null; } // navigation wins over pending writes
|
|
65
|
+
const next = parseQueryString(window.location.search);
|
|
66
|
+
if (stringifyQueryObject(next) !== stringifyQueryObject(params.current)) {
|
|
67
|
+
params.current = next;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
window.addEventListener('popstate', rehydrate);
|
|
71
|
+
|
|
72
|
+
// Router go()/replace() (and any third party) drive history directly with
|
|
73
|
+
// no popstate — wrap so $url reads stay fresh. Our own mirror round-trips
|
|
74
|
+
// through this harmlessly (equality check above makes it a no-op).
|
|
75
|
+
for (const method of ['pushState', 'replaceState']) {
|
|
76
|
+
const original = history[method].bind(history);
|
|
77
|
+
history[method] = function (...args) {
|
|
78
|
+
const result = original(...args);
|
|
79
|
+
rehydrate();
|
|
80
|
+
return result;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
100
83
|
|
|
101
|
-
|
|
84
|
+
// ---- mutations (synchronous into reactive state) ------------------------
|
|
85
|
+
function apply(prop, value, action) {
|
|
86
|
+
const next = Object.assign({}, params.current);
|
|
87
|
+
switch (action) {
|
|
88
|
+
case 'add': {
|
|
89
|
+
const merged = [...new Set([...ensureArray(next[prop]), ...ensureArray(value)])];
|
|
90
|
+
if (merged.length) next[prop] = merged; else delete next[prop];
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case 'remove': {
|
|
94
|
+
const remaining = ensureArray(next[prop]).filter(v => !ensureArray(value).includes(v));
|
|
95
|
+
if (remaining.length) next[prop] = remaining; else delete next[prop];
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'clear':
|
|
99
|
+
delete next[prop];
|
|
100
|
+
break;
|
|
101
|
+
case 'set':
|
|
102
|
+
default:
|
|
103
|
+
if (value == null || value === '' || (Array.isArray(value) && !value.length)) {
|
|
104
|
+
delete next[prop];
|
|
105
|
+
} else {
|
|
106
|
+
next[prop] = value;
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
params.current = next;
|
|
111
|
+
mirror();
|
|
102
112
|
document.dispatchEvent(new CustomEvent('url-updated', {
|
|
103
|
-
detail: { updates, action }
|
|
113
|
+
detail: { updates: { [prop]: value }, action }
|
|
104
114
|
}));
|
|
105
|
-
|
|
106
|
-
return currentParams;
|
|
107
115
|
}
|
|
108
116
|
|
|
109
|
-
//
|
|
117
|
+
// ---- $url magic ---------------------------------------------------------
|
|
110
118
|
Alpine.magic('url', () => {
|
|
111
|
-
const store = Alpine.store('urlParams');
|
|
112
|
-
|
|
113
119
|
return new Proxy({}, {
|
|
114
120
|
get(target, prop) {
|
|
115
|
-
// Handle special keys
|
|
116
121
|
if (prop === Symbol.iterator || prop === 'then' || prop === 'catch' || prop === 'finally') {
|
|
117
122
|
return undefined;
|
|
118
123
|
}
|
|
119
|
-
|
|
120
|
-
// Get current value
|
|
121
|
-
const value = store.current[prop];
|
|
122
|
-
|
|
123
|
-
// Return a proxy for the value
|
|
124
124
|
return new Proxy({}, {
|
|
125
|
-
get(
|
|
125
|
+
get(t, key) {
|
|
126
|
+
// reads happen at access time so effects track them
|
|
126
127
|
if (key === 'value') {
|
|
127
|
-
|
|
128
|
+
const value = params.current[prop];
|
|
128
129
|
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)
|
|
130
|
+
if (typeof value === 'string' && value.includes(',')) return value.split(',').filter(Boolean);
|
|
133
131
|
return value;
|
|
134
132
|
}
|
|
135
|
-
if (key === '
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
};
|
|
133
|
+
if (key === 'first') {
|
|
134
|
+
const all = ensureArray(params.current[prop]);
|
|
135
|
+
return all.length ? all[0] : null;
|
|
136
|
+
}
|
|
137
|
+
if (key === 'all') return ensureArray(params.current[prop]);
|
|
138
|
+
if (key === 'set') return (v) => apply(prop, v, 'set');
|
|
139
|
+
if (key === 'add') return (v) => apply(prop, v, 'add');
|
|
140
|
+
// no-arg remove clears the whole param
|
|
141
|
+
if (key === 'remove') return (v) => apply(prop, v, v === undefined ? 'clear' : 'remove');
|
|
142
|
+
if (key === 'clear') return () => apply(prop, null, 'clear');
|
|
163
143
|
return undefined;
|
|
164
144
|
},
|
|
165
|
-
set(
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
}
|
|
145
|
+
set(t, key, v) {
|
|
146
|
+
// settable value for x-model compatibility
|
|
147
|
+
if (key === 'value') { apply(prop, v, 'set'); return true; }
|
|
175
148
|
return false;
|
|
176
149
|
}
|
|
177
150
|
});
|
|
178
151
|
}
|
|
179
152
|
});
|
|
180
153
|
});
|
|
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
154
|
}
|
|
198
155
|
|
|
199
156
|
// Track initialization to prevent duplicates
|
|
@@ -233,4 +190,4 @@ if (window.Alpine && typeof window.Alpine.directive === 'function') {
|
|
|
233
190
|
}
|
|
234
191
|
}, 10);
|
|
235
192
|
setTimeout(() => clearInterval(checkAlpine), 5000);
|
|
236
|
-
}
|
|
193
|
+
}
|