fix(tabs): strip duplicate shell elements from canvas tab injection
The canvas.html body contained <rstack-tab-bar>, <rstack-space-settings>, and <rstack-history-panel> elements that weren't being stripped by extractCanvasContent (the tab-row regex failed due to extra children). When injected via TabCache, these duplicate elements interfered with the shell's tab management, causing tabs to appear wiped. Fixes: - Server: robust div-counting strip for rstack-tab-row + explicit strips for space-settings and history-panel - Client: DOM-based safety strip in TabCache.extractContent() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
13a7e44e24
commit
e8379541cc
|
|
@ -36,6 +36,29 @@ routes.get("/api/meta", async (c) => {
|
||||||
* Strips the shell chrome (header, tab-bar, welcome overlay) that renderShell provides,
|
* Strips the shell chrome (header, tab-bar, welcome overlay) that renderShell provides,
|
||||||
* and returns just the canvas-specific DOM + inline styles + module scripts.
|
* and returns just the canvas-specific DOM + inline styles + module scripts.
|
||||||
*/
|
*/
|
||||||
|
/** Strip a <div class="className"> and all its nested children by counting open/close tags. */
|
||||||
|
function stripNestedDiv(html: string, className: string): string {
|
||||||
|
const re = new RegExp(`<div\\s[^>]*class="[^"]*${className}[^"]*"[^>]*>`);
|
||||||
|
const match = html.match(re);
|
||||||
|
if (!match || match.index === undefined) return html;
|
||||||
|
|
||||||
|
let depth = 1;
|
||||||
|
let pos = match.index + match[0].length;
|
||||||
|
while (depth > 0 && pos < html.length) {
|
||||||
|
const nextOpen = html.indexOf("<div", pos);
|
||||||
|
const nextClose = html.indexOf("</div>", pos);
|
||||||
|
if (nextClose === -1) break;
|
||||||
|
if (nextOpen !== -1 && nextOpen < nextClose) {
|
||||||
|
depth++;
|
||||||
|
pos = nextOpen + 4;
|
||||||
|
} else {
|
||||||
|
depth--;
|
||||||
|
pos = nextClose + 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return html.substring(0, match.index) + html.substring(pos);
|
||||||
|
}
|
||||||
|
|
||||||
function extractCanvasContent(html: string): { body: string; styles: string; scripts: string } {
|
function extractCanvasContent(html: string): { body: string; styles: string; scripts: string } {
|
||||||
// Extract inline <style> blocks from <head> (canvas CSS)
|
// Extract inline <style> blocks from <head> (canvas CSS)
|
||||||
const headMatch = html.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
|
const headMatch = html.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
|
||||||
|
|
@ -58,11 +81,15 @@ function extractCanvasContent(html: string): { body: string; styles: string; scr
|
||||||
let bodyContent = bodyMatch?.[1] || "";
|
let bodyContent = bodyMatch?.[1] || "";
|
||||||
|
|
||||||
// Strip shell chrome that renderShell already provides:
|
// Strip shell chrome that renderShell already provides:
|
||||||
// header, tab-bar (contains only a <rstack-tab-bar>), and welcome overlay.
|
// header, tab-row (including all children like people badge/panel),
|
||||||
|
// space-settings, history-panel, and welcome overlay.
|
||||||
bodyContent = bodyContent
|
bodyContent = bodyContent
|
||||||
.replace(/<header[\s\S]*?<\/header>/i, "")
|
.replace(/<header[\s\S]*?<\/header>/i, "")
|
||||||
.replace(/<div class="rstack-tab-row"[^>]*>\s*<rstack-tab-bar[^>]*><\/rstack-tab-bar>\s*<\/div>/i, "")
|
.replace(/<rstack-space-settings[^>]*><\/rstack-space-settings>/gi, "")
|
||||||
|
.replace(/<rstack-history-panel[^>]*><\/rstack-history-panel>/gi, "")
|
||||||
.replace(/<!--\s*Welcome overlay[\s\S]*?<\/div>\s*<\/div>\s*<\/div>/i, "");
|
.replace(/<!--\s*Welcome overlay[\s\S]*?<\/div>\s*<\/div>\s*<\/div>/i, "");
|
||||||
|
// Strip the rstack-tab-row div (nested divs make simple regex unreliable)
|
||||||
|
bodyContent = stripNestedDiv(bodyContent, "rstack-tab-row");
|
||||||
|
|
||||||
const styles = styleBlocks.join("\n");
|
const styles = styleBlocks.join("\n");
|
||||||
const scripts = scriptSrcs.map(s => `<script type="module" crossorigin src="${s}"></script>`).join("\n");
|
const scripts = scriptSrcs.map(s => `<script type="module" crossorigin src="${s}"></script>`).join("\n");
|
||||||
|
|
|
||||||
|
|
@ -272,6 +272,10 @@ export class TabCache {
|
||||||
|
|
||||||
let body: string;
|
let body: string;
|
||||||
if (appEl) {
|
if (appEl) {
|
||||||
|
// Remove duplicate shell chrome that shouldn't be in tab panes
|
||||||
|
appEl.querySelectorAll(
|
||||||
|
".rstack-tab-row, rstack-tab-bar, rstack-space-settings, rstack-history-panel",
|
||||||
|
).forEach((el) => el.remove());
|
||||||
body = appEl.innerHTML;
|
body = appEl.innerHTML;
|
||||||
} else if (iframeWrap) {
|
} else if (iframeWrap) {
|
||||||
body = iframeWrap.outerHTML;
|
body = iframeWrap.outerHTML;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue