ultisuite-client/lib/drive/richtext-import.ts
R3D347HR4Y cdff12490a
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
hocuspocus
2026-06-09 14:31:07 +02:00

109 lines
3.2 KiB
TypeScript

import mammoth from "mammoth"
export type TipTapJSON = Record<string, unknown>
function htmlToTipTapDoc(html: string): TipTapJSON {
const parser = typeof DOMParser !== "undefined" ? new DOMParser() : null
if (!parser) {
return {
type: "doc",
content: [{ type: "paragraph", content: [{ type: "text", text: html.replace(/<[^>]+>/g, " ") }] }],
}
}
const doc = parser.parseFromString(html, "text/html")
const blocks: TipTapJSON[] = []
const walk = (node: Node) => {
if (node.nodeType === Node.TEXT_NODE) {
const text = node.textContent ?? ""
if (text.trim()) {
blocks.push({ type: "paragraph", content: [{ type: "text", text }] })
}
return
}
if (node.nodeType !== Node.ELEMENT_NODE) return
const el = node as HTMLElement
const tag = el.tagName.toLowerCase()
if (tag === "p" || tag === "div") {
const text = el.textContent ?? ""
blocks.push({
type: "paragraph",
content: text ? [{ type: "text", text }] : [],
})
return
}
if (/^h[1-6]$/.test(tag)) {
const level = Number(tag[1])
blocks.push({
type: "heading",
attrs: { level },
content: [{ type: "text", text: el.textContent ?? "" }],
})
return
}
if (tag === "ul" || tag === "ol") {
const listType = tag === "ul" ? "bulletList" : "orderedList"
const items = Array.from(el.querySelectorAll(":scope > li")).map((li) => ({
type: "listItem",
content: [{ type: "paragraph", content: [{ type: "text", text: li.textContent ?? "" }] }],
}))
blocks.push({ type: listType, content: items })
return
}
Array.from(el.childNodes).forEach(walk)
}
Array.from(doc.body.childNodes).forEach(walk)
if (blocks.length === 0) {
blocks.push({ type: "paragraph" })
}
return { type: "doc", content: blocks }
}
export async function importDocxToTipTap(buffer: ArrayBuffer): Promise<TipTapJSON> {
try {
const { parseDOCX } = await import("@docen/import-docx")
const content = await parseDOCX(buffer)
if (content && typeof content === "object") {
return content as TipTapJSON
}
} catch {
/* fallback mammoth */
}
const result = await mammoth.convertToHtml({ arrayBuffer: buffer })
return htmlToTipTapDoc(result.value)
}
export async function exportTipTapToDocx(content: TipTapJSON): Promise<Blob> {
try {
const { generateDOCX } = await import("@docen/export-docx")
const buf = await generateDOCX(content, { outputType: "blob" })
if (buf instanceof Blob) return buf
} catch {
/* fallback unavailable */
}
throw new Error("Export DOCX indisponible")
}
export async function importFileToTipTap(
fileName: string,
buffer: ArrayBuffer
): Promise<TipTapJSON> {
const ext = fileName.split(".").pop()?.toLowerCase() ?? ""
if (ext === "docx" || ext === "docm") {
return importDocxToTipTap(buffer)
}
const text = new TextDecoder().decode(buffer)
if (ext === "html" || ext === "htm") {
return htmlToTipTapDoc(text)
}
const lines = text.split(/\r?\n/)
return {
type: "doc",
content: lines.map((line) => ({
type: "paragraph",
content: line ? [{ type: "text", text: line }] : [],
})),
}
}