diff --git a/packages/editor/core/package.json b/packages/editor/core/package.json index 78bf59db7..86921af0c 100644 --- a/packages/editor/core/package.json +++ b/packages/editor/core/package.json @@ -32,10 +32,6 @@ "@tiptap/extension-color": "^2.1.11", "@tiptap/extension-image": "^2.1.7", "@tiptap/extension-link": "^2.1.7", - "@tiptap/extension-table": "^2.1.6", - "@tiptap/extension-table-cell": "^2.1.6", - "@tiptap/extension-table-header": "^2.1.6", - "@tiptap/extension-table-row": "^2.1.6", "@tiptap/extension-task-item": "^2.1.7", "@tiptap/extension-task-list": "^2.1.7", "@tiptap/extension-text-style": "^2.1.11", @@ -56,7 +52,9 @@ "tailwind-merge": "^1.14.0", "tippy.js": "^6.3.7", "tiptap-markdown": "^0.8.2", - "use-debounce": "^9.0.4" + "use-debounce": "^9.0.4", + "@tiptap/prosemirror-tables": "^1.1.4", + "jsx-dom-cjs": "^8.0.3" }, "devDependencies": { "eslint": "^7.32.0", diff --git a/packages/editor/core/src/index.ts b/packages/editor/core/src/index.ts index 590b17172..6268323e5 100644 --- a/packages/editor/core/src/index.ts +++ b/packages/editor/core/src/index.ts @@ -2,6 +2,8 @@ // import "./styles/tailwind.css"; // import "./styles/editor.css"; +export * from "./ui/extensions/table-new/Table"; + // utils export * from "./lib/utils"; export { startImageUpload } from "./ui/plugins/upload-image"; diff --git a/packages/editor/core/src/styles/editor.css b/packages/editor/core/src/styles/editor.css index 85d881eeb..9987c5f06 100644 --- a/packages/editor/core/src/styles/editor.css +++ b/packages/editor/core/src/styles/editor.css @@ -212,9 +212,9 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p { } } -.tableWrapper { - overflow-x: auto; -} +/* .tableWrapper { */ +/* overflow-x: auto; */ +/* } */ .resize-cursor { cursor: ew-resize; diff --git a/packages/editor/core/src/ui/components/editor-container.tsx b/packages/editor/core/src/ui/components/editor-container.tsx index 8de6298b5..bb35fef68 100644 --- a/packages/editor/core/src/ui/components/editor-container.tsx +++ b/packages/editor/core/src/ui/components/editor-container.tsx @@ -13,7 +13,7 @@ export const EditorContainer = ({ editor, editorClassNames, children }: EditorCo onClick={() => { editor?.chain().focus().run(); }} - className={`cursor-text ${editorClassNames}`} + className={`cursor-text editorContainer ${editorClassNames}`} > {children} diff --git a/packages/editor/core/src/ui/components/editor-content.tsx b/packages/editor/core/src/ui/components/editor-content.tsx index 0675a5834..11468e757 100644 --- a/packages/editor/core/src/ui/components/editor-content.tsx +++ b/packages/editor/core/src/ui/components/editor-content.tsx @@ -10,10 +10,10 @@ interface EditorContentProps { } export const EditorContentWrapper = ({ editor, editorContentCustomClassNames = '', children }: EditorContentProps) => ( -
+
{/* @ts-ignore */} - + {/* */} {editor?.isActive("image") && } {children}
diff --git a/packages/editor/core/src/ui/extensions/index.tsx b/packages/editor/core/src/ui/extensions/index.tsx index 62d53e0e1..3c394205d 100644 --- a/packages/editor/core/src/ui/extensions/index.tsx +++ b/packages/editor/core/src/ui/extensions/index.tsx @@ -8,15 +8,19 @@ import TaskList from "@tiptap/extension-task-list"; import { Markdown } from "tiptap-markdown"; import Gapcursor from "@tiptap/extension-gapcursor"; -import { CustomTableCell } from "./table/table-cell"; -import { Table } from "./table"; -import { TableHeader } from "./table/table-header"; -import { TableRow } from "@tiptap/extension-table-row"; +// import { CustomTableCell } from "./table/table-cell"; +// import { Table } from "./table"; +// import { TableHeader } from "./table/table-header"; +// import { TableRow } from "@tiptap/extension-table-row"; import ImageExtension from "./image"; import { DeleteImage } from "../../types/delete-image"; import { isValidHttpUrl } from "../../lib/utils"; +import Table from "./table-new/Table"; +import TableHeader from "./table-new/TableHeader"; +import TableCell from "./table-new/TableCell"; +import TableRow from "./table-new/TableRow"; export const CoreEditorExtensions = ( @@ -92,6 +96,10 @@ export const CoreEditorExtensions = ( }), Table, TableHeader, - CustomTableCell, + TableCell, TableRow, + // Table, + // TableHeader, + // CustomTableCell, + // TableRow, ]; diff --git a/packages/editor/core/src/ui/extensions/table-new/Table/Table.ts b/packages/editor/core/src/ui/extensions/table-new/Table/Table.ts new file mode 100644 index 000000000..1bded42af --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/Table/Table.ts @@ -0,0 +1,336 @@ +import { TextSelection } from "@tiptap/pm/state" + +import { callOrReturn, getExtensionField, mergeAttributes, Node, ParentConfig } from "@tiptap/core" +import { + addColumnAfter, + addColumnBefore, + addRowAfter, + addRowBefore, + CellSelection, + columnResizing, + deleteColumn, + deleteRow, + deleteTable, + fixTables, + goToNextCell, + mergeCells, + setCellAttr, + splitCell, + tableEditing, + toggleHeader, + toggleHeaderCell +} from "@tiptap/prosemirror-tables" + +import { tableControls } from "./tableControls" +import { TableView } from "./TableView" +import { createTable } from "./utilities/createTable" +import { deleteTableWhenAllCellsSelected } from "./utilities/deleteTableWhenAllCellsSelected" + +/** + * Extension based on: + * - Tiptap TableExtension (https://github.com/ueberdosis/tiptap/blob/main/packages/extension-table/src/table.ts) + */ + +export interface TableOptions { + HTMLAttributes: Record + resizable: boolean + handleWidth: number + cellMinWidth: number + lastColumnResizable: boolean + allowTableNodeSelection: boolean +} + +declare module "@tiptap/core" { + interface Commands { + table: { + insertTable: (options?: { + rows?: number + cols?: number + withHeaderRow?: boolean + }) => ReturnType + addColumnBefore: () => ReturnType + addColumnAfter: () => ReturnType + deleteColumn: () => ReturnType + addRowBefore: () => ReturnType + addRowAfter: () => ReturnType + deleteRow: () => ReturnType + deleteTable: () => ReturnType + mergeCells: () => ReturnType + splitCell: () => ReturnType + toggleHeaderColumn: () => ReturnType + toggleHeaderRow: () => ReturnType + toggleHeaderCell: () => ReturnType + mergeOrSplit: () => ReturnType + setCellAttribute: (name: string, value: any) => ReturnType + goToNextCell: () => ReturnType + goToPreviousCell: () => ReturnType + fixTables: () => ReturnType + setCellSelection: (position: { + anchorCell: number + headCell?: number + }) => ReturnType + } + } + + interface NodeConfig { + /** + * Table Role + */ + tableRole?: + | string + | ((this: { + name: string + options: Options + storage: Storage + parent: ParentConfig>["tableRole"] + }) => string) + } +} + +export default Node.create({ + name: "table", + + addOptions() { + return { + HTMLAttributes: {}, + resizable: true, + handleWidth: 5, + cellMinWidth: 100, + lastColumnResizable: true, + allowTableNodeSelection: true + } + }, + + content: "tableRow+", + + tableRole: "table", + + isolating: true, + + group: "block", + + allowGapCursor: false, + + parseHTML() { + return [{ tag: "table" }] + }, + + renderHTML({ HTMLAttributes }) { + return [ + "table", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), + ["tbody", 0] + ] + }, + + addCommands() { + return { + insertTable: + ({ rows = 3, cols = 3, withHeaderRow = true} = {}) => + ({ tr, dispatch, editor }) => { + const node = createTable( + editor.schema, + rows, + cols, + withHeaderRow + ) + + if (dispatch) { + const offset = tr.selection.anchor + 1 + + tr.replaceSelectionWith(node) + .scrollIntoView() + .setSelection( + TextSelection.near(tr.doc.resolve(offset)) + ) + } + + return true + }, + addColumnBefore: + () => + ({ state, dispatch }) => { + return addColumnBefore(state, dispatch) + }, + addColumnAfter: + () => + ({ state, dispatch }) => { + return addColumnAfter(state, dispatch) + }, + deleteColumn: + () => + ({ state, dispatch }) => { + return deleteColumn(state, dispatch) + }, + addRowBefore: + () => + ({ state, dispatch }) => { + return addRowBefore(state, dispatch) + }, + addRowAfter: + () => + ({ state, dispatch }) => { + return addRowAfter(state, dispatch) + }, + deleteRow: + () => + ({ state, dispatch }) => { + return deleteRow(state, dispatch) + }, + deleteTable: + () => + ({ state, dispatch }) => { + return deleteTable(state, dispatch) + }, + mergeCells: + () => + ({ state, dispatch }) => { + return mergeCells(state, dispatch) + }, + splitCell: + () => + ({ state, dispatch }) => { + return splitCell(state, dispatch) + }, + toggleHeaderColumn: + () => + ({ state, dispatch }) => { + return toggleHeader("column")(state, dispatch) + }, + toggleHeaderRow: + () => + ({ state, dispatch }) => { + return toggleHeader("row")(state, dispatch) + }, + toggleHeaderCell: + () => + ({ state, dispatch }) => { + return toggleHeaderCell(state, dispatch) + }, + mergeOrSplit: + () => + ({ state, dispatch }) => { + if (mergeCells(state, dispatch)) { + return true + } + + return splitCell(state, dispatch) + }, + setCellAttribute: + (name, value) => + ({ state, dispatch }) => { + return setCellAttr(name, value)(state, dispatch) + }, + goToNextCell: + () => + ({ state, dispatch }) => { + return goToNextCell(1)(state, dispatch) + }, + goToPreviousCell: + () => + ({ state, dispatch }) => { + return goToNextCell(-1)(state, dispatch) + }, + fixTables: + () => + ({ state, dispatch }) => { + if (dispatch) { + fixTables(state) + } + + return true + }, + setCellSelection: + (position) => + ({ tr, dispatch }) => { + if (dispatch) { + const selection = CellSelection.create( + tr.doc, + position.anchorCell, + position.headCell + ) + + // @ts-ignore + tr.setSelection(selection) + } + + return true + } + } + }, + + addKeyboardShortcuts() { + return { + Tab: () => { + if (this.editor.commands.goToNextCell()) { + return true + } + + if (!this.editor.can().addRowAfter()) { + return false + } + + return this.editor.chain().addRowAfter().goToNextCell().run() + }, + "Shift-Tab": () => this.editor.commands.goToPreviousCell(), + Backspace: deleteTableWhenAllCellsSelected, + "Mod-Backspace": deleteTableWhenAllCellsSelected, + Delete: deleteTableWhenAllCellsSelected, + "Mod-Delete": deleteTableWhenAllCellsSelected + } + }, + + addNodeView() { + return ({ editor, getPos, node, decorations }) => { + const { cellMinWidth } = this.options + + return new TableView( + node, + cellMinWidth, + decorations, + editor, + getPos as () => number + ) + } + }, + + addProseMirrorPlugins() { + const isResizable = this.options.resizable && this.editor.isEditable + + const plugins = [ + tableEditing({ + allowTableNodeSelection: this.options.allowTableNodeSelection + }), + tableControls() + ] + + if (isResizable) { + plugins.unshift( + columnResizing({ + handleWidth: this.options.handleWidth, + cellMinWidth: this.options.cellMinWidth, + // View: TableView, + + // @ts-ignore + lastColumnResizable: this.options.lastColumnResizable + }) + ) + } + + return plugins + }, + + extendNodeSchema(extension) { + const context = { + name: extension.name, + options: extension.options, + storage: extension.storage + } + + return { + tableRole: callOrReturn( + getExtensionField(extension, "tableRole", context) + ) + } + } +}) diff --git a/packages/editor/core/src/ui/extensions/table-new/Table/TableView.ts b/packages/editor/core/src/ui/extensions/table-new/Table/TableView.ts new file mode 100644 index 000000000..c11a69e31 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/Table/TableView.ts @@ -0,0 +1,500 @@ +import { h } from "jsx-dom-cjs" +import { Node as ProseMirrorNode } from "@tiptap/pm/model" +import { Decoration, NodeView } from "@tiptap/pm/view" +import tippy, { Instance, Props, Tippy } from "tippy.js" + +import { Editor } from "@tiptap/core" +import { + CellSelection, + TableMap, + updateColumnsOnResize +} from "@tiptap/prosemirror-tables" + +import icons from "./icons" + +export function updateColumns( + node: ProseMirrorNode, + colgroup: HTMLElement, + table: HTMLElement, + cellMinWidth: number, + overrideCol?: number, + overrideValue?: any +) { + let totalWidth = 0 + let fixedWidth = true + let nextDOM = colgroup.firstChild as HTMLElement + const row = node.firstChild + + if (!row) return + + for (let i = 0, col = 0; i < row.childCount; i += 1) { + const { colspan, colwidth } = row.child(i).attrs + + for (let j = 0; j < colspan; j += 1, col += 1) { + const hasWidth = + overrideCol === col ? overrideValue : colwidth && colwidth[j] + const cssWidth = hasWidth ? `${hasWidth}px` : "" + + totalWidth += hasWidth || cellMinWidth + + if (!hasWidth) { + fixedWidth = false + } + + if (!nextDOM) { + colgroup.appendChild( + document.createElement("col") + ).style.width = cssWidth + } else { + if (nextDOM.style.width !== cssWidth) { + nextDOM.style.width = cssWidth + } + + nextDOM = nextDOM.nextSibling as HTMLElement + } + } + } + + while (nextDOM) { + const after = nextDOM.nextSibling + + nextDOM.parentNode?.removeChild(nextDOM) + nextDOM = after as HTMLElement + } + + if (fixedWidth) { + table.style.width = `${totalWidth}px` + table.style.minWidth = "" + } else { + table.style.width = "" + table.style.minWidth = `${totalWidth}px` + } +} + +const defaultTippyOptions: Partial = { + allowHTML: true, + arrow: false, + trigger: "click", + animation: "scale-subtle", + theme: "light-border no-padding", + interactive: true, + hideOnClick: true, + placement: "right" +} + +function setCellsBackgroundColor(editor: Editor, backgroundColor) { + return editor + .chain() + .focus() + .updateAttributes("tableCell", { + background: backgroundColor + }) + .updateAttributes("tableHeader", { + background: backgroundColor + }) + .run() +} + +const columnsToolboxItems = [ + { + label: "Add Column Before", + icon: icons.insertColumnLeft, + action: ({ editor }: { editor: Editor }) => editor.chain().focus().addColumnBefore().run() + }, + { + label: "Add Column After", + icon: icons.insertColumnRight, + action: ({ editor }: { editor: Editor }) => editor.chain().focus().addColumnAfter().run() + }, + { + label: "Pick Column Color", + icon: icons.colorPicker, + action: ({ editor, triggerButton, controlsContainer }) => { + createColorPickerToolbox({ + triggerButton, + tippyOptions: { + appendTo: controlsContainer + }, + onSelectColor: (color) => setCellsBackgroundColor(editor, color) + }) + } + }, + { + label: "Delete Column", + icon: icons.deleteColumn, + action: ({ editor }: { editor: Editor }) => editor.chain().focus().deleteColumn().run() + } +] + +const rowsToolboxItems = [ + { + label: "Add Row Above", + icon: icons.insertRowTop, + action: ({ editor }: { editor: Editor }) => editor.chain().focus().addRowBefore().run() + }, + { + label: "Add Row Below", + icon: icons.insertRowBottom, + action: ({ editor }: { editor: Editor }) => editor.chain().focus().addRowAfter().run() + }, + { + label: "Pick a Color", + icon: icons.colorPicker, + action: ({ editor, triggerButton, controlsContainer }: { editor: Editor, triggerButton: HTMLElement, controlsContainer: any }) => { + createColorPickerToolbox({ + triggerButton, + tippyOptions: { + appendTo: controlsContainer + }, + onSelectColor: (color) => setCellsBackgroundColor(editor, color) + }) + } + }, + { + label: "Delete Row", + icon: icons.deleteRow, + action: ({ editor }: { editor: Editor }) => editor.chain().focus().deleteRow().run() + } +] + +function createToolbox({ + triggerButton, + items, + tippyOptions, + onClickItem +}: { triggerButton: HTMLElement, items: { icon: string, label: string }[], tippyOptions: any, onClickItem: any }): Instance { + const toolbox = tippy(triggerButton, { + content: h( + "div", + { className: "tableToolbox" }, + items.map((item) => + h( + "div", + { + className: "toolboxItem", + onClick() { + onClickItem(item) + } + }, + [ + h("div", { + className: "iconContainer", + innerHTML: item.icon + }), + h("div", { className: "label" }, item.label) + ] + ) + ) + ), + ...tippyOptions + }) + + return Array.isArray(toolbox) ? toolbox[0] : toolbox +} + +function createColorPickerToolbox({ + triggerButton, + tippyOptions, + onSelectColor = () => { } +}: { + triggerButton: HTMLElement + tippyOptions: Partial + onSelectColor?: (color: string) => void +}) { + const items = { + "Fond par défault": "#ffffff", + "Fond gris clair": "#e7f3f8", + "Fond gris foncé": "#c7d2d7", + "Fond bleu": "#e7f3f8", + "Fond rouge": "#ffc4c7", + "Fond jaune": "#fbf3db" + } + + const colorPicker = tippy(triggerButton, { + ...defaultTippyOptions, + content: h( + "div", + { className: "tableColorPickerToolbox" }, + Object.entries(items).map(([key, value]) => + h( + "div", + { + className: "toolboxItem", + onClick: () => { + onSelectColor(value) + colorPicker.hide() + } + }, + [ + h("div", { + className: "colorContainer", + style: { + backgroundColor: value + } + }), + h( + "div", + { + className: "label" + }, + key + ) + ] + ) + ) + ), + onHidden: (instance) => { + instance.destroy() + }, + showOnCreate: true, + ...tippyOptions + }) + + return colorPicker +} + +export class TableView implements NodeView { + node: ProseMirrorNode + cellMinWidth: number + decorations: Decoration[] + editor: Editor + getPos: () => number + hoveredCell + map: TableMap + root: HTMLElement + table: HTMLElement + colgroup: HTMLElement + tbody: HTMLElement + rowsControl: HTMLElement + columnsControl: HTMLElement + columnsToolbox: Instance + rowsToolbox: Instance + controls: HTMLElement + + get dom() { + return this.root + } + + get contentDOM() { + return this.tbody + } + + constructor( + node: ProseMirrorNode, + cellMinWidth: number, + decorations: Decoration[], + editor: Editor, + getPos: () => number + ) { + this.node = node + this.cellMinWidth = cellMinWidth + this.decorations = decorations + this.editor = editor + this.getPos = getPos + this.hoveredCell = null + this.map = TableMap.get(node) + + /** + * DOM + */ + + // Controllers + if (editor.isEditable) { + this.rowsControl = h( + "div", + { className: "rowsControl" }, + h("button", { + onClick: () => this.selectRow() + }) + ) + + this.columnsControl = h( + "div", + { className: "columnsControl" }, + h("button", { + onClick: () => this.selectColumn() + }) + ) + + this.controls = h( + "div", + { className: "tableControls", contentEditable: "false" }, + this.rowsControl, + this.columnsControl + ) + + this.columnsToolbox = createToolbox({ + triggerButton: this.columnsControl.querySelector("button"), + items: columnsToolboxItems, + tippyOptions: { + ...defaultTippyOptions, + appendTo: this.controls + }, + onClickItem: (item) => { + item.action({ + editor: this.editor, + triggerButton: this.columnsControl.firstElementChild, + controlsContainer: this.controls + }) + this.columnsToolbox.hide() + } + }) + + this.rowsToolbox = createToolbox({ + triggerButton: this.rowsControl.firstElementChild, + items: rowsToolboxItems, + tippyOptions: { + ...defaultTippyOptions, + appendTo: this.controls + }, + onClickItem: (item) => { + item.action({ + editor: this.editor, + triggerButton: this.rowsControl.firstElementChild, + controlsContainer: this.controls + }) + this.rowsToolbox.hide() + } + }) + } + + // Table + + this.colgroup = h( + "colgroup", + null, + Array.from({ length: this.map.width }, () => 1).map(() => h("col")) + ) + this.tbody = h("tbody") + this.table = h("table", null, this.colgroup, this.tbody) + + this.root = h( + "div", + { + className: "tableWrapper controls--disabled" + }, + this.controls, + this.table + ) + + this.render() + } + + update(node: ProseMirrorNode, decorations) { + if (node.type !== this.node.type) { + return false + } + + this.node = node + this.decorations = decorations + this.map = TableMap.get(this.node) + + if (this.editor.isEditable) { + this.updateControls() + } + + this.render() + + return true + } + + render() { + if (this.colgroup.children.length !== this.map.width) { + const cols = Array.from({ length: this.map.width }, () => 1).map( + () => h("col") + ) + this.colgroup.replaceChildren(...cols) + } + + updateColumnsOnResize( + this.node, + this.colgroup, + this.table, + this.cellMinWidth + ) + } + + ignoreMutation() { + return true + } + + updateControls() { + const { hoveredTable: table, hoveredCell: cell } = Object.values( + this.decorations + ).reduce((acc, curr) => { + if (curr.spec.hoveredCell !== undefined) { + acc["hoveredCell"] = curr.spec.hoveredCell + } + + if (curr.spec.hoveredTable !== undefined) { + acc["hoveredTable"] = curr.spec.hoveredTable + } + return acc + }, {}) as any + + if (table === undefined || cell === undefined) { + return this.root.classList.add("controls--disabled") + } + + this.root.classList.remove("controls--disabled") + this.hoveredCell = cell + + const cellDom = this.editor.view.nodeDOM(cell.pos) as HTMLElement + + const tableRect = this.table.getBoundingClientRect() + const cellRect = cellDom.getBoundingClientRect() + + this.columnsControl.style.left = `${cellRect.left - + tableRect.left - + this.table.parentElement!.scrollLeft + }px` + this.columnsControl.style.width = `${cellRect.width}px` + + this.rowsControl.style.top = `${cellRect.top - tableRect.top}px` + this.rowsControl.style.height = `${cellRect.height}px` + } + + selectColumn() { + if (!this.hoveredCell) return + + const colIndex = this.map.colCount( + this.hoveredCell.pos - (this.getPos() + 1) + ) + const anchorCellPos = this.hoveredCell.pos + const headCellPos = + this.map.map[colIndex + this.map.width * (this.map.height - 1)] + + (this.getPos() + 1) + + const cellSelection = CellSelection.create( + this.editor.view.state.doc, + anchorCellPos, + headCellPos + ) + this.editor.view.dispatch( + // @ts-ignore + this.editor.state.tr.setSelection(cellSelection) + ) + } + + selectRow() { + if (!this.hoveredCell) return + + const anchorCellPos = this.hoveredCell.pos + const anchorCellIndex = this.map.map.indexOf( + anchorCellPos - (this.getPos() + 1) + ) + const headCellPos = + this.map.map[anchorCellIndex + (this.map.width - 1)] + + (this.getPos() + 1) + + const cellSelection = CellSelection.create( + this.editor.state.doc, + anchorCellPos, + headCellPos + ) + this.editor.view.dispatch( + // @ts-ignore + this.editor.view.state.tr.setSelection(cellSelection) + ) + } +} diff --git a/packages/editor/core/src/ui/extensions/table-new/Table/icons.ts b/packages/editor/core/src/ui/extensions/table-new/Table/icons.ts new file mode 100644 index 000000000..d24209c8a --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/Table/icons.ts @@ -0,0 +1,11 @@ +const icons = { + insertColumnLeft: ``, + insertColumnRight: ``, + insertRowTop: ``, + insertRowBottom: ``, + colorPicker: ``, + deleteColumn: ``, + deleteRow: `` +} + +export default icons diff --git a/packages/editor/core/src/ui/extensions/table-new/Table/index.ts b/packages/editor/core/src/ui/extensions/table-new/Table/index.ts new file mode 100644 index 000000000..ccdae7fd2 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/Table/index.ts @@ -0,0 +1 @@ +export { default as default } from "./Table" diff --git a/packages/editor/core/src/ui/extensions/table-new/Table/tableControls.ts b/packages/editor/core/src/ui/extensions/table-new/Table/tableControls.ts new file mode 100644 index 000000000..afcb9103e --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/Table/tableControls.ts @@ -0,0 +1,118 @@ +import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state" +import { findParentNode } from "@tiptap/core" +import { DecorationSet, Decoration } from "@tiptap/pm/view" + +const key = new PluginKey("tableControls") + +export function tableControls() { + return new Plugin({ + key, + state: { + init() { + return new TableControlsState() + }, + apply(tr, prev) { + return prev.apply(tr) + } + }, + props: { + handleDOMEvents: { + mousemove: (view, event) => { + const pluginState = key.getState(view.state) + + if ( + !(event.target as HTMLElement).closest( + ".tableWrapper" + ) && + pluginState.values.hoveredTable + ) { + return view.dispatch( + view.state.tr.setMeta(key, { + setHoveredTable: null, + setHoveredCell: null + }) + ) + } + + const pos = view.posAtCoords({ + left: event.clientX, + top: event.clientY + }) + + if (!pos) return + + const table = findParentNode( + (node) => node.type.name === "table" + )(TextSelection.create(view.state.doc, pos.pos)) + const cell = findParentNode( + (node) => + node.type.name === "tableCell" || + node.type.name === "tableHeader" + )(TextSelection.create(view.state.doc, pos.pos)) + + if (!table || !cell) return + + if (pluginState.values.hoveredCell?.pos !== cell.pos) { + return view.dispatch( + view.state.tr.setMeta(key, { + setHoveredTable: table, + setHoveredCell: cell + }) + ) + } + } + }, + decorations: (state) => { + const pluginState = key.getState(state) + if (!pluginState) { + return null + } + + const { hoveredTable, hoveredCell } = pluginState.values + if (hoveredTable) { + const decorations = [ + Decoration.node( + hoveredTable.pos, + hoveredTable.pos + hoveredTable.node.nodeSize, + {}, + { + hoveredTable, + hoveredCell + } + ) + ] + + return DecorationSet.create(state.doc, decorations) + } + + return null + } + } + }) +} + +class TableControlsState { + values + + constructor(props = {}) { + this.values = { + hoveredTable: null, + hoveredCell: null, + ...props + } + } + + apply(tr: any) { + const actions = tr.getMeta(key) + + if (actions?.setHoveredTable !== undefined) { + this.values.hoveredTable = actions.setHoveredTable + } + + if (actions?.setHoveredCell !== undefined) { + this.values.hoveredCell = actions.setHoveredCell + } + + return this + } +} diff --git a/packages/editor/core/src/ui/extensions/table-new/Table/utilities/createCell.ts b/packages/editor/core/src/ui/extensions/table-new/Table/utilities/createCell.ts new file mode 100644 index 000000000..a3d7f2da8 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/Table/utilities/createCell.ts @@ -0,0 +1,12 @@ +import { Fragment, Node as ProsemirrorNode, NodeType } from "prosemirror-model" + +export function createCell( + cellType: NodeType, + cellContent?: Fragment | ProsemirrorNode | Array +): ProsemirrorNode | null | undefined { + if (cellContent) { + return cellType.createChecked(null, cellContent) + } + + return cellType.createAndFill() +} diff --git a/packages/editor/core/src/ui/extensions/table-new/Table/utilities/createTable.ts b/packages/editor/core/src/ui/extensions/table-new/Table/utilities/createTable.ts new file mode 100644 index 000000000..c59aadac1 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/Table/utilities/createTable.ts @@ -0,0 +1,47 @@ +import { Fragment, Node as ProsemirrorNode, Schema } from "@tiptap/pm/model" + +import { ReactNodeViewRenderer } from "@tiptap/react" + +import { createCell } from "./createCell" +import { getTableNodeTypes } from "./getTableNodeTypes" + +export function createTable( + schema: Schema, + rowsCount: number, + colsCount: number, + withHeaderRow: boolean, + cellContent?: Fragment | ProsemirrorNode | Array +): ProsemirrorNode { + const types = getTableNodeTypes(schema) + const headerCells: ProsemirrorNode[] = [] + const cells: ProsemirrorNode[] = [] + + for (let index = 0; index < colsCount; index += 1) { + const cell = createCell(types.cell, cellContent) + + if (cell) { + cells.push(cell) + } + + if (withHeaderRow) { + const headerCell = createCell(types.header_cell, cellContent) + + if (headerCell) { + headerCells.push(headerCell) + } + } + } + + const rows: ProsemirrorNode[] = [] + + for (let index = 0; index < rowsCount; index += 1) { + rows.push( + types.row.createChecked( + null, + withHeaderRow && index === 0 ? headerCells : cells + ) + ) + } + + return types.table.createChecked(null, rows) +} diff --git a/packages/editor/core/src/ui/extensions/table-new/Table/utilities/deleteTableWhenAllCellsSelected.ts b/packages/editor/core/src/ui/extensions/table-new/Table/utilities/deleteTableWhenAllCellsSelected.ts new file mode 100644 index 000000000..b76cdca5d --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/Table/utilities/deleteTableWhenAllCellsSelected.ts @@ -0,0 +1,41 @@ +import { findParentNodeClosestToPos, KeyboardShortcutCommand } from "@tiptap/core" + +import { isCellSelection } from "./isCellSelection" + +export const deleteTableWhenAllCellsSelected: KeyboardShortcutCommand = ({ + editor +}) => { + const { selection } = editor.state + + if (!isCellSelection(selection)) { + return false + } + + let cellCount = 0 + const table = findParentNodeClosestToPos( + selection.ranges[0].$from, + (node) => { + return node.type.name === "table" + } + ) + + table?.node.descendants((node) => { + if (node.type.name === "table") { + return false + } + + if (["tableCell", "tableHeader"].includes(node.type.name)) { + cellCount += 1 + } + }) + + const allCellsSelected = cellCount === selection.ranges.length + + if (!allCellsSelected) { + return false + } + + editor.commands.deleteTable() + + return true +} diff --git a/packages/editor/core/src/ui/extensions/table-new/Table/utilities/getTableNodeTypes.ts b/packages/editor/core/src/ui/extensions/table-new/Table/utilities/getTableNodeTypes.ts new file mode 100644 index 000000000..293878cb0 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/Table/utilities/getTableNodeTypes.ts @@ -0,0 +1,21 @@ +import { NodeType, Schema } from "prosemirror-model" + +export function getTableNodeTypes(schema: Schema): { [key: string]: NodeType } { + if (schema.cached.tableNodeTypes) { + return schema.cached.tableNodeTypes + } + + const roles: { [key: string]: NodeType } = {} + + Object.keys(schema.nodes).forEach((type) => { + const nodeType = schema.nodes[type] + + if (nodeType.spec.tableRole) { + roles[nodeType.spec.tableRole] = nodeType + } + }) + + schema.cached.tableNodeTypes = roles + + return roles +} diff --git a/packages/editor/core/src/ui/extensions/table-new/Table/utilities/isCellSelection.ts b/packages/editor/core/src/ui/extensions/table-new/Table/utilities/isCellSelection.ts new file mode 100644 index 000000000..3c36bf055 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/Table/utilities/isCellSelection.ts @@ -0,0 +1,5 @@ +import { CellSelection } from "@tiptap/prosemirror-tables" + +export function isCellSelection(value: unknown): value is CellSelection { + return value instanceof CellSelection +} diff --git a/packages/editor/core/src/ui/extensions/table-new/TableCell/TableCell.ts b/packages/editor/core/src/ui/extensions/table-new/TableCell/TableCell.ts new file mode 100644 index 000000000..dfa25c94d --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/TableCell/TableCell.ts @@ -0,0 +1,55 @@ +import { mergeAttributes, Node } from "@tiptap/core" + +export interface TableCellOptions { + HTMLAttributes: Record +} + +export default Node.create({ + name: "tableCell", + + addOptions() { + return { + HTMLAttributes: {} + } + }, + + content: "paragraph+", + + addAttributes() { + return { + colspan: { + default: 1 + }, + rowspan: { + default: 1 + }, + colwidth: { + default: null, + parseHTML: (element) => { + const colwidth = element.getAttribute("colwidth") + const value = colwidth ? [parseInt(colwidth, 10)] : null + + return value + } + } + } + }, + + tableRole: "cell", + + isolating: true, + + parseHTML() { + return [{ tag: "td" }] + }, + + renderHTML({ node, HTMLAttributes }) { + return [ + "td", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { + style: `background-color: ${node.attrs.background}` + }), + 0 + ] + } +}) diff --git a/packages/editor/core/src/ui/extensions/table-new/TableCell/index.ts b/packages/editor/core/src/ui/extensions/table-new/TableCell/index.ts new file mode 100644 index 000000000..e4acbea9e --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/TableCell/index.ts @@ -0,0 +1 @@ +export { default as default } from "./TableCell" diff --git a/packages/editor/core/src/ui/extensions/table-new/TableHeader/TableHeader.ts b/packages/editor/core/src/ui/extensions/table-new/TableHeader/TableHeader.ts new file mode 100644 index 000000000..8e8d0f1d4 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/TableHeader/TableHeader.ts @@ -0,0 +1,54 @@ +import { mergeAttributes, Node } from "@tiptap/core" + +export interface TableHeaderOptions { + HTMLAttributes: Record +} +export default Node.create({ + name: "tableHeader", + + addOptions() { + return { + HTMLAttributes: {} + } + }, + + content: "paragraph+", + + addAttributes() { + return { + colspan: { + default: 1 + }, + rowspan: { + default: 1 + }, + colwidth: { + default: null, + parseHTML: (element) => { + const colwidth = element.getAttribute("colwidth") + const value = colwidth ? [parseInt(colwidth, 10)] : null + + return value + } + } + } + }, + + tableRole: "header_cell", + + isolating: true, + + parseHTML() { + return [{ tag: "th" }] + }, + + renderHTML({ node, HTMLAttributes }) { + return [ + "th", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { + style: `background-color: ${node.attrs.background}` + }), + 0 + ] + } +}) diff --git a/packages/editor/core/src/ui/extensions/table-new/TableHeader/index.ts b/packages/editor/core/src/ui/extensions/table-new/TableHeader/index.ts new file mode 100644 index 000000000..f260509cf --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/TableHeader/index.ts @@ -0,0 +1 @@ +export { default as default } from "./TableHeader" diff --git a/packages/editor/core/src/ui/extensions/table-new/TableRow/TableRow.ts b/packages/editor/core/src/ui/extensions/table-new/TableRow/TableRow.ts new file mode 100644 index 000000000..e922e7fa1 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/TableRow/TableRow.ts @@ -0,0 +1,31 @@ +import { mergeAttributes, Node } from "@tiptap/core" + +export interface TableRowOptions { + HTMLAttributes: Record +} + +export default Node.create({ + name: "tableRow", + + addOptions() { + return { + HTMLAttributes: {} + } + }, + + content: "(tableCell | tableHeader)*", + + tableRole: "row", + + parseHTML() { + return [{ tag: "tr" }] + }, + + renderHTML({ HTMLAttributes }) { + return [ + "tr", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), + 0 + ] + } +}) diff --git a/packages/editor/core/src/ui/extensions/table-new/TableRow/index.ts b/packages/editor/core/src/ui/extensions/table-new/TableRow/index.ts new file mode 100644 index 000000000..f01744bf7 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table-new/TableRow/index.ts @@ -0,0 +1 @@ +export { default as default } from "./TableRow" diff --git a/packages/editor/core/src/ui/extensions/table/index.ts b/packages/editor/core/src/ui/extensions/table/index.ts deleted file mode 100644 index 9b727bb51..000000000 --- a/packages/editor/core/src/ui/extensions/table/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Table as BaseTable } from "@tiptap/extension-table"; - -const Table = BaseTable.configure({ - resizable: true, - cellMinWidth: 100, - allowTableNodeSelection: true, -}); - -export { Table }; diff --git a/packages/editor/core/src/ui/extensions/table/table-cell.ts b/packages/editor/core/src/ui/extensions/table/table-cell.ts deleted file mode 100644 index 643cb8c64..000000000 --- a/packages/editor/core/src/ui/extensions/table/table-cell.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { TableCell } from "@tiptap/extension-table-cell"; - -export const CustomTableCell = TableCell.extend({ - addAttributes() { - return { - ...this.parent?.(), - isHeader: { - default: false, - parseHTML: (element) => { - isHeader: element.tagName === "TD"; - }, - renderHTML: (attributes) => { - tag: attributes.isHeader ? "th" : "td"; - }, - }, - }; - }, - renderHTML({ HTMLAttributes }) { - if (HTMLAttributes.isHeader) { - return [ - "th", - { - ...HTMLAttributes, - class: `relative ${HTMLAttributes.class}`, - }, - ["span", { class: "absolute top-0 right-0" }], - 0, - ]; - } - return ["td", HTMLAttributes, 0]; - }, -}); diff --git a/packages/editor/core/src/ui/extensions/table/table-header.ts b/packages/editor/core/src/ui/extensions/table/table-header.ts deleted file mode 100644 index f23aa93ef..000000000 --- a/packages/editor/core/src/ui/extensions/table/table-header.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { TableHeader as BaseTableHeader } from "@tiptap/extension-table-header"; - -const TableHeader = BaseTableHeader.extend({ - content: "paragraph", -}); - -export { TableHeader }; diff --git a/packages/editor/core/src/ui/read-only/extensions.tsx b/packages/editor/core/src/ui/read-only/extensions.tsx index 2246c64f9..6837a3d77 100644 --- a/packages/editor/core/src/ui/read-only/extensions.tsx +++ b/packages/editor/core/src/ui/read-only/extensions.tsx @@ -8,10 +8,10 @@ import TaskList from "@tiptap/extension-task-list"; import { Markdown } from "tiptap-markdown"; import Gapcursor from "@tiptap/extension-gapcursor"; -import { CustomTableCell } from "../extensions/table/table-cell"; -import { Table } from "../extensions/table"; -import { TableHeader } from "../extensions/table/table-header"; -import { TableRow } from "@tiptap/extension-table-row"; +// import { CustomTableCell } from "../extensions/table/table-cell"; +// import { Table } from "../extensions/table"; +// import { TableHeader } from "../extensions/table/table-header"; +// import { TableRow } from "@tiptap/extension-table-row"; import ReadOnlyImageExtension from "../extensions/image/read-only-image"; import { isValidHttpUrl } from "../../lib/utils"; @@ -85,8 +85,8 @@ export const CoreReadOnlyEditorExtensions = [ html: true, transformCopiedText: true, }), - Table, - TableHeader, - CustomTableCell, - TableRow, + // Table, + // TableHeader, + // CustomTableCell, + // TableRow, ]; diff --git a/web/components/issues/comment/comment-card.tsx b/web/components/issues/comment/comment-card.tsx index 29ba21cd2..d59dce7d3 100644 --- a/web/components/issues/comment/comment-card.tsx +++ b/web/components/issues/comment/comment-card.tsx @@ -14,8 +14,8 @@ import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-te import { timeAgo } from "helpers/date-time.helper"; // types import type { IIssueComment } from "types"; -import fileService from "services/file.service"; // services +import fileService from "services/file.service"; type Props = { comment: IIssueComment; diff --git a/web/pages/_app.tsx b/web/pages/_app.tsx index b94e52d6b..53383fbe1 100644 --- a/web/pages/_app.tsx +++ b/web/pages/_app.tsx @@ -6,6 +6,7 @@ import NProgress from "nprogress"; // styles import "styles/globals.css"; import "styles/editor.css"; +import "styles/tables.css"; import "styles/command-pallette.css"; import "styles/nprogress.css"; import "styles/react-datepicker.css"; diff --git a/web/styles/tables.css b/web/styles/tables.css new file mode 100644 index 000000000..f43390120 --- /dev/null +++ b/web/styles/tables.css @@ -0,0 +1,200 @@ +.tableWrapper { + overflow-x: auto; + padding: 2px; + width: fit-content; + max-width: 100%; +} + +.tableWrapper table { + border-collapse: collapse; + table-layout: fixed; + margin: 0; + border: 1px solid rgb(var(--color-border-200)); + width: 100%; +} + +.tableWrapper table td, +.tableWrapper table th { + min-width: 1em; + border: 1px solid rgb(var(--color-border-200)); + padding: 10px 15px; + vertical-align: top; + box-sizing: border-box; + position: relative; + transition: background-color 0.3s ease; + + >* { + margin-bottom: 0; + } +} + +.tableWrapper table td>*, +.tableWrapper table th>* { + margin: 0 !important; + padding: 0.25rem 0 !important; +} + +.tableWrapper table td.has-focus, +.tableWrapper table th.has-focus { + box-shadow: rgba(var(--color-primary-300), 0.1) 0px 0px 0px 2px inset !important; +} + +.tableWrapper table th { + font-weight: bold; + text-align: left; + background-color: rgb(var(--color-primary-100)); +} + +.tableWrapper table td:hover{ + background-color: rgba(var(--color-primary-300), 0.1); +} + +.tableWrapper table th * { + font-weight: 600; +} + +.tableWrapper table .selectedCell:after { + z-index: 2; + position: absolute; + content: ""; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: rgba(var(--color-primary-300), 0.1); + pointer-events: none; +} + +.tableWrapper table .column-resize-handle { + position: absolute; + right: -2px; + top: 0; + bottom: -2px; + width: 4px; + z-index: 99; + background-color: rgb(var(--color-primary-400)); + pointer-events: none; +} + +.tableWrapper .tableControls { + position: absolute; +} + +.tableWrapper .tableControls .columnsControl, +.tableWrapper .tableControls .rowsControl { + transition: opacity ease-in 100ms; + position: absolute; + z-index: 99; + display: flex; + justify-content: center; + align-items: center; +} + +.tableWrapper .tableControls .columnsControl { + height: 20px; + transform: translateY(-50%); +} + +.tableWrapper .tableControls .columnsControl>button { + color: white; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath fill='%238F95B2' d='M4.5 10.5c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S6 12.825 6 12s-.675-1.5-1.5-1.5zm15 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S21 12.825 21 12s-.675-1.5-1.5-1.5zm-7.5 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5 1.5-.675 1.5-1.5-.675-1.5-1.5-1.5z'/%3E%3C/svg%3E"); + width: 30px; + height: 15px; +} + +.tableWrapper .tableControls .rowsControl { + width: 20px; + transform: translateX(-50%); +} + +.tableWrapper .tableControls .rowsControl>button { + color: white; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath fill='%238F95B2' d='M12 3c-.825 0-1.5.675-1.5 1.5S11.175 6 12 6s1.5-.675 1.5-1.5S12.825 3 12 3zm0 15c-.825 0-1.5.675-1.5 1.5S11.175 21 12 21s1.5-.675 1.5-1.5S12.825 18 12 18zm0-7.5c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5 1.5-.675 1.5-1.5-.675-1.5-1.5-1.5z'/%3E%3C/svg%3E"); + height: 30px; + width: 15px; +} + +.tableWrapper .tableControls button { + background-color: rgb(var(--color-primary-100)); + border: 1px solid rgb(var(--color-border-200)); + border-radius: 2px; + background-size: 1.25rem; + background-repeat: no-repeat; + background-position: center; + transition: transform ease-out 100ms, background-color ease-out 100ms; + outline: none; + box-shadow: #000 0px 2px 4px; + cursor: pointer; +} + +/* .tableWrapper .tableControls button:hover { */ +/* transform: scale(1.2, 1.2); */ +/* background-color: var(--color-n50); */ +/* } */ + +.tableWrapper .tableControls .tableToolbox, +.tableWrapper .tableControls .tableColorPickerToolbox { + padding: 0.25rem; + display: flex; + flex-direction: column; + width: 200px; + gap: 0.25rem; +} + +.tableWrapper .tableControls .tableToolbox .toolboxItem, +.tableWrapper .tableControls .tableColorPickerToolbox .toolboxItem { + background-color: rgb(var(--color-background-100)); + display: flex; + align-items: center; + gap: 0.5rem; + border: none; + padding: 0.1rem; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s; +} + +.tableWrapper .tableControls .tableToolbox .toolboxItem:hover, +.tableWrapper .tableControls .tableColorPickerToolbox .toolboxItem:hover { + background-color: var(--color-n100); +} + +.tableWrapper .tableControls .tableToolbox .toolboxItem .iconContainer, +.tableWrapper .tableControls .tableColorPickerToolbox .toolboxItem .iconContainer, +.tableWrapper .tableControls .tableToolbox .toolboxItem .colorContainer, +.tableWrapper .tableControls .tableColorPickerToolbox .toolboxItem .colorContainer { + border: 1px solid #e6e8f0; + border-radius: 3px; + padding: 4px; + display: flex; + align-items: center; + justify-content: center; + width: 1.75rem; + height: 1.75rem; +} + +.tableWrapper .tableControls .tableToolbox .toolboxItem .iconContainer svg, +.tableWrapper .tableControls .tableColorPickerToolbox .toolboxItem .iconContainer svg, +.tableWrapper .tableControls .tableToolbox .toolboxItem .colorContainer svg, +.tableWrapper .tableControls .tableColorPickerToolbox .toolboxItem .colorContainer svg { + width: 1rem; + height: 1rem; +} + +.tableToolbox { + background-color: rgb(var(--color-background-100)); +} + +.tableWrapper .tableControls .tableToolbox .toolboxItem .label, +.tableWrapper .tableControls .tableColorPickerToolbox .toolboxItem .label { + font-size: 0.95rem; + color: var(--color-black); +} + +.resize-cursor .tableWrapper .tableControls .rowsControl, +.tableWrapper.controls--disabled .tableControls .rowsControl, +.resize-cursor .tableWrapper .tableControls .columnsControl, +.tableWrapper.controls--disabled .tableControls .columnsControl { + opacity: 0; + pointer-events: none; +} diff --git a/yarn.lock b/yarn.lock index e3095db2c..8d340b3ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2476,26 +2476,6 @@ resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.1.10.tgz#ec311395d16af15345b63d2dac2d459b9ad5fa9e" integrity sha512-KW63lZLPFIir5AIeh2I7UK6Tx1O3jetD7JIPUzEqp1I1BfJlHGHVQxV8VXAmJl0hTOzjQBsHW42PmBxSC97NUg== -"@tiptap/extension-table-cell@^2.1.6": - version "2.1.10" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table-cell/-/extension-table-cell-2.1.10.tgz#e594b55622435c43a95edf6f2adfaca402f5cbed" - integrity sha512-NQOTKjPOTJrkI7VaR9wFF3UKB9N2THD8zJZJDcECKQxLR740udF6/6jWm1uwkTwdkBekVKHBMQvKKK9W1bOBiw== - -"@tiptap/extension-table-header@^2.1.6": - version "2.1.10" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table-header/-/extension-table-header-2.1.10.tgz#6250676a26946e5b7186198a06990ea70f578a87" - integrity sha512-NSC0Y10kXDvPGiJckJY/QU8VA7HHU0tI20Dj7/r1oD9itBWSnWP0zAOXzHVlQt9GpThhFNo2nu3fAaVQNfKoTg== - -"@tiptap/extension-table-row@^2.1.6": - version "2.1.10" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.1.10.tgz#e7a1ca8342b623a400848b437c82d57680e551e3" - integrity sha512-yMOnAaXE7vK7MwULuVUO8v6AYZu6wxTfHAWQe/FqPeMf9tG0HL6+gyt1audremw0xBFMGPx6v4t8vlqPXW9p2g== - -"@tiptap/extension-table@^2.1.6": - version "2.1.10" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.1.10.tgz#5654426366b547631c647ffc5dacf040e65307e1" - integrity sha512-fsf0c6qA+R6NzbFx+tm1l5POZsgadHjREsedvq5q1i8rCq1Gt1AK+lR7WQsaXlSeIRsWtg4RT0eUjAYNCmKkug== - "@tiptap/extension-task-item@^2.1.7": version "2.1.10" resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.1.10.tgz#8eb0d3e8b1234fa44205dd91619f3f1937ca3254" @@ -2545,6 +2525,11 @@ prosemirror-transform "^1.7.0" prosemirror-view "^1.28.2" +"@tiptap/prosemirror-tables@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@tiptap/prosemirror-tables/-/prosemirror-tables-1.1.4.tgz#e123978f13c9b5f980066ba660ec5df857755916" + integrity sha512-O2XnDhZV7xTHSFxMMl8Ei3UVeCxuMlbGYZ+J2QG8CzkK8mxDpBa66kFr5DdyAhvdi1ptpcH9u7/GMwItQpN4sA== + "@tiptap/react@^2.1.7": version "2.1.10" resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.1.10.tgz#51cd96462e61f6fffa0ca4eb359d8d7d15ebf422" @@ -2805,7 +2790,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^18.0.17": +"@types/react@*", "@types/react@18.0.15", "@types/react@18.0.28", "@types/react@18.2.0", "@types/react@^18.0.17", "@types/react@^18.2.5": version "18.2.0" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.0.tgz#15cda145354accfc09a18d2f2305f9fc099ada21" integrity sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA== @@ -2814,33 +2799,6 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/react@18.0.15": - version "18.0.15" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.15.tgz#d355644c26832dc27f3e6cbf0c4f4603fc4ab7fe" - integrity sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/react@18.0.28": - version "18.0.28" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.28.tgz#accaeb8b86f4908057ad629a26635fe641480065" - integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/react@^18.2.5": - version "18.2.24" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.24.tgz#3c7d68c02e0205a472f04abe4a0c1df35d995c05" - integrity sha512-Ee0Jt4sbJxMu1iDcetZEIKQr99J1Zfb6D4F3qfUWoR1JpInkY1Wdg4WwCyBjL257D0+jGqSl1twBjV8iCaC0Aw== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - "@types/reactcss@*": version "1.2.6" resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.6.tgz#133c1e7e896f2726370d1d5a26bf06a30a038bcc" @@ -5761,6 +5719,13 @@ jsonpointer@^5.0.0: object.assign "^4.1.4" object.values "^1.1.6" +jsx-dom-cjs@^8.0.3: + version "8.0.7" + resolved "https://registry.yarnpkg.com/jsx-dom-cjs/-/jsx-dom-cjs-8.0.7.tgz#098c54680ebf5bb6f6d12cdea5cde3799c172212" + integrity sha512-dQWnuQ+bTm7o72ZlJU4glzeMX8KLxx5U+ZwmEAzVP1+roL7BSM0MrkWdHjdsuNgmxobZCJ+qgiot9EgbJPOoEg== + dependencies: + csstype "^3.1.2" + keycode@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.1.tgz#09c23b2be0611d26117ea2501c2c391a01f39eff" @@ -7035,7 +7000,14 @@ prosemirror-menu@^1.2.1: prosemirror-history "^1.0.0" prosemirror-state "^1.0.0" -prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.18.1, prosemirror-model@^1.19.0, prosemirror-model@^1.8.1: +prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.18.1, prosemirror-model@^1.8.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.18.1.tgz#1d5d6b6de7b983ee67a479dc607165fdef3935bd" + integrity sha512-IxSVBKAEMjD7s3n8cgtwMlxAXZrC7Mlag7zYsAKDndAqnDScvSmp/UdnRTV/B33lTCVU3CCm7dyAn/rVVD0mcw== + dependencies: + orderedmap "^2.0.0" + +prosemirror-model@^1.19.0: version "1.19.3" resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.19.3.tgz#f0d55285487fefd962d0ac695f716f4ec6705006" integrity sha512-tgSnwN7BS7/UM0sSARcW+IQryx2vODKX4MI7xpqY2X+iaepJdKBPc7I4aACIsDV/LTaTjt12Z56MhDr9LsyuZQ==