Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced turbopack alias for canvas in next.config.mjs. - Updated package.json scripts for development and branding tasks. - Added new dependencies for Tiptap extensions. - Implemented new demo layouts for agenda, contacts, drive, and mail applications. - Enhanced globals.css for improved theming and splash screen animations. - Added OAuth callback handling for drive mounts. - Updated layout components to integrate new demo shells and improve structure.
220 lines
6.5 KiB
TypeScript
220 lines
6.5 KiB
TypeScript
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 { tableEditing } from "@tiptap/pm/tables"
|
|
import {
|
|
buildDocsTableCellStyle,
|
|
buildDocsTableRowStyle,
|
|
buildDocsTableStyle,
|
|
} from "@/lib/drive/docs-table-styles"
|
|
import {
|
|
DocsTableView,
|
|
docsColumnResizing,
|
|
} from "@/lib/drive/extensions/docs-table-column-resizing"
|
|
|
|
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<string, unknown>) => {
|
|
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<string, unknown>) => {
|
|
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<string, unknown>) => {
|
|
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<string, unknown>) => {
|
|
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<string, unknown>) => {
|
|
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<string, unknown>) => {
|
|
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({
|
|
addProseMirrorPlugins() {
|
|
const isResizable = this.options.resizable && this.editor.isEditable
|
|
return [
|
|
...(isResizable
|
|
? [
|
|
docsColumnResizing({
|
|
handleWidth: this.options.handleWidth,
|
|
cellMinWidth: this.options.cellMinWidth,
|
|
defaultCellMinWidth: this.options.cellMinWidth,
|
|
View: DocsTableView,
|
|
lastColumnResizable: this.options.lastColumnResizable,
|
|
}),
|
|
]
|
|
: []),
|
|
tableEditing({
|
|
allowTableNodeSelection: this.options.allowTableNodeSelection,
|
|
}),
|
|
]
|
|
},
|
|
addAttributes() {
|
|
return {
|
|
alignment: {
|
|
default: null,
|
|
parseHTML: (element: HTMLElement) => element.getAttribute("data-alignment"),
|
|
renderHTML: (attributes: Record<string, unknown>) => {
|
|
if (!attributes.alignment) return {}
|
|
return { "data-alignment": String(attributes.alignment) }
|
|
},
|
|
},
|
|
layout: {
|
|
default: null,
|
|
parseHTML: (element: HTMLElement) => element.getAttribute("data-layout"),
|
|
renderHTML: (attributes: Record<string, unknown>) => {
|
|
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<string, unknown>) => {
|
|
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
|