mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
fix(studio): a first animation in a sub-composition file runs from inside its template (#4806)
This commit is contained in:
@@ -196,6 +196,14 @@ describe("htmlDocument helpers", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("puts markup before the outer </template>, past a nested one and a commented one", () => {
|
||||
const sub =
|
||||
"<template id=t><div><template><i></i></template></div></template>\n<!-- </template> -->";
|
||||
expect(insertBeforeCloseTag(sub, "template", "X")).toBe(
|
||||
"<template id=t><div><template><i></i></template></div>X</template>\n<!-- </template> -->",
|
||||
);
|
||||
});
|
||||
|
||||
it("ends a comment at --!>, but not on its own opening dashes", () => {
|
||||
expect(insertBeforeCloseTag("<head><!-- a --!></head>", "head", "X")).toBe(
|
||||
"<head><!-- a --!>X</head>",
|
||||
|
||||
@@ -207,7 +207,7 @@ function inlineScriptTags(scripts: readonly string[]): string {
|
||||
|
||||
const RAW_TEXT_TAGS = ["script", "style", "title", "textarea"] as const;
|
||||
|
||||
type DocumentTag = "<head" | "</head" | "<body" | "</body";
|
||||
type DocumentTag = "<head" | "</head" | "<body" | "</body" | "</template";
|
||||
const COMMENT_END = /--!?>/g;
|
||||
|
||||
function* markupStarts(lowered: string): Generator<number> {
|
||||
@@ -290,15 +290,25 @@ function findRawTextClose(lowered: string, name: string, from: number): number {
|
||||
return -1;
|
||||
}
|
||||
|
||||
function findOuterTemplateClose(html: string): number {
|
||||
const lowered = lowerAscii(html);
|
||||
let depth = 0;
|
||||
for (const open of markupStarts(lowered)) {
|
||||
if (isTagAt(lowered, open, "<template")) depth++;
|
||||
else if (isTagAt(lowered, open, "</template") && --depth === 0) return open;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function insertBeforeDocumentTag(html: string, tag: DocumentTag, markup: string): string | null {
|
||||
const at = findDocumentTag(html, tag);
|
||||
const at = tag === "</template" ? findOuterTemplateClose(html) : findDocumentTag(html, tag);
|
||||
return at === -1 ? null : html.slice(0, at) + markup + html.slice(at);
|
||||
}
|
||||
|
||||
/** Insert markup just before the document's own `</head>` or `</body>`; null when it has none. */
|
||||
/** Insert markup before the document's own `</head>`, `</body>` or `</template>`; null if it has none. */
|
||||
export function insertBeforeCloseTag(
|
||||
html: string,
|
||||
name: "head" | "body",
|
||||
name: "head" | "body" | "template",
|
||||
markup: string,
|
||||
): string | null {
|
||||
return insertBeforeDocumentTag(html, `</${name}`, markup);
|
||||
|
||||
@@ -1721,6 +1721,49 @@ const tl = gsap.timeline({ paused: true });
|
||||
expect(html.indexOf('tl.set("#card"')).toBeGreaterThan(declaration);
|
||||
});
|
||||
|
||||
async function addFirstAnimation(sub: string): Promise<string> {
|
||||
const projectDir = createProjectDir();
|
||||
const app = new Hono();
|
||||
registerFileRoutes(app, createAdapter(projectDir));
|
||||
mkdirSync(join(projectDir, "compositions"), { recursive: true });
|
||||
writeHtml(projectDir, "compositions/sub.html", sub);
|
||||
const res = await app.request(
|
||||
"http://localhost/projects/demo/gsap-mutations/compositions/sub.html",
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
type: "add",
|
||||
targetSelector: "#card",
|
||||
method: "to",
|
||||
position: 0,
|
||||
properties: { opacity: 0 },
|
||||
}),
|
||||
},
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
return readFileSync(join(projectDir, "compositions/sub.html"), "utf-8");
|
||||
}
|
||||
|
||||
it("a first animation in a sub-composition file lands inside its template", async () => {
|
||||
const html = await addFirstAnimation(
|
||||
'<template id="sub-template"><div data-composition-id="sub"><div id="card"></div></div></template>\n',
|
||||
);
|
||||
const close = html.indexOf("</template>");
|
||||
expect(html.indexOf('window.__timelines["sub"]')).toBeGreaterThan(-1);
|
||||
expect(html.lastIndexOf("<script")).toBeLessThan(close);
|
||||
expect(html.slice(close).trim()).toBe("</template>");
|
||||
expect(html).not.toContain("<body");
|
||||
});
|
||||
|
||||
it("a first animation in a <body> file lands before </body>, outside its template", async () => {
|
||||
const html = await addFirstAnimation(
|
||||
'<body><template><div data-composition-id="sub"><div id="card"></div></div></template></body>\n',
|
||||
);
|
||||
expect(html.indexOf("<script")).toBeGreaterThan(html.indexOf("</template>"));
|
||||
expect(html.lastIndexOf("</script>")).toBeLessThan(html.indexOf("</body>"));
|
||||
});
|
||||
|
||||
it("consolidate-position-writes leaves exactly one position write per selector", async () => {
|
||||
const projectDir = createProjectDir();
|
||||
const CORRUPTED = `<!DOCTYPE html><html><body><script data-hyperframes-gsap>
|
||||
|
||||
@@ -1319,7 +1319,10 @@ async function prepareGsapMutationScript(
|
||||
`window.__timelines["${compId}"] = tl;`,
|
||||
"</script>",
|
||||
].join("\n");
|
||||
html = insertBeforeCloseTag(html, "body", `${bootstrap}\n`) ?? `${html}\n${bootstrap}`;
|
||||
html =
|
||||
insertBeforeCloseTag(html, "body", `${bootstrap}\n`) ??
|
||||
insertBeforeCloseTag(html, "template", `${bootstrap}\n`) ??
|
||||
`${html}\n${bootstrap}`;
|
||||
block = extractGsapScriptBlock(html);
|
||||
}
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user