Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
co-authored by
renee-png
parent
7f3a638ddb
commit
610eb2dcb2
@@ -170,14 +170,20 @@ export function CustomFormBuilder() {
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit.configure({ heading: { levels: [1, 2, 3] } }),
|
||||
StarterKit.configure({ heading: { levels: [1, 2, 3] }, paragraph: false }),
|
||||
PaddedParagraph,
|
||||
Underline,
|
||||
TextAlign.configure({ types: ["heading", "paragraph"] }),
|
||||
Table.configure({ resizable: true, HTMLAttributes: { class: "cf-table" } }),
|
||||
TableRow,
|
||||
TableHeader,
|
||||
TableCell,
|
||||
],
|
||||
content: "<p>Start typing here…</p>",
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: "prose prose-sm max-w-none focus:outline-none px-10 py-8 bg-white text-black min-h-[500px]",
|
||||
class:
|
||||
"prose prose-sm max-w-none focus:outline-none px-10 py-8 bg-white text-black min-h-[500px]",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -212,46 +218,101 @@ export function CustomFormBuilder() {
|
||||
|
||||
const insertSignatureLine = (label: string = "Signature") => {
|
||||
if (!editor) return;
|
||||
editor.chain().focus().insertContent(`
|
||||
<p> </p>
|
||||
<p>______________________________</p>
|
||||
<p>${label}</p>
|
||||
`).run();
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.insertContent([
|
||||
{ type: "paragraph", content: [{ type: "text", text: " " }] },
|
||||
{ type: "paragraph", content: [{ type: "text", text: "______________________________" }] },
|
||||
{ type: "paragraph", content: [{ type: "text", text: label }] },
|
||||
])
|
||||
.run();
|
||||
};
|
||||
|
||||
const INDENT_STEP = 36; // ~0.5"
|
||||
|
||||
const indent = () => {
|
||||
if (!editor) return;
|
||||
// tiptap doesn't have indent built-in; emulate with non-breaking spaces at start
|
||||
editor.chain().focus().insertContent(" ").run();
|
||||
const cur = Number(editor.getAttributes("paragraph").indent ?? 0);
|
||||
editor.chain().focus().updateAttributes("paragraph", { indent: cur + INDENT_STEP }).run();
|
||||
};
|
||||
|
||||
const outdent = () => {
|
||||
if (!editor) return;
|
||||
// Best-effort: not perfectly removing — let user use undo. We expose for symmetry.
|
||||
editor.chain().focus().run();
|
||||
const cur = Number(editor.getAttributes("paragraph").indent ?? 0);
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.updateAttributes("paragraph", { indent: Math.max(0, cur - INDENT_STEP) })
|
||||
.run();
|
||||
};
|
||||
|
||||
// Convert editor HTML into plain text lines for PDF/docx (preserves paragraphs).
|
||||
const getRenderedSegments = (): { text: string; align: "left" | "center" | "right" | "justify"; bold?: boolean; italic?: boolean }[] => {
|
||||
const insertTable = () => {
|
||||
if (!editor) return;
|
||||
editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run();
|
||||
};
|
||||
|
||||
const toggleCaption = () => {
|
||||
if (!editor) return;
|
||||
const cur = Boolean(editor.getAttributes("paragraph").caption);
|
||||
editor.chain().focus().updateAttributes("paragraph", { caption: !cur }).run();
|
||||
};
|
||||
|
||||
// Linearized document segments for export. Tables are flattened into a "table" segment
|
||||
// containing 2D rows of strings; paragraphs become text segments preserving alignment + indent.
|
||||
type Segment =
|
||||
| {
|
||||
kind: "text";
|
||||
text: string;
|
||||
align: "left" | "center" | "right" | "justify";
|
||||
indent: number;
|
||||
caption?: boolean;
|
||||
heading?: 0 | 1 | 2 | 3;
|
||||
}
|
||||
| { kind: "table"; rows: string[][]; hasHeader: boolean };
|
||||
|
||||
const getRenderedSegments = (): Segment[] => {
|
||||
if (!editor) return [];
|
||||
const html = editor.getHTML();
|
||||
const tmp = document.createElement("div");
|
||||
tmp.innerHTML = html;
|
||||
const out: { text: string; align: "left" | "center" | "right" | "justify"; bold?: boolean; italic?: boolean }[] = [];
|
||||
const walk = (node: ChildNode) => {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const el = node as HTMLElement;
|
||||
const tag = el.tagName.toLowerCase();
|
||||
if (["p", "h1", "h2", "h3", "li"].includes(tag)) {
|
||||
const align = (el.style.textAlign as any) || "left";
|
||||
const text = el.textContent || "";
|
||||
out.push({ text, align });
|
||||
return;
|
||||
}
|
||||
const out: Segment[] = [];
|
||||
|
||||
const cellText = (cell: HTMLElement) => (cell.textContent ?? "").replace(/\s+/g, " ").trim();
|
||||
|
||||
const pushBlock = (el: HTMLElement) => {
|
||||
const tag = el.tagName.toLowerCase();
|
||||
if (tag === "table") {
|
||||
const rows: string[][] = [];
|
||||
let hasHeader = false;
|
||||
el.querySelectorAll("tr").forEach((tr) => {
|
||||
const cells = Array.from(tr.children) as HTMLElement[];
|
||||
if (cells.some((c) => c.tagName.toLowerCase() === "th")) hasHeader = true;
|
||||
rows.push(cells.map(cellText));
|
||||
});
|
||||
out.push({ kind: "table", rows, hasHeader });
|
||||
return;
|
||||
}
|
||||
node.childNodes.forEach(walk);
|
||||
if (["p", "h1", "h2", "h3", "li"].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";
|
||||
const heading = tag === "h1" ? 1 : tag === "h2" ? 2 : tag === "h3" ? 3 : 0;
|
||||
out.push({
|
||||
kind: "text",
|
||||
text: el.textContent || "",
|
||||
align,
|
||||
indent: indentPx,
|
||||
caption,
|
||||
heading: heading as 0 | 1 | 2 | 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Recurse into wrappers (lists, etc.)
|
||||
Array.from(el.children).forEach((child) => pushBlock(child as HTMLElement));
|
||||
};
|
||||
tmp.childNodes.forEach(walk);
|
||||
|
||||
Array.from(tmp.children).forEach((el) => pushBlock(el as HTMLElement));
|
||||
return out;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user