@hyperframes/studio-server 0.7.55 → 0.7.56
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/helpers/sourceMutation.js +5 -1
- package/dist/helpers/sourceMutation.js.map +1 -1
- package/dist/index.js +172 -90
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -3,6 +3,7 @@ import { parseHTML } from "linkedom";
|
|
|
3
3
|
import postcss from "postcss";
|
|
4
4
|
import selectorParser from "postcss-selector-parser";
|
|
5
5
|
import { isAllowedHtmlAttribute, isSafeAttributeValue } from "@hyperframes/core/html-attr-safety";
|
|
6
|
+
import { ensureHfIds } from "@hyperframes/parsers/hf-ids";
|
|
6
7
|
|
|
7
8
|
// src/helpers/sourceStyleMutation.ts
|
|
8
9
|
function parseStyleDecls(style) {
|
|
@@ -286,8 +287,11 @@ function splitElementInHtml(source, target, splitTime, newId, fallbackTiming) {
|
|
|
286
287
|
} else {
|
|
287
288
|
el.parentElement.appendChild(clone);
|
|
288
289
|
}
|
|
290
|
+
const html = wrappedFragment ? document.body.innerHTML || "" : document.toString();
|
|
289
291
|
return {
|
|
290
|
-
|
|
292
|
+
// The split owns its new nodes' stable ids. Leaving the clone unstamped makes
|
|
293
|
+
// the next preview request persist different bytes after history is recorded.
|
|
294
|
+
html: ensureHfIds(html),
|
|
291
295
|
matched: true,
|
|
292
296
|
newId
|
|
293
297
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/helpers/sourceMutation.ts","../../src/helpers/sourceStyleMutation.ts"],"sourcesContent":["import { parseHTML } from \"linkedom\";\nimport postcss from \"postcss\";\nimport selectorParser from \"postcss-selector-parser\";\nimport { isAllowedHtmlAttribute, isSafeAttributeValue } from \"@hyperframes/core/html-attr-safety\";\nimport { parseStyleDecls, patchStyleAttrString } from \"./sourceStyleMutation.js\";\n\nexport interface SourceMutationTarget {\n id?: string | null;\n hfId?: string;\n selector?: string;\n selectorIndex?: number;\n}\n\nfunction parseSourceDocument(source: string): { document: Document; wrappedFragment: boolean } {\n const hasDocumentShell = /<!doctype|<html[\\s>]/i.test(source);\n if (hasDocumentShell) {\n return { document: parseHTML(source).document, wrappedFragment: false };\n }\n return {\n document: parseHTML(`<!DOCTYPE html><html><head></head><body>${source}</body></html>`).document,\n wrappedFragment: true,\n };\n}\n\nfunction duplicateCssRulesForId(document: Document, originalId: string, newId: string): void {\n const idToken = `#${originalId}`;\n const transform = selectorParser((selectors) => {\n selectors.walkIds((node) => {\n if (node.value === originalId) node.value = newId;\n });\n });\n for (const styleEl of document.querySelectorAll(\"style\")) {\n const css = styleEl.textContent ?? \"\";\n let root: postcss.Root;\n try {\n root = postcss.parse(css);\n } catch {\n continue;\n }\n const clones: postcss.Rule[] = [];\n root.walkRules((rule) => {\n if (!rule.selector.includes(idToken)) return;\n const newSelector = transform.processSync(rule.selector);\n if (newSelector === rule.selector) return;\n const clone = rule.clone({ selector: newSelector });\n clones.push(clone);\n });\n if (clones.length > 0) {\n for (const c of clones) root.append(c);\n styleEl.textContent = root.toString();\n }\n }\n}\n\nfunction querySelectorAllWithTemplates(root: Document | Element, selector: string): Element[] {\n const matches = Array.from(root.querySelectorAll(selector));\n if (matches.length > 0) return matches;\n // querySelectorAll doesn't traverse <template> content in linkedom.\n // Search directly on each template element (NOT .content — removing from\n // .content's DocumentFragment doesn't update the serialized output).\n // Recurse so NESTED templates resolve too — ensureHfIds and the SDK's\n // querySelectorAllDeep descend nested composition templates, so ids exist at\n // any template depth; a one-level search here would silently no-op\n // server-side ops on those ids while the SDK resolves them.\n const templates = Array.from(root.querySelectorAll(\"template\"));\n for (const tmpl of templates) {\n const inner = querySelectorAllWithTemplates(tmpl, selector);\n if (inner.length > 0) return inner;\n }\n return [];\n}\n\n// Prevent CSS attribute-selector injection via a crafted hfId: escape\n// backslashes first, then double-quotes. Keeps a malformed/hostile value from\n// breaking out of the `[data-hf-id=\"…\"]` selector once callers beyond the\n// internal mint contract (R2+ user flows) pass values here.\nfunction escapeCssAttrValue(value: string): string {\n return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n}\n\nfunction findByHfId(document: Document, hfId: string): Element | null {\n try {\n const matches = querySelectorAllWithTemplates(\n document,\n `[data-hf-id=\"${escapeCssAttrValue(hfId)}\"]`,\n );\n if (matches.length > 1) {\n // The mint contract guarantees uniqueness; a duplicate means upstream\n // id drift. Don't silently patch an arbitrary one — surface it.\n // eslint-disable-next-line no-console\n console.warn(\n `sourceMutation: data-hf-id \"${hfId}\" matched ${matches.length} elements; using the first. ids must be unique per document.`,\n );\n }\n return matches[0] ?? null;\n } catch {\n // Malformed selector despite escaping — let the caller fall back.\n return null;\n }\n}\n\nfunction findTargetElement(document: Document, target: SourceMutationTarget): Element | null {\n if (target.hfId) {\n const el = findByHfId(document, target.hfId);\n if (el) return el;\n }\n\n if (target.id) {\n const byId = document.getElementById(target.id);\n if (byId) return byId;\n }\n\n if (!target.selector) return null;\n try {\n const matches = querySelectorAllWithTemplates(document, target.selector);\n return matches[target.selectorIndex ?? 0] ?? null;\n } catch {\n return null;\n }\n}\n\nexport function removeElementFromHtml(source: string, target: SourceMutationTarget): string {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const element = findTargetElement(document, target);\n if (!element) return source;\n\n element.remove();\n return wrappedFragment ? document.body.innerHTML || \"\" : document.toString();\n}\n\nexport function isHTMLElement(el: Node): el is HTMLElement {\n const HTMLEl = el.ownerDocument?.defaultView?.HTMLElement;\n return HTMLEl ? el instanceof HTMLEl : el.nodeType === 1 && \"style\" in el;\n}\n\nexport interface PatchOperation {\n type: \"inline-style\" | \"attribute\" | \"html-attribute\" | \"text-content\";\n property: string;\n value: string | null;\n childSelector?: string;\n childIndex?: number;\n}\n\ninterface ResolvedPatchOperation {\n op: PatchOperation;\n target: HTMLElement;\n}\n\nfunction resolveOperationTarget(parent: HTMLElement, op: PatchOperation): HTMLElement | null {\n if (op.childSelector === undefined) return parent;\n try {\n const child = parent.querySelectorAll(op.childSelector)[op.childIndex ?? 0] ?? null;\n return child && isHTMLElement(child) ? child : null;\n } catch {\n return null;\n }\n}\n\n// fallow-ignore-next-line complexity\nexport function patchElementInHtml(\n source: string,\n target: SourceMutationTarget,\n operations: PatchOperation[],\n): { html: string; matched: boolean } {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const el = findTargetElement(document, target);\n if (!el || !isHTMLElement(el)) return { html: source, matched: false };\n const htmlEl = el;\n\n const resolved: ResolvedPatchOperation[] = [];\n for (const op of operations) {\n const opTarget = resolveOperationTarget(htmlEl, op);\n if (!opTarget) return { html: source, matched: false };\n resolved.push({ op, target: opTarget });\n }\n\n for (const { op, target: opTarget } of resolved) {\n switch (op.type) {\n case \"inline-style\":\n // linkedom's CSSStyleDeclaration does not support CSS custom properties\n // (--foo) or newer individual transform properties (translate, rotate,\n // scale) via style.setProperty(). Manipulate the style attribute string\n // directly so all property names survive the round-trip.\n {\n const raw = opTarget.getAttribute(\"style\") ?? \"\";\n const patched = patchStyleAttrString(raw, op.property, op.value);\n opTarget.setAttribute(\"style\", patched);\n }\n break;\n case \"attribute\":\n {\n const fullAttr = op.property.startsWith(\"data-\") ? op.property : `data-${op.property}`;\n if (op.value != null) {\n opTarget.setAttribute(fullAttr, op.value);\n } else {\n opTarget.removeAttribute(fullAttr);\n }\n }\n break;\n case \"html-attribute\":\n if (!isAllowedHtmlAttribute(op.property)) break;\n if (op.value != null) {\n if (!isSafeAttributeValue(op.property, op.value)) break;\n opTarget.setAttribute(op.property, op.value);\n } else {\n opTarget.removeAttribute(op.property);\n }\n break;\n case \"text-content\":\n if (op.value != null) {\n const inner = opTarget.children.length === 1 ? opTarget.firstElementChild : null;\n const textTarget = inner && isHTMLElement(inner) ? inner : opTarget;\n textTarget.textContent = op.value;\n }\n break;\n }\n }\n\n return {\n html: wrappedFragment ? document.body.innerHTML || \"\" : document.toString(),\n matched: true,\n };\n}\n\nexport function probeElementInSource(source: string, target: SourceMutationTarget): boolean {\n if (!target.id && !target.hfId && !target.selector) return false;\n const { document } = parseSourceDocument(source);\n const el = findTargetElement(document, target);\n return el != null && isHTMLElement(el);\n}\n\nexport interface SplitElementResult {\n html: string;\n matched: boolean;\n newId: string | null;\n}\n\nfunction resolveElementTiming(el: Element): {\n start: number;\n duration: number;\n usesDataEnd: boolean;\n} {\n const start = parseFloat(el.getAttribute(\"data-start\") ?? \"0\") || 0;\n const usesDataEnd = el.hasAttribute(\"data-end\");\n const duration = usesDataEnd\n ? parseFloat(el.getAttribute(\"data-end\") ?? \"\") - start || 0\n : parseFloat(el.getAttribute(\"data-duration\") ?? \"0\") || 0;\n return { start, duration, usesDataEnd };\n}\n\nfunction setElementDuration(\n el: Element,\n start: number,\n duration: number,\n usesDataEnd: boolean,\n): void {\n if (usesDataEnd) {\n const endTime = String(Math.round((start + duration) * 1000) / 1000);\n el.setAttribute(\"data-end\", endTime);\n el.removeAttribute(\"data-duration\");\n } else {\n el.setAttribute(\"data-duration\", String(Math.round(duration * 1000) / 1000));\n el.removeAttribute(\"data-end\");\n }\n}\n\n// fallow-ignore-next-line complexity\nexport function splitElementInHtml(\n source: string,\n target: SourceMutationTarget,\n splitTime: number,\n newId: string,\n fallbackTiming?: { start: number; duration: number },\n): SplitElementResult {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const el = findTargetElement(document, target);\n if (!el || !isHTMLElement(el)) return { html: source, matched: false, newId: null };\n\n const timing = resolveElementTiming(el);\n const { usesDataEnd } = timing;\n let { start, duration } = timing;\n // GSAP-animated elements carry their timing in the script, not in data-* attrs,\n // so the source has no authored duration. Fall back to the store's (GSAP-derived)\n // range — the runtime windows visibility off data-start/data-duration regardless\n // of class, so stamping both halves below makes each half show only in its window.\n if (duration <= 0 && fallbackTiming && fallbackTiming.duration > 0) {\n start = fallbackTiming.start;\n duration = fallbackTiming.duration;\n }\n if (duration <= 0 || splitTime <= start || splitTime >= start + duration) {\n return { html: source, matched: false, newId: null };\n }\n\n if (document.getElementById(newId)) {\n let suffix = 2;\n const base = newId;\n while (document.getElementById(newId)) {\n newId = `${base}-${suffix++}`;\n }\n }\n\n const firstDuration = splitTime - start;\n const secondDuration = duration - firstDuration;\n\n const clone = el.cloneNode(true);\n if (!isHTMLElement(clone)) return { html: source, matched: false, newId: null };\n clone.setAttribute(\"id\", newId);\n clone.removeAttribute(\"data-hf-id\");\n // Descendants carry their own data-hf-id; leaving them duplicates the id of\n // every nested node (e.g. an inner <span>), so strip them on the clone too.\n for (const node of clone.querySelectorAll(\"[data-hf-id]\")) node.removeAttribute(\"data-hf-id\");\n clone.setAttribute(\"data-start\", String(Math.round(splitTime * 1000) / 1000));\n setElementDuration(clone, splitTime, secondDuration, usesDataEnd);\n\n // Keep the \"clip\" class — the runtime uses it to control visibility\n // based on data-start/data-duration timing.\n\n // Adjust media trim offset for the second half\n const playbackStartAttr = el.hasAttribute(\"data-playback-start\")\n ? \"data-playback-start\"\n : el.hasAttribute(\"data-media-start\")\n ? \"data-media-start\"\n : null;\n if (playbackStartAttr) {\n const currentTrim = parseFloat(el.getAttribute(playbackStartAttr) ?? \"0\") || 0;\n const rateRaw = parseFloat(el.getAttribute(\"data-playback-rate\") ?? \"\");\n const rate = Number.isFinite(rateRaw) ? rateRaw : 1;\n clone.setAttribute(\n playbackStartAttr,\n String(Math.round((currentTrim + firstDuration * rate) * 1000) / 1000),\n );\n }\n\n // Duplicate CSS rules targeting the original ID so the clone inherits the same styles.\n const originalId = el.getAttribute(\"id\");\n if (originalId) {\n duplicateCssRulesForId(document, originalId, newId);\n }\n\n // Trim the original element's duration. A GSAP element had no data-start; stamp\n // it so the runtime windows the first half (visibility selects on [data-start]).\n el.setAttribute(\"data-start\", String(Math.round(start * 1000) / 1000));\n setElementDuration(el, start, firstDuration, usesDataEnd);\n\n // Insert clone after original\n if (el.nextSibling) {\n el.parentElement!.insertBefore(clone, el.nextSibling);\n } else {\n el.parentElement!.appendChild(clone);\n }\n\n return {\n html: wrappedFragment ? document.body.innerHTML || \"\" : document.toString(),\n matched: true,\n newId,\n };\n}\n\n// --- Element grouping -------------------------------------------------------\n// A group is a real `<div data-hf-group=\"…\">` wrapping its members in the DOM.\n// Wrapping rebases each member's left/top so its absolute position is unchanged:\n// the wrapper sits at the selection bbox top-left, and each child's new left/top\n// is its old left/top minus the wrapper origin (computed client-side, where live\n// layout is available, and passed in via `rebases`). GSAP x/y, CSS translate and\n// --hf-studio-offset vars are deltas relative to flow position and stay untouched.\n\nexport interface WrapElementsResult {\n html: string;\n matched: boolean;\n groupId: string | null;\n error?: string;\n}\n\nexport interface UnwrapElementsResult {\n html: string;\n unwrapped: boolean;\n /** The unwrapped wrapper's id, so callers can strip GSAP that targeted it\n * (the wrapper is gone; a leftover `gsap.set(\"#id\")` would throw at runtime). */\n unwrappedGroupId?: string;\n /** Members (id'd children) with their absolute layout centres (post un-rebase),\n * so the caller can BAKE the group's GSAP transform into each member before\n * stripping it — otherwise the group's moves are lost on ungroup. */\n members?: Array<{ id: string; cx: number; cy: number }>;\n /** The wrapper's layout centre — the pivot for baking the group's rotation/scale. */\n groupCenter?: { cx: number; cy: number };\n}\n\nexport interface ElementRebase {\n target: SourceMutationTarget;\n left: number;\n top: number;\n}\n\nfunction getInlineStylePx(el: Element, property: string): number {\n const style = (isHTMLElement(el) ? el.getAttribute(\"style\") : null) ?? \"\";\n const { props } = parseStyleDecls(style);\n const raw = props.get(property);\n if (!raw) return 0;\n const n = parseFloat(raw);\n return Number.isFinite(n) ? n : 0;\n}\n\nfunction setInlineLeftTop(el: HTMLElement, left: number, top: number): void {\n let style = el.getAttribute(\"style\") ?? \"\";\n style = patchStyleAttrString(style, \"left\", `${left}px`);\n style = patchStyleAttrString(style, \"top\", `${top}px`);\n el.setAttribute(\"style\", style);\n}\n\n// Slug the group name (\"Group 1\" → \"group-1\") into a unique, valid element id.\nfunction uniqueGroupDomId(document: Document, groupId: string): string {\n const base =\n groupId\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\") || \"group\";\n let id = base;\n let n = 2;\n while (document.getElementById(id)) {\n id = `${base}-${n}`;\n n += 1;\n }\n return id;\n}\n\n// fallow-ignore-next-line complexity\nexport function wrapElementsInHtml(\n source: string,\n targets: SourceMutationTarget[],\n groupId: string,\n bbox: { left: number; top: number; width: number; height: number },\n rebases: ElementRebase[],\n): WrapElementsResult {\n const { document, wrappedFragment } = parseSourceDocument(source);\n if (targets.length === 0) {\n return { html: source, matched: false, groupId: null, error: \"no targets\" };\n }\n\n // Resolve + dedupe by element ref (two targets may point at the same node).\n const els: HTMLElement[] = [];\n const seen = new Set<Element>();\n for (const target of targets) {\n const el = findTargetElement(document, target);\n if (!el || !isHTMLElement(el) || seen.has(el)) continue;\n seen.add(el);\n els.push(el);\n }\n if (els.length === 0) {\n return { html: source, matched: false, groupId: null, error: \"no targets matched\" };\n }\n\n // P1: require a single common parent (LCA multi-parent wrapping is P2).\n const parent = els[0]?.parentElement;\n if (!parent || els.some((el) => el.parentElement !== parent)) {\n return {\n html: source,\n matched: false,\n groupId: null,\n error: \"grouped elements must share a single parent\",\n };\n }\n\n // Order members by their position in the parent (= z-order / stacking order).\n const memberSet = new Set<Element>(els);\n const ordered = Array.from(parent.children).filter((c): c is HTMLElement => memberSet.has(c));\n\n // Map each member to its rebased left/top (resolved against the same document).\n const rebaseByEl = new Map<Element, { left: number; top: number }>();\n for (const rebase of rebases) {\n const el = findTargetElement(document, rebase.target);\n if (el) rebaseByEl.set(el, { left: rebase.left, top: rebase.top });\n }\n\n const wrapper = document.createElement(\"div\");\n wrapper.setAttribute(\"data-hf-group\", groupId);\n // A real `id` (slug of the group name) makes the wrapper a first-class node in the\n // clip manifest / timeline parent-map (both keyed by id) and a clean GSAP target —\n // without it the wrapper is invisible to the timeline and breaks child enumeration.\n wrapper.setAttribute(\"id\", uniqueGroupDomId(document, groupId));\n // Adopt the topmost member's stacking level. A group is one stacking unit, so a\n // non-member interleaved between two selected members can't stay \"between\" them\n // once they unify. Matching Figma/Sketch, the group lifts to the topmost selected\n // layer: the wrapper goes at the LAST member's slot and carries the max member\n // z-index — so an interleaved non-member falls below the group instead of hoisting\n // above it, and explicit member z-indexes are honored.\n const memberZIndexes = ordered\n .map((el) =>\n Number.parseInt(\n parseStyleDecls(el.getAttribute(\"style\") ?? \"\").props.get(\"z-index\") ?? \"\",\n 10,\n ),\n )\n .filter((z) => Number.isFinite(z));\n const maxZ = memberZIndexes.length > 0 ? Math.max(...memberZIndexes) : null;\n wrapper.setAttribute(\n \"style\",\n `position: absolute; left: ${bbox.left}px; top: ${bbox.top}px; width: ${bbox.width}px; height: ${bbox.height}px` +\n (maxZ !== null ? `; z-index: ${maxZ}` : \"\"),\n );\n\n // Insert the wrapper at the topmost member's slot, then move members into it.\n parent.insertBefore(wrapper, ordered[ordered.length - 1] ?? null);\n for (const el of ordered) {\n const rebase = rebaseByEl.get(el);\n if (rebase) setInlineLeftTop(el, rebase.left, rebase.top);\n wrapper.appendChild(el); // appendChild moves the node, preserving order\n }\n\n return {\n html: wrappedFragment ? document.body.innerHTML || \"\" : document.toString(),\n matched: true,\n groupId,\n };\n}\n\nexport function unwrapElementsFromHtml(\n source: string,\n groupTarget: SourceMutationTarget,\n): UnwrapElementsResult {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const group = findTargetElement(document, groupTarget);\n if (!group || !isHTMLElement(group)) return { html: source, unwrapped: false };\n // Shape guard mirroring the wrap-side contract: only ever dissolve an actual\n // group wrapper. A stale/desynced selection that resolves to a plain <div>\n // would otherwise be unwrapped — rebasing its children by the parent's origin\n // (silent corruption). Wrap enforces invariants; unwrap must too.\n if (!group.hasAttribute(\"data-hf-group\")) return { html: source, unwrapped: false };\n\n const parent = group.parentElement;\n if (!parent) return { html: source, unwrapped: false };\n\n // Undo the rebase: child absolute position = child (rebased) + wrapper origin.\n const wLeft = getInlineStylePx(group, \"left\");\n const wTop = getInlineStylePx(group, \"top\");\n const groupCenter = {\n cx: wLeft + getInlineStylePx(group, \"width\") / 2,\n cy: wTop + getInlineStylePx(group, \"height\") / 2,\n };\n\n // Move children back to the wrapper's slot, preserving order.\n const members: Array<{ id: string; cx: number; cy: number }> = [];\n for (const child of Array.from(group.children)) {\n if (isHTMLElement(child)) {\n const newLeft = getInlineStylePx(child, \"left\") + wLeft;\n const newTop = getInlineStylePx(child, \"top\") + wTop;\n setInlineLeftTop(child, newLeft, newTop);\n if (child.id) {\n members.push({\n id: child.id,\n cx: newLeft + getInlineStylePx(child, \"width\") / 2,\n cy: newTop + getInlineStylePx(child, \"height\") / 2,\n });\n }\n }\n parent.insertBefore(child, group);\n }\n const groupId = group.id || undefined;\n group.remove();\n\n return {\n html: wrappedFragment ? document.body.innerHTML || \"\" : document.toString(),\n unwrapped: true,\n unwrappedGroupId: groupId,\n members,\n groupCenter,\n };\n}\n","// fallow-ignore-next-line complexity\nexport function parseStyleDecls(style: string): { props: Map<string, string>; order: string[] } {\n const props = new Map<string, string>();\n const order: string[] = [];\n let i = 0;\n while (i < style.length) {\n let depth = 0;\n let inSingle = false;\n let inDouble = false;\n const start = i;\n while (i < style.length) {\n const ch = style[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n else if (ch === '\"' && !inSingle) inDouble = !inDouble;\n else if (!inSingle && !inDouble) {\n if (ch === \"(\") depth++;\n else if (ch === \")\") depth = Math.max(0, depth - 1);\n else if (ch === \";\" && depth === 0) break;\n }\n i++;\n }\n const decl = style.slice(start, i).trim();\n i++;\n if (!decl) continue;\n const colon = decl.indexOf(\":\");\n if (colon < 0) continue;\n const key = decl.slice(0, colon).trim();\n const val = decl.slice(colon + 1).trim();\n if (!key) continue;\n if (!props.has(key)) order.push(key);\n props.set(key, val);\n }\n return { props, order };\n}\n\nfunction serializeStyleDecls(props: Map<string, string>, order: string[]): string {\n return order\n .map((k) => `${k}: ${props.get(k) ?? \"\"}`)\n .filter((d) => d.trim())\n .join(\"; \");\n}\n\nexport function patchStyleAttrString(\n style: string,\n property: string,\n value: string | null,\n): string {\n const { props, order } = parseStyleDecls(style);\n if (value === null) {\n props.delete(property);\n const idx = order.indexOf(property);\n if (idx >= 0) order.splice(idx, 1);\n } else {\n if (!props.has(property)) order.push(property);\n props.set(property, value);\n }\n return serializeStyleDecls(props, order);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/helpers/sourceMutation.ts","../../src/helpers/sourceStyleMutation.ts"],"sourcesContent":["import { parseHTML } from \"linkedom\";\nimport postcss from \"postcss\";\nimport selectorParser from \"postcss-selector-parser\";\nimport { isAllowedHtmlAttribute, isSafeAttributeValue } from \"@hyperframes/core/html-attr-safety\";\nimport { ensureHfIds } from \"@hyperframes/parsers/hf-ids\";\nimport { parseStyleDecls, patchStyleAttrString } from \"./sourceStyleMutation.js\";\n\nexport interface SourceMutationTarget {\n id?: string | null;\n hfId?: string;\n selector?: string;\n selectorIndex?: number;\n}\n\nfunction parseSourceDocument(source: string): { document: Document; wrappedFragment: boolean } {\n const hasDocumentShell = /<!doctype|<html[\\s>]/i.test(source);\n if (hasDocumentShell) {\n return { document: parseHTML(source).document, wrappedFragment: false };\n }\n return {\n document: parseHTML(`<!DOCTYPE html><html><head></head><body>${source}</body></html>`).document,\n wrappedFragment: true,\n };\n}\n\nfunction duplicateCssRulesForId(document: Document, originalId: string, newId: string): void {\n const idToken = `#${originalId}`;\n const transform = selectorParser((selectors) => {\n selectors.walkIds((node) => {\n if (node.value === originalId) node.value = newId;\n });\n });\n for (const styleEl of document.querySelectorAll(\"style\")) {\n const css = styleEl.textContent ?? \"\";\n let root: postcss.Root;\n try {\n root = postcss.parse(css);\n } catch {\n continue;\n }\n const clones: postcss.Rule[] = [];\n root.walkRules((rule) => {\n if (!rule.selector.includes(idToken)) return;\n const newSelector = transform.processSync(rule.selector);\n if (newSelector === rule.selector) return;\n const clone = rule.clone({ selector: newSelector });\n clones.push(clone);\n });\n if (clones.length > 0) {\n for (const c of clones) root.append(c);\n styleEl.textContent = root.toString();\n }\n }\n}\n\nfunction querySelectorAllWithTemplates(root: Document | Element, selector: string): Element[] {\n const matches = Array.from(root.querySelectorAll(selector));\n if (matches.length > 0) return matches;\n // querySelectorAll doesn't traverse <template> content in linkedom.\n // Search directly on each template element (NOT .content — removing from\n // .content's DocumentFragment doesn't update the serialized output).\n // Recurse so NESTED templates resolve too — ensureHfIds and the SDK's\n // querySelectorAllDeep descend nested composition templates, so ids exist at\n // any template depth; a one-level search here would silently no-op\n // server-side ops on those ids while the SDK resolves them.\n const templates = Array.from(root.querySelectorAll(\"template\"));\n for (const tmpl of templates) {\n const inner = querySelectorAllWithTemplates(tmpl, selector);\n if (inner.length > 0) return inner;\n }\n return [];\n}\n\n// Prevent CSS attribute-selector injection via a crafted hfId: escape\n// backslashes first, then double-quotes. Keeps a malformed/hostile value from\n// breaking out of the `[data-hf-id=\"…\"]` selector once callers beyond the\n// internal mint contract (R2+ user flows) pass values here.\nfunction escapeCssAttrValue(value: string): string {\n return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n}\n\nfunction findByHfId(document: Document, hfId: string): Element | null {\n try {\n const matches = querySelectorAllWithTemplates(\n document,\n `[data-hf-id=\"${escapeCssAttrValue(hfId)}\"]`,\n );\n if (matches.length > 1) {\n // The mint contract guarantees uniqueness; a duplicate means upstream\n // id drift. Don't silently patch an arbitrary one — surface it.\n // eslint-disable-next-line no-console\n console.warn(\n `sourceMutation: data-hf-id \"${hfId}\" matched ${matches.length} elements; using the first. ids must be unique per document.`,\n );\n }\n return matches[0] ?? null;\n } catch {\n // Malformed selector despite escaping — let the caller fall back.\n return null;\n }\n}\n\nfunction findTargetElement(document: Document, target: SourceMutationTarget): Element | null {\n if (target.hfId) {\n const el = findByHfId(document, target.hfId);\n if (el) return el;\n }\n\n if (target.id) {\n const byId = document.getElementById(target.id);\n if (byId) return byId;\n }\n\n if (!target.selector) return null;\n try {\n const matches = querySelectorAllWithTemplates(document, target.selector);\n return matches[target.selectorIndex ?? 0] ?? null;\n } catch {\n return null;\n }\n}\n\nexport function removeElementFromHtml(source: string, target: SourceMutationTarget): string {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const element = findTargetElement(document, target);\n if (!element) return source;\n\n element.remove();\n return wrappedFragment ? document.body.innerHTML || \"\" : document.toString();\n}\n\nexport function isHTMLElement(el: Node): el is HTMLElement {\n const HTMLEl = el.ownerDocument?.defaultView?.HTMLElement;\n return HTMLEl ? el instanceof HTMLEl : el.nodeType === 1 && \"style\" in el;\n}\n\nexport interface PatchOperation {\n type: \"inline-style\" | \"attribute\" | \"html-attribute\" | \"text-content\";\n property: string;\n value: string | null;\n childSelector?: string;\n childIndex?: number;\n}\n\ninterface ResolvedPatchOperation {\n op: PatchOperation;\n target: HTMLElement;\n}\n\nfunction resolveOperationTarget(parent: HTMLElement, op: PatchOperation): HTMLElement | null {\n if (op.childSelector === undefined) return parent;\n try {\n const child = parent.querySelectorAll(op.childSelector)[op.childIndex ?? 0] ?? null;\n return child && isHTMLElement(child) ? child : null;\n } catch {\n return null;\n }\n}\n\n// fallow-ignore-next-line complexity\nexport function patchElementInHtml(\n source: string,\n target: SourceMutationTarget,\n operations: PatchOperation[],\n): { html: string; matched: boolean } {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const el = findTargetElement(document, target);\n if (!el || !isHTMLElement(el)) return { html: source, matched: false };\n const htmlEl = el;\n\n const resolved: ResolvedPatchOperation[] = [];\n for (const op of operations) {\n const opTarget = resolveOperationTarget(htmlEl, op);\n if (!opTarget) return { html: source, matched: false };\n resolved.push({ op, target: opTarget });\n }\n\n for (const { op, target: opTarget } of resolved) {\n switch (op.type) {\n case \"inline-style\":\n // linkedom's CSSStyleDeclaration does not support CSS custom properties\n // (--foo) or newer individual transform properties (translate, rotate,\n // scale) via style.setProperty(). Manipulate the style attribute string\n // directly so all property names survive the round-trip.\n {\n const raw = opTarget.getAttribute(\"style\") ?? \"\";\n const patched = patchStyleAttrString(raw, op.property, op.value);\n opTarget.setAttribute(\"style\", patched);\n }\n break;\n case \"attribute\":\n {\n const fullAttr = op.property.startsWith(\"data-\") ? op.property : `data-${op.property}`;\n if (op.value != null) {\n opTarget.setAttribute(fullAttr, op.value);\n } else {\n opTarget.removeAttribute(fullAttr);\n }\n }\n break;\n case \"html-attribute\":\n if (!isAllowedHtmlAttribute(op.property)) break;\n if (op.value != null) {\n if (!isSafeAttributeValue(op.property, op.value)) break;\n opTarget.setAttribute(op.property, op.value);\n } else {\n opTarget.removeAttribute(op.property);\n }\n break;\n case \"text-content\":\n if (op.value != null) {\n const inner = opTarget.children.length === 1 ? opTarget.firstElementChild : null;\n const textTarget = inner && isHTMLElement(inner) ? inner : opTarget;\n textTarget.textContent = op.value;\n }\n break;\n }\n }\n\n return {\n html: wrappedFragment ? document.body.innerHTML || \"\" : document.toString(),\n matched: true,\n };\n}\n\nexport function probeElementInSource(source: string, target: SourceMutationTarget): boolean {\n if (!target.id && !target.hfId && !target.selector) return false;\n const { document } = parseSourceDocument(source);\n const el = findTargetElement(document, target);\n return el != null && isHTMLElement(el);\n}\n\nexport interface SplitElementResult {\n html: string;\n matched: boolean;\n newId: string | null;\n}\n\nfunction resolveElementTiming(el: Element): {\n start: number;\n duration: number;\n usesDataEnd: boolean;\n} {\n const start = parseFloat(el.getAttribute(\"data-start\") ?? \"0\") || 0;\n const usesDataEnd = el.hasAttribute(\"data-end\");\n const duration = usesDataEnd\n ? parseFloat(el.getAttribute(\"data-end\") ?? \"\") - start || 0\n : parseFloat(el.getAttribute(\"data-duration\") ?? \"0\") || 0;\n return { start, duration, usesDataEnd };\n}\n\nfunction setElementDuration(\n el: Element,\n start: number,\n duration: number,\n usesDataEnd: boolean,\n): void {\n if (usesDataEnd) {\n const endTime = String(Math.round((start + duration) * 1000) / 1000);\n el.setAttribute(\"data-end\", endTime);\n el.removeAttribute(\"data-duration\");\n } else {\n el.setAttribute(\"data-duration\", String(Math.round(duration * 1000) / 1000));\n el.removeAttribute(\"data-end\");\n }\n}\n\n// fallow-ignore-next-line complexity\nexport function splitElementInHtml(\n source: string,\n target: SourceMutationTarget,\n splitTime: number,\n newId: string,\n fallbackTiming?: { start: number; duration: number },\n): SplitElementResult {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const el = findTargetElement(document, target);\n if (!el || !isHTMLElement(el)) return { html: source, matched: false, newId: null };\n\n const timing = resolveElementTiming(el);\n const { usesDataEnd } = timing;\n let { start, duration } = timing;\n // GSAP-animated elements carry their timing in the script, not in data-* attrs,\n // so the source has no authored duration. Fall back to the store's (GSAP-derived)\n // range — the runtime windows visibility off data-start/data-duration regardless\n // of class, so stamping both halves below makes each half show only in its window.\n if (duration <= 0 && fallbackTiming && fallbackTiming.duration > 0) {\n start = fallbackTiming.start;\n duration = fallbackTiming.duration;\n }\n if (duration <= 0 || splitTime <= start || splitTime >= start + duration) {\n return { html: source, matched: false, newId: null };\n }\n\n if (document.getElementById(newId)) {\n let suffix = 2;\n const base = newId;\n while (document.getElementById(newId)) {\n newId = `${base}-${suffix++}`;\n }\n }\n\n const firstDuration = splitTime - start;\n const secondDuration = duration - firstDuration;\n\n const clone = el.cloneNode(true);\n if (!isHTMLElement(clone)) return { html: source, matched: false, newId: null };\n clone.setAttribute(\"id\", newId);\n clone.removeAttribute(\"data-hf-id\");\n // Descendants carry their own data-hf-id; leaving them duplicates the id of\n // every nested node (e.g. an inner <span>), so strip them on the clone too.\n for (const node of clone.querySelectorAll(\"[data-hf-id]\")) node.removeAttribute(\"data-hf-id\");\n clone.setAttribute(\"data-start\", String(Math.round(splitTime * 1000) / 1000));\n setElementDuration(clone, splitTime, secondDuration, usesDataEnd);\n\n // Keep the \"clip\" class — the runtime uses it to control visibility\n // based on data-start/data-duration timing.\n\n // Adjust media trim offset for the second half\n const playbackStartAttr = el.hasAttribute(\"data-playback-start\")\n ? \"data-playback-start\"\n : el.hasAttribute(\"data-media-start\")\n ? \"data-media-start\"\n : null;\n if (playbackStartAttr) {\n const currentTrim = parseFloat(el.getAttribute(playbackStartAttr) ?? \"0\") || 0;\n const rateRaw = parseFloat(el.getAttribute(\"data-playback-rate\") ?? \"\");\n const rate = Number.isFinite(rateRaw) ? rateRaw : 1;\n clone.setAttribute(\n playbackStartAttr,\n String(Math.round((currentTrim + firstDuration * rate) * 1000) / 1000),\n );\n }\n\n // Duplicate CSS rules targeting the original ID so the clone inherits the same styles.\n const originalId = el.getAttribute(\"id\");\n if (originalId) {\n duplicateCssRulesForId(document, originalId, newId);\n }\n\n // Trim the original element's duration. A GSAP element had no data-start; stamp\n // it so the runtime windows the first half (visibility selects on [data-start]).\n el.setAttribute(\"data-start\", String(Math.round(start * 1000) / 1000));\n setElementDuration(el, start, firstDuration, usesDataEnd);\n\n // Insert clone after original\n if (el.nextSibling) {\n el.parentElement!.insertBefore(clone, el.nextSibling);\n } else {\n el.parentElement!.appendChild(clone);\n }\n\n const html = wrappedFragment ? document.body.innerHTML || \"\" : document.toString();\n return {\n // The split owns its new nodes' stable ids. Leaving the clone unstamped makes\n // the next preview request persist different bytes after history is recorded.\n html: ensureHfIds(html),\n matched: true,\n newId,\n };\n}\n\n// --- Element grouping -------------------------------------------------------\n// A group is a real `<div data-hf-group=\"…\">` wrapping its members in the DOM.\n// Wrapping rebases each member's left/top so its absolute position is unchanged:\n// the wrapper sits at the selection bbox top-left, and each child's new left/top\n// is its old left/top minus the wrapper origin (computed client-side, where live\n// layout is available, and passed in via `rebases`). GSAP x/y, CSS translate and\n// --hf-studio-offset vars are deltas relative to flow position and stay untouched.\n\nexport interface WrapElementsResult {\n html: string;\n matched: boolean;\n groupId: string | null;\n error?: string;\n}\n\nexport interface UnwrapElementsResult {\n html: string;\n unwrapped: boolean;\n /** The unwrapped wrapper's id, so callers can strip GSAP that targeted it\n * (the wrapper is gone; a leftover `gsap.set(\"#id\")` would throw at runtime). */\n unwrappedGroupId?: string;\n /** Members (id'd children) with their absolute layout centres (post un-rebase),\n * so the caller can BAKE the group's GSAP transform into each member before\n * stripping it — otherwise the group's moves are lost on ungroup. */\n members?: Array<{ id: string; cx: number; cy: number }>;\n /** The wrapper's layout centre — the pivot for baking the group's rotation/scale. */\n groupCenter?: { cx: number; cy: number };\n}\n\nexport interface ElementRebase {\n target: SourceMutationTarget;\n left: number;\n top: number;\n}\n\nfunction getInlineStylePx(el: Element, property: string): number {\n const style = (isHTMLElement(el) ? el.getAttribute(\"style\") : null) ?? \"\";\n const { props } = parseStyleDecls(style);\n const raw = props.get(property);\n if (!raw) return 0;\n const n = parseFloat(raw);\n return Number.isFinite(n) ? n : 0;\n}\n\nfunction setInlineLeftTop(el: HTMLElement, left: number, top: number): void {\n let style = el.getAttribute(\"style\") ?? \"\";\n style = patchStyleAttrString(style, \"left\", `${left}px`);\n style = patchStyleAttrString(style, \"top\", `${top}px`);\n el.setAttribute(\"style\", style);\n}\n\n// Slug the group name (\"Group 1\" → \"group-1\") into a unique, valid element id.\nfunction uniqueGroupDomId(document: Document, groupId: string): string {\n const base =\n groupId\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\") || \"group\";\n let id = base;\n let n = 2;\n while (document.getElementById(id)) {\n id = `${base}-${n}`;\n n += 1;\n }\n return id;\n}\n\n// fallow-ignore-next-line complexity\nexport function wrapElementsInHtml(\n source: string,\n targets: SourceMutationTarget[],\n groupId: string,\n bbox: { left: number; top: number; width: number; height: number },\n rebases: ElementRebase[],\n): WrapElementsResult {\n const { document, wrappedFragment } = parseSourceDocument(source);\n if (targets.length === 0) {\n return { html: source, matched: false, groupId: null, error: \"no targets\" };\n }\n\n // Resolve + dedupe by element ref (two targets may point at the same node).\n const els: HTMLElement[] = [];\n const seen = new Set<Element>();\n for (const target of targets) {\n const el = findTargetElement(document, target);\n if (!el || !isHTMLElement(el) || seen.has(el)) continue;\n seen.add(el);\n els.push(el);\n }\n if (els.length === 0) {\n return { html: source, matched: false, groupId: null, error: \"no targets matched\" };\n }\n\n // P1: require a single common parent (LCA multi-parent wrapping is P2).\n const parent = els[0]?.parentElement;\n if (!parent || els.some((el) => el.parentElement !== parent)) {\n return {\n html: source,\n matched: false,\n groupId: null,\n error: \"grouped elements must share a single parent\",\n };\n }\n\n // Order members by their position in the parent (= z-order / stacking order).\n const memberSet = new Set<Element>(els);\n const ordered = Array.from(parent.children).filter((c): c is HTMLElement => memberSet.has(c));\n\n // Map each member to its rebased left/top (resolved against the same document).\n const rebaseByEl = new Map<Element, { left: number; top: number }>();\n for (const rebase of rebases) {\n const el = findTargetElement(document, rebase.target);\n if (el) rebaseByEl.set(el, { left: rebase.left, top: rebase.top });\n }\n\n const wrapper = document.createElement(\"div\");\n wrapper.setAttribute(\"data-hf-group\", groupId);\n // A real `id` (slug of the group name) makes the wrapper a first-class node in the\n // clip manifest / timeline parent-map (both keyed by id) and a clean GSAP target —\n // without it the wrapper is invisible to the timeline and breaks child enumeration.\n wrapper.setAttribute(\"id\", uniqueGroupDomId(document, groupId));\n // Adopt the topmost member's stacking level. A group is one stacking unit, so a\n // non-member interleaved between two selected members can't stay \"between\" them\n // once they unify. Matching Figma/Sketch, the group lifts to the topmost selected\n // layer: the wrapper goes at the LAST member's slot and carries the max member\n // z-index — so an interleaved non-member falls below the group instead of hoisting\n // above it, and explicit member z-indexes are honored.\n const memberZIndexes = ordered\n .map((el) =>\n Number.parseInt(\n parseStyleDecls(el.getAttribute(\"style\") ?? \"\").props.get(\"z-index\") ?? \"\",\n 10,\n ),\n )\n .filter((z) => Number.isFinite(z));\n const maxZ = memberZIndexes.length > 0 ? Math.max(...memberZIndexes) : null;\n wrapper.setAttribute(\n \"style\",\n `position: absolute; left: ${bbox.left}px; top: ${bbox.top}px; width: ${bbox.width}px; height: ${bbox.height}px` +\n (maxZ !== null ? `; z-index: ${maxZ}` : \"\"),\n );\n\n // Insert the wrapper at the topmost member's slot, then move members into it.\n parent.insertBefore(wrapper, ordered[ordered.length - 1] ?? null);\n for (const el of ordered) {\n const rebase = rebaseByEl.get(el);\n if (rebase) setInlineLeftTop(el, rebase.left, rebase.top);\n wrapper.appendChild(el); // appendChild moves the node, preserving order\n }\n\n return {\n html: wrappedFragment ? document.body.innerHTML || \"\" : document.toString(),\n matched: true,\n groupId,\n };\n}\n\nexport function unwrapElementsFromHtml(\n source: string,\n groupTarget: SourceMutationTarget,\n): UnwrapElementsResult {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const group = findTargetElement(document, groupTarget);\n if (!group || !isHTMLElement(group)) return { html: source, unwrapped: false };\n // Shape guard mirroring the wrap-side contract: only ever dissolve an actual\n // group wrapper. A stale/desynced selection that resolves to a plain <div>\n // would otherwise be unwrapped — rebasing its children by the parent's origin\n // (silent corruption). Wrap enforces invariants; unwrap must too.\n if (!group.hasAttribute(\"data-hf-group\")) return { html: source, unwrapped: false };\n\n const parent = group.parentElement;\n if (!parent) return { html: source, unwrapped: false };\n\n // Undo the rebase: child absolute position = child (rebased) + wrapper origin.\n const wLeft = getInlineStylePx(group, \"left\");\n const wTop = getInlineStylePx(group, \"top\");\n const groupCenter = {\n cx: wLeft + getInlineStylePx(group, \"width\") / 2,\n cy: wTop + getInlineStylePx(group, \"height\") / 2,\n };\n\n // Move children back to the wrapper's slot, preserving order.\n const members: Array<{ id: string; cx: number; cy: number }> = [];\n for (const child of Array.from(group.children)) {\n if (isHTMLElement(child)) {\n const newLeft = getInlineStylePx(child, \"left\") + wLeft;\n const newTop = getInlineStylePx(child, \"top\") + wTop;\n setInlineLeftTop(child, newLeft, newTop);\n if (child.id) {\n members.push({\n id: child.id,\n cx: newLeft + getInlineStylePx(child, \"width\") / 2,\n cy: newTop + getInlineStylePx(child, \"height\") / 2,\n });\n }\n }\n parent.insertBefore(child, group);\n }\n const groupId = group.id || undefined;\n group.remove();\n\n return {\n html: wrappedFragment ? document.body.innerHTML || \"\" : document.toString(),\n unwrapped: true,\n unwrappedGroupId: groupId,\n members,\n groupCenter,\n };\n}\n","// fallow-ignore-next-line complexity\nexport function parseStyleDecls(style: string): { props: Map<string, string>; order: string[] } {\n const props = new Map<string, string>();\n const order: string[] = [];\n let i = 0;\n while (i < style.length) {\n let depth = 0;\n let inSingle = false;\n let inDouble = false;\n const start = i;\n while (i < style.length) {\n const ch = style[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n else if (ch === '\"' && !inSingle) inDouble = !inDouble;\n else if (!inSingle && !inDouble) {\n if (ch === \"(\") depth++;\n else if (ch === \")\") depth = Math.max(0, depth - 1);\n else if (ch === \";\" && depth === 0) break;\n }\n i++;\n }\n const decl = style.slice(start, i).trim();\n i++;\n if (!decl) continue;\n const colon = decl.indexOf(\":\");\n if (colon < 0) continue;\n const key = decl.slice(0, colon).trim();\n const val = decl.slice(colon + 1).trim();\n if (!key) continue;\n if (!props.has(key)) order.push(key);\n props.set(key, val);\n }\n return { props, order };\n}\n\nfunction serializeStyleDecls(props: Map<string, string>, order: string[]): string {\n return order\n .map((k) => `${k}: ${props.get(k) ?? \"\"}`)\n .filter((d) => d.trim())\n .join(\"; \");\n}\n\nexport function patchStyleAttrString(\n style: string,\n property: string,\n value: string | null,\n): string {\n const { props, order } = parseStyleDecls(style);\n if (value === null) {\n props.delete(property);\n const idx = order.indexOf(property);\n if (idx >= 0) order.splice(idx, 1);\n } else {\n if (!props.has(property)) order.push(property);\n props.set(property, value);\n }\n return serializeStyleDecls(props, order);\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -447,6 +447,7 @@ import { parseHTML } from "linkedom";
|
|
|
447
447
|
import postcss from "postcss";
|
|
448
448
|
import selectorParser from "postcss-selector-parser";
|
|
449
449
|
import { isAllowedHtmlAttribute, isSafeAttributeValue } from "@hyperframes/core/html-attr-safety";
|
|
450
|
+
import { ensureHfIds } from "@hyperframes/parsers/hf-ids";
|
|
450
451
|
|
|
451
452
|
// src/helpers/sourceStyleMutation.ts
|
|
452
453
|
function parseStyleDecls(style) {
|
|
@@ -730,8 +731,11 @@ function splitElementInHtml(source, target, splitTime, newId, fallbackTiming) {
|
|
|
730
731
|
} else {
|
|
731
732
|
el.parentElement.appendChild(clone);
|
|
732
733
|
}
|
|
734
|
+
const html = wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
|
|
733
735
|
return {
|
|
734
|
-
|
|
736
|
+
// The split owns its new nodes' stable ids. Leaving the clone unstamped makes
|
|
737
|
+
// the next preview request persist different bytes after history is recorded.
|
|
738
|
+
html: ensureHfIds(html),
|
|
735
739
|
matched: true,
|
|
736
740
|
newId
|
|
737
741
|
};
|
|
@@ -892,6 +896,16 @@ function resolveProjectFile(c, adapter, opts) {
|
|
|
892
896
|
function resolveFileMutationContext(c, adapter, operation) {
|
|
893
897
|
return resolveProjectPath(c, adapter, (id) => `/projects/${id}/file-mutations/${operation}/`);
|
|
894
898
|
}
|
|
899
|
+
function foldElementPatches(originalContent, patches) {
|
|
900
|
+
let content = originalContent;
|
|
901
|
+
const matched = [];
|
|
902
|
+
for (const patch of patches) {
|
|
903
|
+
const result = patchElementInHtml(content, patch.target, patch.operations);
|
|
904
|
+
content = result.html;
|
|
905
|
+
matched.push(result.matched);
|
|
906
|
+
}
|
|
907
|
+
return { content, matched };
|
|
908
|
+
}
|
|
895
909
|
function writeIfChanged(c, projectDir, filePath, absPath, original, next) {
|
|
896
910
|
if (next === original) {
|
|
897
911
|
return c.json({ ok: true, changed: false, content: original, path: filePath });
|
|
@@ -1207,6 +1221,97 @@ async function executeGsapMutation(body, block, respond) {
|
|
|
1207
1221
|
}
|
|
1208
1222
|
return executeGsapMutationAcorn(body, block, respond);
|
|
1209
1223
|
}
|
|
1224
|
+
function validateGsapMutationRequest(c, body) {
|
|
1225
|
+
if (!body || typeof body !== "object" || !("type" in body) || !body.type) {
|
|
1226
|
+
return c.json({ error: "mutation type required" }, 400);
|
|
1227
|
+
}
|
|
1228
|
+
const unsafeFields = findUnsafeMutationValues(body);
|
|
1229
|
+
if (unsafeFields.length > 0) return rejectUnsafeMutationValues(c, unsafeFields);
|
|
1230
|
+
if (body.type === "shift-positions-batch" && (!("shifts" in body) || !Array.isArray(body.shifts))) {
|
|
1231
|
+
return c.json({ error: "shift-positions-batch requires a `shifts` array" }, 400);
|
|
1232
|
+
}
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
async function prepareGsapMutationScript(c, res, firstMutation) {
|
|
1236
|
+
let html = readFileSync3(res.absPath, "utf-8");
|
|
1237
|
+
let block = extractGsapScriptBlock(html);
|
|
1238
|
+
if (!block && (firstMutation.type === "add" || firstMutation.type === "add-with-keyframes")) {
|
|
1239
|
+
const compId = html.match(/data-composition-id="([^"]+)"/)?.[1] ?? "main";
|
|
1240
|
+
const { GSAP_CDN } = await import("@hyperframes/core");
|
|
1241
|
+
const bootstrap = [
|
|
1242
|
+
`<script src="${GSAP_CDN}"></script>`,
|
|
1243
|
+
"<script>",
|
|
1244
|
+
"window.__timelines = window.__timelines || {};",
|
|
1245
|
+
"const tl = gsap.timeline({ paused: true });",
|
|
1246
|
+
`window.__timelines["${compId}"] = tl;`,
|
|
1247
|
+
"</script>"
|
|
1248
|
+
].join("\n");
|
|
1249
|
+
html = html.includes("</body>") ? html.replace("</body>", `${bootstrap}
|
|
1250
|
+
</body>`) : `${html}
|
|
1251
|
+
${bootstrap}`;
|
|
1252
|
+
block = extractGsapScriptBlock(html);
|
|
1253
|
+
}
|
|
1254
|
+
if (!block && (firstMutation.type === "shift-positions" || firstMutation.type === "scale-positions" || firstMutation.type === "shift-positions-batch")) {
|
|
1255
|
+
return c.json({
|
|
1256
|
+
ok: true,
|
|
1257
|
+
changed: false,
|
|
1258
|
+
mutated: false,
|
|
1259
|
+
parsed: { animations: [], timelineVar: "tl", preamble: "", postamble: "" },
|
|
1260
|
+
before: html,
|
|
1261
|
+
after: html,
|
|
1262
|
+
scriptText: "",
|
|
1263
|
+
path: res.filePath,
|
|
1264
|
+
backupPath: null
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
if (!block) return c.json({ error: "no GSAP script found in file" }, 400);
|
|
1268
|
+
return { html, block };
|
|
1269
|
+
}
|
|
1270
|
+
async function applyGsapMutations(c, res, mutations) {
|
|
1271
|
+
const firstMutation = mutations[0];
|
|
1272
|
+
if (!firstMutation) return c.json({ error: "mutations array required" }, 400);
|
|
1273
|
+
const prepared = await prepareGsapMutationScript(c, res, firstMutation);
|
|
1274
|
+
if (prepared instanceof Response) return prepared;
|
|
1275
|
+
const { html, block } = prepared;
|
|
1276
|
+
const initialScript = block.scriptText;
|
|
1277
|
+
const skippedSelectors = /* @__PURE__ */ new Set();
|
|
1278
|
+
const respond = (data, status) => status ? c.json(data, status) : c.json(data);
|
|
1279
|
+
for (const mutation of mutations) {
|
|
1280
|
+
const result = await executeGsapMutation(mutation, block, respond);
|
|
1281
|
+
if (result instanceof Response) return result;
|
|
1282
|
+
let newScript = typeof result === "string" ? result : result.script;
|
|
1283
|
+
if (typeof result !== "string") {
|
|
1284
|
+
for (const selector of result.skippedSelectors) skippedSelectors.add(selector);
|
|
1285
|
+
}
|
|
1286
|
+
if (HOLD_SYNC_MUTATION_TYPES.has(mutation.type)) {
|
|
1287
|
+
const parser = await loadGsapParser();
|
|
1288
|
+
newScript = parser.syncPositionHoldsBeforeKeyframes(newScript);
|
|
1289
|
+
}
|
|
1290
|
+
block.scriptText = newScript;
|
|
1291
|
+
}
|
|
1292
|
+
const changed = block.scriptText !== initialScript;
|
|
1293
|
+
const newHtml = changed ? block.replaceScript(block.scriptText) : html;
|
|
1294
|
+
let backupPath = null;
|
|
1295
|
+
if (changed) {
|
|
1296
|
+
const backup = snapshotBeforeWrite(res.project.dir, res.absPath);
|
|
1297
|
+
if (backup.error) console.warn(`Failed to create backup for ${res.filePath}: ${backup.error}`);
|
|
1298
|
+
backupPath = backupPathForResponse(res.project.dir, backup.backupPath);
|
|
1299
|
+
writeFileSync4(res.absPath, newHtml, "utf-8");
|
|
1300
|
+
}
|
|
1301
|
+
const responsePayload = {
|
|
1302
|
+
ok: true,
|
|
1303
|
+
changed,
|
|
1304
|
+
mutated: changed,
|
|
1305
|
+
parsed: parseGsapScriptAcorn(block.scriptText),
|
|
1306
|
+
before: html,
|
|
1307
|
+
after: newHtml,
|
|
1308
|
+
scriptText: block.scriptText,
|
|
1309
|
+
path: res.filePath,
|
|
1310
|
+
backupPath
|
|
1311
|
+
};
|
|
1312
|
+
if (skippedSelectors.size > 0) responsePayload.skippedSelectors = [...skippedSelectors];
|
|
1313
|
+
return c.json(responsePayload);
|
|
1314
|
+
}
|
|
1210
1315
|
function executeGsapMutationAcorn(body, block, respond) {
|
|
1211
1316
|
function requireAnimation(scriptText, animationId) {
|
|
1212
1317
|
const parsed = parseGsapScriptAcorn(scriptText);
|
|
@@ -2007,6 +2112,47 @@ function registerFileRoutes(api, adapter) {
|
|
|
2007
2112
|
backupPath: backupPathForResponse(ctx.project.dir, backup.backupPath)
|
|
2008
2113
|
});
|
|
2009
2114
|
});
|
|
2115
|
+
api.post("/projects/:id/file-mutations/patch-elements-batch/*", async (c) => {
|
|
2116
|
+
const ctx = await resolveFileMutationContext(c, adapter, "patch-elements-batch");
|
|
2117
|
+
if ("error" in ctx) return ctx.error;
|
|
2118
|
+
const body = await c.req.json().catch(() => null);
|
|
2119
|
+
if (!body || !Array.isArray(body.patches) || body.patches.length === 0 || body.patches.some(
|
|
2120
|
+
(patch) => !patch?.target || !Array.isArray(patch.operations) || patch.operations.length === 0
|
|
2121
|
+
)) {
|
|
2122
|
+
return c.json({ error: "patches with target and operations required" }, 400);
|
|
2123
|
+
}
|
|
2124
|
+
const unsafeFields = body.patches.flatMap((patch) => findUnsafeDomPatchValues(patch));
|
|
2125
|
+
if (unsafeFields.length > 0) {
|
|
2126
|
+
return rejectUnsafeMutationValues(c, unsafeFields);
|
|
2127
|
+
}
|
|
2128
|
+
let originalContent;
|
|
2129
|
+
try {
|
|
2130
|
+
originalContent = readFileSync3(ctx.absPath, "utf-8");
|
|
2131
|
+
} catch {
|
|
2132
|
+
return c.json({ error: "not found" }, 404);
|
|
2133
|
+
}
|
|
2134
|
+
const result = foldElementPatches(originalContent, body.patches);
|
|
2135
|
+
if (result.content === originalContent) {
|
|
2136
|
+
return c.json({
|
|
2137
|
+
ok: true,
|
|
2138
|
+
changed: false,
|
|
2139
|
+
matched: result.matched,
|
|
2140
|
+
content: originalContent,
|
|
2141
|
+
path: ctx.filePath
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
const backup = snapshotBeforeWrite(ctx.project.dir, ctx.absPath);
|
|
2145
|
+
if (backup.error) console.warn(`Failed to create backup for ${ctx.filePath}: ${backup.error}`);
|
|
2146
|
+
writeFileSync4(ctx.absPath, result.content, "utf-8");
|
|
2147
|
+
return c.json({
|
|
2148
|
+
ok: true,
|
|
2149
|
+
changed: true,
|
|
2150
|
+
matched: result.matched,
|
|
2151
|
+
content: result.content,
|
|
2152
|
+
path: ctx.filePath,
|
|
2153
|
+
backupPath: backupPathForResponse(ctx.project.dir, backup.backupPath)
|
|
2154
|
+
});
|
|
2155
|
+
});
|
|
2010
2156
|
api.post("/projects/:id/file-mutations/wrap-elements/*", async (c) => {
|
|
2011
2157
|
const ctx = await resolveFileMutationContext(c, adapter, "wrap-elements");
|
|
2012
2158
|
if ("error" in ctx) return ctx.error;
|
|
@@ -2188,92 +2334,28 @@ function registerFileRoutes(api, adapter) {
|
|
|
2188
2334
|
});
|
|
2189
2335
|
if ("error" in res) return res.error;
|
|
2190
2336
|
const body = await c.req.json().catch(() => null);
|
|
2191
|
-
if (!body
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
let block = extractGsapScriptBlock(html);
|
|
2203
|
-
if (!block && (body.type === "add" || body.type === "add-with-keyframes")) {
|
|
2204
|
-
const compId = html.match(/data-composition-id="([^"]+)"/)?.[1] ?? "main";
|
|
2205
|
-
const { GSAP_CDN } = await import("@hyperframes/core");
|
|
2206
|
-
const gsapCdn = `<script src="${GSAP_CDN}"></script>`;
|
|
2207
|
-
const bootstrap = [
|
|
2208
|
-
gsapCdn,
|
|
2209
|
-
"<script>",
|
|
2210
|
-
"window.__timelines = window.__timelines || {};",
|
|
2211
|
-
`const tl = gsap.timeline({ paused: true });`,
|
|
2212
|
-
`window.__timelines["${compId}"] = tl;`,
|
|
2213
|
-
"</script>"
|
|
2214
|
-
].join("\n");
|
|
2215
|
-
if (html.includes("</body>")) {
|
|
2216
|
-
html = html.replace("</body>", `${bootstrap}
|
|
2217
|
-
</body>`);
|
|
2218
|
-
} else {
|
|
2219
|
-
html += `
|
|
2220
|
-
${bootstrap}`;
|
|
2221
|
-
}
|
|
2222
|
-
block = extractGsapScriptBlock(html);
|
|
2223
|
-
}
|
|
2224
|
-
if (!block && (body.type === "shift-positions" || body.type === "scale-positions" || body.type === "shift-positions-batch")) {
|
|
2225
|
-
return c.json({
|
|
2226
|
-
ok: true,
|
|
2227
|
-
changed: false,
|
|
2228
|
-
mutated: false,
|
|
2229
|
-
parsed: { animations: [], timelineVar: "tl", preamble: "", postamble: "" },
|
|
2230
|
-
before: html,
|
|
2231
|
-
after: html,
|
|
2232
|
-
scriptText: "",
|
|
2233
|
-
path: res.filePath,
|
|
2234
|
-
backupPath: null
|
|
2235
|
-
});
|
|
2236
|
-
}
|
|
2237
|
-
if (!block) {
|
|
2238
|
-
return c.json({ error: "no GSAP script found in file" }, 400);
|
|
2239
|
-
}
|
|
2240
|
-
const respond = (data, status) => (
|
|
2241
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- bridge between generic status and Hono's literal union
|
|
2242
|
-
status ? c.json(data, status) : c.json(data)
|
|
2337
|
+
if (!body) return c.json({ error: "mutation type required" }, 400);
|
|
2338
|
+
const error = validateGsapMutationRequest(c, body);
|
|
2339
|
+
if (error) return error;
|
|
2340
|
+
return applyGsapMutations(c, res, [body]);
|
|
2341
|
+
});
|
|
2342
|
+
api.post("/projects/:id/gsap-mutations-batch/*", async (c) => {
|
|
2343
|
+
const res = await resolveProjectPath(
|
|
2344
|
+
c,
|
|
2345
|
+
adapter,
|
|
2346
|
+
(id) => `/projects/${id}/gsap-mutations-batch/`,
|
|
2347
|
+
{ mustExist: true }
|
|
2243
2348
|
);
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
const parser = await loadGsapParser();
|
|
2249
|
-
newScript = parser.syncPositionHoldsBeforeKeyframes(newScript);
|
|
2349
|
+
if ("error" in res) return res.error;
|
|
2350
|
+
const body = await c.req.json().catch(() => null);
|
|
2351
|
+
if (!body || !Array.isArray(body.mutations) || body.mutations.length === 0) {
|
|
2352
|
+
return c.json({ error: "mutations array required" }, 400);
|
|
2250
2353
|
}
|
|
2251
|
-
const
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
if (changed) {
|
|
2255
|
-
const backup = snapshotBeforeWrite(res.project.dir, res.absPath);
|
|
2256
|
-
if (backup.error)
|
|
2257
|
-
console.warn(`Failed to create backup for ${res.filePath}: ${backup.error}`);
|
|
2258
|
-
backupPath = backupPathForResponse(res.project.dir, backup.backupPath);
|
|
2259
|
-
writeFileSync4(res.absPath, newHtml, "utf-8");
|
|
2260
|
-
}
|
|
2261
|
-
const freshParsed = parseGsapScriptAcorn(newScript);
|
|
2262
|
-
const responsePayload = {
|
|
2263
|
-
ok: true,
|
|
2264
|
-
changed,
|
|
2265
|
-
mutated: changed,
|
|
2266
|
-
parsed: freshParsed,
|
|
2267
|
-
before: html,
|
|
2268
|
-
after: newHtml,
|
|
2269
|
-
scriptText: newScript,
|
|
2270
|
-
path: res.filePath,
|
|
2271
|
-
backupPath
|
|
2272
|
-
};
|
|
2273
|
-
if (typeof result !== "string" && result.skippedSelectors.length > 0) {
|
|
2274
|
-
responsePayload.skippedSelectors = result.skippedSelectors;
|
|
2354
|
+
for (const mutation of body.mutations) {
|
|
2355
|
+
const error = validateGsapMutationRequest(c, mutation);
|
|
2356
|
+
if (error) return error;
|
|
2275
2357
|
}
|
|
2276
|
-
return c.
|
|
2358
|
+
return applyGsapMutations(c, res, body.mutations);
|
|
2277
2359
|
});
|
|
2278
2360
|
}
|
|
2279
2361
|
|
|
@@ -2786,10 +2868,10 @@ function studioMotionRenderRuntime(manifestContent, activeCompositionPath) {
|
|
|
2786
2868
|
}
|
|
2787
2869
|
|
|
2788
2870
|
// src/routes/preview.ts
|
|
2789
|
-
import { ensureHfIds as
|
|
2871
|
+
import { ensureHfIds as ensureHfIds3 } from "@hyperframes/parsers/hf-ids";
|
|
2790
2872
|
|
|
2791
2873
|
// src/helpers/hfIdPersist.ts
|
|
2792
|
-
import { ensureHfIds } from "@hyperframes/parsers/hf-ids";
|
|
2874
|
+
import { ensureHfIds as ensureHfIds2 } from "@hyperframes/parsers/hf-ids";
|
|
2793
2875
|
import {
|
|
2794
2876
|
closeSync,
|
|
2795
2877
|
constants,
|
|
@@ -2801,7 +2883,7 @@ import {
|
|
|
2801
2883
|
writeSync
|
|
2802
2884
|
} from "fs";
|
|
2803
2885
|
function persistHfIdsIfNeeded(filePath, html) {
|
|
2804
|
-
const normalized =
|
|
2886
|
+
const normalized = ensureHfIds2(html);
|
|
2805
2887
|
const idsBefore = (html.match(/\bdata-hf-id=/g) ?? []).length;
|
|
2806
2888
|
const idsAfter = (normalized.match(/\bdata-hf-id=/g) ?? []).length;
|
|
2807
2889
|
if (idsAfter > idsBefore) {
|
|
@@ -2835,7 +2917,7 @@ function stampFileHfIds(filePath) {
|
|
|
2835
2917
|
try {
|
|
2836
2918
|
if (!fstatSync(fd).isFile()) return null;
|
|
2837
2919
|
const html = readFileSync6(fd, "utf-8");
|
|
2838
|
-
const normalized =
|
|
2920
|
+
const normalized = ensureHfIds2(html);
|
|
2839
2921
|
const idsBefore = (html.match(/\bdata-hf-id=/g) ?? []).length;
|
|
2840
2922
|
const idsAfter = (normalized.match(/\bdata-hf-id=/g) ?? []).length;
|
|
2841
2923
|
if (writable && idsAfter > idsBefore) {
|
|
@@ -3086,7 +3168,7 @@ ${runtimeTag}`;
|
|
|
3086
3168
|
bundled = bundled.replace(/<head>/i, `<head><base href="${baseHref}">`);
|
|
3087
3169
|
}
|
|
3088
3170
|
bundled = injectStudioPreviewAugmentations(
|
|
3089
|
-
|
|
3171
|
+
ensureHfIds3(await transformPreviewHtml(bundled, adapter, project, mainCompositionPath)),
|
|
3090
3172
|
adapter,
|
|
3091
3173
|
project.dir,
|
|
3092
3174
|
mainCompositionPath
|
|
@@ -3148,7 +3230,7 @@ ${runtimeTag}`;
|
|
|
3148
3230
|
stamped
|
|
3149
3231
|
);
|
|
3150
3232
|
if (!html) return c.text("not found", 404);
|
|
3151
|
-
html =
|
|
3233
|
+
html = ensureHfIds3(await transformPreviewHtml(html, adapter, project, compPath));
|
|
3152
3234
|
html = injectStudioPreviewAugmentations(html, adapter, project.dir, compPath);
|
|
3153
3235
|
if (previewVariables) html = injectPreviewVariables(html, previewVariables);
|
|
3154
3236
|
return c.html(html, 200, previewCacheHeaders(etag));
|