fix: HTML 预览子页面改用 JS 切换,避免沙箱 blob 导航被拦截
上一版通过设置 location.hash 触发 CSS :target 切页,但沙箱 blob iframe 禁止导航到自身 blob URL,浏览器报 Not allowed to load local resource,且 :target 仅在真实片段导航时更新,注定失效。改为完全 不动 hash,用 JS 模拟 html-multipage 技能的 .page/.pages 显示隐藏 (内联 display 覆盖类级 CSS),普通目录锚点仍平滑滚动。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
21e53310f8
commit
2f414c6e08
1 file changed
+31
-21
@@ -102,14 +102,18 @@ function candidateTextFromHref(href: string): string {
|
|||||||
* Injects a small runtime script into generated HTML so that clicking an
|
* Injects a small runtime script into generated HTML so that clicking an
|
||||||
* in-page anchor (`<a href="#section">`) works inside the preview iframe.
|
* in-page anchor (`<a href="#section">`) works inside the preview iframe.
|
||||||
*
|
*
|
||||||
* The hash MUST really change on click: multi-page documents (the
|
* The preview loads from a sandboxed `blob:` URL (opaque origin). Such a frame
|
||||||
* html-multipage skill) show/hide their sub-page `<section class="page">`
|
* is FORBIDDEN from navigating to its own `blob:` URL, so changing the hash
|
||||||
* blocks with CSS `:target` rules, which only fire on a fragment navigation.
|
* throws "Not allowed to load local resource: blob:…#page-x". And because CSS
|
||||||
* Intercepting with preventDefault + scrollIntoView alone leaves the hash
|
* `:target` only updates on a real fragment navigation, the html-multipage
|
||||||
* untouched, so sub-pages stay `display:none` and clicks look dead. The
|
* skill's `:target`-based sub-page switching can never fire here.
|
||||||
* handler therefore syncs `location.hash` (a same-document navigation —
|
*
|
||||||
* safe in the sandboxed blob: frame) and only smooth-scrolls afterwards when
|
* So we never touch the hash. Instead the handler emulates the skill's
|
||||||
* the target is actually rendered.
|
* show/hide in JS: a "page" is a `.page` inside a `.pages` container; clicking
|
||||||
|
* a link to one shows it and hides its siblings (mirroring the
|
||||||
|
* `.page:target` + `~ #page-home` rules via inline `display`, which outranks
|
||||||
|
* the skill's class-level CSS). Plain in-page anchors (TOC headings, etc.) just
|
||||||
|
* smooth-scroll as before.
|
||||||
*/
|
*/
|
||||||
export function injectInPageAnchorHandler(html: string): string {
|
export function injectInPageAnchorHandler(html: string): string {
|
||||||
const script = `<script>(function(){
|
const script = `<script>(function(){
|
||||||
@@ -119,6 +123,22 @@ export function injectInPageAnchorHandler(html: string): string {
|
|||||||
var esc=window.CSS&&CSS.escape?CSS.escape(id):id.replace(/["\\\\]/g,"\\\\$&");
|
var esc=window.CSS&&CSS.escape?CSS.escape(id):id.replace(/["\\\\]/g,"\\\\$&");
|
||||||
try{return document.querySelector("#"+esc+",[name=\\""+esc+"\\"]");}catch(e){return null;}
|
try{return document.querySelector("#"+esc+",[name=\\""+esc+"\\"]");}catch(e){return null;}
|
||||||
}
|
}
|
||||||
|
function find(raw){
|
||||||
|
var id=decode(raw);
|
||||||
|
return document.getElementById(id)||document.getElementById(raw)||
|
||||||
|
document.getElementsByName(id)[0]||document.getElementsByName(raw)[0]||
|
||||||
|
bySelector(id)||bySelector(raw);
|
||||||
|
}
|
||||||
|
function switchPage(target){
|
||||||
|
var container=target.parentElement;
|
||||||
|
while(container&&!(container.classList&&container.classList.contains("pages"))){container=container.parentElement;}
|
||||||
|
var pages=(container||document).querySelectorAll(".page");
|
||||||
|
if(!pages.length)return false;
|
||||||
|
for(var i=0;i<pages.length;i++){pages[i].style.display="none";}
|
||||||
|
target.style.display="block";
|
||||||
|
if(window.scrollTo){window.scrollTo(0,0);}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
document.addEventListener("click",function(e){
|
document.addEventListener("click",function(e){
|
||||||
var el=e.target;
|
var el=e.target;
|
||||||
while(el&&el.tagName!=="A"){el=el.parentElement;}
|
while(el&&el.tagName!=="A"){el=el.parentElement;}
|
||||||
@@ -126,20 +146,10 @@ export function injectInPageAnchorHandler(html: string): string {
|
|||||||
var href=el.getAttribute("href");
|
var href=el.getAttribute("href");
|
||||||
if(!href||href.charAt(0)!=="#"||href.length<2)return;
|
if(!href||href.charAt(0)!=="#"||href.length<2)return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var raw=href.slice(1);
|
var target=find(href.slice(1));
|
||||||
var id=decode(raw);
|
|
||||||
var target=
|
|
||||||
document.getElementById(id)||
|
|
||||||
document.getElementById(raw)||
|
|
||||||
document.getElementsByName(id)[0]||
|
|
||||||
document.getElementsByName(raw)[0]||
|
|
||||||
bySelector(id)||
|
|
||||||
bySelector(raw);
|
|
||||||
try{if(location.hash!=="#"+raw){location.hash=raw;}}catch(err){}
|
|
||||||
if(!target)return;
|
if(!target)return;
|
||||||
requestAnimationFrame(function(){
|
if(target.classList&&target.classList.contains("page")&&switchPage(target))return;
|
||||||
if(target.getClientRects().length){target.scrollIntoView({behavior:"smooth",block:"start"});}
|
target.scrollIntoView({behavior:"smooth",block:"start"});
|
||||||
});
|
|
||||||
},true);
|
},true);
|
||||||
})();</script>`;
|
})();</script>`;
|
||||||
if (/<\/body>/i.test(html)) {
|
if (/<\/body>/i.test(html)) {
|
||||||
|
|||||||
Reference in new issue
Block a user