mnfst 0.5.45 → 0.5.47
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/dist/manifest.data.js +61 -0
- package/dist/manifest.localization.js +32 -0
- package/package.json +1 -1
package/dist/manifest.data.js
CHANGED
|
@@ -207,6 +207,10 @@ const rawDataStore = new Map();
|
|
|
207
207
|
let isInitializing = false;
|
|
208
208
|
let initializationComplete = false;
|
|
209
209
|
|
|
210
|
+
// Render-ready: debounced timer used by checkAndDispatchRenderReady
|
|
211
|
+
let _renderReadyTimer = null;
|
|
212
|
+
const RENDER_READY_QUIET_MS = 150; // ms of quiet (no loading) before firing
|
|
213
|
+
|
|
210
214
|
// Deep seal an object to prevent Alpine from making it reactive
|
|
211
215
|
// This prevents double-proxying which causes recursion errors
|
|
212
216
|
function deepSeal(obj) {
|
|
@@ -301,6 +305,12 @@ function updateStore(dataSourceName, data, options = {}) {
|
|
|
301
305
|
|
|
302
306
|
Alpine.store('data', updatedStore);
|
|
303
307
|
|
|
308
|
+
// When a source finishes loading (success or error), check if everything is settled.
|
|
309
|
+
// This is the primary trigger for manifest:render-ready.
|
|
310
|
+
if (!newState.loading) {
|
|
311
|
+
checkAndDispatchRenderReady();
|
|
312
|
+
}
|
|
313
|
+
|
|
304
314
|
// Attach methods to array if it's an array (for new architecture)
|
|
305
315
|
// This ensures methods are available on the new array reference
|
|
306
316
|
if (Array.isArray(reactiveData) && window.ManifestDataProxies?.attachArrayMethods) {
|
|
@@ -766,6 +776,49 @@ function setupTeamChangeListener() {
|
|
|
766
776
|
}
|
|
767
777
|
}
|
|
768
778
|
|
|
779
|
+
// Dispatch manifest:render-ready when all tracked data sources have settled.
|
|
780
|
+
// Uses a debounce so rapid sequential source completions coalesce into one event.
|
|
781
|
+
// The render script listens for this event instead of polling internal store state.
|
|
782
|
+
function checkAndDispatchRenderReady() {
|
|
783
|
+
if (_renderReadyTimer) {
|
|
784
|
+
clearTimeout(_renderReadyTimer);
|
|
785
|
+
}
|
|
786
|
+
_renderReadyTimer = setTimeout(() => {
|
|
787
|
+
_renderReadyTimer = null;
|
|
788
|
+
try {
|
|
789
|
+
if (typeof window === 'undefined' || typeof Alpine === 'undefined') return;
|
|
790
|
+
const store = Alpine.store('data');
|
|
791
|
+
if (!store) return;
|
|
792
|
+
|
|
793
|
+
// Don't fire while a locale change is still in progress
|
|
794
|
+
if (store._localeChanging) return;
|
|
795
|
+
|
|
796
|
+
// Don't fire if any source state still shows loading
|
|
797
|
+
for (const key of Object.keys(store)) {
|
|
798
|
+
if (key.startsWith('_') && key.endsWith('_state')) {
|
|
799
|
+
if (store[key]?.loading) return;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Don't fire if any fetch promises are still in flight
|
|
804
|
+
if (loadingPromises.size > 0) return;
|
|
805
|
+
|
|
806
|
+
// All settled — dispatch the authoritative prerender signal
|
|
807
|
+
const locale =
|
|
808
|
+
(typeof document !== 'undefined' && document.documentElement.lang) ||
|
|
809
|
+
Alpine.store('locale')?.current ||
|
|
810
|
+
'en';
|
|
811
|
+
const sources = Object.keys(store).filter(k => !k.startsWith('_') && k !== 'all');
|
|
812
|
+
|
|
813
|
+
window.dispatchEvent(new CustomEvent('manifest:render-ready', {
|
|
814
|
+
detail: { locale, sources }
|
|
815
|
+
}));
|
|
816
|
+
} catch {
|
|
817
|
+
// Silently fail — the render script has its own timeout fallback
|
|
818
|
+
}
|
|
819
|
+
}, RENDER_READY_QUIET_MS);
|
|
820
|
+
}
|
|
821
|
+
|
|
769
822
|
// Listen for locale changes to reload data
|
|
770
823
|
function setupLocaleChangeListener() {
|
|
771
824
|
window.addEventListener('localechange', async (event) => {
|
|
@@ -900,6 +953,10 @@ function setupLocaleChangeListener() {
|
|
|
900
953
|
);
|
|
901
954
|
}
|
|
902
955
|
|
|
956
|
+
// All localized sources have reloaded — check if everything is settled.
|
|
957
|
+
// This fires manifest:render-ready after a locale change completes end-to-end.
|
|
958
|
+
checkAndDispatchRenderReady();
|
|
959
|
+
|
|
903
960
|
} catch (error) {
|
|
904
961
|
console.error('[Manifest Data] Error handling locale change:', error);
|
|
905
962
|
// Fallback to full reload if something goes wrong
|
|
@@ -1125,6 +1182,7 @@ window.ManifestDataStore = {
|
|
|
1125
1182
|
initializeStore,
|
|
1126
1183
|
setupLocaleChangeListener,
|
|
1127
1184
|
setupTeamChangeListener,
|
|
1185
|
+
checkAndDispatchRenderReady,
|
|
1128
1186
|
// Operation-specific loading state helpers
|
|
1129
1187
|
setCreatingEntry,
|
|
1130
1188
|
clearCreatingEntry,
|
|
@@ -11732,6 +11790,9 @@ async function initializeDataSourcesPlugin() {
|
|
|
11732
11790
|
if (typeof window !== 'undefined') {
|
|
11733
11791
|
window.dispatchEvent(new CustomEvent('manifest:data-ready'));
|
|
11734
11792
|
}
|
|
11793
|
+
// Check if all sources are settled and dispatch manifest:render-ready if so.
|
|
11794
|
+
// Covers the initial-load path (no data sources, or only content pre-loaded).
|
|
11795
|
+
window.ManifestDataStore?.checkAndDispatchRenderReady?.();
|
|
11735
11796
|
};
|
|
11736
11797
|
if (typeof Alpine !== 'undefined' && Alpine.nextTick) {
|
|
11737
11798
|
Alpine.nextTick(flushThenDispatch);
|
|
@@ -97,6 +97,38 @@ function initializeLocalizationPlugin() {
|
|
|
97
97
|
const currentUrl = new URL(window.location.href);
|
|
98
98
|
const pathParts = currentUrl.pathname.split('/').filter(Boolean);
|
|
99
99
|
const hasLanguageInUrl = pathParts[0] && availableLocales.includes(pathParts[0]);
|
|
100
|
+
|
|
101
|
+
// Determine path segments without any current locale prefix, for exclude-pattern checking.
|
|
102
|
+
const pathWithoutLocale = hasLanguageInUrl ? pathParts.slice(1) : pathParts;
|
|
103
|
+
|
|
104
|
+
// If the current path matches a manifest:locale-route-exclude pattern, do NOT add or
|
|
105
|
+
// change the locale prefix — this prevents an infinite redirect loop on prerendered
|
|
106
|
+
// builds where normalizeRedundantLocalePrefixInUrl() strips the locale from the URL
|
|
107
|
+
// but the localization init would otherwise re-add it via window.location.assign().
|
|
108
|
+
const routeExcludeMeta = document.querySelector('meta[name="manifest:locale-route-exclude"]');
|
|
109
|
+
if (routeExcludeMeta) {
|
|
110
|
+
try {
|
|
111
|
+
const rawContent = (routeExcludeMeta.getAttribute('content') || '').replace(/"/g, '"');
|
|
112
|
+
const patterns = JSON.parse(rawContent);
|
|
113
|
+
if (Array.isArray(patterns) && patterns.length > 0) {
|
|
114
|
+
const lower = pathWithoutLocale.map(s => s.toLowerCase());
|
|
115
|
+
for (const pattern of patterns) {
|
|
116
|
+
const p = String(pattern).trim().replace(/^\/+/, '').split('/').filter(Boolean).map(x => x.toLowerCase());
|
|
117
|
+
if (p.length === 0) continue;
|
|
118
|
+
if (lower.length < p.length) continue;
|
|
119
|
+
let match = true;
|
|
120
|
+
for (let i = 0; i < p.length; i++) {
|
|
121
|
+
if (lower[i] !== p[i]) { match = false; break; }
|
|
122
|
+
}
|
|
123
|
+
if (match) {
|
|
124
|
+
// Path is locale-excluded — return URL unchanged so no navigation is triggered
|
|
125
|
+
return currentUrl.toString();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch { /* ignore JSON parse errors */ }
|
|
130
|
+
}
|
|
131
|
+
|
|
100
132
|
if (hasLanguageInUrl) pathParts[0] = newLang;
|
|
101
133
|
else pathParts.unshift(newLang);
|
|
102
134
|
currentUrl.pathname = '/' + pathParts.join('/');
|