Visual editor (Pro only) (#1828)
<!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Prototype visual editing mode for the preview app. Toggle the mode, pick elements (single or multiple), and edit margin, padding, border, background, static text, and text styles with live updates, then save changes back to code. - **New Features** - Pen tool button to enable/disable visual editing in the preview and toggle single/multi select; pro-only. - Inline toolbar anchored to the selected element for Margin (X/Y), Padding (X/Y), Border (width/radius/color), Background color, Edit Text (when static), and Text Style (font size/weight/color/font family). - Reads computed styles from the iframe and applies changes in real time; auto-appends px; overlay updates on scroll/resize. - Save/Discard dialog batches edits and writes Tailwind classes to source files via IPC; uses AST/recast to update className and text, replacing conflicting classes by prefix; supports multiple components. - New visual editor worker to get/apply styles and enable inline text editing via postMessage; selector client updated for coordinates streaming and highlight/deselect. - Proxy injects the visual editor client; new atoms track selected component, coordinates, and pending changes; component analysis flags dynamic styling and static text. - Uses runtimeId to correctly target and edit duplicate components. - **Dependencies** - Added @babel/parser for AST-based text updates. - Added recast for safer code transformations. <sup>Written for commit cdd50d33387a29103864f4743ae7570d64d61e93. Summary will update automatically on new commits.</sup> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
committed by
GitHub
parent
c174778d5f
commit
352d4330ed
@@ -4,6 +4,9 @@
|
||||
let hoverOverlay = null;
|
||||
let hoverLabel = null;
|
||||
let currentHoveredElement = null;
|
||||
let highlightedElement = null;
|
||||
let componentCoordinates = null; // Store the last selected component's coordinates
|
||||
let isProMode = false; // Track if pro mode is enabled
|
||||
//detect if the user is using Mac
|
||||
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
|
||||
|
||||
@@ -51,7 +54,7 @@
|
||||
return { overlay, label };
|
||||
}
|
||||
|
||||
function updateOverlay(el, isSelected = false) {
|
||||
function updateOverlay(el, isSelected = false, isHighlighted = false) {
|
||||
// If no element, hide hover overlay
|
||||
if (!el) {
|
||||
if (hoverOverlay) hoverOverlay.style.display = "none";
|
||||
@@ -67,14 +70,19 @@
|
||||
overlays.push({ overlay, label, el });
|
||||
|
||||
const rect = el.getBoundingClientRect();
|
||||
const borderColor = isHighlighted ? "#00ff00" : "#7f22fe";
|
||||
const backgroundColor = isHighlighted
|
||||
? "rgba(0, 255, 0, 0.05)"
|
||||
: "rgba(127, 34, 254, 0.05)";
|
||||
|
||||
css(overlay, {
|
||||
top: `${rect.top + window.scrollY}px`,
|
||||
left: `${rect.left + window.scrollX}px`,
|
||||
width: `${rect.width}px`,
|
||||
height: `${rect.height}px`,
|
||||
display: "block",
|
||||
border: "3px solid #7f22fe",
|
||||
background: "rgba(127, 34, 254, 0.05)",
|
||||
border: `3px solid ${borderColor}`,
|
||||
background: backgroundColor,
|
||||
});
|
||||
|
||||
css(label, { display: "none" });
|
||||
@@ -143,6 +151,30 @@
|
||||
height: `${rect.height}px`,
|
||||
});
|
||||
}
|
||||
|
||||
// Send updated coordinates for highlighted or selected component to parent
|
||||
if (highlightedElement) {
|
||||
// Multi-selector mode: send coordinates for the highlighted component
|
||||
const highlightedItem = overlays.find(
|
||||
({ el }) => el === highlightedElement,
|
||||
);
|
||||
|
||||
if (highlightedItem) {
|
||||
const rect = highlightedItem.el.getBoundingClientRect();
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "dyad-component-coordinates-updated",
|
||||
coordinates: {
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
},
|
||||
},
|
||||
"*",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearOverlays() {
|
||||
@@ -156,17 +188,70 @@
|
||||
}
|
||||
|
||||
currentHoveredElement = null;
|
||||
highlightedElement = null;
|
||||
}
|
||||
|
||||
function removeOverlayById(componentId) {
|
||||
const index = overlays.findIndex(
|
||||
({ el }) => el.dataset.dyadId === componentId,
|
||||
);
|
||||
if (index !== -1) {
|
||||
const { overlay } = overlays[index];
|
||||
// Remove all overlays with the same componentId
|
||||
const indicesToRemove = [];
|
||||
overlays.forEach((item, index) => {
|
||||
if (item.el.dataset.dyadId === componentId) {
|
||||
indicesToRemove.push(index);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove in reverse order to maintain correct indices
|
||||
for (let i = indicesToRemove.length - 1; i >= 0; i--) {
|
||||
const { overlay } = overlays[indicesToRemove[i]];
|
||||
overlay.remove();
|
||||
overlays.splice(index, 1);
|
||||
overlays.splice(indicesToRemove[i], 1);
|
||||
}
|
||||
|
||||
if (
|
||||
highlightedElement &&
|
||||
highlightedElement.dataset.dyadId === componentId
|
||||
) {
|
||||
highlightedElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to check if mouse is over the toolbar
|
||||
function isMouseOverToolbar(mouseX, mouseY) {
|
||||
if (!componentCoordinates) return false;
|
||||
|
||||
// Toolbar is positioned at bottom of component: top = coordinates.top + coordinates.height + 4px
|
||||
const toolbarTop =
|
||||
componentCoordinates.top + componentCoordinates.height + 4;
|
||||
const toolbarLeft = componentCoordinates.left;
|
||||
const toolbarHeight = 60;
|
||||
// Add some padding to the width since we don't know exact width
|
||||
const toolbarWidth = componentCoordinates.width || 400;
|
||||
|
||||
return (
|
||||
mouseY >= toolbarTop &&
|
||||
mouseY <= toolbarTop + toolbarHeight &&
|
||||
mouseX >= toolbarLeft &&
|
||||
mouseX <= toolbarLeft + toolbarWidth
|
||||
);
|
||||
}
|
||||
|
||||
// Helper function to check if the highlighted component is inside another selected component
|
||||
function isHighlightedComponentChildOfSelected() {
|
||||
if (!highlightedElement) return null;
|
||||
|
||||
const highlightedItem = overlays.find(
|
||||
({ el }) => el === highlightedElement,
|
||||
);
|
||||
if (!highlightedItem) return null;
|
||||
|
||||
// Check if any other selected component contains the highlighted element
|
||||
for (const item of overlays) {
|
||||
if (item.el === highlightedItem.el) continue; // Skip the highlighted component itself
|
||||
if (item.el.contains(highlightedItem.el)) {
|
||||
return item; // Return the parent component
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Helper function to show/hide and populate label for a selected overlay
|
||||
@@ -227,11 +312,43 @@
|
||||
|
||||
/* ---------- event handlers -------------------------------------------- */
|
||||
function onMouseMove(e) {
|
||||
// Check if mouse is over toolbar - if so, hide the label and treat as if mouse left component
|
||||
if (isMouseOverToolbar(e.clientX, e.clientY)) {
|
||||
if (currentHoveredElement) {
|
||||
const previousItem = overlays.find(
|
||||
(item) => item.el === currentHoveredElement,
|
||||
);
|
||||
if (previousItem) {
|
||||
updateSelectedOverlayLabel(previousItem, false);
|
||||
}
|
||||
currentHoveredElement = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let el = e.target;
|
||||
while (el && !el.dataset.dyadId) el = el.parentElement;
|
||||
|
||||
const hoveredItem = overlays.find((item) => item.el === el);
|
||||
|
||||
// Check if the highlighted component is a child of another selected component
|
||||
const parentOfHighlighted = isHighlightedComponentChildOfSelected();
|
||||
|
||||
// If hovering over the highlighted component and it has a parent, hide the parent's label
|
||||
if (
|
||||
hoveredItem &&
|
||||
hoveredItem.el === highlightedElement &&
|
||||
parentOfHighlighted
|
||||
) {
|
||||
// Hide the parent component's label
|
||||
updateSelectedOverlayLabel(parentOfHighlighted, false);
|
||||
// Also clear currentHoveredElement if it's the parent
|
||||
if (currentHoveredElement === parentOfHighlighted.el) {
|
||||
currentHoveredElement = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentHoveredElement && currentHoveredElement !== el) {
|
||||
const previousItem = overlays.find(
|
||||
(item) => item.el === currentHoveredElement,
|
||||
@@ -243,8 +360,8 @@
|
||||
|
||||
currentHoveredElement = el;
|
||||
|
||||
// If hovering over a selected component, show its label
|
||||
if (hoveredItem) {
|
||||
// If hovering over a selected component, show its label only if it's not highlighted
|
||||
if (hoveredItem && hoveredItem.el !== highlightedElement) {
|
||||
updateSelectedOverlayLabel(hoveredItem, true);
|
||||
if (hoverOverlay) hoverOverlay.style.display = "none";
|
||||
}
|
||||
@@ -280,29 +397,76 @@
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const selectedItem = overlays.find((item) => item.el === e.target);
|
||||
if (selectedItem) {
|
||||
removeOverlayById(state.element.dataset.dyadId);
|
||||
const clickedComponentId = state.element.dataset.dyadId;
|
||||
const selectedItem = overlays.find((item) => item.el === state.element);
|
||||
|
||||
// If clicking on the currently highlighted component, deselect it
|
||||
if (selectedItem && (highlightedElement === state.element || !isProMode)) {
|
||||
if (state.element.contentEditable === "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
removeOverlayById(clickedComponentId);
|
||||
requestAnimationFrame(updateAllOverlayPositions);
|
||||
highlightedElement = null;
|
||||
|
||||
// Only post message once for all elements with the same ID
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "dyad-component-deselected",
|
||||
componentId: state.element.dataset.dyadId,
|
||||
componentId: clickedComponentId,
|
||||
},
|
||||
"*",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
updateOverlay(state.element, true);
|
||||
// Update only the previously highlighted component
|
||||
if (highlightedElement && highlightedElement !== state.element) {
|
||||
const previousItem = overlays.find(
|
||||
(item) => item.el === highlightedElement,
|
||||
);
|
||||
if (previousItem) {
|
||||
css(previousItem.overlay, {
|
||||
border: `3px solid #7f22fe`,
|
||||
background: "rgba(127, 34, 254, 0.05)",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(updateAllOverlayPositions);
|
||||
highlightedElement = state.element;
|
||||
|
||||
if (selectedItem && isProMode) {
|
||||
css(selectedItem.overlay, {
|
||||
border: `3px solid #00ff00`,
|
||||
background: "rgba(0, 255, 0, 0.05)",
|
||||
});
|
||||
}
|
||||
|
||||
if (!selectedItem) {
|
||||
updateOverlay(state.element, true, isProMode);
|
||||
requestAnimationFrame(updateAllOverlayPositions);
|
||||
}
|
||||
|
||||
// Assign a unique runtime ID to this element if it doesn't have one
|
||||
if (!state.element.dataset.dyadRuntimeId) {
|
||||
state.element.dataset.dyadRuntimeId = `dyad-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
const rect = state.element.getBoundingClientRect();
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "dyad-component-selected",
|
||||
component: {
|
||||
id: state.element.dataset.dyadId,
|
||||
id: clickedComponentId,
|
||||
name: state.element.dataset.dyadName,
|
||||
runtimeId: state.element.dataset.dyadRuntimeId,
|
||||
},
|
||||
coordinates: {
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
},
|
||||
},
|
||||
"*",
|
||||
@@ -362,10 +526,30 @@
|
||||
/* ---------- message bridge -------------------------------------------- */
|
||||
window.addEventListener("message", (e) => {
|
||||
if (e.source !== window.parent) return;
|
||||
if (e.data.type === "dyad-pro-mode") {
|
||||
isProMode = e.data.enabled;
|
||||
}
|
||||
if (e.data.type === "activate-dyad-component-selector") activate();
|
||||
if (e.data.type === "deactivate-dyad-component-selector") deactivate();
|
||||
if (e.data.type === "activate-dyad-visual-editing") {
|
||||
activate();
|
||||
}
|
||||
if (e.data.type === "deactivate-dyad-visual-editing") {
|
||||
deactivate();
|
||||
clearOverlays();
|
||||
}
|
||||
if (e.data.type === "clear-dyad-component-overlays") clearOverlays();
|
||||
if (e.data.type === "remove-dyad-component-overlay") {
|
||||
if (e.data.type === "update-dyad-overlay-positions") {
|
||||
updateAllOverlayPositions();
|
||||
}
|
||||
if (e.data.type === "update-component-coordinates") {
|
||||
// Store component coordinates for toolbar hover detection
|
||||
componentCoordinates = e.data.coordinates;
|
||||
}
|
||||
if (
|
||||
e.data.type === "remove-dyad-component-overlay" ||
|
||||
e.data.type === "deselect-dyad-component"
|
||||
) {
|
||||
if (e.data.componentId) {
|
||||
removeOverlayById(e.data.componentId);
|
||||
}
|
||||
@@ -380,8 +564,9 @@
|
||||
|
||||
document.addEventListener("mouseleave", onMouseLeave, true);
|
||||
|
||||
// Update overlay positions on window resize
|
||||
// Update overlay positions on window resize and scroll
|
||||
window.addEventListener("resize", updateAllOverlayPositions);
|
||||
window.addEventListener("scroll", updateAllOverlayPositions, true);
|
||||
|
||||
function initializeComponentSelector() {
|
||||
if (!document.body) {
|
||||
|
||||
278
worker/dyad-visual-editor-client.js
Normal file
278
worker/dyad-visual-editor-client.js
Normal file
@@ -0,0 +1,278 @@
|
||||
(() => {
|
||||
/* ---------- helpers --------------------------------------------------- */
|
||||
|
||||
// Track text editing state globally
|
||||
let textEditingState = new Map(); // componentId -> { originalText, currentText, cleanup }
|
||||
|
||||
function findElementByDyadId(dyadId, runtimeId) {
|
||||
// If runtimeId is provided, try to find element by runtime ID first
|
||||
if (runtimeId) {
|
||||
const elementByRuntimeId = document.querySelector(
|
||||
`[data-dyad-runtime-id="${runtimeId}"]`,
|
||||
);
|
||||
if (elementByRuntimeId) {
|
||||
return elementByRuntimeId;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to finding by dyad-id (will get first match)
|
||||
const escaped = CSS.escape(dyadId);
|
||||
return document.querySelector(`[data-dyad-id="${escaped}"]`);
|
||||
}
|
||||
|
||||
function applyStyles(element, styles) {
|
||||
if (!element || !styles) return;
|
||||
|
||||
console.debug(
|
||||
`[Dyad Visual Editor] Applying styles:`,
|
||||
styles,
|
||||
"to element:",
|
||||
element,
|
||||
);
|
||||
|
||||
const applySpacing = (type, values) => {
|
||||
if (!values) return;
|
||||
Object.entries(values).forEach(([side, value]) => {
|
||||
const cssProperty = `${type}${side.charAt(0).toUpperCase() + side.slice(1)}`;
|
||||
element.style[cssProperty] = value;
|
||||
});
|
||||
};
|
||||
|
||||
applySpacing("margin", styles.margin);
|
||||
applySpacing("padding", styles.padding);
|
||||
|
||||
if (styles.border) {
|
||||
if (styles.border.width !== undefined) {
|
||||
element.style.borderWidth = styles.border.width;
|
||||
element.style.borderStyle = "solid";
|
||||
}
|
||||
if (styles.border.radius !== undefined) {
|
||||
element.style.borderRadius = styles.border.radius;
|
||||
}
|
||||
if (styles.border.color !== undefined) {
|
||||
element.style.borderColor = styles.border.color;
|
||||
}
|
||||
}
|
||||
|
||||
if (styles.backgroundColor !== undefined) {
|
||||
element.style.backgroundColor = styles.backgroundColor;
|
||||
}
|
||||
|
||||
if (styles.text) {
|
||||
const textProps = {
|
||||
fontSize: "fontSize",
|
||||
fontWeight: "fontWeight",
|
||||
fontFamily: "fontFamily",
|
||||
color: "color",
|
||||
};
|
||||
Object.entries(textProps).forEach(([key, cssProp]) => {
|
||||
if (styles.text[key] !== undefined) {
|
||||
element.style[cssProp] = styles.text[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- message handlers ------------------------------------------ */
|
||||
|
||||
function handleGetStyles(data) {
|
||||
const { elementId, runtimeId } = data;
|
||||
const element = findElementByDyadId(elementId, runtimeId);
|
||||
if (element) {
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
const styles = {
|
||||
margin: {
|
||||
top: computedStyle.marginTop,
|
||||
right: computedStyle.marginRight,
|
||||
bottom: computedStyle.marginBottom,
|
||||
left: computedStyle.marginLeft,
|
||||
},
|
||||
padding: {
|
||||
top: computedStyle.paddingTop,
|
||||
right: computedStyle.paddingRight,
|
||||
bottom: computedStyle.paddingBottom,
|
||||
left: computedStyle.paddingLeft,
|
||||
},
|
||||
border: {
|
||||
width: computedStyle.borderWidth,
|
||||
radius: computedStyle.borderRadius,
|
||||
color: computedStyle.borderColor,
|
||||
},
|
||||
backgroundColor: computedStyle.backgroundColor,
|
||||
text: {
|
||||
fontSize: computedStyle.fontSize,
|
||||
fontWeight: computedStyle.fontWeight,
|
||||
fontFamily: computedStyle.fontFamily,
|
||||
color: computedStyle.color,
|
||||
},
|
||||
};
|
||||
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "dyad-component-styles",
|
||||
data: styles,
|
||||
},
|
||||
"*",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function handleModifyStyles(data) {
|
||||
const { elementId, runtimeId, styles } = data;
|
||||
const element = findElementByDyadId(elementId, runtimeId);
|
||||
if (element) {
|
||||
applyStyles(element, styles);
|
||||
|
||||
// Send updated coordinates after style change
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "dyad-component-coordinates-updated",
|
||||
coordinates: {
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
},
|
||||
},
|
||||
"*",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function handleEnableTextEditing(data) {
|
||||
const { componentId, runtimeId } = data;
|
||||
|
||||
// Clean up any existing text editing states first
|
||||
textEditingState.forEach((state, existingId) => {
|
||||
if (existingId !== componentId) {
|
||||
state.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
const element = findElementByDyadId(componentId, runtimeId);
|
||||
if (element) {
|
||||
const originalText = element.innerText;
|
||||
|
||||
element.contentEditable = "true";
|
||||
element.focus();
|
||||
|
||||
// Select all text
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(element);
|
||||
const sel = window.getSelection();
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
|
||||
// Send updates as user types
|
||||
const onInput = () => {
|
||||
const currentText = element.innerText;
|
||||
|
||||
// Update tracked state
|
||||
const state = textEditingState.get(componentId);
|
||||
if (state) {
|
||||
state.currentText = currentText;
|
||||
}
|
||||
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "dyad-text-updated",
|
||||
componentId,
|
||||
text: currentText,
|
||||
},
|
||||
"*",
|
||||
);
|
||||
};
|
||||
|
||||
element.addEventListener("input", onInput);
|
||||
|
||||
// Prevent click from propagating to selector while editing
|
||||
const stopProp = (e) => e.stopPropagation();
|
||||
element.addEventListener("click", stopProp);
|
||||
|
||||
// Cleanup function
|
||||
const cleanup = () => {
|
||||
element.contentEditable = "false";
|
||||
element.removeEventListener("input", onInput);
|
||||
element.removeEventListener("click", stopProp);
|
||||
|
||||
// Send final text update
|
||||
const finalText = element.innerText;
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "dyad-text-finalized",
|
||||
componentId,
|
||||
text: finalText,
|
||||
},
|
||||
"*",
|
||||
);
|
||||
|
||||
textEditingState.delete(componentId);
|
||||
};
|
||||
|
||||
// Store state
|
||||
textEditingState.set(componentId, {
|
||||
originalText,
|
||||
currentText: originalText,
|
||||
cleanup,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleDisableTextEditing(data) {
|
||||
const { componentId } = data;
|
||||
const state = textEditingState.get(componentId);
|
||||
if (state) {
|
||||
state.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
function handleGetTextContent(data) {
|
||||
const { componentId, runtimeId } = data;
|
||||
const element = findElementByDyadId(componentId, runtimeId);
|
||||
const state = textEditingState.get(componentId);
|
||||
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "dyad-text-content-response",
|
||||
componentId,
|
||||
text: state ? state.currentText : element ? element.innerText : null,
|
||||
isEditing: !!state,
|
||||
},
|
||||
"*",
|
||||
);
|
||||
}
|
||||
|
||||
/* ---------- message bridge -------------------------------------------- */
|
||||
|
||||
window.addEventListener("message", (e) => {
|
||||
if (e.source !== window.parent) return;
|
||||
|
||||
const { type, data } = e.data;
|
||||
|
||||
switch (type) {
|
||||
case "get-dyad-component-styles":
|
||||
handleGetStyles(data);
|
||||
break;
|
||||
case "modify-dyad-component-styles":
|
||||
handleModifyStyles(data);
|
||||
break;
|
||||
case "enable-dyad-text-editing":
|
||||
handleEnableTextEditing(data);
|
||||
break;
|
||||
case "disable-dyad-text-editing":
|
||||
handleDisableTextEditing(data);
|
||||
break;
|
||||
case "get-dyad-text-content":
|
||||
handleGetTextContent(data);
|
||||
break;
|
||||
case "cleanup-all-text-editing":
|
||||
// Clean up all text editing states
|
||||
textEditingState.forEach((state) => {
|
||||
state.cleanup();
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -38,6 +38,7 @@ let rememberedOrigin = null; // e.g. "http://localhost:5173"
|
||||
let stacktraceJsContent = null;
|
||||
let dyadShimContent = null;
|
||||
let dyadComponentSelectorClientContent = null;
|
||||
let dyadVisualEditorClientContent = null;
|
||||
try {
|
||||
const stackTraceLibPath = path.join(
|
||||
__dirname,
|
||||
@@ -83,6 +84,24 @@ try {
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const dyadVisualEditorClientPath = path.join(
|
||||
__dirname,
|
||||
"dyad-visual-editor-client.js",
|
||||
);
|
||||
dyadVisualEditorClientContent = fs.readFileSync(
|
||||
dyadVisualEditorClientPath,
|
||||
"utf-8",
|
||||
);
|
||||
parentPort?.postMessage(
|
||||
"[proxy-worker] dyad-visual-editor-client.js loaded.",
|
||||
);
|
||||
} catch (error) {
|
||||
parentPort?.postMessage(
|
||||
`[proxy-worker] Failed to read dyad-visual-editor-client.js: ${error.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
/* ---------------------- helper: need to inject? ------------------------ */
|
||||
function needsInjection(pathname) {
|
||||
// Inject for routes without a file extension (e.g., "/foo", "/foo/bar", "/")
|
||||
@@ -124,6 +143,13 @@ function injectHTML(buf) {
|
||||
'<script>console.warn("[proxy-worker] dyad component selector client was not injected.");</script>',
|
||||
);
|
||||
}
|
||||
if (dyadVisualEditorClientContent) {
|
||||
scripts.push(`<script>${dyadVisualEditorClientContent}</script>`);
|
||||
} else {
|
||||
scripts.push(
|
||||
'<script>console.warn("[proxy-worker] dyad visual editor client was not injected.");</script>',
|
||||
);
|
||||
}
|
||||
const allScripts = scripts.join("\n");
|
||||
|
||||
const headRegex = /<head[^>]*>/i;
|
||||
|
||||
Reference in New Issue
Block a user