feat: enhance table cells with rich content support (#1409)

- Support multiple content types in table cells and headers: paragraphs, headings, lists (bullet/ordered/task), blockquotes, callouts, images, videos, attachments, math blocks, toggles, and code blocks
- Add custom table extension with smart Tab key handling for list indentation within tables
- Preserve default table navigation when not in lists
This commit is contained in:
Philip Okugbe
2025-07-28 08:22:22 +01:00
committed by GitHub
parent 1f815880a4
commit 0bd7ecb9b0
6 changed files with 72 additions and 6 deletions

View File

@ -10,7 +10,6 @@ import { Highlight } from "@tiptap/extension-highlight";
import { Typography } from "@tiptap/extension-typography"; import { Typography } from "@tiptap/extension-typography";
import { TextStyle } from "@tiptap/extension-text-style"; import { TextStyle } from "@tiptap/extension-text-style";
import { Color } from "@tiptap/extension-color"; import { Color } from "@tiptap/extension-color";
import Table from "@tiptap/extension-table";
import SlashCommand from "@/features/editor/extensions/slash-command"; import SlashCommand from "@/features/editor/extensions/slash-command";
import { Collaboration } from "@tiptap/extension-collaboration"; import { Collaboration } from "@tiptap/extension-collaboration";
import { CollaborationCursor } from "@tiptap/extension-collaboration-cursor"; import { CollaborationCursor } from "@tiptap/extension-collaboration-cursor";
@ -25,6 +24,7 @@ import {
TableCell, TableCell,
TableRow, TableRow,
TableHeader, TableHeader,
CustomTable,
TrailingNode, TrailingNode,
TiptapImage, TiptapImage,
Callout, Callout,
@ -160,7 +160,7 @@ export const mainExtensions = [
return ReactNodeViewRenderer(MentionView); return ReactNodeViewRenderer(MentionView);
}, },
}), }),
Table.configure({ CustomTable.configure({
resizable: true, resizable: true,
lastColumnResizable: false, lastColumnResizable: false,
allowTableNodeSelection: true, allowTableNodeSelection: true,

View File

@ -10,7 +10,6 @@ import { Typography } from '@tiptap/extension-typography';
import { TextStyle } from '@tiptap/extension-text-style'; import { TextStyle } from '@tiptap/extension-text-style';
import { Color } from '@tiptap/extension-color'; import { Color } from '@tiptap/extension-color';
import { Youtube } from '@tiptap/extension-youtube'; import { Youtube } from '@tiptap/extension-youtube';
import Table from '@tiptap/extension-table';
import { import {
Callout, Callout,
Comment, Comment,
@ -24,6 +23,7 @@ import {
TableHeader, TableHeader,
TableCell, TableCell,
TableRow, TableRow,
CustomTable,
TiptapImage, TiptapImage,
TiptapVideo, TiptapVideo,
TrailingNode, TrailingNode,
@ -65,7 +65,7 @@ export const tiptapExtensions = [
Details, Details,
DetailsContent, DetailsContent,
DetailsSummary, DetailsSummary,
Table, CustomTable,
TableCell, TableCell,
TableRow, TableRow,
TableHeader, TableHeader,

View File

@ -2,7 +2,7 @@ import { TableCell as TiptapTableCell } from "@tiptap/extension-table-cell";
export const TableCell = TiptapTableCell.extend({ export const TableCell = TiptapTableCell.extend({
name: "tableCell", name: "tableCell",
content: "paragraph+", content: "(paragraph | heading | bulletList | orderedList | taskList | blockquote | callout | image | video | attachment | mathBlock | details | codeBlock)+",
addAttributes() { addAttributes() {
return { return {

View File

@ -2,7 +2,7 @@ import { TableHeader as TiptapTableHeader } from "@tiptap/extension-table-header
export const TableHeader = TiptapTableHeader.extend({ export const TableHeader = TiptapTableHeader.extend({
name: "tableHeader", name: "tableHeader",
content: "paragraph+", content: "(paragraph | heading | bulletList | orderedList | taskList | blockquote | callout | image | video | attachment | mathBlock | details | codeBlock)+",
addAttributes() { addAttributes() {
return { return {

View File

@ -1,3 +1,4 @@
export * from "./row"; export * from "./row";
export * from "./cell"; export * from "./cell";
export * from "./header"; export * from "./header";
export * from "./table";

View File

@ -0,0 +1,65 @@
import Table from "@tiptap/extension-table";
import { Editor } from "@tiptap/core";
const LIST_TYPES = ["bulletList", "orderedList", "taskList"];
function isInList(editor: Editor): boolean {
const { $from } = editor.state.selection;
for (let depth = $from.depth; depth > 0; depth--) {
const node = $from.node(depth);
if (LIST_TYPES.includes(node.type.name)) {
return true;
}
}
return false;
}
function handleListIndent(editor: Editor): boolean {
return editor.commands.sinkListItem("listItem") ||
editor.commands.sinkListItem("taskItem");
}
function handleListOutdent(editor: Editor): boolean {
return editor.commands.liftListItem("listItem") ||
editor.commands.liftListItem("taskItem");
}
export const CustomTable = Table.extend({
addKeyboardShortcuts() {
return {
...this.parent?.(),
Tab: () => {
// If we're in a list within a table, handle list indentation
if (isInList(this.editor) && this.editor.isActive("table")) {
if (handleListIndent(this.editor)) {
return true;
}
}
// Otherwise, use default table navigation
if (this.editor.commands.goToNextCell()) {
return true;
}
if (!this.editor.can().addRowAfter()) {
return false;
}
return this.editor.chain().addRowAfter().goToNextCell().run();
},
"Shift-Tab": () => {
// If we're in a list within a table, handle list outdentation
if (isInList(this.editor) && this.editor.isActive("table")) {
if (handleListOutdent(this.editor)) {
return true;
}
}
// Otherwise, use default table navigation
return this.editor.commands.goToPreviousCell();
},
};
},
});