Skip to content

Commit 25775d8

Browse files
committed
fix(studio): preserve bare text nodes in mixed-content elements
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.
1 parent 0feb738 commit 25775d8

2 files changed

Lines changed: 40 additions & 6 deletions

File tree

packages/studio/src/components/editor/domEditingLayers.ts

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,41 @@ function buildTextField(
7373
}
7474

7575
export function collectDomEditTextFields(el: HTMLElement): DomEditTextField[] {
76-
const childFields = Array.from(el.children).filter(isHtmlElement).filter(isEditableTextLeaf);
77-
if (childFields.length > 0) {
78-
return childFields.map((child, index) =>
79-
buildTextField(child, index, childFields.length, "child"),
76+
const childElements = Array.from(el.children).filter(isHtmlElement).filter(isEditableTextLeaf);
77+
78+
if (childElements.length > 0) {
79+
const hasMixedContent = Array.from(el.childNodes).some(
80+
(node) => node.nodeType === Node.TEXT_NODE && node.textContent?.trim(),
81+
);
82+
83+
if (hasMixedContent) {
84+
const fields: DomEditTextField[] = [];
85+
let childIdx = 0;
86+
for (const node of el.childNodes) {
87+
if (node.nodeType === Node.TEXT_NODE) {
88+
const text = node.textContent ?? "";
89+
if (!text.trim()) continue;
90+
fields.push({
91+
key: `text-node:${childIdx}`,
92+
label: `Text ${childIdx + 1}`,
93+
value: text,
94+
tagName: "#text",
95+
attributes: [],
96+
inlineStyles: {},
97+
computedStyles: {},
98+
source: "text-node",
99+
});
100+
childIdx++;
101+
} else if (isHtmlElement(node) && isEditableTextLeaf(node)) {
102+
fields.push(buildTextField(node, childIdx, childElements.length, "child"));
103+
childIdx++;
104+
}
105+
}
106+
return fields;
107+
}
108+
109+
return childElements.map((child, index) =>
110+
buildTextField(child, index, childElements.length, "child"),
80111
);
81112
}
82113

@@ -99,8 +130,11 @@ function serializeTextFieldStyle(field: DomEditTextField): string {
99130

100131
export function serializeDomEditTextFields(fields: DomEditTextField[]): string {
101132
return fields
102-
.filter((field) => field.source === "child")
133+
.filter((field) => field.source === "child" || field.source === "text-node")
103134
.map((field) => {
135+
if (field.source === "text-node") {
136+
return escapeHtmlText(field.value);
137+
}
104138
const attrs = [
105139
...field.attributes.filter((attribute) => attribute.name !== "data-hf-text-key"),
106140
{ name: "data-hf-text-key", value: field.key },

packages/studio/src/components/editor/domEditingTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export interface DomEditTextField {
6565
attributes: Array<{ name: string; value: string }>;
6666
inlineStyles: Record<string, string>;
6767
computedStyles: Record<string, string>;
68-
source: "self" | "child";
68+
source: "self" | "child" | "text-node";
6969
}
7070

7171
export interface DomEditSelection extends PatchTarget {

0 commit comments

Comments
 (0)