mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-02 11:47:13 +00:00
fix(studio-server): repeated splits stop stacking id CSS rules (#6874)
* fix(studio-server): repeated splits stop stacking id CSS rules * fix(studio-server): keep rules that exclude an id with :not when dropping its rules
This commit is contained in:
@@ -45,7 +45,44 @@ export function parseSourceDocument(raw: string): {
|
||||
return { document, wrappedFragment: !hasDocumentShell };
|
||||
}
|
||||
|
||||
/** Drops the rules that style `id` alone (every selector in the list names it), in each style sheet of the page. */
|
||||
function dropCssRulesForId(document: Document, id: string): void {
|
||||
const idToken = `#${id}`;
|
||||
const namesId = (selector: string) => {
|
||||
let found = false;
|
||||
selectorParser((selectors) => {
|
||||
selectors.walkIds((node) => {
|
||||
// an id inside :not() excludes the element, so the rule styles the others
|
||||
let up = node.parent;
|
||||
while (up && !(up.type === "pseudo" && up.value === ":not")) up = up.parent as typeof up;
|
||||
if (node.value === id && !up) found = true;
|
||||
});
|
||||
}).processSync(selector);
|
||||
return found;
|
||||
};
|
||||
for (const styleEl of document.querySelectorAll("style")) {
|
||||
const css = styleEl.textContent ?? "";
|
||||
if (!css.includes(idToken)) continue;
|
||||
let root: postcss.Root;
|
||||
try {
|
||||
root = postcss.parse(css);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
let dropped = false;
|
||||
root.walkRules((rule) => {
|
||||
if (rule.selectors.length > 0 && rule.selectors.every(namesId)) {
|
||||
rule.remove();
|
||||
dropped = true;
|
||||
}
|
||||
});
|
||||
if (dropped) styleEl.textContent = root.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// A clone's rules replace whatever an earlier piece of that id left behind, so repeated cuts keep one set per id.
|
||||
function duplicateCssRulesForId(document: Document, originalId: string, newId: string): void {
|
||||
dropCssRulesForId(document, newId);
|
||||
const idToken = `#${originalId}`;
|
||||
const transform = selectorParser((selectors) => {
|
||||
selectors.walkIds((node) => {
|
||||
@@ -161,7 +198,9 @@ export function removeElementsFromHtml(
|
||||
for (const target of targets) {
|
||||
const element = findTargetElement(document, target);
|
||||
if (!element) continue;
|
||||
const id = element.getAttribute("id");
|
||||
removeElementWithGsapCascade(document, element);
|
||||
if (id && !document.getElementById(id)) dropCssRulesForId(document, id);
|
||||
removed = true;
|
||||
}
|
||||
if (!removed) return source;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { parseHTML } from "linkedom";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { readMediaOffsetSeconds } from "@hyperframes/parsers/media-duration";
|
||||
import {
|
||||
removeElementFromHtml,
|
||||
splitElementInHtml,
|
||||
relinkSplitHalvesInHtml,
|
||||
unwrapElementsFromHtml,
|
||||
@@ -539,3 +540,23 @@ describe("relinkSplitHalvesInHtml", () => {
|
||||
expect(relinkSplitHalvesInHtml(source, ["i"])).toBe(source);
|
||||
});
|
||||
});
|
||||
|
||||
describe("splitElementInHtml — id CSS rules across repeated cuts", () => {
|
||||
it("keeps one rule set per live id when a clip is cut again and again", () => {
|
||||
let html =
|
||||
"<html><head><style>#a{opacity:.5;width:10px}#a:hover,#a.on{opacity:1}.clip:not(#a-cut){filter:blur(2px)}</style></head><body>" +
|
||||
'<div id="root" data-composition-id="m"><video id="a" src="a.mp4" data-start="0" data-duration="60" ' +
|
||||
'data-track-index="0"></video></div></body></html>';
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const tail = splitElementInHtml(html, { id: "a" }, 20 + i, `a-${i + 2}`);
|
||||
const middle = splitElementInHtml(tail.html, { id: "a" }, 10 + i, "a-cut");
|
||||
html = removeElementFromHtml(middle.html, { id: "a-cut" });
|
||||
}
|
||||
const css = /<style>([\s\S]*?)<\/style>/.exec(html)?.[1] ?? "";
|
||||
// two rules per live id, none left by the removed cut piece
|
||||
const live = html.match(/<video\b/g)?.length ?? 0;
|
||||
expect(css.match(/\{/g)).toHaveLength(live * 2 + 1);
|
||||
expect(css).not.toContain("#a-cut{");
|
||||
expect(css).toContain(".clip:not(#a-cut){filter:blur(2px)}");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user