@hyperframes/core 0.7.14 → 0.7.15
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/compiler/staticGuard.js +1 -1
- package/dist/compiler/staticGuard.js.map +1 -1
- package/dist/core.types.d.ts +2 -369
- package/dist/core.types.d.ts.map +1 -1
- package/dist/core.types.js +1 -84
- package/dist/core.types.js.map +1 -1
- package/dist/generators/hyperframes.d.ts +1 -1
- package/dist/generators/hyperframes.d.ts.map +1 -1
- package/dist/generators/hyperframes.js +1 -1
- package/dist/generators/hyperframes.js.map +1 -1
- package/dist/index.d.ts +5 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/lint/index.d.ts +2 -2
- package/dist/lint/index.d.ts.map +1 -1
- package/dist/lint/index.js +2 -1
- package/dist/lint/index.js.map +1 -1
- package/dist/parsers/gsapConstants.d.ts +2 -12
- package/dist/parsers/gsapConstants.d.ts.map +1 -1
- package/dist/parsers/gsapConstants.js +2 -110
- package/dist/parsers/gsapConstants.js.map +1 -1
- package/dist/parsers/gsapParserAcorn.d.ts +2 -48
- package/dist/parsers/gsapParserAcorn.d.ts.map +1 -1
- package/dist/parsers/gsapParserAcorn.js +2 -1057
- package/dist/parsers/gsapParserAcorn.js.map +1 -1
- package/dist/parsers/gsapParserExports.d.ts +2 -20
- package/dist/parsers/gsapParserExports.d.ts.map +1 -1
- package/dist/parsers/gsapParserExports.js +2 -8
- package/dist/parsers/gsapParserExports.js.map +1 -1
- package/dist/parsers/gsapWriterAcorn.d.ts +2 -90
- package/dist/parsers/gsapWriterAcorn.d.ts.map +1 -1
- package/dist/parsers/gsapWriterAcorn.js +2 -2016
- package/dist/parsers/gsapWriterAcorn.js.map +1 -1
- package/dist/parsers/hfIds.d.ts +2 -16
- package/dist/parsers/hfIds.d.ts.map +1 -1
- package/dist/parsers/hfIds.js +2 -127
- package/dist/parsers/hfIds.js.map +1 -1
- package/dist/parsers/springEase.d.ts +2 -28
- package/dist/parsers/springEase.d.ts.map +1 -1
- package/dist/parsers/springEase.js +2 -69
- package/dist/parsers/springEase.js.map +1 -1
- package/dist/slideshow/index.d.ts +1 -0
- package/dist/slideshow/index.d.ts.map +1 -1
- package/dist/slideshow/index.js +1 -0
- package/dist/slideshow/index.js.map +1 -1
- package/dist/studio-api/helpers/draftMarkers.d.ts +2 -10
- package/dist/studio-api/helpers/draftMarkers.d.ts.map +1 -1
- package/dist/studio-api/helpers/draftMarkers.js +2 -10
- package/dist/studio-api/helpers/draftMarkers.js.map +1 -1
- package/dist/studio-api/helpers/finiteMutation.d.ts +2 -10
- package/dist/studio-api/helpers/finiteMutation.d.ts.map +1 -1
- package/dist/studio-api/helpers/finiteMutation.js +2 -20
- package/dist/studio-api/helpers/finiteMutation.js.map +1 -1
- package/dist/studio-api/helpers/manualEditsRenderScript.d.ts +2 -12
- package/dist/studio-api/helpers/manualEditsRenderScript.d.ts.map +1 -1
- package/dist/studio-api/helpers/manualEditsRenderScript.js +2 -632
- package/dist/studio-api/helpers/manualEditsRenderScript.js.map +1 -1
- package/dist/studio-api/helpers/screenshotClip.d.ts +2 -7
- package/dist/studio-api/helpers/screenshotClip.d.ts.map +1 -1
- package/dist/studio-api/helpers/screenshotClip.js +2 -21
- package/dist/studio-api/helpers/screenshotClip.js.map +1 -1
- package/dist/studio-api/helpers/studioMotionRenderScript.d.ts +2 -8
- package/dist/studio-api/helpers/studioMotionRenderScript.d.ts.map +1 -1
- package/dist/studio-api/helpers/studioMotionRenderScript.js +2 -207
- package/dist/studio-api/helpers/studioMotionRenderScript.js.map +1 -1
- package/dist/studio-api/index.d.ts +2 -9
- package/dist/studio-api/index.d.ts.map +1 -1
- package/dist/studio-api/index.js +2 -8
- package/dist/studio-api/index.js.map +1 -1
- package/package.json +16 -18
- package/dist/generated/runtime-inline.js +0 -11
- package/dist/hyperframe.manifest.json +0 -22
- package/dist/hyperframe.runtime.mjs +0 -325
- package/dist/lint/context.d.ts +0 -17
- package/dist/lint/context.d.ts.map +0 -1
- package/dist/lint/context.js +0 -37
- package/dist/lint/context.js.map +0 -1
- package/dist/lint/hyperframeLinter.d.ts +0 -14
- package/dist/lint/hyperframeLinter.d.ts.map +0 -1
- package/dist/lint/hyperframeLinter.js +0 -132
- package/dist/lint/hyperframeLinter.js.map +0 -1
- package/dist/lint/rules/adapters.d.ts +0 -3
- package/dist/lint/rules/adapters.d.ts.map +0 -1
- package/dist/lint/rules/adapters.js +0 -40
- package/dist/lint/rules/adapters.js.map +0 -1
- package/dist/lint/rules/captions.d.ts +0 -3
- package/dist/lint/rules/captions.d.ts.map +0 -1
- package/dist/lint/rules/captions.js +0 -242
- package/dist/lint/rules/captions.js.map +0 -1
- package/dist/lint/rules/composition.d.ts +0 -5
- package/dist/lint/rules/composition.d.ts.map +0 -1
- package/dist/lint/rules/composition.js +0 -694
- package/dist/lint/rules/composition.js.map +0 -1
- package/dist/lint/rules/core.d.ts +0 -3
- package/dist/lint/rules/core.d.ts.map +0 -1
- package/dist/lint/rules/core.js +0 -432
- package/dist/lint/rules/core.js.map +0 -1
- package/dist/lint/rules/fonts.d.ts +0 -3
- package/dist/lint/rules/fonts.d.ts.map +0 -1
- package/dist/lint/rules/fonts.js +0 -205
- package/dist/lint/rules/fonts.js.map +0 -1
- package/dist/lint/rules/gsap.d.ts +0 -4
- package/dist/lint/rules/gsap.d.ts.map +0 -1
- package/dist/lint/rules/gsap.js +0 -1039
- package/dist/lint/rules/gsap.js.map +0 -1
- package/dist/lint/rules/media.d.ts +0 -3
- package/dist/lint/rules/media.d.ts.map +0 -1
- package/dist/lint/rules/media.js +0 -491
- package/dist/lint/rules/media.js.map +0 -1
- package/dist/lint/rules/slideshow.d.ts +0 -4
- package/dist/lint/rules/slideshow.d.ts.map +0 -1
- package/dist/lint/rules/slideshow.js +0 -74
- package/dist/lint/rules/slideshow.js.map +0 -1
- package/dist/lint/rules/textures.d.ts +0 -3
- package/dist/lint/rules/textures.d.ts.map +0 -1
- package/dist/lint/rules/textures.js +0 -192
- package/dist/lint/rules/textures.js.map +0 -1
- package/dist/lint/types.d.ts +0 -36
- package/dist/lint/types.d.ts.map +0 -1
- package/dist/lint/types.js +0 -2
- package/dist/lint/types.js.map +0 -1
- package/dist/lint/utils.d.ts +0 -55
- package/dist/lint/utils.d.ts.map +0 -1
- package/dist/lint/utils.js +0 -264
- package/dist/lint/utils.js.map +0 -1
- package/dist/parsers/gsapInline.d.ts +0 -47
- package/dist/parsers/gsapInline.d.ts.map +0 -1
- package/dist/parsers/gsapInline.js +0 -513
- package/dist/parsers/gsapInline.js.map +0 -1
- package/dist/parsers/gsapParser.d.ts +0 -158
- package/dist/parsers/gsapParser.d.ts.map +0 -1
- package/dist/parsers/gsapParser.js +0 -2634
- package/dist/parsers/gsapParser.js.map +0 -1
- package/dist/parsers/gsapParser.test-helpers.d.ts +0 -49
- package/dist/parsers/gsapParser.test-helpers.d.ts.map +0 -1
- package/dist/parsers/gsapParser.test-helpers.js +0 -97
- package/dist/parsers/gsapParser.test-helpers.js.map +0 -1
- package/dist/parsers/gsapSerialize.d.ts +0 -191
- package/dist/parsers/gsapSerialize.d.ts.map +0 -1
- package/dist/parsers/gsapSerialize.js +0 -410
- package/dist/parsers/gsapSerialize.js.map +0 -1
- package/dist/parsers/gsapUnroll.d.ts +0 -7
- package/dist/parsers/gsapUnroll.d.ts.map +0 -1
- package/dist/parsers/gsapUnroll.js +0 -137
- package/dist/parsers/gsapUnroll.js.map +0 -1
- package/dist/parsers/htmlParser.d.ts +0 -27
- package/dist/parsers/htmlParser.d.ts.map +0 -1
- package/dist/parsers/htmlParser.js +0 -731
- package/dist/parsers/htmlParser.js.map +0 -1
- package/dist/studio-api/createStudioApi.d.ts +0 -10
- package/dist/studio-api/createStudioApi.d.ts.map +0 -1
- package/dist/studio-api/createStudioApi.js +0 -32
- package/dist/studio-api/createStudioApi.js.map +0 -1
- package/dist/studio-api/helpers/backupJournal.d.ts +0 -9
- package/dist/studio-api/helpers/backupJournal.d.ts.map +0 -1
- package/dist/studio-api/helpers/backupJournal.js +0 -81
- package/dist/studio-api/helpers/backupJournal.js.map +0 -1
- package/dist/studio-api/helpers/hfIdPersist.d.ts +0 -11
- package/dist/studio-api/helpers/hfIdPersist.d.ts.map +0 -1
- package/dist/studio-api/helpers/hfIdPersist.js +0 -39
- package/dist/studio-api/helpers/hfIdPersist.js.map +0 -1
- package/dist/studio-api/helpers/mediaValidation.d.ts +0 -20
- package/dist/studio-api/helpers/mediaValidation.d.ts.map +0 -1
- package/dist/studio-api/helpers/mediaValidation.js +0 -56
- package/dist/studio-api/helpers/mediaValidation.js.map +0 -1
- package/dist/studio-api/helpers/mime.d.ts +0 -4
- package/dist/studio-api/helpers/mime.d.ts.map +0 -1
- package/dist/studio-api/helpers/mime.js +0 -39
- package/dist/studio-api/helpers/mime.js.map +0 -1
- package/dist/studio-api/helpers/previewAdapter.d.ts +0 -43
- package/dist/studio-api/helpers/previewAdapter.d.ts.map +0 -1
- package/dist/studio-api/helpers/previewAdapter.js +0 -118
- package/dist/studio-api/helpers/previewAdapter.js.map +0 -1
- package/dist/studio-api/helpers/projectSignature.d.ts +0 -5
- package/dist/studio-api/helpers/projectSignature.d.ts.map +0 -1
- package/dist/studio-api/helpers/projectSignature.js +0 -154
- package/dist/studio-api/helpers/projectSignature.js.map +0 -1
- package/dist/studio-api/helpers/safePath.d.ts +0 -12
- package/dist/studio-api/helpers/safePath.d.ts.map +0 -1
- package/dist/studio-api/helpers/safePath.js +0 -38
- package/dist/studio-api/helpers/safePath.js.map +0 -1
- package/dist/studio-api/helpers/sourceMutation.d.ts +0 -28
- package/dist/studio-api/helpers/sourceMutation.d.ts.map +0 -1
- package/dist/studio-api/helpers/sourceMutation.js +0 -339
- package/dist/studio-api/helpers/sourceMutation.js.map +0 -1
- package/dist/studio-api/helpers/subComposition.d.ts +0 -20
- package/dist/studio-api/helpers/subComposition.d.ts.map +0 -1
- package/dist/studio-api/helpers/subComposition.js +0 -289
- package/dist/studio-api/helpers/subComposition.js.map +0 -1
- package/dist/studio-api/helpers/waveform.d.ts +0 -4
- package/dist/studio-api/helpers/waveform.d.ts.map +0 -1
- package/dist/studio-api/helpers/waveform.js +0 -80
- package/dist/studio-api/helpers/waveform.js.map +0 -1
- package/dist/studio-api/routes/files.d.ts +0 -4
- package/dist/studio-api/routes/files.d.ts.map +0 -1
- package/dist/studio-api/routes/files.js +0 -1200
- package/dist/studio-api/routes/files.js.map +0 -1
- package/dist/studio-api/routes/fonts.d.ts +0 -3
- package/dist/studio-api/routes/fonts.d.ts.map +0 -1
- package/dist/studio-api/routes/fonts.js +0 -157
- package/dist/studio-api/routes/fonts.js.map +0 -1
- package/dist/studio-api/routes/lint.d.ts +0 -4
- package/dist/studio-api/routes/lint.d.ts.map +0 -1
- package/dist/studio-api/routes/lint.js +0 -29
- package/dist/studio-api/routes/lint.js.map +0 -1
- package/dist/studio-api/routes/preview.d.ts +0 -4
- package/dist/studio-api/routes/preview.d.ts.map +0 -1
- package/dist/studio-api/routes/preview.js +0 -331
- package/dist/studio-api/routes/preview.js.map +0 -1
- package/dist/studio-api/routes/projects.d.ts +0 -4
- package/dist/studio-api/routes/projects.d.ts.map +0 -1
- package/dist/studio-api/routes/projects.js +0 -45
- package/dist/studio-api/routes/projects.js.map +0 -1
- package/dist/studio-api/routes/registry.d.ts +0 -4
- package/dist/studio-api/routes/registry.d.ts.map +0 -1
- package/dist/studio-api/routes/registry.js +0 -31
- package/dist/studio-api/routes/registry.js.map +0 -1
- package/dist/studio-api/routes/render.d.ts +0 -4
- package/dist/studio-api/routes/render.d.ts.map +0 -1
- package/dist/studio-api/routes/render.js +0 -269
- package/dist/studio-api/routes/render.js.map +0 -1
- package/dist/studio-api/routes/storyboard.d.ts +0 -4
- package/dist/studio-api/routes/storyboard.d.ts.map +0 -1
- package/dist/studio-api/routes/storyboard.js +0 -65
- package/dist/studio-api/routes/storyboard.js.map +0 -1
- package/dist/studio-api/routes/thumbnail.d.ts +0 -4
- package/dist/studio-api/routes/thumbnail.d.ts.map +0 -1
- package/dist/studio-api/routes/thumbnail.js +0 -107
- package/dist/studio-api/routes/thumbnail.js.map +0 -1
- package/dist/studio-api/routes/waveform.d.ts +0 -4
- package/dist/studio-api/routes/waveform.d.ts.map +0 -1
- package/dist/studio-api/routes/waveform.js +0 -41
- package/dist/studio-api/routes/waveform.js.map +0 -1
- package/dist/studio-api/types.d.ts +0 -120
- package/dist/studio-api/types.d.ts.map +0 -1
- package/dist/studio-api/types.js +0 -2
- package/dist/studio-api/types.js.map +0 -1
|
@@ -1,731 +0,0 @@
|
|
|
1
|
-
import { validateCompositionGsap } from "./gsapSerialize.js";
|
|
2
|
-
import { ensureHfIds } from "./hfIds.js";
|
|
3
|
-
import { parseGsapScriptAcornForWrite } from "./gsapParserAcorn.js";
|
|
4
|
-
import { queryByAttr } from "../utils/cssSelector.js";
|
|
5
|
-
import { removeAnimationFromScript } from "./gsapWriterAcorn.js";
|
|
6
|
-
const MEDIA_TYPES = new Set(["video", "image", "audio"]);
|
|
7
|
-
function getElementType(el) {
|
|
8
|
-
const tag = el.tagName.toLowerCase();
|
|
9
|
-
if (tag === "video")
|
|
10
|
-
return "video";
|
|
11
|
-
if (tag === "img")
|
|
12
|
-
return "image";
|
|
13
|
-
if (tag === "audio")
|
|
14
|
-
return "audio";
|
|
15
|
-
// Check for explicit data-type attribute first
|
|
16
|
-
const dataType = el.getAttribute("data-type");
|
|
17
|
-
if (dataType === "composition")
|
|
18
|
-
return "composition";
|
|
19
|
-
if (dataType === "text")
|
|
20
|
-
return "text";
|
|
21
|
-
// Fall back to tag-based detection for backwards compatibility
|
|
22
|
-
if (tag === "div" ||
|
|
23
|
-
tag === "p" ||
|
|
24
|
-
tag === "h1" ||
|
|
25
|
-
tag === "h2" ||
|
|
26
|
-
tag === "h3" ||
|
|
27
|
-
tag === "span") {
|
|
28
|
-
return "text";
|
|
29
|
-
}
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
function getElementName(el) {
|
|
33
|
-
const dataName = el.getAttribute("data-name");
|
|
34
|
-
if (dataName)
|
|
35
|
-
return dataName;
|
|
36
|
-
const type = getElementType(el);
|
|
37
|
-
if (type === "text") {
|
|
38
|
-
const text = el.textContent?.trim().slice(0, 30) || "Text";
|
|
39
|
-
return text.length === 30 ? text + "..." : text;
|
|
40
|
-
}
|
|
41
|
-
const src = el.getAttribute("src");
|
|
42
|
-
if (src) {
|
|
43
|
-
const filename = src.split("/").pop() || src;
|
|
44
|
-
return filename.split("?")[0] ?? filename;
|
|
45
|
-
}
|
|
46
|
-
return el.id || el.className?.toString().split(" ")[0] || "Element";
|
|
47
|
-
}
|
|
48
|
-
function getZIndex(el) {
|
|
49
|
-
const dataLayer = el.getAttribute("data-layer");
|
|
50
|
-
if (dataLayer)
|
|
51
|
-
return parseInt(dataLayer, 10) || 0;
|
|
52
|
-
const style = el.style?.zIndex;
|
|
53
|
-
if (style)
|
|
54
|
-
return parseInt(style, 10) || 0;
|
|
55
|
-
return 0;
|
|
56
|
-
}
|
|
57
|
-
function parseResolutionFromCss(doc, cssText) {
|
|
58
|
-
const stage = doc.getElementById("stage") || doc.querySelector("#stage");
|
|
59
|
-
if (stage) {
|
|
60
|
-
const inlineStyle = stage.style;
|
|
61
|
-
if (inlineStyle?.width && inlineStyle?.height) {
|
|
62
|
-
const w = parseInt(inlineStyle.width, 10);
|
|
63
|
-
const h = parseInt(inlineStyle.height, 10);
|
|
64
|
-
if (w && h) {
|
|
65
|
-
return resolveResolutionFromDimensions(w, h);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (cssText) {
|
|
70
|
-
const stageMatch = cssText.match(/#stage\s*\{[^}]*width:\s*(\d+)px[^}]*height:\s*(\d+)px[^}]*\}/);
|
|
71
|
-
if (stageMatch) {
|
|
72
|
-
const w = parseInt(stageMatch[1] ?? "", 10);
|
|
73
|
-
const h = parseInt(stageMatch[2] ?? "", 10);
|
|
74
|
-
return resolveResolutionFromDimensions(w, h);
|
|
75
|
-
}
|
|
76
|
-
const stageMatchReverse = cssText.match(/#stage\s*\{[^}]*height:\s*(\d+)px[^}]*width:\s*(\d+)px[^}]*\}/);
|
|
77
|
-
if (stageMatchReverse) {
|
|
78
|
-
const h = parseInt(stageMatchReverse[1] ?? "", 10);
|
|
79
|
-
const w = parseInt(stageMatchReverse[2] ?? "", 10);
|
|
80
|
-
return resolveResolutionFromDimensions(w, h);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return "portrait";
|
|
84
|
-
}
|
|
85
|
-
function parseResolutionFromHtml(doc) {
|
|
86
|
-
const htmlEl = doc.documentElement;
|
|
87
|
-
const resolutionAttr = htmlEl.getAttribute("data-resolution");
|
|
88
|
-
if (resolutionAttr === "landscape" ||
|
|
89
|
-
resolutionAttr === "portrait" ||
|
|
90
|
-
resolutionAttr === "landscape-4k" ||
|
|
91
|
-
resolutionAttr === "portrait-4k" ||
|
|
92
|
-
resolutionAttr === "square" ||
|
|
93
|
-
resolutionAttr === "square-4k") {
|
|
94
|
-
return resolutionAttr;
|
|
95
|
-
}
|
|
96
|
-
const widthAttr = htmlEl.getAttribute("data-composition-width");
|
|
97
|
-
const heightAttr = htmlEl.getAttribute("data-composition-height");
|
|
98
|
-
if (widthAttr && heightAttr) {
|
|
99
|
-
const width = parseInt(widthAttr, 10);
|
|
100
|
-
const height = parseInt(heightAttr, 10);
|
|
101
|
-
if (width && height) {
|
|
102
|
-
return resolveResolutionFromDimensions(width, height);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
const UHD_SQUARE_MIN = 2160;
|
|
108
|
-
const UHD_RECT_MIN = 3840;
|
|
109
|
-
function resolveResolutionFromDimensions(width, height) {
|
|
110
|
-
const longSide = Math.max(width, height);
|
|
111
|
-
if (width === height) {
|
|
112
|
-
return longSide >= UHD_SQUARE_MIN ? "square-4k" : "square";
|
|
113
|
-
}
|
|
114
|
-
const isLandscape = width > height;
|
|
115
|
-
const isUhd = longSide >= UHD_RECT_MIN;
|
|
116
|
-
if (isLandscape)
|
|
117
|
-
return isUhd ? "landscape-4k" : "landscape";
|
|
118
|
-
return isUhd ? "portrait-4k" : "portrait";
|
|
119
|
-
}
|
|
120
|
-
export function parseHtml(html) {
|
|
121
|
-
const withIds = ensureHfIds(html);
|
|
122
|
-
const parser = new DOMParser();
|
|
123
|
-
const doc = parser.parseFromString(withIds, "text/html");
|
|
124
|
-
const elements = [];
|
|
125
|
-
const keyframes = {};
|
|
126
|
-
let idCounter = 0;
|
|
127
|
-
const htmlEl = doc.documentElement;
|
|
128
|
-
const customStylesAttr = htmlEl.getAttribute("data-custom-styles");
|
|
129
|
-
let customStyles = null;
|
|
130
|
-
if (customStylesAttr) {
|
|
131
|
-
try {
|
|
132
|
-
customStyles = JSON.parse(customStylesAttr);
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
customStyles = customStylesAttr;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
const timedElements = doc.querySelectorAll("[data-start]");
|
|
139
|
-
timedElements.forEach((el) => {
|
|
140
|
-
const type = getElementType(el);
|
|
141
|
-
if (!type)
|
|
142
|
-
return;
|
|
143
|
-
const start = parseFloat(el.getAttribute("data-start") || "0");
|
|
144
|
-
const dataEnd = el.getAttribute("data-end");
|
|
145
|
-
let duration;
|
|
146
|
-
if (dataEnd) {
|
|
147
|
-
duration = Math.max(0, parseFloat(dataEnd) - start);
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
duration = 5;
|
|
151
|
-
}
|
|
152
|
-
// R1: stable hf- id minted by ensureHfIds above; clips just read it.
|
|
153
|
-
// Legacy/migration note: ensureHfIds pins a pre-existing `data-hf-id`, and
|
|
154
|
-
// the generator emits `data-hf-id="${element.id}"`. So a clip authored
|
|
155
|
-
// before R1 with `id="my-title"` round-trips as `data-hf-id="my-title"` —
|
|
156
|
-
// a non-`hf-`-shaped but still stable, exact-match handle. This is safe
|
|
157
|
-
// indefinitely: targeting uses exact `[data-hf-id="…"]` match (it does not
|
|
158
|
-
// require the hf- prefix). ensureHfIds skips elements that already carry
|
|
159
|
-
// data-hf-id, so legacy values are NOT re-minted automatically — they
|
|
160
|
-
// persist until the user re-saves the composition through Studio. Not a bug.
|
|
161
|
-
const id = el.getAttribute("data-hf-id") || el.id || `element-${++idCounter}`;
|
|
162
|
-
const name = getElementName(el);
|
|
163
|
-
const zIndex = getZIndex(el);
|
|
164
|
-
// Parse data-keyframes attribute if present
|
|
165
|
-
const keyframesAttr = el.getAttribute("data-keyframes");
|
|
166
|
-
if (keyframesAttr) {
|
|
167
|
-
try {
|
|
168
|
-
const parsedKeyframes = JSON.parse(keyframesAttr);
|
|
169
|
-
if (Array.isArray(parsedKeyframes) && parsedKeyframes.length > 0) {
|
|
170
|
-
keyframes[id] = parsedKeyframes;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
catch {
|
|
174
|
-
// skip invalid keyframes
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
// Parse transform properties (x, y, scale, opacity)
|
|
178
|
-
const xAttr = el.getAttribute("data-x");
|
|
179
|
-
const yAttr = el.getAttribute("data-y");
|
|
180
|
-
const scaleAttr = el.getAttribute("data-scale");
|
|
181
|
-
const opacityAttr = el.getAttribute("data-opacity");
|
|
182
|
-
const x = xAttr ? parseFloat(xAttr) : undefined;
|
|
183
|
-
const y = yAttr ? parseFloat(yAttr) : undefined;
|
|
184
|
-
const scale = scaleAttr ? parseFloat(scaleAttr) : undefined;
|
|
185
|
-
const opacity = opacityAttr ? parseFloat(opacityAttr) : undefined;
|
|
186
|
-
if (type === "text") {
|
|
187
|
-
const textEl = el.firstElementChild;
|
|
188
|
-
const content = textEl?.textContent || name;
|
|
189
|
-
const color = el.getAttribute("data-color") || undefined;
|
|
190
|
-
const fontSizeAttr = el.getAttribute("data-font-size");
|
|
191
|
-
const fontSize = fontSizeAttr ? parseInt(fontSizeAttr, 10) : undefined;
|
|
192
|
-
const fontWeightAttr = el.getAttribute("data-font-weight");
|
|
193
|
-
const fontWeight = fontWeightAttr ? parseInt(fontWeightAttr, 10) : undefined;
|
|
194
|
-
const fontFamily = el.getAttribute("data-font-family") || undefined;
|
|
195
|
-
const textShadowAttr = el.getAttribute("data-text-shadow");
|
|
196
|
-
const textShadow = textShadowAttr === "false" ? false : undefined;
|
|
197
|
-
// Parse outline properties
|
|
198
|
-
const textOutlineAttr = el.getAttribute("data-text-outline");
|
|
199
|
-
const textOutline = textOutlineAttr === "true" ? true : undefined;
|
|
200
|
-
const textOutlineColor = el.getAttribute("data-text-outline-color") || undefined;
|
|
201
|
-
const textOutlineWidthAttr = el.getAttribute("data-text-outline-width");
|
|
202
|
-
const textOutlineWidth = textOutlineWidthAttr
|
|
203
|
-
? parseInt(textOutlineWidthAttr, 10)
|
|
204
|
-
: undefined;
|
|
205
|
-
// Parse highlight properties
|
|
206
|
-
const textHighlightAttr = el.getAttribute("data-text-highlight");
|
|
207
|
-
const textHighlight = textHighlightAttr === "true" ? true : undefined;
|
|
208
|
-
const textHighlightColor = el.getAttribute("data-text-highlight-color") || undefined;
|
|
209
|
-
const textHighlightPaddingAttr = el.getAttribute("data-text-highlight-padding");
|
|
210
|
-
const textHighlightPadding = textHighlightPaddingAttr
|
|
211
|
-
? parseInt(textHighlightPaddingAttr, 10)
|
|
212
|
-
: undefined;
|
|
213
|
-
const textHighlightRadiusAttr = el.getAttribute("data-text-highlight-radius");
|
|
214
|
-
const textHighlightRadius = textHighlightRadiusAttr
|
|
215
|
-
? parseInt(textHighlightRadiusAttr, 10)
|
|
216
|
-
: undefined;
|
|
217
|
-
const textElement = {
|
|
218
|
-
id,
|
|
219
|
-
type: "text",
|
|
220
|
-
name,
|
|
221
|
-
content,
|
|
222
|
-
startTime: start,
|
|
223
|
-
duration,
|
|
224
|
-
zIndex,
|
|
225
|
-
x,
|
|
226
|
-
y,
|
|
227
|
-
scale,
|
|
228
|
-
opacity,
|
|
229
|
-
color,
|
|
230
|
-
fontSize,
|
|
231
|
-
fontWeight,
|
|
232
|
-
fontFamily,
|
|
233
|
-
textShadow,
|
|
234
|
-
textOutline,
|
|
235
|
-
textOutlineColor,
|
|
236
|
-
textOutlineWidth,
|
|
237
|
-
textHighlight,
|
|
238
|
-
textHighlightColor,
|
|
239
|
-
textHighlightPadding,
|
|
240
|
-
textHighlightRadius,
|
|
241
|
-
};
|
|
242
|
-
elements.push(textElement);
|
|
243
|
-
}
|
|
244
|
-
else if (type === "composition") {
|
|
245
|
-
// Composition is a div container with iframe inside
|
|
246
|
-
const iframe = el.querySelector("iframe");
|
|
247
|
-
const src = iframe?.getAttribute("src") || el.getAttribute("src") || "";
|
|
248
|
-
const compositionId = el.getAttribute("data-composition-id") || "";
|
|
249
|
-
const sourceDurationAttr = el.getAttribute("data-source-duration");
|
|
250
|
-
const sourceDuration = sourceDurationAttr ? parseFloat(sourceDurationAttr) : undefined;
|
|
251
|
-
const sourceWidthAttr = el.getAttribute("data-source-width");
|
|
252
|
-
const sourceWidth = sourceWidthAttr ? parseInt(sourceWidthAttr, 10) : undefined;
|
|
253
|
-
const sourceHeightAttr = el.getAttribute("data-source-height");
|
|
254
|
-
const sourceHeight = sourceHeightAttr ? parseInt(sourceHeightAttr, 10) : undefined;
|
|
255
|
-
// Parse variable values if present
|
|
256
|
-
const variableValuesAttr = el.getAttribute("data-variable-values");
|
|
257
|
-
let variableValues;
|
|
258
|
-
if (variableValuesAttr) {
|
|
259
|
-
try {
|
|
260
|
-
variableValues = JSON.parse(variableValuesAttr);
|
|
261
|
-
}
|
|
262
|
-
catch {
|
|
263
|
-
// skip invalid variable values
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
const compositionElement = {
|
|
267
|
-
id,
|
|
268
|
-
type: "composition",
|
|
269
|
-
name,
|
|
270
|
-
src,
|
|
271
|
-
compositionId,
|
|
272
|
-
startTime: start,
|
|
273
|
-
duration,
|
|
274
|
-
zIndex,
|
|
275
|
-
x,
|
|
276
|
-
y,
|
|
277
|
-
scale,
|
|
278
|
-
opacity,
|
|
279
|
-
sourceDuration,
|
|
280
|
-
sourceWidth,
|
|
281
|
-
sourceHeight,
|
|
282
|
-
variableValues,
|
|
283
|
-
};
|
|
284
|
-
elements.push(compositionElement);
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
if (!MEDIA_TYPES.has(type))
|
|
288
|
-
return;
|
|
289
|
-
const src = el.getAttribute("src") || "";
|
|
290
|
-
const mediaStartTimeAttr = el.getAttribute("data-media-start");
|
|
291
|
-
const mediaStartTime = mediaStartTimeAttr ? parseFloat(mediaStartTimeAttr) : undefined;
|
|
292
|
-
const sourceDurationAttr = el.getAttribute("data-source-duration");
|
|
293
|
-
const sourceDuration = sourceDurationAttr ? parseFloat(sourceDurationAttr) : undefined;
|
|
294
|
-
const isArollAttr = el.getAttribute("data-aroll");
|
|
295
|
-
const isAroll = isArollAttr === "true" ? true : undefined;
|
|
296
|
-
const volumeAttr = el.getAttribute("data-volume");
|
|
297
|
-
const volume = volumeAttr ? parseFloat(volumeAttr) : undefined;
|
|
298
|
-
const hasAudioAttr = el.getAttribute("data-has-audio");
|
|
299
|
-
const hasAudio = hasAudioAttr === "true" ? true : undefined;
|
|
300
|
-
const mediaElement = {
|
|
301
|
-
id,
|
|
302
|
-
type: type,
|
|
303
|
-
name,
|
|
304
|
-
src,
|
|
305
|
-
startTime: start,
|
|
306
|
-
duration,
|
|
307
|
-
zIndex,
|
|
308
|
-
x,
|
|
309
|
-
y,
|
|
310
|
-
scale,
|
|
311
|
-
opacity,
|
|
312
|
-
mediaStartTime,
|
|
313
|
-
sourceDuration,
|
|
314
|
-
isAroll,
|
|
315
|
-
volume,
|
|
316
|
-
hasAudio,
|
|
317
|
-
};
|
|
318
|
-
elements.push(mediaElement);
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
const scriptTags = doc.querySelectorAll("script");
|
|
322
|
-
let gsapScript = null;
|
|
323
|
-
for (const script of scriptTags) {
|
|
324
|
-
const src = script.getAttribute("src");
|
|
325
|
-
if (src && src.includes("gsap"))
|
|
326
|
-
continue;
|
|
327
|
-
const content = script.textContent?.trim();
|
|
328
|
-
if (content && (content.includes("gsap") || content.includes("timeline"))) {
|
|
329
|
-
gsapScript = content;
|
|
330
|
-
break;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
// Normalize keyframes (clamp negative time, convert absolute -> relative if detected)
|
|
334
|
-
for (const element of elements) {
|
|
335
|
-
const elementKeyframes = keyframes[element.id];
|
|
336
|
-
if (!elementKeyframes || elementKeyframes.length === 0)
|
|
337
|
-
continue;
|
|
338
|
-
const baseX = element.x ?? 0;
|
|
339
|
-
const baseY = element.y ?? 0;
|
|
340
|
-
const baseScale = element.type === "video" || element.type === "image" || element.type === "composition"
|
|
341
|
-
? (element.scale ?? 1)
|
|
342
|
-
: 1;
|
|
343
|
-
keyframes[element.id] = normalizeKeyframes(elementKeyframes, baseX, baseY, baseScale);
|
|
344
|
-
}
|
|
345
|
-
const styleTags = doc.querySelectorAll("style");
|
|
346
|
-
const allStyles = Array.from(styleTags)
|
|
347
|
-
.map((s) => s.textContent?.trim())
|
|
348
|
-
.filter(Boolean)
|
|
349
|
-
.join("\n\n") || null;
|
|
350
|
-
const customStyleTags = Array.from(styleTags).filter((s) => s.getAttribute("data-hf-custom") === "true");
|
|
351
|
-
const customStylesFromTags = customStyleTags
|
|
352
|
-
.map((s) => s.textContent?.trim())
|
|
353
|
-
.filter(Boolean)
|
|
354
|
-
.join("\n\n") || null;
|
|
355
|
-
const styles = customStyles ?? customStylesFromTags ?? null;
|
|
356
|
-
const resolution = parseResolutionFromHtml(doc) ?? parseResolutionFromCss(doc, allStyles);
|
|
357
|
-
// Parse stage zoom keyframes from zoom container
|
|
358
|
-
const stageZoomKeyframes = parseStageZoomKeyframes(doc);
|
|
359
|
-
return {
|
|
360
|
-
elements,
|
|
361
|
-
gsapScript,
|
|
362
|
-
styles,
|
|
363
|
-
resolution,
|
|
364
|
-
keyframes,
|
|
365
|
-
stageZoomKeyframes,
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
function parseStageZoomKeyframes(doc) {
|
|
369
|
-
const zoomContainer = doc.getElementById("stage-zoom-container");
|
|
370
|
-
if (!zoomContainer) {
|
|
371
|
-
return [];
|
|
372
|
-
}
|
|
373
|
-
const zoomKeyframesAttr = zoomContainer.getAttribute("data-zoom-keyframes");
|
|
374
|
-
if (!zoomKeyframesAttr) {
|
|
375
|
-
return [];
|
|
376
|
-
}
|
|
377
|
-
try {
|
|
378
|
-
const parsed = JSON.parse(zoomKeyframesAttr);
|
|
379
|
-
if (Array.isArray(parsed)) {
|
|
380
|
-
return parsed.filter((kf) => typeof kf === "object" &&
|
|
381
|
-
kf !== null &&
|
|
382
|
-
typeof kf.id === "string" &&
|
|
383
|
-
typeof kf.time === "number" &&
|
|
384
|
-
typeof kf.zoom === "object" &&
|
|
385
|
-
kf.zoom !== null &&
|
|
386
|
-
typeof kf.zoom.scale === "number" &&
|
|
387
|
-
typeof kf.zoom.focusX === "number" &&
|
|
388
|
-
typeof kf.zoom.focusY === "number");
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
catch {
|
|
392
|
-
// skip invalid zoom keyframes
|
|
393
|
-
}
|
|
394
|
-
return [];
|
|
395
|
-
}
|
|
396
|
-
function normalizeKeyframes(keyframes, baseX, baseY, baseScale) {
|
|
397
|
-
const timeEpsilon = 0.001;
|
|
398
|
-
const valueEpsilon = 0.00001;
|
|
399
|
-
const hasBaseCheck = (value, base) => value !== undefined && Math.abs(value - base) <= valueEpsilon && Math.abs(base) > valueEpsilon;
|
|
400
|
-
const timeZeroKeyframes = keyframes.filter((kf) => Math.abs(kf.time) <= timeEpsilon);
|
|
401
|
-
const treatAsAbsolute = timeZeroKeyframes.some((kf) => {
|
|
402
|
-
const props = kf.properties || {};
|
|
403
|
-
if (hasBaseCheck(props.x, baseX) ||
|
|
404
|
-
hasBaseCheck(props.y, baseY) ||
|
|
405
|
-
(baseScale !== 1 && hasBaseCheck(props.scale, baseScale))) {
|
|
406
|
-
return true;
|
|
407
|
-
}
|
|
408
|
-
return false;
|
|
409
|
-
});
|
|
410
|
-
return keyframes.map((kf) => {
|
|
411
|
-
const normalizedProps = {};
|
|
412
|
-
for (const [key, value] of Object.entries(kf.properties || {})) {
|
|
413
|
-
if (typeof value !== "number")
|
|
414
|
-
continue;
|
|
415
|
-
if (treatAsAbsolute && key === "x") {
|
|
416
|
-
normalizedProps.x = value - baseX;
|
|
417
|
-
}
|
|
418
|
-
else if (treatAsAbsolute && key === "y") {
|
|
419
|
-
normalizedProps.y = value - baseY;
|
|
420
|
-
}
|
|
421
|
-
else if (treatAsAbsolute && key === "scale") {
|
|
422
|
-
normalizedProps.scale = baseScale !== 0 ? value / baseScale : value;
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
normalizedProps[key] = value;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
return {
|
|
429
|
-
...kf,
|
|
430
|
-
time: Math.max(0, kf.time),
|
|
431
|
-
properties: normalizedProps,
|
|
432
|
-
};
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
export function updateElementInHtml(html, elementId, updates) {
|
|
436
|
-
const parser = new DOMParser();
|
|
437
|
-
const doc = parser.parseFromString(html, "text/html");
|
|
438
|
-
const el = doc.getElementById(elementId) || queryByAttr(doc, "data-name", elementId);
|
|
439
|
-
if (!el)
|
|
440
|
-
return html;
|
|
441
|
-
if (updates.startTime !== undefined) {
|
|
442
|
-
el.setAttribute("data-start", String(updates.startTime));
|
|
443
|
-
if (el.hasAttribute("data-end") && updates.duration !== undefined) {
|
|
444
|
-
el.setAttribute("data-end", String(updates.startTime + updates.duration));
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
if (updates.duration !== undefined) {
|
|
448
|
-
const start = parseFloat(el.getAttribute("data-start") || "0");
|
|
449
|
-
el.setAttribute("data-end", String(start + updates.duration));
|
|
450
|
-
el.removeAttribute("data-duration"); // Clean up legacy
|
|
451
|
-
}
|
|
452
|
-
if (updates.name !== undefined) {
|
|
453
|
-
el.setAttribute("data-name", updates.name);
|
|
454
|
-
}
|
|
455
|
-
if (updates.zIndex !== undefined) {
|
|
456
|
-
el.setAttribute("data-layer", String(updates.zIndex));
|
|
457
|
-
}
|
|
458
|
-
// Handle media-specific property
|
|
459
|
-
if ("src" in updates && updates.src !== undefined) {
|
|
460
|
-
el.setAttribute("src", updates.src);
|
|
461
|
-
}
|
|
462
|
-
// Handle text-specific properties
|
|
463
|
-
if ("content" in updates && updates.content !== undefined) {
|
|
464
|
-
const textEl = el.firstElementChild;
|
|
465
|
-
if (textEl) {
|
|
466
|
-
textEl.textContent = updates.content;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
if ("color" in updates && updates.color !== undefined) {
|
|
470
|
-
el.setAttribute("data-color", updates.color);
|
|
471
|
-
}
|
|
472
|
-
if ("fontSize" in updates && updates.fontSize !== undefined) {
|
|
473
|
-
el.setAttribute("data-font-size", String(updates.fontSize));
|
|
474
|
-
}
|
|
475
|
-
if ("textShadow" in updates) {
|
|
476
|
-
if (updates.textShadow === false) {
|
|
477
|
-
el.setAttribute("data-text-shadow", "false");
|
|
478
|
-
}
|
|
479
|
-
else {
|
|
480
|
-
el.removeAttribute("data-text-shadow");
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
// Handle volume property for audio/video
|
|
484
|
-
if ("volume" in updates) {
|
|
485
|
-
if (updates.volume !== undefined && updates.volume !== 1) {
|
|
486
|
-
el.setAttribute("data-volume", String(updates.volume));
|
|
487
|
-
}
|
|
488
|
-
else {
|
|
489
|
-
el.removeAttribute("data-volume");
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
// Handle hasAudio property for videos
|
|
493
|
-
if ("hasAudio" in updates) {
|
|
494
|
-
if (updates.hasAudio === true) {
|
|
495
|
-
el.setAttribute("data-has-audio", "true");
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
498
|
-
el.removeAttribute("data-has-audio");
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
return "<!DOCTYPE html>\n" + doc.documentElement.outerHTML;
|
|
502
|
-
}
|
|
503
|
-
export function addElementToHtml(html, element) {
|
|
504
|
-
const parser = new DOMParser();
|
|
505
|
-
const doc = parser.parseFromString(html, "text/html");
|
|
506
|
-
// Prefer zoom container, fall back to stage, then container, then body
|
|
507
|
-
const container = doc.querySelector("#stage-zoom-container") ||
|
|
508
|
-
doc.querySelector(".container") ||
|
|
509
|
-
doc.querySelector("#stage") ||
|
|
510
|
-
doc.body;
|
|
511
|
-
const id = element.id || `element-${Date.now()}`;
|
|
512
|
-
let newEl;
|
|
513
|
-
function applyMediaAttrs(el, mediaEl) {
|
|
514
|
-
if (mediaEl.src)
|
|
515
|
-
el.setAttribute("src", mediaEl.src);
|
|
516
|
-
if (mediaEl.volume !== undefined && mediaEl.volume !== 1) {
|
|
517
|
-
el.setAttribute("data-volume", String(mediaEl.volume));
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
switch (element.type) {
|
|
521
|
-
case "video": {
|
|
522
|
-
const mediaEl = element;
|
|
523
|
-
newEl = doc.createElement("video");
|
|
524
|
-
newEl.setAttribute("muted", "");
|
|
525
|
-
newEl.setAttribute("playsinline", "");
|
|
526
|
-
applyMediaAttrs(newEl, mediaEl);
|
|
527
|
-
if (mediaEl.hasAudio) {
|
|
528
|
-
newEl.setAttribute("data-has-audio", "true");
|
|
529
|
-
}
|
|
530
|
-
break;
|
|
531
|
-
}
|
|
532
|
-
case "image": {
|
|
533
|
-
const mediaEl = element;
|
|
534
|
-
newEl = doc.createElement("img");
|
|
535
|
-
if (mediaEl.src)
|
|
536
|
-
newEl.setAttribute("src", mediaEl.src);
|
|
537
|
-
newEl.setAttribute("alt", element.name);
|
|
538
|
-
break;
|
|
539
|
-
}
|
|
540
|
-
case "audio": {
|
|
541
|
-
const mediaEl = element;
|
|
542
|
-
newEl = doc.createElement("audio");
|
|
543
|
-
applyMediaAttrs(newEl, mediaEl);
|
|
544
|
-
break;
|
|
545
|
-
}
|
|
546
|
-
case "text":
|
|
547
|
-
default: {
|
|
548
|
-
const textEl = element;
|
|
549
|
-
newEl = doc.createElement("div");
|
|
550
|
-
const textContent = doc.createElement("div");
|
|
551
|
-
textContent.textContent = textEl.content || element.name;
|
|
552
|
-
newEl.appendChild(textContent);
|
|
553
|
-
if (textEl.color) {
|
|
554
|
-
newEl.setAttribute("data-color", textEl.color);
|
|
555
|
-
}
|
|
556
|
-
if (textEl.fontSize) {
|
|
557
|
-
newEl.setAttribute("data-font-size", String(textEl.fontSize));
|
|
558
|
-
}
|
|
559
|
-
break;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
newEl.id = id;
|
|
563
|
-
newEl.setAttribute("data-start", String(element.startTime));
|
|
564
|
-
newEl.setAttribute("data-end", String(element.startTime + element.duration));
|
|
565
|
-
newEl.setAttribute("data-layer", String(element.zIndex));
|
|
566
|
-
newEl.setAttribute("data-name", element.name);
|
|
567
|
-
container.appendChild(newEl);
|
|
568
|
-
return {
|
|
569
|
-
html: "<!DOCTYPE html>\n" + doc.documentElement.outerHTML,
|
|
570
|
-
id,
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
function selectorTargetsId(selector, id) {
|
|
574
|
-
return (selector === `#${id}` ||
|
|
575
|
-
selector === `[data-hf-id="${id}"]` ||
|
|
576
|
-
selector === `[data-hf-id='${id}']`);
|
|
577
|
-
}
|
|
578
|
-
function stripGsapForId(script, elementId) {
|
|
579
|
-
// Re-parse after every removal. Animation ids are count-based (positional), so
|
|
580
|
-
// removing one tween renumbers the survivors — ids captured from a single
|
|
581
|
-
// up-front parse go stale and silently no-op, orphaning later tweens on the
|
|
582
|
-
// now-deleted element. Always remove the FIRST still-matching animation in a
|
|
583
|
-
// freshly-parsed script until none remain.
|
|
584
|
-
let current = script;
|
|
585
|
-
for (;;) {
|
|
586
|
-
const parsed = parseGsapScriptAcornForWrite(current);
|
|
587
|
-
if (!parsed)
|
|
588
|
-
return current;
|
|
589
|
-
const match = parsed.located.find((l) => selectorTargetsId(l.animation.targetSelector, elementId));
|
|
590
|
-
if (!match)
|
|
591
|
-
return current;
|
|
592
|
-
const updated = removeAnimationFromScript(current, match.id);
|
|
593
|
-
// Guard against a non-removing match (would otherwise loop forever).
|
|
594
|
-
if (updated === current)
|
|
595
|
-
return current;
|
|
596
|
-
current = updated;
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
function cascadeRemoveGsapById(doc, elementId) {
|
|
600
|
-
for (const script of Array.from(doc.querySelectorAll("script"))) {
|
|
601
|
-
const text = script.textContent ?? "";
|
|
602
|
-
if (!text.includes("gsap") && !text.includes("ScrollTrigger"))
|
|
603
|
-
continue;
|
|
604
|
-
const updated = stripGsapForId(text, elementId);
|
|
605
|
-
if (updated !== text)
|
|
606
|
-
script.textContent = updated;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
export function removeElementFromHtml(html, elementId) {
|
|
610
|
-
const parser = new DOMParser();
|
|
611
|
-
const doc = parser.parseFromString(html, "text/html");
|
|
612
|
-
doc.getElementById(elementId)?.remove();
|
|
613
|
-
cascadeRemoveGsapById(doc, elementId);
|
|
614
|
-
return "<!DOCTYPE html>\n" + doc.documentElement.outerHTML;
|
|
615
|
-
}
|
|
616
|
-
export function extractCompositionMetadata(html) {
|
|
617
|
-
const parser = new DOMParser();
|
|
618
|
-
const doc = parser.parseFromString(html, "text/html");
|
|
619
|
-
const htmlEl = doc.documentElement;
|
|
620
|
-
const compositionId = htmlEl.getAttribute("data-composition-id");
|
|
621
|
-
const durationStr = htmlEl.getAttribute("data-composition-duration");
|
|
622
|
-
const compositionDuration = durationStr ? parseFloat(durationStr) : null;
|
|
623
|
-
const variables = parseCompositionVariables(htmlEl);
|
|
624
|
-
return {
|
|
625
|
-
compositionId,
|
|
626
|
-
compositionDuration: compositionDuration && isFinite(compositionDuration) ? compositionDuration : null,
|
|
627
|
-
variables,
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
function parseCompositionVariables(htmlEl) {
|
|
631
|
-
const variablesAttr = htmlEl.getAttribute("data-composition-variables");
|
|
632
|
-
if (!variablesAttr) {
|
|
633
|
-
return [];
|
|
634
|
-
}
|
|
635
|
-
try {
|
|
636
|
-
const parsed = JSON.parse(variablesAttr);
|
|
637
|
-
if (!Array.isArray(parsed)) {
|
|
638
|
-
return [];
|
|
639
|
-
}
|
|
640
|
-
return parsed.filter((v) => {
|
|
641
|
-
if (typeof v !== "object" || v === null)
|
|
642
|
-
return false;
|
|
643
|
-
if (typeof v.id !== "string" || typeof v.label !== "string")
|
|
644
|
-
return false;
|
|
645
|
-
if (!["string", "number", "color", "boolean", "enum", "font", "image"].includes(v.type))
|
|
646
|
-
return false;
|
|
647
|
-
switch (v.type) {
|
|
648
|
-
case "string":
|
|
649
|
-
return typeof v.default === "string";
|
|
650
|
-
case "number":
|
|
651
|
-
return typeof v.default === "number";
|
|
652
|
-
case "color":
|
|
653
|
-
return typeof v.default === "string";
|
|
654
|
-
case "boolean":
|
|
655
|
-
return typeof v.default === "boolean";
|
|
656
|
-
case "enum":
|
|
657
|
-
return typeof v.default === "string" && Array.isArray(v.options);
|
|
658
|
-
case "font":
|
|
659
|
-
// default is the font-family name string; extra metadata fields are optional
|
|
660
|
-
return typeof v.default === "string";
|
|
661
|
-
case "image":
|
|
662
|
-
// default is the fallback image URL string; extra metadata fields are optional
|
|
663
|
-
return typeof v.default === "string";
|
|
664
|
-
default:
|
|
665
|
-
return false;
|
|
666
|
-
}
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
catch {
|
|
670
|
-
return [];
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
export function validateCompositionHtml(html) {
|
|
674
|
-
const errors = [];
|
|
675
|
-
const warnings = [];
|
|
676
|
-
const parser = new DOMParser();
|
|
677
|
-
const doc = parser.parseFromString(html, "text/html");
|
|
678
|
-
const htmlEl = doc.documentElement;
|
|
679
|
-
const compositionId = htmlEl.getAttribute("data-composition-id");
|
|
680
|
-
if (!compositionId) {
|
|
681
|
-
errors.push("Missing data-composition-id attribute on <html> element");
|
|
682
|
-
}
|
|
683
|
-
const durationStr = htmlEl.getAttribute("data-composition-duration");
|
|
684
|
-
if (!durationStr) {
|
|
685
|
-
errors.push("Missing data-composition-duration attribute on <html> element");
|
|
686
|
-
}
|
|
687
|
-
else {
|
|
688
|
-
const duration = parseFloat(durationStr);
|
|
689
|
-
if (!isFinite(duration) || duration <= 0) {
|
|
690
|
-
errors.push("data-composition-duration must be a positive finite number");
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
const stage = doc.getElementById("stage");
|
|
694
|
-
if (!stage) {
|
|
695
|
-
errors.push("Missing #stage element");
|
|
696
|
-
}
|
|
697
|
-
if (/\son\w+\s*=/i.test(html)) {
|
|
698
|
-
errors.push("Inline event handlers (onclick, onload, etc.) not allowed");
|
|
699
|
-
}
|
|
700
|
-
if (/javascript\s*:/i.test(html)) {
|
|
701
|
-
errors.push("javascript: URLs not allowed");
|
|
702
|
-
}
|
|
703
|
-
const scripts = doc.querySelectorAll("script");
|
|
704
|
-
if (scripts.length > 2) {
|
|
705
|
-
warnings.push("Multiple script tags detected - only GSAP CDN and main script expected");
|
|
706
|
-
}
|
|
707
|
-
const gsapScript = extractGsapScript(doc);
|
|
708
|
-
if (gsapScript) {
|
|
709
|
-
const gsapValidation = validateCompositionGsap(gsapScript);
|
|
710
|
-
errors.push(...gsapValidation.errors);
|
|
711
|
-
warnings.push(...gsapValidation.warnings);
|
|
712
|
-
}
|
|
713
|
-
return {
|
|
714
|
-
valid: errors.length === 0,
|
|
715
|
-
errors,
|
|
716
|
-
warnings,
|
|
717
|
-
};
|
|
718
|
-
}
|
|
719
|
-
function extractGsapScript(doc) {
|
|
720
|
-
const scripts = doc.querySelectorAll("script");
|
|
721
|
-
for (const script of scripts) {
|
|
722
|
-
const content = script.textContent || "";
|
|
723
|
-
if (content.includes("gsap.timeline") ||
|
|
724
|
-
content.includes(".set(") ||
|
|
725
|
-
content.includes(".to(")) {
|
|
726
|
-
return content;
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
return null;
|
|
730
|
-
}
|
|
731
|
-
//# sourceMappingURL=htmlParser.js.map
|