import { mergeAttributes } from "@tiptap/core" import { Table } from "@tiptap/extension-table" import { TableCell } from "@tiptap/extension-table-cell" import { TableHeader } from "@tiptap/extension-table-header" import { TableRow } from "@tiptap/extension-table-row" import { buildDocsTableCellStyle, buildDocsTableRowStyle, buildDocsTableStyle, } from "@/lib/drive/docs-table-styles" function parseJsonAttr(raw: string | null): unknown { if (!raw) return null try { return JSON.parse(raw) } catch { return null } } function borderAttribute(name: string) { return { default: null, parseHTML: (element: HTMLElement) => { const fromData = parseJsonAttr(element.getAttribute(`data-${name}`)) if (fromData) return fromData const cssName = name.replace(/([A-Z])/g, "-$1").toLowerCase() const fromStyle = element.style.getPropertyValue(cssName) return fromStyle || null }, renderHTML: (attributes: Record) => { const value = attributes[name] if (!value) return {} return { [`data-${name}`]: JSON.stringify(value) } }, } } const sharedCellAttributes = () => ({ backgroundColor: { default: null, parseHTML: (element: HTMLElement) => element.getAttribute("data-background-color") || element.style.backgroundColor || null, renderHTML: (attributes: Record) => { if (!attributes.backgroundColor) return {} return { "data-background-color": String(attributes.backgroundColor) } }, }, verticalAlign: { default: null, parseHTML: (element: HTMLElement) => element.getAttribute("data-vertical-align") || element.style.verticalAlign || null, renderHTML: (attributes: Record) => { if (!attributes.verticalAlign) return {} return { "data-vertical-align": String(attributes.verticalAlign) } }, }, borderTop: borderAttribute("borderTop"), borderRight: borderAttribute("borderRight"), borderBottom: borderAttribute("borderBottom"), borderLeft: borderAttribute("borderLeft"), }) export const DocsTableCell = TableCell.extend({ renderHTML({ node, HTMLAttributes }) { const style = buildDocsTableCellStyle(node.attrs) return [ "td", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, style ? { style } : {}), 0, ] }, addAttributes() { return { ...this.parent?.(), ...sharedCellAttributes(), } }, }) export const DocsTableHeader = TableHeader.extend({ renderHTML({ node, HTMLAttributes }) { const style = buildDocsTableCellStyle(node.attrs) return [ "th", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, style ? { style } : {}), 0, ] }, addAttributes() { return { ...this.parent?.(), ...sharedCellAttributes(), } }, }) export const DocsTableRow = TableRow.extend({ addAttributes() { return { ...this.parent?.(), rowHeight: { default: null, parseHTML: (element: HTMLElement) => element.getAttribute("data-row-height") || element.style.height || null, renderHTML: (attributes: Record) => { if (!attributes.rowHeight) return {} return { "data-row-height": String(attributes.rowHeight) } }, }, rowHeightRule: { default: null, parseHTML: (element: HTMLElement) => element.getAttribute("data-row-height-rule"), renderHTML: (attributes: Record) => { if (!attributes.rowHeightRule) return {} return { "data-row-height-rule": String(attributes.rowHeightRule) } }, }, header: { default: false, parseHTML: (element: HTMLElement) => element.getAttribute("data-header-row") === "true", renderHTML: (attributes: Record) => { if (!attributes.header) return {} return { "data-header-row": "true" } }, }, } }, renderHTML({ node, HTMLAttributes }) { const style = buildDocsTableRowStyle(node.attrs) return [ "tr", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, style ? { style } : {}), 0, ] }, }) export const DocsTable = Table.extend({ addAttributes() { return { alignment: { default: null, parseHTML: (element: HTMLElement) => element.getAttribute("data-alignment"), renderHTML: (attributes: Record) => { if (!attributes.alignment) return {} return { "data-alignment": String(attributes.alignment) } }, }, layout: { default: null, parseHTML: (element: HTMLElement) => element.getAttribute("data-layout"), renderHTML: (attributes: Record) => { if (!attributes.layout) return {} return { "data-layout": String(attributes.layout) } }, }, cellSpacing: { default: null, parseHTML: (element: HTMLElement) => { const raw = element.getAttribute("data-cell-spacing") return raw ? Number(raw) : null }, renderHTML: (attributes: Record) => { if (attributes.cellSpacing == null) return {} return { "data-cell-spacing": String(attributes.cellSpacing) } }, }, } }, renderHTML({ node, HTMLAttributes }) { const style = buildDocsTableStyle(node.attrs) return [ "table", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, style ? { style } : {}), 0, ] }, }) export const DOCS_TABLE_EXTENSION_OPTIONS = { resizable: true, renderWrapper: true, cellMinWidth: 48, handleWidth: 6, lastColumnResizable: true, allowTableNodeSelection: true, HTMLAttributes: { class: "docs-table", }, } as const