Fixed numbered/bulleted lists

X-Lovable-Edit-ID: edt-4aee7814-50ef-4eaa-b6fd-cf25b979decb
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-18 16:28:59 +00:00
co-authored by renee-png
+56 -5
View File
@@ -395,6 +395,7 @@ export function CustomFormBuilder() {
indent: number;
caption?: boolean;
heading?: 0 | 1 | 2 | 3;
marker?: string; // list marker prefix like "1." or "•"
}
| { kind: "table"; rows: TableCellSeg[][] };
@@ -472,6 +473,44 @@ export function CustomFormBuilder() {
tmp.innerHTML = html;
const out: Segment[] = [];
const LIST_INDENT = 24; // px per nesting level
const pushListItems = (
listEl: HTMLElement,
ordered: boolean,
depth: number,
) => {
const items = Array.from(listEl.children).filter(
(c) => (c as HTMLElement).tagName.toLowerCase() === "li",
) as HTMLElement[];
let n = 1;
for (const li of items) {
// Collect inline runs from this li, excluding nested lists.
const inlineNodes = Array.from(li.childNodes).filter((c) => {
if (c.nodeType !== Node.ELEMENT_NODE) return true;
const t = (c as HTMLElement).tagName.toLowerCase();
return t !== "ol" && t !== "ul";
});
const runs: InlineRun[] = [];
inlineNodes.forEach((n) => runs.push(...collectRuns(n)));
const merged = mergeRuns(runs);
out.push({
kind: "text",
runs: merged,
align: "left",
indent: depth * LIST_INDENT,
marker: ordered ? `${n}.` : "•",
});
n += 1;
// Recurse into any nested lists.
Array.from(li.children).forEach((child) => {
const tag = (child as HTMLElement).tagName.toLowerCase();
if (tag === "ol") pushListItems(child as HTMLElement, true, depth + 1);
else if (tag === "ul") pushListItems(child as HTMLElement, false, depth + 1);
});
}
};
const pushBlock = (el: HTMLElement) => {
const tag = el.tagName.toLowerCase();
if (tag === "table") {
@@ -489,7 +528,9 @@ export function CustomFormBuilder() {
out.push({ kind: "table", rows });
return;
}
if (["p", "h1", "h2", "h3", "li"].includes(tag)) {
if (tag === "ol") return pushListItems(el, true, 0);
if (tag === "ul") return pushListItems(el, false, 0);
if (["p", "h1", "h2", "h3"].includes(tag)) {
const align = (el.style.textAlign as any) || "left";
const indentPx = parseInt(el.style.paddingLeft || "0", 10) || 0;
const caption = el.getAttribute("data-caption") === "true";
@@ -504,7 +545,7 @@ export function CustomFormBuilder() {
});
return;
}
// Recurse into wrappers (lists, etc.)
// Recurse into wrappers we don't recognize.
Array.from(el.children).forEach((child) => pushBlock(child as HTMLElement));
};
@@ -717,7 +758,10 @@ export function CustomFormBuilder() {
const indentPt = indentPx * 0.75; // px → pt
const segMaxW = maxW - indentPt;
const startX = margin + indentPt;
const visualLines = runsToLines(seg.runs);
const runsForLayout: InlineRun[] = seg.marker
? [{ text: `${seg.marker} ` }, ...seg.runs]
: seg.runs;
const visualLines = runsToLines(runsForLayout);
if (visualLines.length === 0 || (visualLines.length === 1 && visualLines[0].length === 0)) {
y += lineH;
} else {
@@ -858,12 +902,19 @@ export function CustomFormBuilder() {
? AlignmentType.JUSTIFIED
: AlignmentType.LEFT;
const indentTwips = Math.round((seg.indent || 0) * 15); // ~px → twips (1px≈15twip)
const baseRuns = buildDocxRuns(seg.runs, { italic: seg.caption });
const runsWithMarker = seg.marker
? [
new TextRun({ text: `${seg.marker}\t`, font: fontFamily, size: fontSize * 2 }),
...baseRuns,
]
: baseRuns;
children.push(
new DocxParagraph({
alignment: align,
spacing: docxLineSpacing,
indent: indentTwips ? { left: indentTwips } : undefined,
children: buildDocxRuns(seg.runs, { italic: seg.caption }),
indent: indentTwips ? { left: indentTwips, hanging: seg.marker ? 360 : undefined } : seg.marker ? { left: 360, hanging: 360 } : undefined,
children: runsWithMarker,
}),
);
}