mnfst 0.5.41 → 0.5.42
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.router.js +145 -12
- package/package.json +1 -1
package/dist/manifest.router.js
CHANGED
|
@@ -173,6 +173,87 @@ function getBasePath() {
|
|
|
173
173
|
return (typeof window.getManifestBasePath === 'function' ? window.getManifestBasePath() : '') || '';
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
// Locale codes from manifest data (same idea as ManifestRouting.matchesCondition).
|
|
177
|
+
function getLocalizationCodesFromManifest() {
|
|
178
|
+
const localizationCodes = [];
|
|
179
|
+
try {
|
|
180
|
+
const manifest = window.ManifestComponentsRegistry?.manifest || window.manifest;
|
|
181
|
+
if (manifest?.data && typeof manifest.data === 'object') {
|
|
182
|
+
Object.values(manifest.data).forEach((dataSource) => {
|
|
183
|
+
if (typeof dataSource === 'object' && dataSource !== null) {
|
|
184
|
+
Object.keys(dataSource).forEach((key) => {
|
|
185
|
+
if (key.match(/^[a-z]{2}(-[A-Z]{2})?$/)) {
|
|
186
|
+
localizationCodes.push(key);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
} catch {
|
|
193
|
+
/* ignore */
|
|
194
|
+
}
|
|
195
|
+
return [...new Set(localizationCodes)];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function logicalSegmentsFromPathname(pathname) {
|
|
199
|
+
const logical = pathnameToLogical(pathname);
|
|
200
|
+
const s = logical.replace(/^\/+|\/+$/g, '');
|
|
201
|
+
return s ? s.split('/') : [];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const STICKY_LOCALE_SKIP_FIRST_SEGMENTS = new Set([
|
|
205
|
+
'api',
|
|
206
|
+
'assets',
|
|
207
|
+
'static',
|
|
208
|
+
'public',
|
|
209
|
+
'dist',
|
|
210
|
+
'icons',
|
|
211
|
+
'fonts',
|
|
212
|
+
'media',
|
|
213
|
+
'.well-known',
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
const STICKY_LOCALE_STATIC_FILE_EXT = new Set([
|
|
217
|
+
'js', 'mjs', 'cjs', 'css', 'map', 'png', 'jpg', 'jpeg', 'webp', 'gif', 'svg', 'ico',
|
|
218
|
+
'woff', 'woff2', 'ttf', 'eot', 'json', 'xml', 'txt', 'pdf', 'zip', 'wasm', 'avif',
|
|
219
|
+
'mp4', 'webm', 'mp3',
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
function shouldSkipStickyLocaleForLogicalSegments(segments) {
|
|
223
|
+
if (!segments.length) return false;
|
|
224
|
+
if (STICKY_LOCALE_SKIP_FIRST_SEGMENTS.has(segments[0])) return true;
|
|
225
|
+
const last = segments[segments.length - 1];
|
|
226
|
+
if (!last || !last.includes('.')) return false;
|
|
227
|
+
const ext = last.slice(last.lastIndexOf('.') + 1).toLowerCase();
|
|
228
|
+
return STICKY_LOCALE_STATIC_FILE_EXT.has(ext);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// When the URL already has a locale prefix (e.g. /zh/pricing), keep it for same-origin links
|
|
232
|
+
// that omit the prefix (/articles → /zh/articles). No-op on default-locale URLs (/pricing).
|
|
233
|
+
function applyStickyLocaleToPathname(absolutePathname) {
|
|
234
|
+
const codes = getLocalizationCodesFromManifest();
|
|
235
|
+
if (!codes.length) return absolutePathname;
|
|
236
|
+
|
|
237
|
+
const currentSegs = logicalSegmentsFromPathname(window.location.pathname);
|
|
238
|
+
const sticky = currentSegs.length && codes.includes(currentSegs[0]) ? currentSegs[0] : null;
|
|
239
|
+
if (!sticky) return absolutePathname;
|
|
240
|
+
|
|
241
|
+
const targetSegs = logicalSegmentsFromPathname(absolutePathname);
|
|
242
|
+
if (targetSegs.length && codes.includes(targetSegs[0])) {
|
|
243
|
+
return absolutePathname;
|
|
244
|
+
}
|
|
245
|
+
if (shouldSkipStickyLocaleForLogicalSegments(targetSegs)) {
|
|
246
|
+
return absolutePathname;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const base = getBasePath();
|
|
250
|
+
const newLogical = targetSegs.length ? `/${sticky}/${targetSegs.join('/')}` : `/${sticky}`;
|
|
251
|
+
const normalizedLogical = newLogical.replace(/\/{2,}/g, '/') || '/';
|
|
252
|
+
if (!base) return normalizedLogical;
|
|
253
|
+
const combined = `${base}${normalizedLogical}`.replace(/([^:])\/{2,}/g, '$1/');
|
|
254
|
+
return combined.startsWith('/') ? combined : `/${combined}`;
|
|
255
|
+
}
|
|
256
|
+
|
|
176
257
|
function pathnameToLogical(pathname) {
|
|
177
258
|
const base = getBasePath();
|
|
178
259
|
if (!base) {
|
|
@@ -252,27 +333,79 @@ function resolveHref(href) {
|
|
|
252
333
|
const url = new URL(href, baseUrl);
|
|
253
334
|
if (url.origin !== window.location.origin) return href;
|
|
254
335
|
let path = url.pathname.replace(/\/$/, '') || '/';
|
|
255
|
-
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
if (path.startsWith('/')) {
|
|
336
|
+
let resolved;
|
|
337
|
+
if (!base) {
|
|
338
|
+
resolved = path.startsWith('/') ? path : '/' + path;
|
|
339
|
+
} else if (path === base || path.startsWith(base + '/')) {
|
|
340
|
+
resolved = path;
|
|
341
|
+
} else if (path.startsWith('/')) {
|
|
259
342
|
const pathSegs = path.split('/').filter(Boolean);
|
|
260
343
|
const baseSegs = base.split('/').filter(Boolean);
|
|
261
344
|
let i = 0;
|
|
262
345
|
while (i < baseSegs.length && i < pathSegs.length && baseSegs[i] === pathSegs[i]) i++;
|
|
263
346
|
const routeSegs = pathSegs.slice(i);
|
|
264
|
-
if (routeSegs.length)
|
|
347
|
+
if (routeSegs.length) {
|
|
348
|
+
resolved = base + '/' + routeSegs.join('/');
|
|
349
|
+
} else {
|
|
350
|
+
const out = base + (path.startsWith('/') ? path : '/' + path);
|
|
351
|
+
resolved = out.startsWith('/') ? out : '/' + out;
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
const out = base + (path.startsWith('/') ? path : '/' + path);
|
|
355
|
+
resolved = out.startsWith('/') ? out : '/' + out;
|
|
265
356
|
}
|
|
266
|
-
|
|
267
|
-
return out.startsWith('/') ? out : '/' + out;
|
|
357
|
+
return applyStickyLocaleToPathname(resolved);
|
|
268
358
|
} catch {
|
|
269
359
|
const base = getBasePath();
|
|
270
360
|
const safe = (href || '').trim();
|
|
271
|
-
if (!safe) return base || '/';
|
|
272
|
-
|
|
361
|
+
if (!safe) return applyStickyLocaleToPathname(base || '/');
|
|
362
|
+
const raw = base ? (base + (safe.startsWith('/') ? safe : '/' + safe)) : (safe.startsWith('/') ? safe : '/' + safe);
|
|
363
|
+
return applyStickyLocaleToPathname(raw);
|
|
273
364
|
}
|
|
274
365
|
}
|
|
275
366
|
|
|
367
|
+
// Prerendered MPA: same-origin navigations use full page loads; rewrite targets so locale prefix sticks.
|
|
368
|
+
function installMpaStickyLocaleLinks() {
|
|
369
|
+
document.addEventListener('click', (event) => {
|
|
370
|
+
if (event.defaultPrevented) return;
|
|
371
|
+
if (event.button !== 0) return;
|
|
372
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
373
|
+
|
|
374
|
+
const link = event.target.closest('a');
|
|
375
|
+
if (!link || link.closest('[data-manifest-skip-locale-sticky]')) return;
|
|
376
|
+
if (link.hasAttribute('download')) return;
|
|
377
|
+
|
|
378
|
+
const hrefAttr = link.getAttribute('href');
|
|
379
|
+
if (!hrefAttr || hrefAttr.startsWith('mailto:') || hrefAttr.startsWith('tel:') || hrefAttr.startsWith('javascript:')) return;
|
|
380
|
+
if (hrefAttr.startsWith('#')) return;
|
|
381
|
+
|
|
382
|
+
let url;
|
|
383
|
+
try {
|
|
384
|
+
url = new URL(hrefAttr, window.location.href);
|
|
385
|
+
} catch {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (url.origin !== window.location.origin) return;
|
|
389
|
+
|
|
390
|
+
const path = url.pathname.replace(/\/$/, '') || '/';
|
|
391
|
+
const adjusted = applyStickyLocaleToPathname(path);
|
|
392
|
+
if (adjusted === path) return;
|
|
393
|
+
|
|
394
|
+
event.preventDefault();
|
|
395
|
+
url.pathname = adjusted;
|
|
396
|
+
|
|
397
|
+
const dest = url.toString();
|
|
398
|
+
if (link.target === '_blank') {
|
|
399
|
+
const features = link.relList?.contains('noopener') || link.relList?.contains('noreferrer')
|
|
400
|
+
? 'noopener,noreferrer'
|
|
401
|
+
: undefined;
|
|
402
|
+
window.open(dest, '_blank', features);
|
|
403
|
+
} else {
|
|
404
|
+
window.location.assign(dest);
|
|
405
|
+
}
|
|
406
|
+
}, true);
|
|
407
|
+
}
|
|
408
|
+
|
|
276
409
|
// Intercept link clicks to prevent page reloads
|
|
277
410
|
function interceptLinkClicks() {
|
|
278
411
|
// Use capture phase to intercept before other handlers
|
|
@@ -359,17 +492,17 @@ function initializeNavigation() {
|
|
|
359
492
|
// Set initial route (logical path for matching)
|
|
360
493
|
currentRoute = pathnameToLogical(window.location.pathname);
|
|
361
494
|
|
|
362
|
-
// In prerendered/static output, use default browser navigation (no interception)
|
|
495
|
+
// In prerendered/static output, use default browser navigation (no SPA interception)
|
|
363
496
|
if (!isPrerenderedStaticBuild()) {
|
|
364
|
-
// Intercept link clicks
|
|
365
497
|
interceptLinkClicks();
|
|
366
498
|
|
|
367
|
-
// Listen for popstate events (browser back/forward)
|
|
368
499
|
window.addEventListener('popstate', () => {
|
|
369
500
|
if (!isInternalNavigation) {
|
|
370
501
|
handleRouteChange();
|
|
371
502
|
}
|
|
372
503
|
});
|
|
504
|
+
} else {
|
|
505
|
+
installMpaStickyLocaleLinks();
|
|
373
506
|
}
|
|
374
507
|
|
|
375
508
|
// Handle initial route
|