109 lines
3.2 KiB
TypeScript
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 }] : [],
|
|
})),
|
|
}
|
|
}
|