feat(studio): add Ctrl+C/V/X copy/paste for timeline clips and DOM elements#887
Closed
miguel-heygen wants to merge 9 commits into
Closed
feat(studio): add Ctrl+C/V/X copy/paste for timeline clips and DOM elements#887miguel-heygen wants to merge 9 commits into
miguel-heygen wants to merge 9 commits into
Conversation
Elements from the preview iframe are from a different window context, so `el instanceof HTMLElement` always returns false. Use `"outerHTML" in el` instead to correctly detect elements across frame boundaries.
Comment on lines
+39
to
+41
| const response = await fetch( | ||
| `/api/projects/${projectId}/files/${encodeURIComponent(targetPath)}`, | ||
| ); |
reloadPreview() used location.reload() which bypassed the NLELayout saveSeekPosition effect, causing the playhead to reset to 0:00 after paste. Switch to setRefreshKey which triggers the effect and restores the seek position after the iframe reloads.
DOM element paste was inserting at the composition root, losing the parent context that provides CSS styles and positioning. Now stores the origin selector on copy and inserts the paste as a sibling immediately after the original element, preserving style inheritance. Falls back to root insertion if the selector can't be matched.
| searchPattern = new RegExp(`<[a-z][^>]*\\bclass="[^"]*\\b${cls}\\b[^"]*"[^>]*>`, "gi"); | ||
| } else if (selector.startsWith("[")) { | ||
| const inner = selector.slice(1, -1); | ||
| searchPattern = new RegExp(`<[a-z][^>]*\\b${inner.replace(/"/g, '"')}[^>]*>`, "gi"); |
…rdown Content refreshes (paste, move, resize, delete, asset drop) previously triggered setRefreshKey which changed the Player's React key, causing full web-component destruction + iframe teardown + crossfade animation + re-initialization of all event listeners and asset polling. Now NLELayout intercepts refreshKey changes and calls refreshPlayer() which just appends a cache-busting _t param to the iframe src. The Player web component stays alive, event listeners persist, and the reload is ~10x faster with no "waiting for media" flash. Key-based teardown is preserved for actual structural changes (project switch, composition drill-down via directUrl change).
The asset-loading overlay ("Preparing preview assets") polled for
video/audio readyState on every iframe load, including content
refreshes from paste/move/resize. On reloads the browser serves
assets from cache so they resolve near-instantly — the overlay
just created a disruptive flash. Now skips the polling on
subsequent loads (loadCountRef > 1), only showing it on the
initial cold load.
Adds Start, End, and Duration fields to the Design panel when the selected element has data-start/data-duration attributes. Editing any field commits via the attribute patch pipeline (same as timeline edits) and refreshes the preview. End is computed from start+duration and writing End adjusts duration accordingly.
collectDomEditTextFields only captured child HTML elements, ignoring bare text nodes. For elements like: <div class="headline">If you're <span>turning 65</span> soon...</div> only the <span> was collected as a text field. When commitDomTextFields serialized back, "If you're " and " soon..." were lost. Now walks childNodes and creates text-node fields for bare text nodes alongside child element fields. serializeDomEditTextFields emits bare text for text-node fields, preserving the complete mixed content.
25775d8 to
f6202d3
Compare
Collaborator
Author
|
Splitting into stacked PRs for easier review. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
isEditableTargetguard prevents intercepting native copy/paste in text inputsImplementation
clipboardPayload.ts— payload types, JSON serialization with version marker,deduplicateIdsfor collision-safe pasteuseClipboard.ts— hook owning an in-memory clipboard ref + copy/paste/cut handlers. Reads element HTML from the preview iframe, inserts viainsertTimelineAssetIntoSource, records edits viasaveProjectFilesWithHistoryuseAppHotkeys.ts— Ctrl+C/V/X wired into the consolidated keydown handler with stable refsinstanceoffix: elements from the preview iframe failel instanceof HTMLElement(different window context), switched to"outerHTML" in elduck-typingTest plan
clipboardPayload(dedup collisions, no-collision passthrough, round-trip serialization, invalid JSON rejection)