Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,4 @@
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3"
}
}
}
69 changes: 69 additions & 0 deletions packages/core/src/editor/transformPasted.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,54 @@ import { Fragment, Schema, Slice } from "@tiptap/pm/model";
import { EditorView } from "@tiptap/pm/view";

import { getBlockInfoFromSelection } from "../api/getBlockInfoFromPos.js";
import { findParentNodeClosestToPos } from "@tiptap/core";

/**
* Checks if the current selection is inside a table cell.
* Returns the depth of the tableCell/tableHeader node if found, -1 otherwise.
*/
function isInTableCell(view: EditorView): boolean {
return (
findParentNodeClosestToPos(view.state.selection.$from, (n) => {
return n.type.name === "tableCell" || n.type.name === "tableHeader";
}) !== undefined
);
}

/**
* Converts block content to inline content with hard breaks.
* This is used when pasting into table cells which can only contain inline content.
*/
function convertBlocksToInlineContent(
fragment: Fragment,
schema: Schema,
): Fragment {
const hardBreak = schema.nodes.hardBreak;
let result = Fragment.empty;

fragment.forEach((node) => {
if (node.isTextblock && node.childCount > 0) {
// Extract inline content from paragraphs, headings, etc.
result = result.append(node.content);
result = result.addToEnd(hardBreak.create());
} else if (node.isText) {
result = result.addToEnd(node);
} else if (node.isBlock && node.childCount > 0) {
// Recurse into block containers, blockGroups, etc.
result = result.append(
convertBlocksToInlineContent(node.content, schema),
);
result = result.addToEnd(hardBreak.create());
}
});

// Remove trailing hard break
if (result.lastChild?.type === hardBreak) {
result = result.cut(0, result.size - 1);
}

return result;
}

// helper function to remove a child from a fragment
function removeChild(node: Fragment, n: number) {
Expand Down Expand Up @@ -65,6 +113,27 @@ export function transformPasted(slice: Slice, view: EditorView) {
let f = Fragment.from(slice.content);
f = wrapTableRows(f, view.state.schema);

if (isInTableCell(view)) {
let hasTableContent = false;
f.descendants((node) => {
if (node.type.isInGroup("tableContent")) {
hasTableContent = true;
}
});
if (
!hasTableContent &&
// is the content valid for a table paragraph?
!view.state.schema.nodes.tableParagraph.validContent(f)
) {
// if not, convert the content to inline content
return new Slice(
convertBlocksToInlineContent(f, view.state.schema),
0,
0,
);
}
}

if (!shouldApplyFix(f, view)) {
// Don't apply the fix.
return new Slice(f, slice.openStart, slice.openEnd);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
[
{
"children": [],
"content": {
"columnWidths": [
undefined,
undefined,
],
"headerCols": undefined,
"headerRows": undefined,
"rows": [
{
"cells": [
{
"content": [
{
"styles": {},
"text": "Cell 1ABC
Unit tests covering the new feature have been added.
All existing tests pass.",
"type": "text",
},
],
"props": {
"backgroundColor": "default",
"colspan": 1,
"rowspan": 1,
"textAlignment": "left",
"textColor": "default",
},
"type": "tableCell",
},
{
"content": [
{
"styles": {},
"text": "Cell 2",
"type": "text",
},
],
"props": {
"backgroundColor": "default",
"colspan": 1,
"rowspan": 1,
"textAlignment": "left",
"textColor": "default",
},
"type": "tableCell",
},
],
},
],
"type": "tableContent",
},
"id": "1",
"props": {
"textColor": "default",
},
"type": "table",
},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
[
{
"children": [],
"content": {
"columnWidths": [
undefined,
undefined,
],
"headerCols": undefined,
"headerRows": undefined,
"rows": [
{
"cells": [
{
"content": [
{
"styles": {},
"text": "Cell 1Paragraph 1
Paragraph 2
Paragraph 3",
"type": "text",
},
],
"props": {
"backgroundColor": "default",
"colspan": 1,
"rowspan": 1,
"textAlignment": "left",
"textColor": "default",
},
"type": "tableCell",
},
{
"content": [
{
"styles": {},
"text": "Cell 2",
"type": "text",
},
],
"props": {
"backgroundColor": "default",
"colspan": 1,
"rowspan": 1,
"textAlignment": "left",
"textColor": "default",
},
"type": "tableCell",
},
],
},
],
"type": "tableContent",
},
"id": "1",
"props": {
"textColor": "default",
},
"type": "table",
},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
[
{
"children": [],
"content": {
"columnWidths": [
undefined,
undefined,
],
"headerCols": undefined,
"headerRows": undefined,
"rows": [
{
"cells": [
{
"content": [
{
"styles": {},
"text": "Cell 1<p>Paragraph 1</p><p>Paragraph 2</p><p>Paragraph 3</p>",
"type": "text",
},
],
"props": {
"backgroundColor": "default",
"colspan": 1,
"rowspan": 1,
"textAlignment": "left",
"textColor": "default",
},
"type": "tableCell",
},
{
"content": [
{
"styles": {},
"text": "Cell 2",
"type": "text",
},
],
"props": {
"backgroundColor": "default",
"colspan": 1,
"rowspan": 1,
"textAlignment": "left",
"textColor": "default",
},
"type": "tableCell",
},
],
},
],
"type": "tableContent",
},
"id": "1",
"props": {
"textColor": "default",
},
"type": "table",
},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
[
{
"children": [],
"content": {
"columnWidths": [
undefined,
undefined,
],
"headerCols": undefined,
"headerRows": undefined,
"rows": [
{
"cells": [
{
"content": [
{
"styles": {},
"text": "Cell 1Line 1
Line 2
Line 3",
"type": "text",
},
],
"props": {
"backgroundColor": "default",
"colspan": 1,
"rowspan": 1,
"textAlignment": "left",
"textColor": "default",
},
"type": "tableCell",
},
{
"content": [
{
"styles": {},
"text": "Cell 2",
"type": "text",
},
],
"props": {
"backgroundColor": "default",
"colspan": 1,
"rowspan": 1,
"textAlignment": "left",
"textColor": "default",
},
"type": "tableCell",
},
],
},
],
"type": "tableContent",
},
"id": "1",
"props": {
"textColor": "default",
},
"type": "table",
},
]
Loading
Loading