stx-router 0.2.706 → 0.2.708
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/client.js +44 -4
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -590,7 +590,38 @@ function routerSource() {
|
|
|
590
590
|
// Apply a page title captured from the fragment response's X-STX-Title
|
|
591
591
|
// header (URI-encoded) so SPA swaps keep document.title in sync with the
|
|
592
592
|
// page \u2014 full-document swaps set the title from the <title> tag directly.
|
|
593
|
-
function applyTitle(t){
|
|
593
|
+
function applyTitle(t,html){
|
|
594
|
+
// The parsed metadata payload already set the title with the browser's
|
|
595
|
+
// complete entity decoder. A legacy header must not overwrite it.
|
|
596
|
+
if(html&&/^<!--stx-fragment(?: rt=[01])?--><template data-stx-page-head="1">/.test(html))return;
|
|
597
|
+
if(t){try{document.title=decodeURIComponent(t)}catch(e){document.title=t}}
|
|
598
|
+
}
|
|
599
|
+
// Only these page-owned tags are reconciled. Assets, security tokens,
|
|
600
|
+
// theme metadata and arbitrary attributes never enter the contract (#2121).
|
|
601
|
+
function pageMetadataTag(node){
|
|
602
|
+
var tag=node.tagName.toLowerCase(),out;
|
|
603
|
+
if(tag==='meta'){
|
|
604
|
+
var kind=node.hasAttribute('property')?'property':'name';
|
|
605
|
+
var name=(node.getAttribute(kind)||'').toLowerCase();
|
|
606
|
+
if(name!=='description'&&!/^(og|twitter):[\\w:]+$/.test(name))return null;
|
|
607
|
+
out=ce('meta');out.setAttribute(kind,name);out.setAttribute('content',node.getAttribute('content')||'');return out;
|
|
608
|
+
}
|
|
609
|
+
if(tag==='link'){
|
|
610
|
+
var rel=(node.getAttribute('rel')||'').toLowerCase(),lang=node.getAttribute('hreflang');
|
|
611
|
+
if(rel!=='canonical'&&!(rel==='alternate'&&lang))return null;
|
|
612
|
+
out=ce('link');out.setAttribute('rel',rel);out.setAttribute('href',node.getAttribute('href')||'');
|
|
613
|
+
if(rel==='alternate')out.setAttribute('hreflang',lang);return out;
|
|
614
|
+
}
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
function pageMetadataNodes(root){return Array.prototype.slice.call(root.querySelectorAll('meta,link'))}
|
|
618
|
+
function capturePageHead(){return pageMetadataNodes(document.head).map(function(node){var tag=pageMetadataTag(node);return tag?tag.outerHTML:''}).join('')}
|
|
619
|
+
function applyPageHead(html){
|
|
620
|
+
var doc=new DOMParser().parseFromString('<html><head>'+html+'</head><body></body></html>','text/html');
|
|
621
|
+
pageMetadataNodes(document.head).forEach(function(node){if(pageMetadataTag(node))node.remove()});
|
|
622
|
+
pageMetadataNodes(doc.head).forEach(function(node){var tag=pageMetadataTag(node);if(tag)document.head.appendChild(tag)});
|
|
623
|
+
var title=doc.head.querySelector('title');if(title)document.title=title.textContent;
|
|
624
|
+
}
|
|
594
625
|
// \u2500\u2500 Container attributes \u2500\u2500
|
|
595
626
|
// A fragment carries only the container's INNER content, so any layout that
|
|
596
627
|
// lives on the destination's <main> itself (e.g. a centered auth page whose
|
|
@@ -1355,7 +1386,7 @@ function routerSource() {
|
|
|
1355
1386
|
var loading=beforeSwap(cached,url);
|
|
1356
1387
|
return (loading?loading.then(function(ok){if(!ok){if(!stale())recoverUnbound(url,ctx);return false}return swapCached()}):Promise.resolve(swapCached())).then(function(swapped){
|
|
1357
1388
|
if(swapped===false)return false;
|
|
1358
|
-
applyTitle(titleCache[targetPath]);
|
|
1389
|
+
applyTitle(titleCache[targetPath],cached);
|
|
1359
1390
|
// Shown from the cache at once; asked again behind it, so a page that
|
|
1360
1391
|
// changed since says so (stx:updated) instead of staying stale.
|
|
1361
1392
|
revalidate(targetPath,url);
|
|
@@ -1431,7 +1462,7 @@ else {
|
|
|
1431
1462
|
if(swapped===false)return false;
|
|
1432
1463
|
// Fragment swaps carry no <head>; apply the title from the header.
|
|
1433
1464
|
// Full-document swaps already set document.title from the <title> tag.
|
|
1434
|
-
if(result.isFragment)applyTitle(result.title);
|
|
1465
|
+
if(result.isFragment)applyTitle(result.title,result.html);
|
|
1435
1466
|
return true;
|
|
1436
1467
|
});
|
|
1437
1468
|
}).catch(failed).finally(done);
|
|
@@ -1727,6 +1758,11 @@ else {
|
|
|
1727
1758
|
var isFragment=!!fragMark;
|
|
1728
1759
|
var declaredRuntime=fragMark&&fragMark[1]?fragMark[1]:'';
|
|
1729
1760
|
if(isFragment)html=html.slice(fragMark[0].length);
|
|
1761
|
+
var incomingHead=null;
|
|
1762
|
+
if(isFragment){
|
|
1763
|
+
var headPayload=/^<template data-stx-page-head="1">([\\s\\S]*?)<\\/template>\\n?/.exec(html);
|
|
1764
|
+
if(headPayload){incomingHead=headPayload[1];html=html.slice(headPayload[0].length)}
|
|
1765
|
+
}
|
|
1730
1766
|
if(!isFragment&&!isStxDocument(html)){log('[router] non-stx document \u2014 full navigation to:',url);location.href=url;return Promise.resolve(false)}
|
|
1731
1767
|
var ctx=pendingCtx||{direction:pushState==='replace'?'replace':pushState==='tab'?'tab':pushState===false?'pop':'push'};
|
|
1732
1768
|
pendingCtx=null;
|
|
@@ -1974,6 +2010,7 @@ else {
|
|
|
1974
2010
|
setDirection(ctx.direction);
|
|
1975
2011
|
if(useScreens){scr=screenBegin(host,ctx,html,pushState!==false);currentContent=scr.el}
|
|
1976
2012
|
beginPageSwap();
|
|
2013
|
+
if(incomingHead!==null)applyPageHead(incomingHead);
|
|
1977
2014
|
doFragSwap();
|
|
1978
2015
|
if(scr)screenEnd(scr);
|
|
1979
2016
|
resolve(true);
|
|
@@ -2330,6 +2367,7 @@ else {
|
|
|
2330
2367
|
// Update title
|
|
2331
2368
|
var newTitle=doc.querySelector('title');
|
|
2332
2369
|
if(newTitle)document.title=newTitle.textContent;
|
|
2370
|
+
applyPageHead(doc.head.innerHTML);
|
|
2333
2371
|
|
|
2334
2372
|
// Update <html lang> from the destination doc so screen readers,
|
|
2335
2373
|
// CSS :lang() selectors, and any i18n picker that mirrors
|
|
@@ -2586,6 +2624,7 @@ else {
|
|
|
2586
2624
|
function saveEntry(e){
|
|
2587
2625
|
var stx=window.stx||{};
|
|
2588
2626
|
e.title=document.title;
|
|
2627
|
+
e.head=capturePageHead();
|
|
2589
2628
|
e.setup=stx._latestSetup;
|
|
2590
2629
|
e.data=window.__STX_DATA__;
|
|
2591
2630
|
e.config=window.__STX_RUNTIME_CONFIG__;
|
|
@@ -2595,6 +2634,7 @@ else {
|
|
|
2595
2634
|
function restoreEntry(e){
|
|
2596
2635
|
var stx=window.stx||{};
|
|
2597
2636
|
if(e.title)document.title=e.title;
|
|
2637
|
+
if(typeof e.head==='string')applyPageHead(e.head);
|
|
2598
2638
|
if(e.setup)stx._latestSetup=e.setup;
|
|
2599
2639
|
window.__STX_DATA__=e.data||{};
|
|
2600
2640
|
window.__STX_RUNTIME_CONFIG__=e.config||{};
|
|
@@ -3418,7 +3458,7 @@ else {
|
|
|
3418
3458
|
// 'replace' so re-rendered validation errors do not stack one history
|
|
3419
3459
|
// entry per attempt.
|
|
3420
3460
|
return Promise.resolve(swap(marked,cacheKey(action),'replace','')).then(function(){
|
|
3421
|
-
applyTitle(r.headers.get('X-STX-Title')||'');
|
|
3461
|
+
applyTitle(r.headers.get('X-STX-Title')||'',marked);
|
|
3422
3462
|
});
|
|
3423
3463
|
});
|
|
3424
3464
|
}).catch(function(err){
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stx-router",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.708",
|
|
5
5
|
"description": "File-based router for STX — .stx template discovery, nested layouts, typed route params, middleware, and client-side SPA navigation.",
|
|
6
6
|
"author": "Chris Breuer <chris@stacksjs.org>",
|
|
7
7
|
"license": "MIT",
|