From 88889fb92a66fde994f7880c2b1243b9f677ff16 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 12 Feb 2025 14:42:41 +0100 Subject: [PATCH 01/72] feat: got the initial types working --- package-lock.json | 232 ++++++++++-------- packages/core/package.json | 42 ++-- .../src/api/nodeConversions/blockToNode.ts | 40 ++- .../src/api/nodeConversions/nodeToBlock.ts | 20 +- .../src/api/testUtil/partialBlockTestUtil.ts | 31 ++- .../TableBlockContent/TableBlockContent.ts | 21 +- packages/core/src/blocks/defaultBlocks.ts | 3 +- .../BackgroundColorExtension.ts | 2 +- .../TableHandles/TableHandlesPlugin.ts | 2 +- .../TextAlignment/TextAlignmentExtension.ts | 1 + .../TextColor/TextColorExtension.ts | 2 +- packages/core/src/schema/blocks/types.ts | 54 +++- .../ExtendButton/ExtendButton.tsx | 21 +- .../DefaultButtons/AddButton.tsx | 12 +- .../DefaultButtons/DeleteButton.tsx | 6 +- .../src/docx/defaultSchema/blocks.ts | 4 + .../xl-docx-exporter/src/docx/util/Table.tsx | 3 +- .../src/pdf/defaultSchema/blocks.tsx | 3 + .../src/pdf/util/table/Table.tsx | 3 +- vitest.workspace.ts | 13 + 20 files changed, 350 insertions(+), 165 deletions(-) create mode 100644 vitest.workspace.ts diff --git a/package-lock.json b/package-lock.json index f790a1dd20..06cea97b2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9634,9 +9634,9 @@ } }, "node_modules/@tiptap/core": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.7.1.tgz", - "integrity": "sha512-/sOJ3J2OWxQrho6MWgE9xaRBln5MC4BEuevTYIGia4zrc523lX9s+h/lUeLtCPhI0+J6z9Vz+v3G/uoEqWCL+A==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.11.5.tgz", + "integrity": "sha512-jb0KTdUJaJY53JaN7ooY3XAxHQNoMYti/H6ANo707PsLXVeEqJ9o8+eBup1JU5CuwzrgnDc2dECt2WIGX9f8Jw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9646,9 +9646,9 @@ } }, "node_modules/@tiptap/extension-bold": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.7.1.tgz", - "integrity": "sha512-k03srawDKtS8P4w2TKv59MzgvZoKGssTGqvOF1YDxArriB2pK3jz6i4jbGs79qRlyGyWFSCEkpF9amFrCajlIw==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.11.5.tgz", + "integrity": "sha512-OAq03MHEbl7MtYCUzGuwb0VpOPnM0k5ekMbEaRILFU5ZC7cEAQ36XmPIw1dQayrcuE8GZL35BKub2qtRxyC9iA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9674,9 +9674,9 @@ } }, "node_modules/@tiptap/extension-code": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.7.1.tgz", - "integrity": "sha512-VC9/AzIyfIKaxvTW1hIJUc373IY3D5Z3ykODPaNqplrvqonwULxrciLLh+GcCAwTjH8XnJtc66IaM9VDYxrSTw==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.11.5.tgz", + "integrity": "sha512-xOvHevNIQIcCCVn9tpvXa1wBp0wHN/2umbAZGTVzS+AQtM7BTo0tz8IyzwxkcZJaImONcUVYLOLzt2AgW1LltA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9686,9 +9686,9 @@ } }, "node_modules/@tiptap/extension-collaboration": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-2.7.1.tgz", - "integrity": "sha512-u6eDKDuzJ9Fd9ZPul7wAnSzqaq+ovRaVsDRYrNDYUYT2QlbWJtI8HKE/5Mt1Ml0Xnpwn3UcLwJdo0iN4/A4IOQ==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-2.11.5.tgz", + "integrity": "sha512-3tMMq0E+FM3/3YBUMq5rLvks2DC/t1XLH2Kz/VcuVCxqg1Zg5s9nKOl6CcUZ8gbdvZoEd/GYoQyROJ957v9wzw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9700,9 +9700,9 @@ } }, "node_modules/@tiptap/extension-collaboration-cursor": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.7.1.tgz", - "integrity": "sha512-aYVuztLFTFfoh+bQMQV+f/+iTrq6L99YJF+TRW4CfnD6gsThX3fk4tbGXWVoE8Zd6SQMAjote4TZZBwGbANtdg==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.11.5.tgz", + "integrity": "sha512-sazBzi5HCHgGRihSzWHhHFMSI9oU0v/qqiDZYJ/zhzCKEWONx8WlS6WTxo6z3l6/Qz9lm7clmHNUQNWxnssAOA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9729,9 +9729,9 @@ } }, "node_modules/@tiptap/extension-gapcursor": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.7.1.tgz", - "integrity": "sha512-oSfir7KRnnZ46PsZrSpgpSYr+0zYmIvwL0HhCzaUg8a44ISg730MhrD0jZibQ/+AWatLVHx8Pcd/eHm/R9+rXA==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.11.5.tgz", + "integrity": "sha512-kcWa+Xq9cb6lBdiICvLReuDtz/rLjFKHWpW3jTTF3FiP3wx4H8Rs6bzVtty7uOVTfwupxZRiKICAMEU6iT0xrQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9742,9 +9742,9 @@ } }, "node_modules/@tiptap/extension-hard-break": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.7.1.tgz", - "integrity": "sha512-zy7lK2d0tu6BagzqkBwp2cIXM2t/QbHiKap1roj9B+QafsF9Im9p92bEK6fJEw+qC/j0PucR668MYsfUU7d2gQ==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.11.5.tgz", + "integrity": "sha512-q9doeN+Yg9F5QNTG8pZGYfNye3tmntOwch683v0CCVCI4ldKaLZ0jG3NbBTq+mosHYdgOH2rNbIORlRRsQ+iYQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9754,9 +9754,9 @@ } }, "node_modules/@tiptap/extension-history": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.7.1.tgz", - "integrity": "sha512-cQrnZxqdFykGllQ4icivEw1IwCm9s8cB/nVqJj09Sl8VyR28PuOVJUDBXfD81c3id+R152hoCEgZmzwkBmNrHQ==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.11.5.tgz", + "integrity": "sha512-b+wOS33Dz1azw6F1i9LFTEIJ/gUui0Jwz5ZvmVDpL2ZHBhq1Ui0/spTT+tuZOXq7Y/uCbKL8Liu4WoedIvhboQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9767,9 +9767,9 @@ } }, "node_modules/@tiptap/extension-horizontal-rule": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.7.1.tgz", - "integrity": "sha512-6MAUp5gahrKk0jIHJOgmcfmuscU/UU+7zyTUqTWPI6lll1Wu2HYQPzQKlFT3L8QAE+XXj7vlY9vMFMleGsw1Eg==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.11.5.tgz", + "integrity": "sha512-3up2r1Du8/5/4ZYzTC0DjTwhgPI3dn8jhOCLu73m5F3OGvK/9whcXoeWoX103hYMnGDxBlfOje71yQuN35FL4A==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9780,9 +9780,9 @@ } }, "node_modules/@tiptap/extension-italic": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.7.1.tgz", - "integrity": "sha512-xH8X63C3ewEpN53qdV1UsdkeQA6/srOtuwGf0rLhiYI4whg02WQTQ2A66VEuRk1kPTkpu8nmD3bf9OvvlPqE0Q==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.11.5.tgz", + "integrity": "sha512-9VGfb2/LfPhQ6TjzDwuYLRvw0A6VGbaIp3F+5Mql8XVdTBHb2+rhELbyhNGiGVR78CaB/EiKb6dO9xu/tBWSYA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9792,11 +9792,11 @@ } }, "node_modules/@tiptap/extension-link": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.7.1.tgz", - "integrity": "sha512-CoM43k2xDVN+SaDctuFNf/ELox1hCizAXnyt4oEMXpEfOZCQ4BjwKYBior0LxClg6vEvO0n8KzhS9LAAVZHS8Q==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.11.5.tgz", + "integrity": "sha512-4Iu/aPzevbYpe50xDI0ZkqRa6nkZ9eF270Ue2qaF3Ab47nehj+9Jl78XXzo8+LTyFMnrETI73TAs1aC/IGySeQ==", "dependencies": { - "linkifyjs": "^4.1.0" + "linkifyjs": "^4.2.0" }, "funding": { "type": "github", @@ -9808,9 +9808,9 @@ } }, "node_modules/@tiptap/extension-paragraph": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.7.1.tgz", - "integrity": "sha512-qMZvyadhzKCQ5oqpeh9AwJnHgaH8T8NYu+cQUT27EPGSzsvLjVq1CdgMpmh5WRvwVpi7GtelHfokle/nenH14w==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.11.5.tgz", + "integrity": "sha512-YFBWeg7xu/sBnsDIF/+nh9Arf7R0h07VZMd0id5Ydd2Qe3c1uIZwXxeINVtH0SZozuPIQFAT8ICe9M0RxmE+TA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9820,9 +9820,9 @@ } }, "node_modules/@tiptap/extension-strike": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.7.1.tgz", - "integrity": "sha512-NF9z/7suQ+vg+a6HmbCk5yWPxuue68D1fy/T/fkcoqLHO+gGtp0CvmZfJK5eBDFWrfHsSVzr/YNNZktcW4ApPg==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.11.5.tgz", + "integrity": "sha512-PVfUiCqrjvsLpbIoVlegSY8RlkR64F1Rr2RYmiybQfGbg+AkSZXDeO0eIrc03//4gua7D9DfIozHmAKv1KN3ow==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9832,9 +9832,9 @@ } }, "node_modules/@tiptap/extension-table-cell": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.7.1.tgz", - "integrity": "sha512-nEkCnQcdgtmPrOSMGM+g/Qvmy3dkSKxlFvnwgqZOlDTE3eYfNZCWu6pYchuy4wkTmEGnesZtO2ngHipJKnhKHw==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.11.5.tgz", + "integrity": "sha512-S967Au0pgeULstP3FaasOf/LEh72p61Ooh1PcUMF/az4x8EeGgpcEUARpVUxsGxLFvogv6LmhPHZdtcGgdHcBw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9844,9 +9844,9 @@ } }, "node_modules/@tiptap/extension-table-header": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.7.1.tgz", - "integrity": "sha512-KKFMk1kXGpGZDshK17hEMu4HSAfw/Ux31mfIaKfuL9QJJ/J5Fd/Lnatz9SCfVzGnFFa1JO+qelCw+/uRbcsAMg==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.11.5.tgz", + "integrity": "sha512-O1iBtzZP1XZDi4h1Xmgq1T63il+fpKPvBIMZ0JJH9TyCw5i5rcrMLL2dyy5zaWK3BFRJuYBNSke4c+VWnr/g6w==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9856,9 +9856,9 @@ } }, "node_modules/@tiptap/extension-table-row": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.7.1.tgz", - "integrity": "sha512-nUMvMtRCSCV1w5AJVcRE1+/MiK/sdM3ZiR3x+plOo6m24+DRlETg8AN5ierOvWoObb56apl5onk8A75/ZRPlAg==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.11.5.tgz", + "integrity": "sha512-+/VWhCuW24BcM5aaIc/f0bC6ZR1Q5gnuqw13MIo7gyPx7iIY6BXK8roGiZSs8wYAN4uBEf3EKFm0bSZwQuAeyg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9868,9 +9868,9 @@ } }, "node_modules/@tiptap/extension-text": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.7.1.tgz", - "integrity": "sha512-4VsX661judEpjzFokTDe1ZBI/6tJxvPeo2qZ4gFMUOH133sgEtMPwx0KtUi1uDtXjxa3G5w5Frdu8SNTB048sA==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.11.5.tgz", + "integrity": "sha512-Gq1WwyhFpCbEDrLPIHt5A8aLSlf8bfz4jm417c8F/JyU0J5dtYdmx0RAxjnLw1i7ZHE7LRyqqAoS0sl7JHDNSQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9880,9 +9880,9 @@ } }, "node_modules/@tiptap/extension-underline": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.7.1.tgz", - "integrity": "sha512-J5LH1DsHNke4f1nnY0x0O3vdGIKgawhhDsVkp6PncCBN+Q/ZQP+q8elaCsLXd4WpqTdkN4LKV7Kf2fALgLoG4g==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.11.5.tgz", + "integrity": "sha512-YpWHXNIkSoRSuzT2cvgKpyJ2tTz3LzqkTM64uC+uTJ8cUkvXIWUWejJR42q8ma/mTlQe4lHff4IQ0Sf58Digtw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9892,28 +9892,28 @@ } }, "node_modules/@tiptap/pm": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.7.1.tgz", - "integrity": "sha512-gG++eBQu9SObWCmxZDv6tkwFHVmbg7phowy0F7Nihq9Um7/oae5Ag9skfiG8GG9eYdw54paEAY/MP+tE3x/smA==", + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.11.5.tgz", + "integrity": "sha512-z9JFtqc5ZOsdQLd9vRnXfTCQ8v5ADAfRt9Nm7SqP6FUHII8E1hs38ACzf5xursmth/VonJYb5+73Pqxk1hGIPw==", "dependencies": { "prosemirror-changeset": "^2.2.1", "prosemirror-collab": "^1.3.1", - "prosemirror-commands": "^1.6.0", + "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", - "prosemirror-markdown": "^1.13.0", + "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", - "prosemirror-model": "^1.22.3", + "prosemirror-model": "^1.23.0", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.4.1", "prosemirror-state": "^1.4.3", - "prosemirror-tables": "^1.4.0", + "prosemirror-tables": "^1.6.3", "prosemirror-trailing-node": "^3.0.0", - "prosemirror-transform": "^1.10.0", - "prosemirror-view": "^1.33.10" + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.37.0" }, "funding": { "type": "github", @@ -10191,6 +10191,11 @@ "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==" }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" + }, "node_modules/@types/lodash": { "version": "4.17.5", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz", @@ -10224,6 +10229,15 @@ "@types/lodash": "*" } }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -10232,6 +10246,11 @@ "@types/unist": "^2" } }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" + }, "node_modules/@types/mdx": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", @@ -19463,9 +19482,9 @@ } }, "node_modules/linkifyjs": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.3.tgz", - "integrity": "sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.2.0.tgz", + "integrity": "sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==" }, "node_modules/load-json-file": { "version": "6.2.0", @@ -24663,13 +24682,13 @@ } }, "node_modules/prosemirror-commands": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.0.tgz", - "integrity": "sha512-xn1U/g36OqXn2tn5nGmvnnimAj/g1pUx2ypJJIe8WkVX83WyJVC5LTARaxZa2AtQRwntu9Jc5zXs9gL9svp/mg==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.2.tgz", + "integrity": "sha512-0nDHH++qcf/BuPLYvmqZTUUsPJUCPBUXt0J1ErTcDIS369CTp773itzLGIgIXG4LJXOlwYCr44+Mh4ii6MP1QA==", "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" + "prosemirror-transform": "^1.10.2" } }, "node_modules/prosemirror-dropcursor": { @@ -24723,10 +24742,11 @@ } }, "node_modules/prosemirror-markdown": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.0.tgz", - "integrity": "sha512-UziddX3ZYSYibgx8042hfGKmukq5Aljp2qoBiJRejD/8MH70siQNz5RB1TrdTPheqLMy4aCe4GYNF10/3lQS5g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.1.tgz", + "integrity": "sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw==", "dependencies": { + "@types/markdown-it": "^14.0.0", "markdown-it": "^14.0.0", "prosemirror-model": "^1.20.0" } @@ -24743,9 +24763,9 @@ } }, "node_modules/prosemirror-model": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.23.0.tgz", - "integrity": "sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.24.1.tgz", + "integrity": "sha512-YM053N+vTThzlWJ/AtPtF1j0ebO36nvbmDy4U7qA2XQB8JVaQp1FmB9Jhrps8s+z+uxhhVTny4m20ptUvhk0Mg==", "dependencies": { "orderedmap": "^2.0.0" } @@ -24779,15 +24799,15 @@ } }, "node_modules/prosemirror-tables": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.6.1.tgz", - "integrity": "sha512-p8WRJNA96jaNQjhJolmbxTzd6M4huRE5xQ8OxjvMhQUP0Nzpo4zz6TztEiwk6aoqGBhz9lxRWR1yRZLlpQN98w==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.6.4.tgz", + "integrity": "sha512-TkDY3Gw52gRFRfRn2f4wJv5WOgAOXLJA2CQJYIJ5+kdFbfj3acR4JUW6LX2e1hiEBiUwvEhzH5a3cZ5YSztpIA==", "dependencies": { - "prosemirror-keymap": "^1.1.2", - "prosemirror-model": "^1.8.1", - "prosemirror-state": "^1.3.1", - "prosemirror-transform": "^1.2.1", - "prosemirror-view": "^1.13.3" + "prosemirror-keymap": "^1.2.2", + "prosemirror-model": "^1.24.1", + "prosemirror-state": "^1.4.3", + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.37.2" } }, "node_modules/prosemirror-trailing-node": { @@ -30191,32 +30211,32 @@ "license": "MPL-2.0", "dependencies": { "@emoji-mart/data": "^1.2.1", - "@tiptap/core": "^2.7.1", - "@tiptap/extension-bold": "^2.7.1", - "@tiptap/extension-code": "^2.7.1", - "@tiptap/extension-collaboration": "^2.7.1", - "@tiptap/extension-collaboration-cursor": "^2.7.1", - "@tiptap/extension-gapcursor": "^2.7.1", - "@tiptap/extension-hard-break": "^2.7.1", - "@tiptap/extension-history": "^2.7.1", - "@tiptap/extension-horizontal-rule": "^2.7.1", - "@tiptap/extension-italic": "^2.7.1", - "@tiptap/extension-link": "^2.7.1", - "@tiptap/extension-paragraph": "^2.7.1", - "@tiptap/extension-strike": "^2.7.1", - "@tiptap/extension-table-cell": "^2.7.1", - "@tiptap/extension-table-header": "^2.7.1", - "@tiptap/extension-table-row": "^2.7.1", - "@tiptap/extension-text": "^2.7.1", - "@tiptap/extension-underline": "^2.7.1", - "@tiptap/pm": "^2.7.1", + "@tiptap/core": "^2.11.5", + "@tiptap/extension-bold": "^2.11.5", + "@tiptap/extension-code": "^2.11.5", + "@tiptap/extension-collaboration": "^2.11.5", + "@tiptap/extension-collaboration-cursor": "^2.11.5", + "@tiptap/extension-gapcursor": "^2.11.5", + "@tiptap/extension-hard-break": "^2.11.5", + "@tiptap/extension-history": "^2.11.5", + "@tiptap/extension-horizontal-rule": "^2.11.5", + "@tiptap/extension-italic": "^2.11.5", + "@tiptap/extension-link": "^2.11.5", + "@tiptap/extension-paragraph": "^2.11.5", + "@tiptap/extension-strike": "^2.11.5", + "@tiptap/extension-table-cell": "^2.11.5", + "@tiptap/extension-table-header": "^2.11.5", + "@tiptap/extension-table-row": "^2.11.5", + "@tiptap/extension-text": "^2.11.5", + "@tiptap/extension-underline": "^2.11.5", + "@tiptap/pm": "^2.11.5", "emoji-mart": "^5.6.0", "hast-util-from-dom": "^4.2.0", "prosemirror-dropcursor": "^1.8.1", "prosemirror-highlight": "^0.9.0", - "prosemirror-model": "^1.23.0", + "prosemirror-model": "^1.24.1", "prosemirror-state": "^1.4.3", - "prosemirror-tables": "^1.6.1", + "prosemirror-tables": "^1.6.4", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.38.0", "rehype-format": "^5.0.0", diff --git a/packages/core/package.json b/packages/core/package.json index 5d676c1dcc..51de58799d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,32 +58,32 @@ }, "dependencies": { "@emoji-mart/data": "^1.2.1", - "@tiptap/core": "^2.7.1", - "@tiptap/extension-bold": "^2.7.1", - "@tiptap/extension-code": "^2.7.1", - "@tiptap/extension-collaboration": "^2.7.1", - "@tiptap/extension-collaboration-cursor": "^2.7.1", - "@tiptap/extension-gapcursor": "^2.7.1", - "@tiptap/extension-hard-break": "^2.7.1", - "@tiptap/extension-history": "^2.7.1", - "@tiptap/extension-horizontal-rule": "^2.7.1", - "@tiptap/extension-italic": "^2.7.1", - "@tiptap/extension-link": "^2.7.1", - "@tiptap/extension-paragraph": "^2.7.1", - "@tiptap/extension-strike": "^2.7.1", - "@tiptap/extension-table-cell": "^2.7.1", - "@tiptap/extension-table-header": "^2.7.1", - "@tiptap/extension-table-row": "^2.7.1", - "@tiptap/extension-text": "^2.7.1", - "@tiptap/extension-underline": "^2.7.1", - "@tiptap/pm": "^2.7.1", + "@tiptap/core": "^2.11.5", + "@tiptap/extension-bold": "^2.11.5", + "@tiptap/extension-code": "^2.11.5", + "@tiptap/extension-collaboration": "^2.11.5", + "@tiptap/extension-collaboration-cursor": "^2.11.5", + "@tiptap/extension-gapcursor": "^2.11.5", + "@tiptap/extension-hard-break": "^2.11.5", + "@tiptap/extension-history": "^2.11.5", + "@tiptap/extension-horizontal-rule": "^2.11.5", + "@tiptap/extension-italic": "^2.11.5", + "@tiptap/extension-link": "^2.11.5", + "@tiptap/extension-paragraph": "^2.11.5", + "@tiptap/extension-strike": "^2.11.5", + "@tiptap/extension-table-cell": "^2.11.5", + "@tiptap/extension-table-header": "^2.11.5", + "@tiptap/extension-table-row": "^2.11.5", + "@tiptap/extension-text": "^2.11.5", + "@tiptap/extension-underline": "^2.11.5", + "@tiptap/pm": "^2.11.5", "emoji-mart": "^5.6.0", "hast-util-from-dom": "^4.2.0", "prosemirror-dropcursor": "^1.8.1", "prosemirror-highlight": "^0.9.0", - "prosemirror-model": "^1.23.0", + "prosemirror-model": "^1.24.1", "prosemirror-state": "^1.4.3", - "prosemirror-tables": "^1.6.1", + "prosemirror-tables": "^1.6.4", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.38.0", "rehype-format": "^5.0.0", diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 41ae6b6d3e..02135b6e8b 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -1,4 +1,4 @@ -import { Mark, Node, Schema } from "@tiptap/pm/model"; +import { Attrs, Fragment, Mark, Node, Schema } from "@tiptap/pm/model"; import UniqueID from "../../extensions/UniqueID/UniqueID.js"; import type { @@ -16,6 +16,7 @@ import { isPartialLinkInlineContent, isStyledTextInlineContent, } from "../../schema/inlineContent/types.js"; +import { isPartialTableCell } from "../../schema/blocks/types.js"; import { UnreachableCaseError } from "../../util/typescript.js"; /** @@ -179,29 +180,48 @@ export function tableContentToNodes< const columnNodes: Node[] = []; for (let i = 0; i < row.cells.length; i++) { const cell = row.cells[i]; - let pNode: Node; + let attrs: Attrs | null = null; + let content: Fragment | Node | readonly Node[] | null = null; + const marks: undefined | readonly Mark[] = undefined; if (!cell) { - pNode = schema.nodes["tableParagraph"].createChecked({}); + attrs = {}; } else if (typeof cell === "string") { - pNode = schema.nodes["tableParagraph"].createChecked( - {}, - schema.text(cell) - ); + content = schema.text(cell); + } else if (isPartialTableCell(cell)) { + if (cell.content) { + content = inlineContentToNodes(cell.content, schema, styleSchema); + } else if (cell.props) { + attrs = cell.props; + } } else { - const textNodes = inlineContentToNodes(cell, schema, styleSchema); - pNode = schema.nodes["tableParagraph"].createChecked({}, textNodes); + content = inlineContentToNodes(cell, schema, styleSchema); } const cellNode = schema.nodes["tableCell"].createChecked( { + // TODO modify // The colwidth array should have multiple values when the colspan of // a cell is greater than 1. However, this is not yet implemented so // we can always assume a length of 1. colwidth: tableContent.columnWidths?.[i] ? [tableContent.columnWidths[i]] : null, + ...(isPartialTableCell(cell) && cell.props + ? { + rowspan: cell.props.rowspan, + colspan: cell.props.colspan, + textColor: cell.props.textColor, + } + : {}), }, - pNode + schema.nodes["tableParagraph"].createChecked(attrs, content, marks), + [ + isPartialTableCell(cell) && cell.props?.backgroundColor + ? schema.marks["backgroundColor"].create({ + stringValue: cell.props.backgroundColor, + }) + : false, + ].filter((a): a is Mark => Boolean(a)) ); columnNodes.push(cellNode); } diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 4e8ae7884c..da9c2124ca 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -10,6 +10,7 @@ import type { InlineContentSchema, StyleSchema, Styles, + TableCell, TableContent, } from "../../schema/index.js"; import { getBlockInfoWithManualOffset } from "../getBlockInfoFromPos.js"; @@ -41,6 +42,7 @@ export function contentNodeToTableContent< if (index === 0) { rowNode.content.forEach((cellNode) => { + // TODO implement // The colwidth array should have multiple values when the colspan of a // cell is greater than 1. However, this is not yet implemented so we // can always assume a length of 1. @@ -48,14 +50,22 @@ export function contentNodeToTableContent< }); } - rowNode.content.forEach((cellNode) => { - row.cells.push( - contentNodeToInlineContent( + row.cells = rowNode.content.content.map((cellNode) => { + return { + type: "tableCell", + content: contentNodeToInlineContent( cellNode.firstChild!, inlineContentSchema, styleSchema - ) - ); + ), + props: { + colspan: cellNode.attrs.colspan, + rowspan: cellNode.attrs.rowspan, + backgroundColor: cellNode.attrs.backgroundColor, + textColor: cellNode.attrs.textColor, + textAlignment: cellNode.attrs.textAlignment, + }, + } satisfies TableCell; }); ret.rows.push(row); diff --git a/packages/core/src/api/testUtil/partialBlockTestUtil.ts b/packages/core/src/api/testUtil/partialBlockTestUtil.ts index e6e8802cc1..ecfeefece6 100644 --- a/packages/core/src/api/testUtil/partialBlockTestUtil.ts +++ b/packages/core/src/api/testUtil/partialBlockTestUtil.ts @@ -1,7 +1,12 @@ import { Block, PartialBlock } from "../../blocks/defaultBlocks.js"; import { BlockNoteSchema } from "../../editor/BlockNoteSchema.js"; import UniqueID from "../../extensions/UniqueID/UniqueID.js"; -import { BlockSchema, TableContent } from "../../schema/blocks/types.js"; +import { + BlockSchema, + PartialTableCell, + TableCell, + TableContent, +} from "../../schema/blocks/types.js"; import { InlineContent, InlineContentSchema, @@ -28,8 +33,16 @@ function textShorthandToStyledText( } function partialContentToInlineContent( - content: PartialInlineContent | TableContent | undefined -): InlineContent[] | TableContent | undefined { + content: + | PartialInlineContent + | PartialTableCell + | TableContent + | undefined +): + | InlineContent[] + | TableContent + | TableCell + | undefined { if (typeof content === "string") { return textShorthandToStyledText(content); } @@ -66,6 +79,18 @@ function partialContentToInlineContent( ), })), }; + } else if (content?.type === "tableCell") { + return { + type: "tableCell", + content: partialContentToInlineContent(content.content) as any[], + props: { + backgroundColor: content.props?.backgroundColor ?? "default", + textColor: content.props?.textColor ?? "default", + textAlignment: content.props?.textAlignment ?? "left", + colspan: content.props?.colspan ?? 1, + rowspan: content.props?.rowspan ?? 1, + }, + } satisfies TableCell; } return content; diff --git a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts index 48c7c41b24..07162ceb29 100644 --- a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts +++ b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts @@ -1,5 +1,4 @@ -import { Node } from "@tiptap/core"; -import { TableCell } from "@tiptap/extension-table-cell"; +import { TableCell as TiptapTableCell } from "@tiptap/extension-table-cell"; import { TableHeader } from "@tiptap/extension-table-header"; import { TableRow } from "@tiptap/extension-table-row"; import { Node as PMNode } from "prosemirror-model"; @@ -14,11 +13,19 @@ import { mergeCSSClasses } from "../../util/browser.js"; import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js"; import { defaultProps } from "../defaultProps.js"; import { EMPTY_CELL_WIDTH, TableExtension } from "./TableExtension.js"; +import { PropSchema } from "../../schema/index.js"; +import { NodeView } from "prosemirror-view"; export const tablePropSchema = { textColor: defaultProps.textColor, }; +export const tableCellPropSchema = { + ...defaultProps, + colspan: { default: 1 }, + rowspan: { default: 1 }, +} satisfies PropSchema; + export const TableBlockContent = createStronglyTypedTiptapNode({ name: "table", content: "tableRow+", @@ -107,7 +114,7 @@ export const TableBlockContent = createStronglyTypedTiptapNode({ }, }); -const TableParagraph = Node.create({ +const TableParagraph = createStronglyTypedTiptapNode({ name: "tableParagraph", group: "tableContent", content: "inline*", @@ -153,16 +160,20 @@ const TableParagraph = Node.create({ }, }); +export const TableCell = createBlockSpecFromStronglyTypedTiptapNode( + TableParagraph, + tableCellPropSchema +); + export const Table = createBlockSpecFromStronglyTypedTiptapNode( TableBlockContent, tablePropSchema, [ TableExtension, - TableParagraph, TableHeader.extend({ content: "tableContent", }), - TableCell.extend({ + TiptapTableCell.extend({ content: "tableContent", }), TableRow, diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index 81dc0e49ab..ddcae2d77b 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -29,7 +29,7 @@ import { BulletListItem } from "./ListItemBlockContent/BulletListItemBlockConten import { CheckListItem } from "./ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.js"; import { NumberedListItem } from "./ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.js"; import { Paragraph } from "./ParagraphBlockContent/ParagraphBlockContent.js"; -import { Table } from "./TableBlockContent/TableBlockContent.js"; +import { Table, TableCell } from "./TableBlockContent/TableBlockContent.js"; import { VideoBlock } from "./VideoBlockContent/VideoBlockContent.js"; export { customizeCodeBlock } from "./CodeBlockContent/CodeBlockContent.js"; @@ -42,6 +42,7 @@ export const defaultBlockSpecs = { numberedListItem: NumberedListItem, checkListItem: CheckListItem, table: Table, + tableCell: TableCell, file: FileBlock, image: ImageBlock, video: VideoBlock, diff --git a/packages/core/src/extensions/BackgroundColor/BackgroundColorExtension.ts b/packages/core/src/extensions/BackgroundColor/BackgroundColorExtension.ts index 63ca193e65..e9dfe16818 100644 --- a/packages/core/src/extensions/BackgroundColor/BackgroundColorExtension.ts +++ b/packages/core/src/extensions/BackgroundColor/BackgroundColorExtension.ts @@ -7,7 +7,7 @@ export const BackgroundColorExtension = Extension.create({ addGlobalAttributes() { return [ { - types: ["blockContainer"], + types: ["blockContainer", "tableCell"], attributes: { backgroundColor: { default: defaultProps.backgroundColor.default, diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 135c5a67d5..5143be5e50 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -455,7 +455,7 @@ export class TableHandlesView< ); rows.forEach((row, rowIndex) => { row.cells.splice(draggingState.originalIndex, 1); - row.cells.splice(colIndex, 0, cellsToMove[rowIndex]); + row.cells.splice(colIndex, 0, cellsToMove[rowIndex] as any); }); } diff --git a/packages/core/src/extensions/TextAlignment/TextAlignmentExtension.ts b/packages/core/src/extensions/TextAlignment/TextAlignmentExtension.ts index 358ddd0d54..dc894053ef 100644 --- a/packages/core/src/extensions/TextAlignment/TextAlignmentExtension.ts +++ b/packages/core/src/extensions/TextAlignment/TextAlignmentExtension.ts @@ -14,6 +14,7 @@ export const TextAlignmentExtension = Extension.create({ "bulletListItem", "numberedListItem", "checkListItem", + "tableParagraph", ], attributes: { textAlignment: { diff --git a/packages/core/src/extensions/TextColor/TextColorExtension.ts b/packages/core/src/extensions/TextColor/TextColorExtension.ts index a2f4637cf8..1b949f20e1 100644 --- a/packages/core/src/extensions/TextColor/TextColorExtension.ts +++ b/packages/core/src/extensions/TextColor/TextColorExtension.ts @@ -7,7 +7,7 @@ export const TextColorExtension = Extension.create({ addGlobalAttributes() { return [ { - types: ["blockContainer"], + types: ["blockContainer", "tableCell"], attributes: { textColor: { default: defaultProps.textColor.default, diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index 082ccc4469..967a58f5da 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -144,14 +144,33 @@ export type BlockSchemaWithBlock< [k in BType]: C; }; +export type TableItemProps = { + backgroundColor: string; + textColor: string; + textAlignment: "left" | "center" | "right" | "justify"; + colspan?: number; + rowspan?: number; +}; + +export type TableCell< + I extends InlineContentSchema, + S extends StyleSchema = StyleSchema +> = { + type: "tableCell"; + props: TableItemProps; + content: InlineContent[]; +}; + export type TableContent< I extends InlineContentSchema, S extends StyleSchema = StyleSchema > = { type: "tableContent"; columnWidths: (number | undefined)[]; + headerRows?: number; + headerCols?: number; rows: { - cells: InlineContent[][]; + cells: InlineContent[][] | TableCell[]; }[]; }; @@ -220,14 +239,25 @@ export type SpecificBlock< * */ +export type PartialTableCell< + I extends InlineContentSchema, + S extends StyleSchema = StyleSchema +> = { + type: "tableCell"; + props?: Partial; + content?: PartialInlineContent; +}; + export type PartialTableContent< I extends InlineContentSchema, S extends StyleSchema = StyleSchema > = { type: "tableContent"; columnWidths?: (number | undefined)[]; + headerRows?: number; + headerCols?: number; rows: { - cells: PartialInlineContent[]; + cells: PartialInlineContent[] | PartialTableCell[]; }[]; }; @@ -289,3 +319,23 @@ export type PartialBlockFromConfig< }; export type BlockIdentifier = { id: string } | string; + +export function isPartialTableCell( + content: PartialInlineContent | PartialTableCell +): content is PartialTableCell { + return ( + typeof content !== "string" && + !Array.isArray(content) && + content.type === "tableCell" + ); +} + +export function isTableCell( + content: InlineContent[] | TableCell +): content is TableCell { + return ( + isPartialTableCell(content) && + content.props !== undefined && + content.content !== undefined + ); +} diff --git a/packages/react/src/components/TableHandles/ExtendButton/ExtendButton.tsx b/packages/react/src/components/TableHandles/ExtendButton/ExtendButton.tsx index 60aaaeabe4..b2b63a2baa 100644 --- a/packages/react/src/components/TableHandles/ExtendButton/ExtendButton.tsx +++ b/packages/react/src/components/TableHandles/ExtendButton/ExtendButton.tsx @@ -4,6 +4,7 @@ import { EMPTY_CELL_HEIGHT, EMPTY_CELL_WIDTH, InlineContentSchema, + isPartialTableCell, mergeCSSClasses, PartialTableContent, StyleSchema, @@ -21,6 +22,16 @@ import { RiAddFill } from "react-icons/ri"; import { useComponentsContext } from "../../../editor/ComponentsContext.js"; import { ExtendButtonProps } from "./ExtendButtonProps.js"; +function isCellEmpty( + cell: PartialTableContent["rows"][number]["cells"][number] +): boolean { + if (isPartialTableCell(cell)) { + return (cell.content?.length ?? 0) === 0; + } else { + return cell.length === 0; + } +} + function cropEmptyRowsOrColumns< I extends InlineContentSchema, S extends StyleSchema @@ -33,7 +44,7 @@ function cropEmptyRowsOrColumns< if (removeEmpty === "columns") { // strips empty columns to the right and empty rows at the bottom for (let i = content.rows[0].cells.length - 1; i >= 0; i--) { - const isEmpty = content.rows.every((row) => row.cells[i].length === 0); + const isEmpty = content.rows.every((row) => isCellEmpty(row.cells[i])); if (!isEmpty) { break; } @@ -47,7 +58,7 @@ function cropEmptyRowsOrColumns< if (removeEmpty === "rows") { if ( rows.length === 0 && - content.rows[i].cells.every((cell) => cell.length === 0) + content.rows[i].cells.every((cell) => isCellEmpty(cell)) ) { // empty row at bottom continue; @@ -116,7 +127,8 @@ const getContentWithAddedCols = < []; const newCells: PartialTableContent["rows"][number]["cells"] = []; for (let i = 0; i < colsToAdd; i++) { - newCells.push(newCell); + // TODO: fix this + newCells.push(newCell as any); } return { @@ -125,7 +137,8 @@ const getContentWithAddedCols = < ? [...content.columnWidths, ...newCells.map(() => undefined)] : undefined, rows: content.rows.map((row) => ({ - cells: [...row.cells, ...newCells], + // TODO: fix this + cells: [...row.cells, ...newCells] as any[], })), }; }; diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx index e90289647f..d4dc3704e9 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx @@ -2,9 +2,11 @@ import { DefaultBlockSchema, DefaultInlineContentSchema, DefaultStyleSchema, + InlineContentFromConfig, InlineContentSchema, PartialTableContent, StyleSchema, + TableCell, } from "@blocknote/core"; import { useComponentsContext } from "../../../../editor/ComponentsContext.js"; @@ -84,8 +86,14 @@ export const AddColumnButton = < type: "tableContent", columnWidths, rows: props.block.content.rows.map((row) => { - const cells = [...row.cells]; - cells.splice(props.index + (props.side === "right" ? 1 : 0), 0, []); + const cells = [...row.cells] as + | InlineContentFromConfig[] + | TableCell[]; + cells.splice( + props.index + (props.side === "right" ? 1 : 0), + 0, + [] as any + ); return { cells }; }), }; diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/DeleteButton.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/DeleteButton.tsx index dc71823af8..955b9c9aab 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/DeleteButton.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/DeleteButton.tsx @@ -2,9 +2,11 @@ import { DefaultBlockSchema, DefaultInlineContentSchema, DefaultStyleSchema, + InlineContentFromConfig, InlineContentSchema, PartialTableContent, StyleSchema, + TableCell, } from "@blocknote/core"; import { useComponentsContext } from "../../../../editor/ComponentsContext.js"; @@ -75,7 +77,9 @@ export const DeleteColumnButton = < (_, index) => index !== props.index ), rows: props.block.content.rows.map((row) => ({ - cells: row.cells.filter((_, index) => index !== props.index), + cells: row.cells.filter((_, index) => index !== props.index) as + | InlineContentFromConfig[] + | TableCell[], })), }; diff --git a/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts b/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts index 1b457f3c35..854eb1b8f6 100644 --- a/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts +++ b/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts @@ -193,6 +193,10 @@ export const docxBlockMappingForDefaultSchema: BlockMapping< table: (block, exporter) => { return Table(block.content, exporter); }, + tableCell: () => { + throw new Error("tableCell is not supported"); + // return TableCell(block.content, exporter); + }, }; function file( diff --git a/packages/xl-docx-exporter/src/docx/util/Table.tsx b/packages/xl-docx-exporter/src/docx/util/Table.tsx index 6ff4f2069b..71db4f24af 100644 --- a/packages/xl-docx-exporter/src/docx/util/Table.tsx +++ b/packages/xl-docx-exporter/src/docx/util/Table.tsx @@ -32,7 +32,8 @@ export const Table = ( : undefined, children: [ new Paragraph({ - children: t.transformInlineContent(cell), + // TODO: fix this + children: t.transformInlineContent(cell as any), }), ], }); diff --git a/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx b/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx index 9e75c2d273..cf31709834 100644 --- a/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx +++ b/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx @@ -165,6 +165,9 @@ export const pdfBlockMappingForDefaultSchema: BlockMapping< table: (block, t) => { return ; }, + tableCell: () => { + throw new Error("tableCell is not supported"); + }, }; function file( diff --git a/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx b/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx index 5670fdfd31..6072cf5e82 100644 --- a/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx +++ b/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx @@ -67,7 +67,8 @@ export const Table = (props: { : { flex: 1 }, ]} key={index}> - {props.transformer.transformInlineContent(cell)} + {/* TODO: fix this */} + {props.transformer.transformInlineContent(cell as any)} ))} diff --git a/vitest.workspace.ts b/vitest.workspace.ts new file mode 100644 index 0000000000..4c3c4592c1 --- /dev/null +++ b/vitest.workspace.ts @@ -0,0 +1,13 @@ +import { defineWorkspace } from "vitest/config"; + +export default defineWorkspace([ + "./packages/xl-docx-exporter/vite.config.ts", + "./packages/react/vite.config.ts", + "./packages/shadcn/vite.config.ts", + "./packages/server-util/vite.config.ts", + "./packages/xl-pdf-exporter/vite.config.ts", + "./packages/mantine/vite.config.ts", + "./packages/core/vite.config.ts", + "./packages/xl-multi-column/vite.config.ts", + "./packages/ariakit/vite.config.ts", +]); From 9c7fa179b8bf634cc64ba9aae524624c21456b33 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 12 Feb 2025 15:15:14 +0100 Subject: [PATCH 02/72] fix: resolve dragging --- packages/core/src/api/clipboard/toClipboard/copyExtension.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts index ab70211bdd..30d57bd66f 100644 --- a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts +++ b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts @@ -2,9 +2,8 @@ import { Extension } from "@tiptap/core"; import { Fragment, Node } from "prosemirror-model"; import { NodeSelection, Plugin } from "prosemirror-state"; import { CellSelection } from "prosemirror-tables"; -import * as pmView from "prosemirror-view"; +import type { EditorView } from "prosemirror-view"; -import { EditorView } from "prosemirror-view"; import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; import { BlockSchema, @@ -24,7 +23,7 @@ function fragmentToExternalHTML< I extends InlineContentSchema, S extends StyleSchema >( - view: pmView.EditorView, + view: EditorView, selectedFragment: Fragment, editor: BlockNoteEditor ) { From 21b4de1188c002df57055f5fe704680218e0da55 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 12 Feb 2025 16:11:38 +0100 Subject: [PATCH 03/72] feat: getting somewhere --- .../__snapshots__/insertBlocks.test.ts.snap | 1296 +++-- .../__snapshots__/mergeBlocks.test.ts.snap | 990 ++-- .../__snapshots__/moveBlocks.test.ts.snap | 4320 ++++++++++++----- .../__snapshots__/removeBlocks.test.ts.snap | 594 ++- .../__snapshots__/replaceBlocks.test.ts.snap | 1584 ++++-- .../__snapshots__/splitBlock.test.ts.snap | 1296 +++-- .../__snapshots__/updateBlock.test.ts.snap | 3468 +++++++++---- .../commands/updateBlock/updateBlock.test.ts | 29 +- .../__snapshots__/selection.test.ts.snap | 396 +- .../src/api/blockManipulation/setupTestEnv.ts | 15 +- .../external/pasteEndOfParagraph.html | 88 +- .../external/pasteEndOfParagraphText.html | 88 +- .../__snapshots__/external/pasteImage.html | 88 +- .../external/pasteParagraphInCustomBlock.html | 88 +- .../__snapshots__/external/pasteTable.html | 176 +- .../external/pasteTableInExistingTable.html | 178 +- .../table/mixedCellColors/external.html | 1 + .../table/mixedCellColors/internal.html | 1 + .../table/mixedCellColors/markdown.md | 5 + .../nodeConversions.test.ts.snap | 324 ++ .../src/api/nodeConversions/blockToNode.ts | 19 +- .../html/__snapshots__/parse-notion-html.json | 189 +- .../src/api/testUtil/cases/defaultSchema.ts | 449 +- .../TextAlignment/TextAlignmentExtension.ts | 2 +- .../DefaultButtons/TextAlignButton.tsx | 6 +- 25 files changed, 11415 insertions(+), 4275 deletions(-) create mode 100644 packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html create mode 100644 packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html create mode 100644 packages/core/src/api/exporters/markdown/__snapshots__/table/mixedCellColors/markdown.md diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap index 144e780f5d..cedc597ced 100644 --- a/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap @@ -317,77 +317,167 @@ exports[`Test insertBlocks > Insert multiple blocks after 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -843,77 +933,167 @@ exports[`Test insertBlocks > Insert multiple blocks before 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -1329,77 +1509,167 @@ exports[`Test insertBlocks > Insert single basic block after 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -1815,77 +2085,167 @@ exports[`Test insertBlocks > Insert single basic block before 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -2358,77 +2718,167 @@ exports[`Test insertBlocks > Insert single complex block after 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -2901,77 +3351,167 @@ exports[`Test insertBlocks > Insert single complex block before 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap index c3a3a512fd..e208fff975 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap @@ -249,77 +249,167 @@ exports[`Test mergeBlocks > Basic 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -707,77 +797,167 @@ exports[`Test mergeBlocks > Blocks have different types 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -1165,77 +1345,167 @@ exports[`Test mergeBlocks > First block has children 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -1622,77 +1892,167 @@ exports[`Test mergeBlocks > Second block has children 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -2097,77 +2457,167 @@ exports[`Test mergeBlocks > Second block is empty 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap index a190ac024b..a70ab4e070 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap @@ -266,77 +266,167 @@ exports[`Test moveBlocksDown > Basic 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -741,77 +831,167 @@ exports[`Test moveBlocksDown > Into children 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -1216,77 +1396,167 @@ exports[`Test moveBlocksDown > Last block 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -1691,77 +1961,167 @@ exports[`Test moveBlocksDown > Multiple blocks 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -2166,77 +2526,167 @@ exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`] "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -2641,77 +3091,167 @@ exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -3126,77 +3666,167 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -3601,77 +4231,167 @@ exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1 "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -4075,77 +4795,167 @@ exports[`Test moveBlocksDown > Multiple blocks starting in nested block 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -4550,77 +5360,167 @@ exports[`Test moveBlocksDown > Out of children 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -5024,77 +5924,167 @@ exports[`Test moveBlocksUp > Basic 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -5499,77 +6489,167 @@ exports[`Test moveBlocksUp > First block 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -5974,77 +7054,167 @@ exports[`Test moveBlocksUp > Into children 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -6449,77 +7619,167 @@ exports[`Test moveBlocksUp > Multiple blocks 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -6924,77 +8184,167 @@ exports[`Test moveBlocksUp > Multiple blocks ending in block with children 1`] = "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -7399,77 +8749,167 @@ exports[`Test moveBlocksUp > Multiple blocks ending in nested block 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -7856,77 +9296,167 @@ exports[`Test moveBlocksUp > Multiple blocks starting and ending in nested block "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -8348,77 +9878,167 @@ exports[`Test moveBlocksUp > Multiple blocks starting in block with children 1`] "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -8822,77 +10442,167 @@ exports[`Test moveBlocksUp > Multiple blocks starting in nested block 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -9297,77 +11007,167 @@ exports[`Test moveBlocksUp > Out of children 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap index bf74b07893..2b677b1e23 100644 --- a/packages/core/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap @@ -230,77 +230,167 @@ exports[`Test removeBlocks > Remove all child blocks 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -618,77 +708,167 @@ exports[`Test removeBlocks > Remove multiple consecutive blocks 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -1373,77 +1553,167 @@ exports[`Test removeBlocks > Remove single block 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap index d2a59a02a8..e01f7bb5c3 100644 --- a/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap @@ -179,77 +179,167 @@ exports[`Test replaceBlocks > Remove multiple consecutive blocks 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -934,77 +1024,167 @@ exports[`Test replaceBlocks > Remove single block 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -1373,77 +1553,167 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with multiple "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -1772,77 +2042,167 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single ba "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -2228,77 +2588,167 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single co "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -3758,77 +4208,167 @@ exports[`Test replaceBlocks > Replace single block with multiple 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -4227,77 +4767,167 @@ exports[`Test replaceBlocks > Replace single block with single basic 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -4753,77 +5383,167 @@ exports[`Test replaceBlocks > Replace single block with single complex 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap index 3a4217b2e9..c92733c5f7 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap @@ -283,77 +283,167 @@ exports[`Test splitBlocks > Basic 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -775,77 +865,167 @@ exports[`Test splitBlocks > Block has children 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -1267,77 +1447,167 @@ exports[`Test splitBlocks > Don't keep props 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -1759,77 +2029,167 @@ exports[`Test splitBlocks > Don't keep type 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -2245,77 +2605,167 @@ exports[`Test splitBlocks > End of content 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -2738,77 +3188,167 @@ exports[`Test splitBlocks > Keep type 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap index d85aed11c5..7e20863ec9 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap @@ -266,77 +266,167 @@ exports[`Test updateBlock > Revert all props 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -741,77 +831,167 @@ exports[`Test updateBlock > Revert single prop 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -1216,77 +1396,167 @@ exports[`Test updateBlock > Update all props 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -1691,77 +1961,167 @@ exports[`Test updateBlock > Update children 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -2164,77 +2524,167 @@ exports[`Test updateBlock > Update inline content to no content 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -2386,77 +2836,167 @@ exports[`Test updateBlock > Update inline content to table content 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -2715,77 +3255,167 @@ exports[`Test updateBlock > Update inline content to table content 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -3186,77 +3816,167 @@ exports[`Test updateBlock > Update no content to empty inline content 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -3620,16 +4340,14 @@ exports[`Test updateBlock > Update no content to empty table content 1`] = ` }, { "children": [], - "content": { - "columnWidths": [], - "rows": [], - "type": "tableContent", - }, + "content": [], "id": "image-0", "props": { + "backgroundColor": "default", + "textAlignment": "left", "textColor": "default", }, - "type": "table", + "type": "paragraph", }, { "children": [], @@ -3659,77 +4377,167 @@ exports[`Test updateBlock > Update no content to empty table content 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -4136,77 +4944,167 @@ exports[`Test updateBlock > Update no content to inline content 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -4579,77 +5477,167 @@ exports[`Test updateBlock > Update no content to table content 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "red", + "colspan": 1, + "rowspan": 1, + "textAlignment": "right", + "textColor": "red", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -4688,78 +5676,168 @@ exports[`Test updateBlock > Update no content to table content 1`] = ` ], "rows": [ { - "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -5164,77 +6242,167 @@ exports[`Test updateBlock > Update single prop 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -6828,77 +7996,167 @@ exports[`Test updateBlock > Update type 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -7302,77 +8560,167 @@ exports[`Test updateBlock > Update with plain content 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], @@ -7763,77 +9111,167 @@ exports[`Test updateBlock > Update with styled content 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "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", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts index c4ec545b14..dc0e91bb6d 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts @@ -275,7 +275,34 @@ describe("Test updateBlock", () => { type: "tableContent", rows: [ { - cells: ["Cell 1", "Cell 2", "Cell 3"], + cells: [ + { + type: "tableCell", + content: ["Cell 1"], + }, + { + type: "tableCell", + content: ["Cell 2"], + props: { + backgroundColor: "red", + colspan: 1, + rowspan: 1, + textAlignment: "right", + textColor: "red", + }, + }, + { + type: "tableCell", + content: ["Cell 3"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], }, { cells: ["Cell 4", "Cell 5", "Cell 6"], diff --git a/packages/core/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap b/packages/core/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap index b0fd1378ad..d62828d09b 100644 --- a/packages/core/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap +++ b/packages/core/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap @@ -320,77 +320,167 @@ exports[`Test getSelection & setSelection > Ends in table 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -556,77 +646,167 @@ exports[`Test getSelection & setSelection > Starts in table 1`] = ` "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/blockManipulation/setupTestEnv.ts b/packages/core/src/api/blockManipulation/setupTestEnv.ts index 3182816494..537847a810 100644 --- a/packages/core/src/api/blockManipulation/setupTestEnv.ts +++ b/packages/core/src/api/blockManipulation/setupTestEnv.ts @@ -117,7 +117,20 @@ const testDocument: PartialBlock[] = [ type: "tableContent", rows: [ { - cells: ["Cell 1", "Cell 2", "Cell 3"], + cells: [ + { + type: "tableCell", + content: ["Cell 1"], + }, + { + type: "tableCell", + content: ["Cell 2"], + }, + { + type: "tableCell", + content: ["Cell 3"], + }, + ], }, { cells: ["Cell 4", "Cell 5", "Cell 6"], diff --git a/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraph.html b/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraph.html index 326207757a..77b6ae34de 100644 --- a/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraph.html +++ b/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraph.html @@ -26,38 +26,78 @@ "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraphText.html b/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraphText.html index 326207757a..77b6ae34de 100644 --- a/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraphText.html +++ b/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraphText.html @@ -26,38 +26,78 @@ "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/clipboard/__snapshots__/external/pasteImage.html b/packages/core/src/api/clipboard/__snapshots__/external/pasteImage.html index 5bf0ff1271..f66cb46c43 100644 --- a/packages/core/src/api/clipboard/__snapshots__/external/pasteImage.html +++ b/packages/core/src/api/clipboard/__snapshots__/external/pasteImage.html @@ -41,38 +41,78 @@ "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/clipboard/__snapshots__/external/pasteParagraphInCustomBlock.html b/packages/core/src/api/clipboard/__snapshots__/external/pasteParagraphInCustomBlock.html index 8b979aa417..e3f80b520b 100644 --- a/packages/core/src/api/clipboard/__snapshots__/external/pasteParagraphInCustomBlock.html +++ b/packages/core/src/api/clipboard/__snapshots__/external/pasteParagraphInCustomBlock.html @@ -26,38 +26,78 @@ "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/clipboard/__snapshots__/external/pasteTable.html b/packages/core/src/api/clipboard/__snapshots__/external/pasteTable.html index 4e9713621a..9816c0fedd 100644 --- a/packages/core/src/api/clipboard/__snapshots__/external/pasteTable.html +++ b/packages/core/src/api/clipboard/__snapshots__/external/pasteTable.html @@ -26,38 +26,78 @@ "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], @@ -79,38 +119,78 @@ "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/clipboard/__snapshots__/external/pasteTableInExistingTable.html b/packages/core/src/api/clipboard/__snapshots__/external/pasteTableInExistingTable.html index 2e6fbce5d2..bd9d13231b 100644 --- a/packages/core/src/api/clipboard/__snapshots__/external/pasteTableInExistingTable.html +++ b/packages/core/src/api/clipboard/__snapshots__/external/pasteTableInExistingTable.html @@ -27,65 +27,155 @@ "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [], + "type": "tableCell", + }, + { + "content": [], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Table Cell", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + { + "content": [], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html b/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html new file mode 100644 index 0000000000..780396996c --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html @@ -0,0 +1 @@ +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html b/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html new file mode 100644 index 0000000000..10e00b6852 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html @@ -0,0 +1 @@ +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/table/mixedCellColors/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/table/mixedCellColors/markdown.md new file mode 100644 index 0000000000..3e52272fe0 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/table/mixedCellColors/markdown.md @@ -0,0 +1,5 @@ +| | | | +| ---------- | ---------- | ---------- | +| Table Cell | Table Cell | Table Cell | +| Table Cell | Table Cell | Table Cell | +| Table Cell | Table Cell | Table Cell | diff --git a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap index aa439ea041..614231e3b6 100644 --- a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +++ b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap @@ -1879,11 +1879,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "content": [ { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 100, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -1900,11 +1903,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 200, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -1921,11 +1927,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 300, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -1947,11 +1956,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "content": [ { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 100, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -1968,11 +1980,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 200, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -1989,11 +2004,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 300, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2015,11 +2033,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "content": [ { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 100, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2036,11 +2057,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 200, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2057,11 +2081,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 300, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2101,9 +2128,12 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "content": [ { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": null, "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2120,9 +2150,12 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": null, "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2139,9 +2172,12 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": null, "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2163,9 +2199,12 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "content": [ { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": null, "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2182,9 +2221,12 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": null, "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2201,9 +2243,12 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": null, "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2225,9 +2270,12 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "content": [ { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": null, "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2244,9 +2292,12 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": null, "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2263,9 +2314,255 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": null, "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", +} +`; + +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert table/mixedCellColors to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "red", + "colspan": 1, + "colwidth": [ + 100, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "blue", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "blue", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "yellow", + "colspan": 1, + "colwidth": [ + 300, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "red", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 100, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 300, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 100, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 300, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2305,11 +2602,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "content": [ { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 100, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2326,9 +2626,12 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": null, "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2345,11 +2648,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 300, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2371,11 +2677,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "content": [ { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 100, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2392,9 +2701,12 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": null, "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2411,11 +2723,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 300, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2437,11 +2752,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "content": [ { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 100, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2458,9 +2776,12 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": null, "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { @@ -2477,11 +2798,14 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert }, { "attrs": { + "backgroundColor": "default", "colspan": 1, "colwidth": [ 300, ], "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, "content": [ { diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 02135b6e8b..c0fc7d635c 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -190,8 +190,6 @@ export function tableContentToNodes< } else if (isPartialTableCell(cell)) { if (cell.content) { content = inlineContentToNodes(cell.content, schema, styleSchema); - } else if (cell.props) { - attrs = cell.props; } } else { content = inlineContentToNodes(cell, schema, styleSchema); @@ -206,22 +204,9 @@ export function tableContentToNodes< colwidth: tableContent.columnWidths?.[i] ? [tableContent.columnWidths[i]] : null, - ...(isPartialTableCell(cell) && cell.props - ? { - rowspan: cell.props.rowspan, - colspan: cell.props.colspan, - textColor: cell.props.textColor, - } - : {}), + ...(isPartialTableCell(cell) ? cell.props : {}), }, - schema.nodes["tableParagraph"].createChecked(attrs, content, marks), - [ - isPartialTableCell(cell) && cell.props?.backgroundColor - ? schema.marks["backgroundColor"].create({ - stringValue: cell.props.backgroundColor, - }) - : false, - ].filter((a): a is Mark => Boolean(a)) + schema.nodes["tableParagraph"].createChecked(attrs, content, marks) ); columnNodes.push(cellNode); } diff --git a/packages/core/src/api/parsers/html/__snapshots__/parse-notion-html.json b/packages/core/src/api/parsers/html/__snapshots__/parse-notion-html.json index 62f9034427..d99f504380 100644 --- a/packages/core/src/api/parsers/html/__snapshots__/parse-notion-html.json +++ b/packages/core/src/api/parsers/html/__snapshots__/parse-notion-html.json @@ -377,77 +377,158 @@ "rows": [ { "cells": [ - [ - { - "type": "text", - "text": "Cell 1", - "styles": {} + { + "type": "tableCell", + "content": [ + { + "type": "text", + "text": "Cell 1", + "styles": {} + } + ], + "props": { + "colspan": 1, + "rowspan": 1 } - ], - [ - { - "type": "text", - "text": "Cell 2", - "styles": {} + }, + { + "type": "tableCell", + "content": [ + { + "type": "text", + "text": "Cell 2", + "styles": {} + } + ], + "props": { + "colspan": 1, + "rowspan": 1 } - ], - [ - { - "type": "text", - "text": "Cell 3", - "styles": {} + }, + { + "type": "tableCell", + "content": [ + { + "type": "text", + "text": "Cell 3", + "styles": {} + } + ], + "props": { + "colspan": 1, + "rowspan": 1 } - ] + } ] }, { "cells": [ - [ - { - "type": "text", - "text": "Cell 4", - "styles": {} + { + "type": "tableCell", + "content": [ + { + "type": "text", + "text": "Cell 4", + "styles": {} + } + ], + "props": { + "colspan": 1, + "rowspan": 1, + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" } - ], - [ - { - "type": "text", - "text": "Cell 5", - "styles": {} + }, + { + "type": "tableCell", + "content": [ + { + "type": "text", + "text": "Cell 5", + "styles": {} + } + ], + "props": { + "colspan": 1, + "rowspan": 1, + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" } - ], - [ - { - "type": "text", - "text": "Cell 6", - "styles": {} + }, + { + "type": "tableCell", + "content": [ + { + "type": "text", + "text": "Cell 6", + "styles": {} + } + ], + "props": { + "colspan": 1, + "rowspan": 1, + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" } - ] + } ] }, { "cells": [ - [ - { - "type": "text", - "text": "Cell 7", - "styles": {} + { + "type": "tableCell", + "content": [ + { + "type": "text", + "text": "Cell 7", + "styles": {} + } + ], + "props": { + "colspan": 1, + "rowspan": 1, + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" } - ], - [ - { - "type": "text", - "text": "Cell 8", - "styles": {} + }, + { + "type": "tableCell", + "content": [ + { + "type": "text", + "text": "Cell 8", + "styles": {} + } + ], + "props": { + "colspan": 1, + "rowspan": 1, + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" } - ], - [ - { - "type": "text", - "text": "Cell 9", - "styles": {} + }, + { + "type": "tableCell", + "content": [ + { + "type": "text", + "text": "Cell 9", + "styles": {} + } + ], + "props": { + "colspan": 1, + "rowspan": 1, + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" } - ] + } ] } ] diff --git a/packages/core/src/api/testUtil/cases/defaultSchema.ts b/packages/core/src/api/testUtil/cases/defaultSchema.ts index b533d62157..72ca3b7b9b 100644 --- a/packages/core/src/api/testUtil/cases/defaultSchema.ts +++ b/packages/core/src/api/testUtil/cases/defaultSchema.ts @@ -391,13 +391,115 @@ export const defaultSchemaTestCases: EditorTestCases< type: "tableContent", rows: [ { - cells: ["Table Cell", "Table Cell", "Table Cell"], + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], }, { - cells: ["Table Cell", "Table Cell", "Table Cell"], + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], }, { - cells: ["Table Cell", "Table Cell", "Table Cell"], + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], }, ], }, @@ -414,13 +516,115 @@ export const defaultSchemaTestCases: EditorTestCases< columnWidths: [100, 200, 300], rows: [ { - cells: ["Table Cell", "Table Cell", "Table Cell"], + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], }, { - cells: ["Table Cell", "Table Cell", "Table Cell"], + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], }, { - cells: ["Table Cell", "Table Cell", "Table Cell"], + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], }, ], }, @@ -437,13 +641,240 @@ export const defaultSchemaTestCases: EditorTestCases< columnWidths: [100, undefined, 300], rows: [ { - cells: ["Table Cell", "Table Cell", "Table Cell"], + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], }, { - cells: ["Table Cell", "Table Cell", "Table Cell"], + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], }, { - cells: ["Table Cell", "Table Cell", "Table Cell"], + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + ], + }, + }, + ], + }, + { + name: "table/mixedCellColors", + blocks: [ + { + type: "table", + content: { + type: "tableContent", + columnWidths: [100, undefined, 300], + rows: [ + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "red", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "blue", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "blue", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "yellow", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "red", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], }, ], }, diff --git a/packages/core/src/extensions/TextAlignment/TextAlignmentExtension.ts b/packages/core/src/extensions/TextAlignment/TextAlignmentExtension.ts index dc894053ef..814b040322 100644 --- a/packages/core/src/extensions/TextAlignment/TextAlignmentExtension.ts +++ b/packages/core/src/extensions/TextAlignment/TextAlignmentExtension.ts @@ -14,7 +14,7 @@ export const TextAlignmentExtension = Extension.create({ "bulletListItem", "numberedListItem", "checkListItem", - "tableParagraph", + "tableCell", ], attributes: { textAlignment: { diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx index b946dace16..420154e38e 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx @@ -67,7 +67,11 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { ); const show = useMemo(() => { - return !!selectedBlocks.find((block) => "textAlignment" in block.props); + return !!selectedBlocks.find( + (block) => + "textAlignment" in block.props || + (block.type === "table" && block.children) + ); }, [selectedBlocks]); if (!show || !editor.isEditable) { From 4c55225d3f7c8361116970f18f3530ab85133bf4 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 13 Feb 2025 09:27:54 +0100 Subject: [PATCH 04/72] fix: remove tableCell from blocks --- .../core/src/blocks/TableBlockContent/TableBlockContent.ts | 1 - packages/core/src/blocks/defaultBlocks.ts | 3 +-- packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts | 4 ---- packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx | 3 --- 4 files changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts index 07162ceb29..6e99b3cc01 100644 --- a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts +++ b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts @@ -14,7 +14,6 @@ import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js"; import { defaultProps } from "../defaultProps.js"; import { EMPTY_CELL_WIDTH, TableExtension } from "./TableExtension.js"; import { PropSchema } from "../../schema/index.js"; -import { NodeView } from "prosemirror-view"; export const tablePropSchema = { textColor: defaultProps.textColor, diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index ddcae2d77b..81dc0e49ab 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -29,7 +29,7 @@ import { BulletListItem } from "./ListItemBlockContent/BulletListItemBlockConten import { CheckListItem } from "./ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.js"; import { NumberedListItem } from "./ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.js"; import { Paragraph } from "./ParagraphBlockContent/ParagraphBlockContent.js"; -import { Table, TableCell } from "./TableBlockContent/TableBlockContent.js"; +import { Table } from "./TableBlockContent/TableBlockContent.js"; import { VideoBlock } from "./VideoBlockContent/VideoBlockContent.js"; export { customizeCodeBlock } from "./CodeBlockContent/CodeBlockContent.js"; @@ -42,7 +42,6 @@ export const defaultBlockSpecs = { numberedListItem: NumberedListItem, checkListItem: CheckListItem, table: Table, - tableCell: TableCell, file: FileBlock, image: ImageBlock, video: VideoBlock, diff --git a/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts b/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts index 854eb1b8f6..1b457f3c35 100644 --- a/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts +++ b/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts @@ -193,10 +193,6 @@ export const docxBlockMappingForDefaultSchema: BlockMapping< table: (block, exporter) => { return Table(block.content, exporter); }, - tableCell: () => { - throw new Error("tableCell is not supported"); - // return TableCell(block.content, exporter); - }, }; function file( diff --git a/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx b/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx index cf31709834..9e75c2d273 100644 --- a/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx +++ b/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx @@ -165,9 +165,6 @@ export const pdfBlockMappingForDefaultSchema: BlockMapping< table: (block, t) => { return ; }, - tableCell: () => { - throw new Error("tableCell is not supported"); - }, }; function file( From 0ae7cf8fbb6f32fd835b441eafe193b03eb55edd Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 13 Feb 2025 11:52:39 +0100 Subject: [PATCH 05/72] fix: use table-cell --- .../core/src/blocks/TableBlockContent/TableBlockContent.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts index 6e99b3cc01..0a039de0d3 100644 --- a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts +++ b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts @@ -159,16 +159,12 @@ const TableParagraph = createStronglyTypedTiptapNode({ }, }); -export const TableCell = createBlockSpecFromStronglyTypedTiptapNode( - TableParagraph, - tableCellPropSchema -); - export const Table = createBlockSpecFromStronglyTypedTiptapNode( TableBlockContent, tablePropSchema, [ TableExtension, + TableParagraph, TableHeader.extend({ content: "tableContent", }), From cb36ad331db4680ec692cf317677413277a8fcfa Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Mon, 17 Feb 2025 16:56:33 +0100 Subject: [PATCH 06/72] feat: support for merging & splitting cells --- .../blockManipulation/tables/tables.test.ts | 1065 +++++++++++++++++ .../api/blockManipulation/tables/tables.ts | 219 ++++ .../TableBlockContent/TableBlockContent.ts | 4 +- .../core/src/blocks/defaultBlockTypeGuards.ts | 6 + packages/core/src/editor/BlockNoteEditor.ts | 16 +- .../TableHandles/TableHandlesPlugin.ts | 109 +- packages/core/src/i18n/locales/en.ts | 6 + packages/core/src/schema/blocks/types.ts | 68 +- packages/mantine/src/style.css | 3 +- .../DefaultButtons/CreateLinkButton.tsx | 7 +- .../DefaultButtons/TableCellMergeButton.tsx | 129 ++ .../DefaultButtons/TextAlignButton.tsx | 13 +- .../FormattingToolbar/FormattingToolbar.tsx | 2 + .../TableHandles/TableCellHandle.tsx | 57 + .../DefaultButtons/ColorPicker.tsx | 129 ++ .../DefaultButtons/SplitButton.tsx | 39 + .../TableCellHandleMenu.tsx | 41 + .../TableCellHandleMenuProps.ts | 17 + .../TableHandles/TableCellHandleProps.ts | 41 + .../DefaultButtons/ColorPicker.tsx | 132 ++ .../TableHandleMenu/TableHandleMenu.tsx | 6 + .../TableHandles/TableHandleProps.ts | 7 +- .../TableHandles/TableHandlesController.tsx | 23 +- .../hooks/useTableHandlesPositioning.ts | 44 +- 24 files changed, 2151 insertions(+), 32 deletions(-) create mode 100644 packages/core/src/api/blockManipulation/tables/tables.test.ts create mode 100644 packages/core/src/api/blockManipulation/tables/tables.ts create mode 100644 packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx create mode 100644 packages/react/src/components/TableHandles/TableCellHandle.tsx create mode 100644 packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx create mode 100644 packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx create mode 100644 packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellHandleMenu.tsx create mode 100644 packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellHandleMenuProps.ts create mode 100644 packages/react/src/components/TableHandles/TableCellHandleProps.ts create mode 100644 packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx diff --git a/packages/core/src/api/blockManipulation/tables/tables.test.ts b/packages/core/src/api/blockManipulation/tables/tables.test.ts new file mode 100644 index 0000000000..4501f9324f --- /dev/null +++ b/packages/core/src/api/blockManipulation/tables/tables.test.ts @@ -0,0 +1,1065 @@ +import { describe, expect, it } from "vitest"; + +import { Block, DefaultBlockSchema } from "../../../blocks/defaultBlocks.js"; +import { + getColumn, + getRow, + resolveAbsoluteTableCellIndices, + resolveRelativeTableCellIndices, +} from "./tables.js"; + +const simpleTable = { + type: "table", + id: "table-0", + props: { + textColor: "default", + }, + content: { + type: "tableContent", + columnWidths: [100, 100], + rows: [ + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "1-1", styles: {} }], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "1-2", styles: {} }], + }, + ], + }, + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "2-1", styles: {} }], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "2-2", styles: {} }], + }, + ], + }, + ], + }, + children: [], +} satisfies Block< + { + table: DefaultBlockSchema["table"]; + }, + any, + any +>; + +const tableWithColspan = { + type: "table", + id: "table-0", + props: { + textColor: "default", + }, + content: { + type: "tableContent", + columnWidths: [100, 100], + rows: [ + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 2, + rowspan: 1, + }, + content: [ + { type: "text", text: "1-1", styles: {} }, + { type: "text", text: "1-2", styles: {} }, + ], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "1-3", styles: {} }], + }, + ], + }, + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "2-1", styles: {} }], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "2-2", styles: {} }], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "2-3", styles: {} }], + }, + ], + }, + ], + }, + children: [], +} satisfies Block< + { + table: DefaultBlockSchema["table"]; + }, + any, + any +>; + +const tableWithRowspan = { + type: "table", + id: "table-0", + props: { + textColor: "default", + }, + content: { + type: "tableContent", + columnWidths: [100, 100], + rows: [ + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 2, + }, + content: [ + { type: "text", text: "1-1", styles: {} }, + { type: "text", text: "2-1", styles: {} }, + ], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "1-2", styles: {} }], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "1-3", styles: {} }], + }, + ], + }, + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "3-2", styles: {} }], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "3-3", styles: {} }], + }, + ], + }, + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "3-1", styles: {} }], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "3-2", styles: {} }], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "3-3", styles: {} }], + }, + ], + }, + ], + }, + children: [], +} satisfies Block< + { + table: DefaultBlockSchema["table"]; + }, + any, + any +>; + +const tableWithColspanAndRowspan = { + type: "table", + id: "table-0", + props: { + textColor: "default", + }, + content: { + type: "tableContent", + columnWidths: [100, 100], + rows: [ + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 2, + }, + content: [ + { type: "text", text: "1-1", styles: {} }, + { type: "text", text: "2-1", styles: {} }, + ], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "1-2", styles: {} }], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "1-3", styles: {} }], + }, + ], + }, + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 2, + rowspan: 1, + }, + content: [ + { type: "text", text: "2-2", styles: {} }, + { type: "text", text: "2-3", styles: {} }, + ], + }, + ], + }, + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "3-1", styles: {} }], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "3-2", styles: {} }], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "3-3", styles: {} }], + }, + ], + }, + ], + }, + children: [], +} satisfies Block< + { + table: DefaultBlockSchema["table"]; + }, + any, + any +>; + +const tableWithColspansAndRowspans = { + type: "table", + id: "table-0", + props: { + textColor: "default", + }, + content: { + type: "tableContent", + columnWidths: [100, 100], + rows: [ + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 2, + }, + content: [ + { type: "text", text: "1-1", styles: {} }, + { type: "text", text: "2-1", styles: {} }, + ], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 2, + rowspan: 1, + }, + content: [ + { type: "text", text: "1-2", styles: {} }, + { type: "text", text: "1-3", styles: {} }, + ], + }, + ], + }, + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 2, + rowspan: 2, + }, + content: [ + { type: "text", text: "2-2", styles: {} }, + { type: "text", text: "2-3", styles: {} }, + { type: "text", text: "3-2", styles: {} }, + { type: "text", text: "3-3", styles: {} }, + ], + }, + ], + }, + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "3-1", styles: {} }], + }, + ], + }, + ], + }, + children: [], +} satisfies Block< + { + table: DefaultBlockSchema["table"]; + }, + any, + any +>; + +describe("Test resolveRelativeTableCellIndices", () => { + it("should resolve relative table cell indices to absolute table cell indices", () => { + expect( + resolveRelativeTableCellIndices({ row: 0, col: 0 }, simpleTable) + ).toEqual({ row: 0, col: 0 }); + expect( + resolveRelativeTableCellIndices({ row: 0, col: 1 }, simpleTable) + ).toEqual({ row: 0, col: 1 }); + expect( + resolveRelativeTableCellIndices({ row: 1, col: 0 }, simpleTable) + ).toEqual({ row: 1, col: 0 }); + expect( + resolveRelativeTableCellIndices({ row: 1, col: 1 }, simpleTable) + ).toEqual({ row: 1, col: 1 }); + }); + + it("should resolve relative table cell indices to absolute table cell indices with colspan", () => { + expect( + resolveRelativeTableCellIndices({ row: 0, col: 0 }, tableWithColspan) + ).toEqual({ row: 0, col: 0 }); + expect( + resolveRelativeTableCellIndices({ row: 0, col: 1 }, tableWithColspan) + ).toEqual({ row: 0, col: 2 }); + expect( + resolveRelativeTableCellIndices({ row: 1, col: 0 }, tableWithColspan) + ).toEqual({ row: 1, col: 0 }); + expect( + resolveRelativeTableCellIndices({ row: 1, col: 1 }, tableWithColspan) + ).toEqual({ row: 1, col: 1 }); + expect( + resolveRelativeTableCellIndices({ row: 1, col: 2 }, tableWithColspan) + ).toEqual({ row: 1, col: 2 }); + }); + + it("should resolve relative table cell indices to absolute table cell indices with rowspan", () => { + expect( + resolveRelativeTableCellIndices({ row: 0, col: 0 }, tableWithRowspan) + ).toEqual({ row: 0, col: 0 }); + expect( + resolveRelativeTableCellIndices({ row: 0, col: 1 }, tableWithRowspan) + ).toEqual({ row: 0, col: 1 }); + expect( + resolveRelativeTableCellIndices({ row: 0, col: 2 }, tableWithRowspan) + ).toEqual({ row: 0, col: 2 }); + expect( + resolveRelativeTableCellIndices({ row: 1, col: 0 }, tableWithRowspan) + ).toEqual({ row: 2, col: 0 }); + expect( + resolveRelativeTableCellIndices({ row: 1, col: 1 }, tableWithRowspan) + ).toEqual({ row: 1, col: 1 }); + expect( + resolveRelativeTableCellIndices({ row: 1, col: 2 }, tableWithRowspan) + ).toEqual({ row: 1, col: 2 }); + expect( + resolveRelativeTableCellIndices({ row: 2, col: 1 }, tableWithRowspan) + ).toEqual({ row: 2, col: 1 }); + expect( + resolveRelativeTableCellIndices({ row: 2, col: 2 }, tableWithRowspan) + ).toEqual({ row: 2, col: 2 }); + }); +}); + +describe("Test resolveAbsoluteTableCellIndices", () => { + it("should resolve absolute table cell indices to relative table cell indices", () => { + expect( + resolveAbsoluteTableCellIndices({ row: 0, col: 0 }, simpleTable) + ).toEqual({ row: 0, col: 0, cell: simpleTable.content.rows[0].cells[0] }); + expect( + resolveAbsoluteTableCellIndices({ row: 0, col: 1 }, simpleTable) + ).toEqual({ row: 0, col: 1, cell: simpleTable.content.rows[0].cells[1] }); + expect( + resolveAbsoluteTableCellIndices({ row: 1, col: 0 }, simpleTable) + ).toEqual({ row: 1, col: 0, cell: simpleTable.content.rows[1].cells[0] }); + expect( + resolveAbsoluteTableCellIndices({ row: 1, col: 1 }, simpleTable) + ).toEqual({ row: 1, col: 1, cell: simpleTable.content.rows[1].cells[1] }); + }); + + it("should resolve absolute table cell indices to relative table cell indices with colspan", () => { + expect( + resolveAbsoluteTableCellIndices({ row: 0, col: 0 }, tableWithColspan) + ).toEqual({ + row: 0, + col: 0, + cell: tableWithColspan.content.rows[0].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices({ row: 0, col: 1 }, tableWithColspan) + ).toEqual({ + row: 0, + col: 0, + cell: tableWithColspan.content.rows[0].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices({ row: 0, col: 2 }, tableWithColspan) + ).toEqual({ + row: 0, + col: 1, + cell: tableWithColspan.content.rows[0].cells[1], + }); + expect( + resolveAbsoluteTableCellIndices({ row: 1, col: 0 }, tableWithColspan) + ).toEqual({ + row: 1, + col: 0, + cell: tableWithColspan.content.rows[1].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices({ row: 1, col: 1 }, tableWithColspan) + ).toEqual({ + row: 1, + col: 1, + cell: tableWithColspan.content.rows[1].cells[1], + }); + expect( + resolveAbsoluteTableCellIndices({ row: 1, col: 2 }, tableWithColspan) + ).toEqual({ + row: 1, + col: 2, + cell: tableWithColspan.content.rows[1].cells[2], + }); + }); + + it("should resolve absolute table cell indices to relative table cell indices with rowspan", () => { + expect( + resolveAbsoluteTableCellIndices({ row: 0, col: 0 }, tableWithRowspan) + ).toEqual({ + row: 0, + col: 0, + cell: tableWithRowspan.content.rows[0].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices({ row: 0, col: 1 }, tableWithRowspan) + ).toEqual({ + row: 0, + col: 1, + cell: tableWithRowspan.content.rows[0].cells[1], + }); + expect( + resolveAbsoluteTableCellIndices({ row: 0, col: 2 }, tableWithRowspan) + ).toEqual({ + row: 0, + col: 2, + cell: tableWithRowspan.content.rows[0].cells[2], + }); + expect( + resolveAbsoluteTableCellIndices({ row: 1, col: 0 }, tableWithRowspan) + ).toEqual({ + row: 0, + col: 0, + cell: tableWithRowspan.content.rows[0].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices({ row: 1, col: 1 }, tableWithRowspan) + ).toEqual({ + row: 1, + col: 0, + cell: tableWithRowspan.content.rows[1].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices({ row: 1, col: 2 }, tableWithRowspan) + ).toEqual({ + row: 1, + col: 1, + cell: tableWithRowspan.content.rows[1].cells[1], + }); + expect( + resolveAbsoluteTableCellIndices({ row: 2, col: 0 }, tableWithRowspan) + ).toEqual({ + row: 2, + col: 0, + cell: tableWithRowspan.content.rows[2].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices({ row: 2, col: 1 }, tableWithRowspan) + ).toEqual({ + row: 2, + col: 1, + cell: tableWithRowspan.content.rows[2].cells[1], + }); + expect( + resolveAbsoluteTableCellIndices({ row: 2, col: 2 }, tableWithRowspan) + ).toEqual({ + row: 2, + col: 2, + cell: tableWithRowspan.content.rows[2].cells[2], + }); + }); + + it("should resolve absolute table cell indices to relative table cell indices with colspan and rowspan", () => { + expect( + resolveAbsoluteTableCellIndices( + { row: 0, col: 0 }, + tableWithColspanAndRowspan + ) + ).toEqual({ + row: 0, + col: 0, + cell: tableWithColspanAndRowspan.content.rows[0].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 0, col: 1 }, + tableWithColspanAndRowspan + ) + ).toEqual({ + row: 0, + col: 1, + cell: tableWithColspanAndRowspan.content.rows[0].cells[1], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 0, col: 2 }, + tableWithColspanAndRowspan + ) + ).toEqual({ + row: 0, + col: 2, + cell: tableWithColspanAndRowspan.content.rows[0].cells[2], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 1, col: 0 }, + tableWithColspanAndRowspan + ) + ).toEqual({ + row: 0, + col: 0, + cell: tableWithColspanAndRowspan.content.rows[0].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 1, col: 1 }, + tableWithColspanAndRowspan + ) + ).toEqual({ + row: 1, + col: 0, + cell: tableWithColspanAndRowspan.content.rows[1].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 1, col: 2 }, + tableWithColspanAndRowspan + ) + ).toEqual({ + row: 1, + col: 0, + cell: tableWithColspanAndRowspan.content.rows[1].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 2, col: 0 }, + tableWithColspanAndRowspan + ) + ).toEqual({ + row: 2, + col: 0, + cell: tableWithColspanAndRowspan.content.rows[2].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 2, col: 1 }, + tableWithColspanAndRowspan + ) + ).toEqual({ + row: 2, + col: 1, + cell: tableWithColspanAndRowspan.content.rows[2].cells[1], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 2, col: 2 }, + tableWithColspanAndRowspan + ) + ).toEqual({ + row: 2, + col: 2, + cell: tableWithColspanAndRowspan.content.rows[2].cells[2], + }); + }); + + it("should resolve absolute table cell indices to relative table cell indices with colspans and rowspans", () => { + expect( + resolveAbsoluteTableCellIndices( + { row: 0, col: 0 }, + tableWithColspansAndRowspans + ) + ).toEqual({ + row: 0, + col: 0, + cell: tableWithColspansAndRowspans.content.rows[0].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 0, col: 1 }, + tableWithColspansAndRowspans + ) + ).toEqual({ + row: 0, + col: 1, + cell: tableWithColspansAndRowspans.content.rows[0].cells[1], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 0, col: 2 }, + tableWithColspansAndRowspans + ) + ).toEqual({ + row: 0, + col: 1, + cell: tableWithColspansAndRowspans.content.rows[0].cells[1], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 1, col: 0 }, + tableWithColspansAndRowspans + ) + ).toEqual({ + row: 0, + col: 0, + cell: tableWithColspansAndRowspans.content.rows[0].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 1, col: 1 }, + tableWithColspansAndRowspans + ) + ).toEqual({ + row: 1, + col: 0, + cell: tableWithColspansAndRowspans.content.rows[1].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 1, col: 2 }, + tableWithColspansAndRowspans + ) + ).toEqual({ + row: 1, + col: 0, + cell: tableWithColspansAndRowspans.content.rows[1].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 2, col: 0 }, + tableWithColspansAndRowspans + ) + ).toEqual({ + row: 2, + col: 0, + cell: tableWithColspansAndRowspans.content.rows[2].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 2, col: 1 }, + tableWithColspansAndRowspans + ) + ).toEqual({ + row: 1, + col: 0, + cell: tableWithColspansAndRowspans.content.rows[1].cells[0], + }); + expect( + resolveAbsoluteTableCellIndices( + { row: 2, col: 2 }, + tableWithColspansAndRowspans + ) + ).toEqual({ + row: 1, + col: 0, + cell: tableWithColspansAndRowspans.content.rows[1].cells[0], + }); + }); +}); + +describe("Test getRow", () => { + it("should get the row of the table", () => { + expect(getRow(simpleTable, 0)).toEqual( + simpleTable.content.rows[0].cells.map((cell, col) => ({ + row: 0, + col, + cell, + })) + ); + }); + + it("should get the row of the table with colspan", () => { + expect(getRow(tableWithColspan, 0)).toEqual([ + { + row: 0, + col: 0, + cell: tableWithColspan.content.rows[0].cells[0], + }, + { + row: 0, + col: 1, + cell: tableWithColspan.content.rows[0].cells[1], + }, + ]); + + expect(getRow(tableWithColspan, 1)).toEqual([ + { + row: 1, + col: 0, + cell: tableWithColspan.content.rows[1].cells[0], + }, + { + row: 1, + col: 1, + cell: tableWithColspan.content.rows[1].cells[1], + }, + { + row: 1, + col: 2, + cell: tableWithColspan.content.rows[1].cells[2], + }, + ]); + }); + + it("should get the row of the table with rowspan", () => { + expect(getRow(tableWithRowspan, 0)).toEqual([ + { + row: 0, + col: 0, + cell: tableWithRowspan.content.rows[0].cells[0], + }, + { + row: 0, + col: 1, + cell: tableWithRowspan.content.rows[0].cells[1], + }, + { + row: 0, + col: 2, + cell: tableWithRowspan.content.rows[0].cells[2], + }, + ]); + + expect(getRow(tableWithRowspan, 1)).toEqual([ + { + row: 2, + col: 0, + cell: tableWithRowspan.content.rows[2].cells[0], + }, + { + row: 2, + col: 1, + cell: tableWithRowspan.content.rows[2].cells[1], + }, + { + row: 2, + col: 2, + cell: tableWithRowspan.content.rows[2].cells[2], + }, + ]); + }); +}); + +describe("Test getColumn", () => { + it("should get the column of the table", () => { + expect(getColumn(simpleTable, 0)).toEqual([ + { + row: 0, + col: 0, + cell: simpleTable.content.rows[0].cells[0], + }, + { + row: 1, + col: 0, + cell: simpleTable.content.rows[1].cells[0], + }, + ]); + + expect(getColumn(simpleTable, 1)).toEqual([ + { + row: 0, + col: 1, + cell: simpleTable.content.rows[0].cells[1], + }, + { + row: 1, + col: 1, + cell: simpleTable.content.rows[1].cells[1], + }, + ]); + }); + + it("should get the column of the table with colspan", () => { + expect(getColumn(tableWithColspan, 0)).toEqual([ + { + row: 0, + col: 0, + cell: tableWithColspan.content.rows[0].cells[0], + }, + { + row: 1, + col: 0, + cell: tableWithColspan.content.rows[1].cells[0], + }, + ]); + + expect(getColumn(tableWithColspan, 1)).toEqual([ + { + row: 0, + col: 1, + cell: tableWithColspan.content.rows[0].cells[1], + }, + { + row: 1, + col: 2, + cell: tableWithColspan.content.rows[1].cells[2], + }, + ]); + }); + + it("should get the column of the table with rowspan", () => { + expect(getColumn(tableWithRowspan, 0)).toEqual([ + { + row: 0, + col: 0, + cell: tableWithRowspan.content.rows[0].cells[0], + }, + { + row: 2, + col: 0, + cell: tableWithRowspan.content.rows[2].cells[0], + }, + ]); + + expect(getColumn(tableWithRowspan, 1)).toEqual([ + { + row: 0, + col: 1, + cell: tableWithRowspan.content.rows[0].cells[1], + }, + { + row: 1, + col: 0, + cell: tableWithRowspan.content.rows[1].cells[0], + }, + { + row: 2, + col: 1, + cell: tableWithRowspan.content.rows[2].cells[1], + }, + ]); + + expect(getColumn(tableWithRowspan, 2)).toEqual([ + { + row: 0, + col: 2, + cell: tableWithRowspan.content.rows[0].cells[2], + }, + { + row: 1, + col: 1, + cell: tableWithRowspan.content.rows[1].cells[1], + }, + { + row: 2, + col: 2, + cell: tableWithRowspan.content.rows[2].cells[2], + }, + ]); + }); +}); diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts new file mode 100644 index 0000000000..4413abb0b1 --- /dev/null +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -0,0 +1,219 @@ +import { Block, DefaultBlockSchema } from "../../../blocks/defaultBlocks.js"; +import { isTableCell, TableContent } from "../../../schema/blocks/types.js"; + +/** + * This will resolve the relative cell indices within the table block to the absolute cell indices within the table. + * Accounts for colspan and rowspan. + * + * @returns The absolute cell indices (row and column). + */ +export function resolveRelativeTableCellIndices( + /** + * The relative position of the cell in the table. + */ + relativeCellIndices: { row: number; col: number }, + /** + * The table block containing the cell. + */ + block: Block< + { + table: DefaultBlockSchema["table"]; + }, + any, + any + > +): { row: number; col: number } { + // Calculate column index by summing colspan values of previous cells in the row + let colIndex = 0; + for (let i = 0; i < relativeCellIndices.col; i++) { + const cell = block.content.rows[relativeCellIndices.row].cells[i]; + colIndex += isTableCell(cell) ? cell.props?.colspan ?? 1 : 1; + } + + // Calculate row index by summing rowspan values of cells in previous rows + let rowIndex = 0; + for (let i = 0; i < relativeCellIndices.row; i++) { + const cell = block.content.rows[i].cells[relativeCellIndices.col]; + rowIndex += isTableCell(cell) ? cell.props?.rowspan ?? 1 : 1; + } + + return { row: rowIndex, col: colIndex }; +} + +/** + * This will get the dimensions of the table block. + * + * @returns The height and width of the table. + */ +export function getDimensionsOfTable( + block: Block<{ table: DefaultBlockSchema["table"] }, any, any> +) { + const height = block.content.rows.length; + const width = block.content.rows.reduce((acc, { cells }) => { + return Math.max( + acc, + cells.reduce((acc, cell) => { + return acc + (isTableCell(cell) ? cell.props?.colspan ?? 1 : 1); + }, 0) + ); + }, 0); + + return { height, width }; +} + +/** + * This will resolve the absolute cell indices within the table block to the relative cell indices within the table. + * Accounts for colspan and rowspan. + * + * @returns The relative cell indices (row and column). + */ +export function resolveAbsoluteTableCellIndices( + /** + * The absolute position of the cell in the table. + */ + absoluteCellIndices: { row: number; col: number }, + /** + * The table block containing the cell. + */ + block: Block< + { + table: DefaultBlockSchema["table"]; + }, + any, + any + > +): { + row: number; + col: number; + cell: TableContent["rows"][number]["cells"][number]; +} | null { + const { height, width } = getDimensionsOfTable(block); + + /* + * Create a grid to track occupied cells + * This is used because rowspans and colspans take up multiple spaces + * So, we need to track the occupied cells in the grid to know where to place the next cell + */ + const grid: boolean[][] = new Array(height) + .fill(false) + .map(() => new Array(width).fill(false)); + + if (absoluteCellIndices.row >= height || absoluteCellIndices.col >= width) { + return null; + } + + // Find the next unoccupied cell in the table, row-major order + function findNextAvailable(row: number, col: number) { + for (let i = row; i < height; i++) { + for (let j = col; j < width; j++) { + if (!grid[i][j]) { + return { row: i, col: j }; + } + } + } + return null; + } + + // Build up the grid, trying to fill in the cells with the correct relative row and column indices + for (let row = 0; row < block.content.rows.length; row++) { + for (let col = 0; col < block.content.rows[row].cells.length; col++) { + const cell = block.content.rows[row].cells[col]; + const rowspan = isTableCell(cell) ? cell.props?.rowspan ?? 1 : 1; + const colspan = isTableCell(cell) ? cell.props?.colspan ?? 1 : 1; + + // Rowspan and colspan complicate things, by taking up multiple cells in the grid + // We need to iterate over the cells that the rowspan and colspan take up + // and fill in the grid with the correct relative row and column indices + const nextAvailableCell = findNextAvailable(row, col); + + if (nextAvailableCell === null) { + // No more available cells in the table, the table is invalid + return null; + } + + const { row: startRow, col: startCol } = nextAvailableCell; + + // Fill in the rowspan X colspan cells, starting from the next available cell, with the correct relative row and column indices + for (let i = startRow; i < startRow + rowspan; i++) { + for (let j = startCol; j < startCol + colspan; j++) { + if (grid[i][j]) { + // The cell is already occupied, the table is invalid + return null; + } + + grid[i][j] = true; + + if (i === absoluteCellIndices.row && j === absoluteCellIndices.col) { + // If the cell is occupied, return the relative cell indices + return { + row, + col, + cell, + }; + } + } + } + } + } + + // We did not find the cell, so return null + return null; +} + +/** + * This will get all the cells in a row of the table block. + * + * @returns The row of the table. + */ +export function getRow( + block: Block<{ table: DefaultBlockSchema["table"] }, any, any>, + relativeRowIndex: number +) { + const { width } = getDimensionsOfTable(block); + // First need to resolve the relative row index to an absolute row index + const { row: absoluteRow } = resolveRelativeTableCellIndices( + { row: relativeRowIndex, col: 0 }, + block + ); + + // Then for each column, get the cell at the absolute row index as a relative cell index + const cells = new Array(width).fill(false).map((_v, col) => { + return resolveAbsoluteTableCellIndices({ row: absoluteRow, col }, block)!; + }); + + // Filter out duplicates based on row and col properties + return cells.filter((cell, index) => { + return ( + cells.findIndex((c) => c.row === cell.row && c.col === cell.col) === index + ); + }); +} + +/** + * This will get all the cells in a column of the table block. + * + * @returns The column of the table. + */ +export function getColumn( + block: Block<{ table: DefaultBlockSchema["table"] }, any, any>, + relativeColumnIndex: number +) { + const { height } = getDimensionsOfTable(block); + // First need to resolve the relative column index to an absolute column index + const { col: absoluteCol } = resolveRelativeTableCellIndices( + { row: 0, col: relativeColumnIndex }, + block + ); + + // Then for each row, get the cell at the absolute column index as a relative cell index + const cells = new Array(height).fill(false).map((_v, row) => { + return resolveAbsoluteTableCellIndices({ row, col: absoluteCol }, block)!; + }); + + // Filter out duplicates based on row and col properties + return cells.filter((cell, index) => { + return ( + cells.findIndex((c) => c.row === cell.row && c.col === cell.col) === index + ); + }); +} diff --git a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts index 0a039de0d3..20fddeb4a5 100644 --- a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts +++ b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts @@ -1,4 +1,4 @@ -import { TableCell as TiptapTableCell } from "@tiptap/extension-table-cell"; +import { TableCell } from "@tiptap/extension-table-cell"; import { TableHeader } from "@tiptap/extension-table-header"; import { TableRow } from "@tiptap/extension-table-row"; import { Node as PMNode } from "prosemirror-model"; @@ -168,7 +168,7 @@ export const Table = createBlockSpecFromStronglyTypedTiptapNode( TableHeader.extend({ content: "tableContent", }), - TiptapTableCell.extend({ + TableCell.extend({ content: "tableContent", }), TableRow, diff --git a/packages/core/src/blocks/defaultBlockTypeGuards.ts b/packages/core/src/blocks/defaultBlockTypeGuards.ts index 169f9d97ea..15e7bf10cd 100644 --- a/packages/core/src/blocks/defaultBlockTypeGuards.ts +++ b/packages/core/src/blocks/defaultBlockTypeGuards.ts @@ -1,3 +1,4 @@ +import { CellSelection } from "prosemirror-tables"; import type { BlockNoteEditor } from "../editor/BlockNoteEditor.js"; import { BlockFromConfig, @@ -14,6 +15,7 @@ import { defaultInlineContentSchema, } from "./defaultBlocks.js"; import { defaultProps } from "./defaultProps.js"; +import { Selection } from "prosemirror-state"; export function checkDefaultBlockTypeInSchema< BlockType extends keyof DefaultBlockSchema, @@ -159,3 +161,7 @@ export function checkBlockHasDefaultProp< > { return checkBlockTypeHasDefaultProp(prop, block.type, editor); } + +export function isTableCellSelection(selection: Selection) { + return selection instanceof CellSelection; +} diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index e2ae4a7256..8bee9d3dbe 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -580,9 +580,9 @@ export class BlockNoteEditor< } } - dispatch(tr: Transaction) { + dispatch = (tr: Transaction) => { this._tiptapEditor.dispatch(tr); - } + }; /** * Mount the editor to a parent DOM element. Call mount(undefined) to clean up @@ -593,10 +593,20 @@ export class BlockNoteEditor< this._tiptapEditor.mount(parentElement); }; + /** + * Get the underlying prosemirror view + */ public get prosemirrorView() { return this._tiptapEditor.view; } + /** + * Get the underlying prosemirror state + */ + public get prosemirrorState() { + return this._tiptapEditor.state; + } + public get domElement() { return this.prosemirrorView?.dom as HTMLDivElement | undefined; } @@ -645,7 +655,7 @@ export class BlockNoteEditor< public get document(): Block[] { const blocks: Block[] = []; - this._tiptapEditor.state.doc.firstChild!.descendants((node) => { + this.prosemirrorState.doc.firstChild!.descendants((node) => { blocks.push( nodeToBlock( node, diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 5143be5e50..1d96468698 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -1,9 +1,16 @@ import { Plugin, PluginKey, PluginView } from "prosemirror-state"; +import { CellSelection, mergeCells, splitCell } from "prosemirror-tables"; import { Decoration, DecorationSet, EditorView } from "prosemirror-view"; +import { + getColumn, + getDimensionsOfTable, + getRow, + resolveRelativeTableCellIndices, +} from "../../api/blockManipulation/tables/tables.js"; import { nodeToBlock } from "../../api/nodeConversions/nodeToBlock.js"; import { getNodeById } from "../../api/nodeUtil.js"; import { checkBlockIsDefaultType } from "../../blocks/defaultBlockTypeGuards.js"; -import { DefaultBlockSchema } from "../../blocks/defaultBlocks.js"; +import { Block, DefaultBlockSchema } from "../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { BlockFromConfigNoChildren, @@ -16,6 +23,7 @@ import { getDraggableBlockFromElement } from "../getDraggableBlockFromElement.js let dragImageElement: HTMLElement | undefined; +// TODO consider switching this to jotai, it is a bit messy and noisy export type TableHandlesState< I extends InlineContentSchema, S extends StyleSchema @@ -445,6 +453,7 @@ export class TableHandlesView< const rows = this.state.block.content.rows; + // TODO need to handle rowspan and colspan, by resolving relative indices if (draggingState.draggedCellOrientation === "row") { const rowToMove = rows[draggingState.originalIndex]; rows.splice(draggingState.originalIndex, 1); @@ -488,8 +497,9 @@ export class TableHandlesView< return; } - const rowCount = this.state.block.content.rows.length; - const colCount = this.state.block.content.rows[0].cells.length; + const { height: rowCount, width: colCount } = getDimensionsOfTable( + this.state.block as any + ); if ( this.state.rowIndex !== undefined && @@ -834,4 +844,97 @@ export class TableHandlesProsemirrorPlugin< unfreezeHandles = () => { this.view!.menuFrozen = false; }; + + resolveRelativeTableCellIndices = ( + relativeCellIndices: { row: number; col: number }, + block: Block<{ table: DefaultBlockSchema["table"] }, any, any> + ) => { + return resolveRelativeTableCellIndices(relativeCellIndices, block); + }; + + getRow = ( + block: Block<{ table: DefaultBlockSchema["table"] }, any, any>, + relativeRowIndex: number + ) => { + return getRow(block, relativeRowIndex); + }; + + /** + * Get all the cells in a column of the table block. + */ + getColumn = ( + block: Block<{ table: DefaultBlockSchema["table"] }, any, any>, + relativeColumnIndex: number + ) => { + return getColumn(block, relativeColumnIndex); + }; + + /** + * Sets the selection to the given cell or a range of cells. + * @returns The new state after the selection has been set. + */ + private setCellSelection = ( + startCell: { row: number; col: number }, + endCell: { row: number; col: number } = startCell + ) => { + const view = this.view; + + if (!view) { + throw new Error("Table handles view not initialized"); + } + + const state = this.editor.prosemirrorState; + const tableResolvedPos = state.doc.resolve(view.tablePos! + 1); + const startRowResolvedPos = state.doc.resolve( + tableResolvedPos.posAtIndex(startCell.row) + 1 + ); + const startCellResolvedPos = state.doc.resolve( + // No need for +1, since CellSelection expects the position before the cell + startRowResolvedPos.posAtIndex(startCell.col) + ); + const endRowResolvedPos = state.doc.resolve( + tableResolvedPos.posAtIndex(endCell.row) + 1 + ); + const endCellResolvedPos = state.doc.resolve( + // No need for +1, since CellSelection expects the position before the cell + endRowResolvedPos.posAtIndex(endCell.col) + ); + + // Begin a new transaction to set the selection + const tr = state.tr; + + // Set the selection to the given cell or a range of cells + tr.setSelection( + new CellSelection(startCellResolvedPos, endCellResolvedPos) + ); + + // Quickly apply the transaction to get the new state to update the selection before splitting the cell + return state.apply(tr); + }; + + /** + * Merges the cells in the table block. + */ + mergeCells = (cellsToMerge?: { + start: { row: number; col: number }; + end: { row: number; col: number }; + }) => { + const state = cellsToMerge + ? this.setCellSelection(cellsToMerge.start, cellsToMerge.end) + : this.editor.prosemirrorState; + + return mergeCells(state, this.editor.dispatch); + }; + + /** + * Splits the cell in the table block. + * If no cell is provided, the current cell selected will be split. + */ + splitCell = (cellToSplit?: { row: number; col: number }) => { + const state = cellToSplit + ? this.setCellSelection(cellToSplit) + : this.editor.prosemirrorState; + + return splitCell(state, this.editor.dispatch); + }; } diff --git a/packages/core/src/i18n/locales/en.ts b/packages/core/src/i18n/locales/en.ts index 442f7a3895..90ac64d603 100644 --- a/packages/core/src/i18n/locales/en.ts +++ b/packages/core/src/i18n/locales/en.ts @@ -160,6 +160,9 @@ export const en = { add_right_menuitem: "Add column right", add_above_menuitem: "Add row above", add_below_menuitem: "Add row below", + split_cell_menuitem: "Split cell", + merge_cells_menuitem: "Merge cells", + background_color_menuitem: "Background color", }, suggestion_menu: { no_items_title: "No items found", @@ -275,6 +278,9 @@ export const en = { align_justify: { tooltip: "Justify text", }, + table_cell_merge: { + tooltip: "Merge cells", + }, }, file_panel: { upload: { diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index 967a58f5da..0f0769bd77 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -320,19 +320,75 @@ export type PartialBlockFromConfig< export type BlockIdentifier = { id: string } | string; -export function isPartialTableCell( - content: PartialInlineContent | PartialTableCell -): content is PartialTableCell { +/** + * This will map a table cell to a TableCell object. + * This is useful for when we want to get the full table cell object from a partial table cell. + * It is guaranteed to return a new TableCell object. + */ +export function mapTableCell< + T extends InlineContentSchema, + S extends StyleSchema +>( + content: PartialInlineContent | PartialTableCell | TableCell +): TableCell { + return isTableCell(content) + ? { ...content } + : isPartialTableCell(content) + ? { + type: "tableCell", + props: { + backgroundColor: content.props?.backgroundColor ?? "default", + textColor: content.props?.textColor ?? "default", + textAlignment: content.props?.textAlignment ?? "left", + colspan: content.props?.colspan ?? 1, + rowspan: content.props?.rowspan ?? 1, + }, + content: ([] as InlineContent[]).concat(content.content as any), + } + : { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: ([] as InlineContent[]).concat(content as any), + }; +} + +export function isPartialTableCell< + T extends InlineContentSchema, + S extends StyleSchema +>( + content: + | TableCell + | PartialInlineContent + | PartialTableCell + | undefined + | null +): content is PartialTableCell { return ( + content !== undefined && + content !== null && typeof content !== "string" && !Array.isArray(content) && content.type === "tableCell" ); } -export function isTableCell( - content: InlineContent[] | TableCell -): content is TableCell { +export function isTableCell< + T extends InlineContentSchema, + S extends StyleSchema +>( + content: + | TableCell + | PartialInlineContent + | PartialTableCell + | undefined + | null +): content is TableCell { return ( isPartialTableCell(content) && content.props !== undefined && diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css index 9fe02136fa..0028eb8435 100644 --- a/packages/mantine/src/style.css +++ b/packages/mantine/src/style.css @@ -536,7 +536,8 @@ } /* Drag Handle & Table Handle Menu styling */ -.bn-mantine .bn-drag-handle-menu { +.bn-mantine .bn-drag-handle-menu, +.bn-mantine .bn-table-handle-menu { overflow: visible; } diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx index 3e3b966729..37ca7141a0 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx @@ -6,6 +6,7 @@ import { BlockSchema, formatKeyboardShortcut, InlineContentSchema, + isTableCellSelection, StyleSchema, } from "@blocknote/core"; @@ -92,8 +93,12 @@ export const CreateLinkButton = () => { } } + if (isTableCellSelection(editor._tiptapEditor.state.selection)) { + return false; + } + return true; - }, [linkInSchema, selectedBlocks]); + }, [linkInSchema, selectedBlocks, editor._tiptapEditor.state.selection]); if ( !show || diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx new file mode 100644 index 0000000000..69000ee631 --- /dev/null +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx @@ -0,0 +1,129 @@ +import { + Block, + BlockNoteEditor, + DefaultBlockSchema, + InlineContentSchema, + isTableCellSelection, + StyleSchema, +} from "@blocknote/core"; +import { useCallback, useMemo } from "react"; +import { RiMergeCellsHorizontal, RiMergeCellsVertical } from "react-icons/ri"; + +import { useComponentsContext } from "../../../editor/ComponentsContext.js"; +import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js"; +import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks.js"; +import { useDictionary } from "../../../i18n/dictionary.js"; + +/** + * Gets the direction of the merge. Based on the current cell selection if there is one. + */ +function getMergeDirection( + editor: BlockNoteEditor< + { + table: DefaultBlockSchema["table"]; + }, + any, + any + >, + block: + | Block< + { + table: DefaultBlockSchema["table"]; + }, + any, + any + > + | undefined +): null | "horizontal" | "vertical" { + const tableHandles = editor.tableHandles; + + const cellSelection = isTableCellSelection( + editor._tiptapEditor.state.selection + ) + ? editor._tiptapEditor.state.selection + : undefined; + + if ( + !cellSelection || + !block || + !tableHandles || + // Only offer the merge button if there is more than one cell selected. + cellSelection.ranges.length <= 1 + ) { + return null; + } + + const { $anchorCell, $headCell } = cellSelection; + const anchorRowIndex = $anchorCell.index($anchorCell.depth - 1); + const anchorColIndex = $anchorCell.index(); + const anchorResolved = tableHandles.resolveRelativeTableCellIndices( + { row: anchorRowIndex, col: anchorColIndex }, + block + ); + + const headRowIndex = $headCell.index($headCell.depth - 1); + const headColIndex = $headCell.index(); + const headResolved = tableHandles.resolveRelativeTableCellIndices( + { row: headRowIndex, col: headColIndex }, + block + ); + + if (anchorResolved.col === headResolved.col) { + return "vertical"; + } + + return "horizontal"; +} + +export const TableCellMergeButton = () => { + const dict = useDictionary(); + const Components = useComponentsContext()!; + + const editor = useBlockNoteEditor< + { + table: DefaultBlockSchema["table"]; + }, + InlineContentSchema, + StyleSchema + >(); + + const selectedBlocks = useSelectedBlocks(editor); + const mergeDirection = useMemo(() => { + // Checks if only one block is selected. + if (selectedBlocks.length !== 1) { + return null; + } + + const block = selectedBlocks[0]; + + if (block.type === "table") { + return getMergeDirection(editor, block); + } + + return null; + }, [editor, selectedBlocks]); + + const onClick = useCallback(() => { + editor.tableHandles?.mergeCells(); + }, [editor]); + + if (!editor.isEditable || mergeDirection === null) { + return null; + } + + return ( + + ) : ( + + ) + } + onClick={onClick} + /> + ); +}; diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx index 420154e38e..a2237ca6ed 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx @@ -4,6 +4,7 @@ import { checkBlockTypeHasDefaultProp, DefaultProps, InlineContentSchema, + isTableCellSelection, StyleSchema, } from "@blocknote/core"; import { useCallback, useMemo } from "react"; @@ -67,12 +68,14 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { ); const show = useMemo(() => { - return !!selectedBlocks.find( - (block) => - "textAlignment" in block.props || - (block.type === "table" && block.children) + return ( + !!selectedBlocks.find( + (block) => + "textAlignment" in block.props || + (block.type === "table" && block.children) + ) && !isTableCellSelection(editor._tiptapEditor.state.selection) ); - }, [selectedBlocks]); + }, [editor._tiptapEditor.state.selection, selectedBlocks]); if (!show || !editor.isEditable) { return null; diff --git a/packages/react/src/components/FormattingToolbar/FormattingToolbar.tsx b/packages/react/src/components/FormattingToolbar/FormattingToolbar.tsx index e6b77b6050..72ef1538b7 100644 --- a/packages/react/src/components/FormattingToolbar/FormattingToolbar.tsx +++ b/packages/react/src/components/FormattingToolbar/FormattingToolbar.tsx @@ -19,6 +19,7 @@ import { import { FileDownloadButton } from "./DefaultButtons/FileDownloadButton.js"; import { FilePreviewButton } from "./DefaultButtons/FilePreviewButton.js"; +import { TableCellMergeButton } from "./DefaultButtons/TableCellMergeButton.js"; import { TextAlignButton } from "./DefaultButtons/TextAlignButton.js"; import { FormattingToolbarProps } from "./FormattingToolbarProps.js"; @@ -26,6 +27,7 @@ export const getFormattingToolbarItems = ( blockTypeSelectItems?: BlockTypeSelectItem[] ): JSX.Element[] => [ , + , , , , diff --git a/packages/react/src/components/TableHandles/TableCellHandle.tsx b/packages/react/src/components/TableHandles/TableCellHandle.tsx new file mode 100644 index 0000000000..1cf36ee259 --- /dev/null +++ b/packages/react/src/components/TableHandles/TableCellHandle.tsx @@ -0,0 +1,57 @@ +import { + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { ReactNode } from "react"; + +import { createPortal } from "react-dom"; +import { MdSettings } from "react-icons/md"; +import { useComponentsContext } from "../../editor/ComponentsContext.js"; +import { TableCellHandleProps } from "./TableCellHandleProps.js"; +import { TableCellHandleMenu } from "./TableCellHandleMenu/TableCellHandleMenu.js"; + +/** + * By default, the TableHandle component will render with the default icon. + * However, you can override the icon to render by passing children. + */ +export const TableCellHandle = < + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: TableCellHandleProps & { children?: ReactNode } +) => { + const Components = useComponentsContext()!; + + const Component = props.tableCellHandleMenu || TableCellHandleMenu; + + return ( + { + if (open) { + props.freezeHandles(); + } else { + props.unfreezeHandles(); + props.editor.focus(); + } + }} + position={"right"}> + + {/* TODO we should probably make a generic button (wait for comments PR to land) */} +
+ {props.children || } +
+
+ {/* the menu can extend outside of the table, so we use a portal to prevent clipping */} + {createPortal( + , + props.menuContainer + )} +
+ ); +}; diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx new file mode 100644 index 0000000000..c55abba3cb --- /dev/null +++ b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx @@ -0,0 +1,129 @@ +import { + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + isPartialTableCell, + mapTableCell, + StyleSchema, +} from "@blocknote/core"; + +import { useComponentsContext } from "../../../../editor/ComponentsContext.js"; +import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor.js"; +import { useDictionary } from "../../../../i18n/dictionary.js"; +import { ColorPicker } from "../../../ColorPicker/ColorPicker.js"; +import { TableCellHandleMenuProps } from "../TableCellHandleMenuProps.js"; +import { ReactNode } from "react"; + +export const ColorPickerButton = < + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: TableCellHandleMenuProps & { + children?: ReactNode; + } +) => { + const Components = useComponentsContext()!; + const dict = useDictionary(); + const editor = useBlockNoteEditor< + { table: DefaultBlockSchema["table"] }, + I, + S + >(); + + const currentCell = + props.block.content.rows[props.rowIndex]?.cells?.[props.colIndex]; + + if (!currentCell) { + return null; + } + + return ( + + + + {/* TODO should I be using the dictionary here? */} + {props.children || dict.drag_handle.colors_menuitem} + + + + + + editor.updateBlock(props.block, { + type: "table", + content: { + ...props.block.content, + rows: props.block.content.rows.map( + (row, rowIndex) => + ({ + ...row, + cells: + props.rowIndex === rowIndex + ? row.cells + .map(mapTableCell) + .map((cell, cellIndex) => + cellIndex === props.colIndex + ? { + ...cell, + props: { + ...cell.props, + textColor: color, + }, + } + : cell + ) + : row.cells.map(mapTableCell), + } as any) + ), + }, + }), + }} + background={{ + color: isPartialTableCell(currentCell) + ? currentCell.props.backgroundColor + : "default", + setColor: (color) => + editor.updateBlock(props.block, { + type: "table", + content: { + ...props.block.content, + rows: props.block.content.rows.map( + (row, rowIndex) => + ({ + ...row, + cells: + props.rowIndex === rowIndex + ? row.cells + .map(mapTableCell) + .map((cell, cellIndex) => + cellIndex === props.colIndex + ? { + ...cell, + props: { + ...cell.props, + backgroundColor: color, + }, + } + : cell + ) + : row.cells.map(mapTableCell), + } as any) + ), + }, + }), + }} + /> + + + ); +}; diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx new file mode 100644 index 0000000000..e2a3ad508f --- /dev/null +++ b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx @@ -0,0 +1,39 @@ +import { + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; + +import { useComponentsContext } from "../../../../editor/ComponentsContext.js"; +import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor.js"; +import { useDictionary } from "../../../../i18n/dictionary.js"; +import { TableCellHandleMenuProps } from "../TableCellHandleMenuProps.js"; + +export const SplitButton = < + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: TableCellHandleMenuProps +) => { + const Components = useComponentsContext()!; + const dict = useDictionary(); + const editor = useBlockNoteEditor< + { table: DefaultBlockSchema["table"] }, + I, + S + >(); + + return ( + { + editor.tableHandles?.splitCell({ + row: props.rowIndex, + col: props.colIndex, + }); + }}> + {dict.table_handle.split_cell_menuitem} + + ); +}; diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellHandleMenu.tsx b/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellHandleMenu.tsx new file mode 100644 index 0000000000..c7ad1bd00c --- /dev/null +++ b/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellHandleMenu.tsx @@ -0,0 +1,41 @@ +import { + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { ReactNode } from "react"; + +import { useComponentsContext } from "../../../editor/ComponentsContext.js"; +import { ColorPickerButton } from "./DefaultButtons/ColorPicker.js"; +import { SplitButton } from "./DefaultButtons/SplitButton.js"; +import { TableCellHandleMenuProps } from "./TableCellHandleMenuProps.js"; + +export const TableCellHandleMenu = < + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: TableCellHandleMenuProps & { children?: ReactNode } +) => { + const Components = useComponentsContext()!; + + return ( + + {props.children || ( + <> + + + + )} + + ); +}; diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellHandleMenuProps.ts b/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellHandleMenuProps.ts new file mode 100644 index 0000000000..7b268f242a --- /dev/null +++ b/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellHandleMenuProps.ts @@ -0,0 +1,17 @@ +import { + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + SpecificBlock, + StyleSchema, +} from "@blocknote/core"; + +export type TableCellHandleMenuProps< + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +> = { + block: SpecificBlock<{ table: DefaultBlockSchema["table"] }, "table", I, S>; + rowIndex: number; + colIndex: number; +}; diff --git a/packages/react/src/components/TableHandles/TableCellHandleProps.ts b/packages/react/src/components/TableHandles/TableCellHandleProps.ts new file mode 100644 index 0000000000..47baf2dd48 --- /dev/null +++ b/packages/react/src/components/TableHandles/TableCellHandleProps.ts @@ -0,0 +1,41 @@ +import { + BlockNoteEditor, + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, + TableHandlesState, +} from "@blocknote/core"; +import { TableCellHandleMenuProps } from "./TableCellHandleMenu/TableCellHandleMenuProps.js"; +import { FC } from "react"; + +export type TableCellHandleProps< + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +> = { + editor: BlockNoteEditor< + { + table: DefaultBlockSchema["table"]; + }, + I, + S + >; + rowIndex: number; + colIndex: number; + menuContainer: HTMLDivElement; + tableCellHandleMenu?: FC>; +} & Pick, "block"> & + Pick< + Exclude< + BlockNoteEditor< + { + table: DefaultBlockSchema["table"]; + }, + I, + S + >["tableHandles"], + undefined + >, + "freezeHandles" | "unfreezeHandles" + >; diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx new file mode 100644 index 0000000000..08fbbb72a9 --- /dev/null +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx @@ -0,0 +1,132 @@ +import { + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + isPartialTableCell, + mapTableCell, + StyleSchema, +} from "@blocknote/core"; + +import { useComponentsContext } from "../../../../editor/ComponentsContext.js"; +import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor.js"; +import { useDictionary } from "../../../../i18n/dictionary.js"; +import { ColorPicker } from "../../../ColorPicker/ColorPicker.js"; +import { TableHandleMenuProps } from "../TableHandleMenuProps.js"; +import { ReactNode, useMemo } from "react"; + +export const ColorPickerButton = < + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: TableHandleMenuProps & { + children?: ReactNode; + } +) => { + const Components = useComponentsContext()!; + const dict = useDictionary(); + const editor = useBlockNoteEditor< + { table: DefaultBlockSchema["table"] }, + I, + S + >(); + const tableHandles = editor.tableHandles; + + const currentCells = useMemo(() => { + if (!tableHandles) { + return []; + } + + if (props.orientation === "row") { + return tableHandles.getRow(props.block, props.index); + } + + return tableHandles.getColumn(props.block, props.index); + }, [props.block, props.index, props.orientation, tableHandles]); + + if (!currentCells || !tableHandles) { + return null; + } + + const firstCell = mapTableCell(currentCells[0].cell); + + return ( + + + + {/* TODO should I be using the dictionary here? */} + {props.children || dict.drag_handle.colors_menuitem} + + + + + + isPartialTableCell(cell) && + cell.props.textColor === firstCell.props.textColor + ) + ? firstCell.props.textColor + : "default", + setColor: (color) => { + const newTable = props.block.content.rows.map((row) => { + return { + ...row, + cells: row.cells.map((cell) => mapTableCell(cell)), + }; + }); + + currentCells.forEach(({ row, col }) => { + newTable[row].cells[col].props.textColor = color; + }); + + return editor.updateBlock(props.block, { + type: "table", + content: { + ...props.block.content, + rows: newTable, + }, + }); + }, + }} + background={{ + color: currentCells.every( + ({ cell }) => + isPartialTableCell(cell) && + cell.props.backgroundColor === firstCell.props.backgroundColor + ) + ? firstCell.props.backgroundColor + : "default", + setColor: (color) => { + const newTable = props.block.content.rows.map((row) => { + return { + ...row, + cells: row.cells.map((cell) => mapTableCell(cell)), + }; + }); + + currentCells.forEach(({ row, col }) => { + newTable[row].cells[col].props.backgroundColor = color; + }); + + return editor.updateBlock(props.block, { + type: "table", + content: { + ...props.block.content, + rows: newTable, + }, + }); + }, + }} + /> + + + ); +}; diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenu.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenu.tsx index 57f0cd9bd6..75804155f7 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenu.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenu.tsx @@ -10,6 +10,7 @@ import { useComponentsContext } from "../../../editor/ComponentsContext.js"; import { AddButton } from "./DefaultButtons/AddButton.js"; import { DeleteButton } from "./DefaultButtons/DeleteButton.js"; import { TableHandleMenuProps } from "./TableHandleMenuProps.js"; +import { ColorPickerButton } from "./DefaultButtons/ColorPicker.js"; export const TableHandleMenu = < I extends InlineContentSchema = DefaultInlineContentSchema, @@ -40,6 +41,11 @@ export const TableHandleMenu = < index={props.index} side={props.orientation === "row" ? "below" : ("right" as any)} /> + )} diff --git a/packages/react/src/components/TableHandles/TableHandleProps.ts b/packages/react/src/components/TableHandles/TableHandleProps.ts index c7c2272df3..3d0ab0766d 100644 --- a/packages/react/src/components/TableHandles/TableHandleProps.ts +++ b/packages/react/src/components/TableHandles/TableHandleProps.ts @@ -11,8 +11,6 @@ import { DragEvent, FC } from "react"; import { DragHandleMenuProps } from "../SideMenu/DragHandleMenu/DragHandleMenuProps.js"; -type NonUndefined = T extends undefined ? never : T; - export type TableHandleProps< I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema @@ -41,14 +39,15 @@ export type TableHandleProps< >; } & Pick, "block"> & Pick< - NonUndefined< + Exclude< BlockNoteEditor< { table: DefaultBlockSchema["table"]; }, I, S - >["tableHandles"] + >["tableHandles"], + undefined >, "dragEnd" | "freezeHandles" | "unfreezeHandles" >; diff --git a/packages/react/src/components/TableHandles/TableHandlesController.tsx b/packages/react/src/components/TableHandles/TableHandlesController.tsx index 31ccecf4d3..09ca27cda9 100644 --- a/packages/react/src/components/TableHandles/TableHandlesController.tsx +++ b/packages/react/src/components/TableHandles/TableHandlesController.tsx @@ -16,11 +16,14 @@ import { TableHandle } from "./TableHandle.js"; import { TableHandleProps } from "./TableHandleProps.js"; import { useExtendButtonsPositioning } from "./hooks/useExtendButtonsPositioning.js"; import { useTableHandlesPositioning } from "./hooks/useTableHandlesPositioning.js"; +import { TableCellHandle } from "./TableCellHandle.js"; +import { TableCellHandleProps } from "./TableCellHandleProps.js"; export const TableHandlesController = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >(props: { + tableCellHandle?: FC>; tableHandle?: FC>; extendButton?: FC>; }) => { @@ -75,7 +78,7 @@ export const TableHandlesController = < state?.draggingState?.mousePos, ]); - const { rowHandle, colHandle } = useTableHandlesPositioning( + const { rowHandle, colHandle, cellHandle } = useTableHandlesPositioning( state?.show || false, state?.referencePosCell || null, state?.referencePosTable || null, @@ -98,6 +101,7 @@ export const TableHandlesController = < const TableHandleComponent = props.tableHandle || TableHandle; const ExtendButtonComponent = props.extendButton || ExtendButton; + const TableCellHandleComponent = props.tableCellHandle || TableCellHandle; return ( <> @@ -148,6 +152,23 @@ export const TableHandlesController = < )} + {menuContainerRef && + cellHandle.isMounted && + state.colIndex !== undefined && + state.rowIndex !== undefined && ( +
+ +
+ )} + {/* note that the extend buttons are always shown (we don't look at isMounted etc, because otherwise the table slightly shifts when they unmount */}
{ // Will be null on initial render when used in UI component controllers. - if (referencePosCell === null || referencePosTable === null) { + if ( + referencePosCell === null || + referencePosTable === null || + // Ignore cell handle when dragging + (draggingState && orientation === "cell") + ) { return; } @@ -84,7 +105,9 @@ function useTableHandlePosition( const fn = orientation === "row" ? getBoundingClientRectRow - : getBoundingClientRectCol; + : orientation === "col" + ? getBoundingClientRectCol + : getBoundingClientRectCell; return fn(referencePosCell, referencePosTable, draggingState); }, }); @@ -115,6 +138,7 @@ export function useTableHandlesPositioning( ): { rowHandle: ReturnType; colHandle: ReturnType; + cellHandle: ReturnType; } { const rowHandle = useTableHandlePosition( "row", @@ -130,12 +154,20 @@ export function useTableHandlesPositioning( referencePosTable, draggingState ); + const cellHandle = useTableHandlePosition( + "cell", + show, + referencePosCell, + referencePosTable, + draggingState + ); return useMemo( () => ({ rowHandle, colHandle, + cellHandle, }), - [colHandle, rowHandle] + [colHandle, rowHandle, cellHandle] ); } From fb05167cb91f206f80c7cc4b9591085141a17429 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Mon, 17 Feb 2025 18:12:16 +0100 Subject: [PATCH 07/72] fix: only show split button when it can be split --- .../DefaultButtons/SplitButton.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx index e2a3ad508f..6c08ddaf42 100644 --- a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx +++ b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx @@ -3,6 +3,7 @@ import { DefaultInlineContentSchema, DefaultStyleSchema, InlineContentSchema, + isTableCell, StyleSchema, } from "@blocknote/core"; @@ -25,6 +26,18 @@ export const SplitButton = < S >(); + const currentCell = + props.block.content.rows[props.rowIndex]?.cells?.[props.colIndex]; + + if ( + !currentCell || + !isTableCell(currentCell) || + ((currentCell.props.rowspan ?? 1) === 1 && + (currentCell.props.colspan ?? 1) === 1) + ) { + return null; + } + return ( { From 26f57cfa1b7b8508d08a3e5745d47939e1b201a1 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Mon, 17 Feb 2025 18:13:07 +0100 Subject: [PATCH 08/72] refactor: better typing for table ops --- .../api/blockManipulation/tables/tables.ts | 30 +++++++------------ .../TableHandles/TableHandlesPlugin.ts | 2 +- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 4413abb0b1..ca05a57f6f 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -1,5 +1,9 @@ -import { Block, DefaultBlockSchema } from "../../../blocks/defaultBlocks.js"; -import { isTableCell, TableContent } from "../../../schema/blocks/types.js"; +import { DefaultBlockSchema } from "../../../blocks/defaultBlocks.js"; +import { + BlockFromConfigNoChildren, + isTableCell, + TableContent, +} from "../../../schema/blocks/types.js"; /** * This will resolve the relative cell indices within the table block to the absolute cell indices within the table. @@ -15,13 +19,7 @@ export function resolveRelativeTableCellIndices( /** * The table block containing the cell. */ - block: Block< - { - table: DefaultBlockSchema["table"]; - }, - any, - any - > + block: BlockFromConfigNoChildren ): { row: number; col: number } { // Calculate column index by summing colspan values of previous cells in the row let colIndex = 0; @@ -46,7 +44,7 @@ export function resolveRelativeTableCellIndices( * @returns The height and width of the table. */ export function getDimensionsOfTable( - block: Block<{ table: DefaultBlockSchema["table"] }, any, any> + block: BlockFromConfigNoChildren ) { const height = block.content.rows.length; const width = block.content.rows.reduce((acc, { cells }) => { @@ -75,13 +73,7 @@ export function resolveAbsoluteTableCellIndices( /** * The table block containing the cell. */ - block: Block< - { - table: DefaultBlockSchema["table"]; - }, - any, - any - > + block: BlockFromConfigNoChildren ): { row: number; col: number; @@ -166,7 +158,7 @@ export function resolveAbsoluteTableCellIndices( * @returns The row of the table. */ export function getRow( - block: Block<{ table: DefaultBlockSchema["table"] }, any, any>, + block: BlockFromConfigNoChildren, relativeRowIndex: number ) { const { width } = getDimensionsOfTable(block); @@ -195,7 +187,7 @@ export function getRow( * @returns The column of the table. */ export function getColumn( - block: Block<{ table: DefaultBlockSchema["table"] }, any, any>, + block: BlockFromConfigNoChildren, relativeColumnIndex: number ) { const { height } = getDimensionsOfTable(block); diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 1d96468698..31020955c3 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -498,7 +498,7 @@ export class TableHandlesView< } const { height: rowCount, width: colCount } = getDimensionsOfTable( - this.state.block as any + this.state.block ); if ( From ae9da58a0df4cba73f2a351a14ef56e733347167 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Mon, 17 Feb 2025 18:16:12 +0100 Subject: [PATCH 09/72] feat: allow text align buttons to work on table cell content --- .../DefaultButtons/TextAlignButton.tsx | 86 +++++++++++++++++-- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx index a2237ca6ed..0f14549029 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx @@ -5,7 +5,9 @@ import { DefaultProps, InlineContentSchema, isTableCellSelection, + mapTableCell, StyleSchema, + TableContent, } from "@blocknote/core"; import { useCallback, useMemo } from "react"; import { IconType } from "react-icons"; @@ -61,6 +63,78 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { editor.updateBlock(block, { props: { textAlignment: textAlignment }, }); + } else if (block.type === "table") { + // TODO document this, it is a mess + const state = editor.prosemirrorState; + const selection = state.selection; + + let $fromCell = selection.$from; + let $toCell = selection.$to; + if (isTableCellSelection(selection)) { + const { ranges } = selection; + ranges.forEach((range) => { + $fromCell = range.$from.min($fromCell ?? range.$from); + $toCell = range.$to.max($toCell ?? range.$to); + }); + } else { + // Assumes we are within a tableParagraph + $fromCell = state.doc.resolve( + selection.$from.pos - selection.$from.parentOffset - 1 + ); + $toCell = state.doc.resolve( + selection.$to.pos - selection.$to.parentOffset - 1 + ); + } + + const $fromRow = state.doc.resolve( + $fromCell.pos - $fromCell.parentOffset - 1 + ); + const $toRow = state.doc.resolve( + $toCell.pos - $toCell.parentOffset - 1 + ); + const $table = state.doc.resolve( + $fromRow.pos - $fromRow.parentOffset - 1 + ); + + const fromColIndex = $fromCell.index($fromRow.depth); + const fromRowIndex = $fromRow.index($table.depth); + const toColIndex = $toCell.index($toRow.depth); + const toRowIndex = $toRow.index($table.depth); + + const newTable = (block.content as TableContent).rows.map( + (row, r) => { + return { + ...row, + cells: row.cells.map((cell, c) => { + const mappedCell = mapTableCell(cell); + // If the cell is within the selected range, update the text alignment + if ( + fromRowIndex <= r && + r <= toRowIndex && + fromColIndex <= c && + c <= toColIndex + ) { + return { + ...mappedCell, + props: { + ...mappedCell.props, + textAlignment: textAlignment, + }, + }; + } + return mappedCell; + }), + }; + } + ); + + editor.updateBlock(block, { + type: "table", + content: { + type: "tableContent", + rows: newTable, + } as any, + }); } } }, @@ -68,14 +142,12 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { ); const show = useMemo(() => { - return ( - !!selectedBlocks.find( - (block) => - "textAlignment" in block.props || - (block.type === "table" && block.children) - ) && !isTableCellSelection(editor._tiptapEditor.state.selection) + return !!selectedBlocks.find( + (block) => + "textAlignment" in block.props || + (block.type === "table" && block.children) ); - }, [editor._tiptapEditor.state.selection, selectedBlocks]); + }, [selectedBlocks]); if (!show || !editor.isEditable) { return null; From 951252c892874f6e0ac2fcffec67fdb1064dcd2c Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Tue, 18 Feb 2025 10:51:44 +0100 Subject: [PATCH 10/72] feat: support table header rows & cols --- .../src/api/nodeConversions/blockToNode.ts | 19 ++-- .../src/api/nodeConversions/nodeToBlock.ts | 32 +++++- packages/core/src/editor/editor.css | 1 - .../BackgroundColorExtension.ts | 2 +- .../TextAlignment/TextAlignmentExtension.ts | 1 + .../TextColor/TextColorExtension.ts | 2 +- packages/core/src/i18n/locales/en.ts | 2 + .../DefaultItems/TableHeadersItem.tsx | 102 ++++++++++++++++++ .../DragHandleMenu/DragHandleMenu.tsx | 10 ++ 9 files changed, 159 insertions(+), 12 deletions(-) create mode 100644 packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index c0fc7d635c..95dc16d329 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -175,11 +175,16 @@ export function tableContentToNodes< styleSchema: StyleSchema ): Node[] { const rowNodes: Node[] = []; + const headerRows = new Array(tableContent.headerRows ?? 0).fill(true); + const headerCols = new Array(tableContent.headerCols ?? 0).fill(true); - for (const row of tableContent.rows) { + for (let rowIndex = 0; rowIndex < tableContent.rows.length; rowIndex++) { + const row = tableContent.rows[rowIndex]; const columnNodes: Node[] = []; - for (let i = 0; i < row.cells.length; i++) { - const cell = row.cells[i]; + const isHeaderRow = headerRows[rowIndex]; + for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex++) { + const cell = row.cells[cellIndex]; + const isHeaderCol = headerCols[cellIndex]; let attrs: Attrs | null = null; let content: Fragment | Node | readonly Node[] | null = null; const marks: undefined | readonly Mark[] = undefined; @@ -195,14 +200,16 @@ export function tableContentToNodes< content = inlineContentToNodes(cell, schema, styleSchema); } - const cellNode = schema.nodes["tableCell"].createChecked( + const cellNode = schema.nodes[ + isHeaderCol || isHeaderRow ? "tableHeader" : "tableCell" + ].createChecked( { // TODO modify // The colwidth array should have multiple values when the colspan of // a cell is greater than 1. However, this is not yet implemented so // we can always assume a length of 1. - colwidth: tableContent.columnWidths?.[i] - ? [tableContent.columnWidths[i]] + colwidth: tableContent.columnWidths?.[cellIndex] + ? [tableContent.columnWidths[cellIndex]] : null, ...(isPartialTableCell(cell) ? cell.props : {}), }, diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index da9c2124ca..d9e0e29531 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -32,15 +32,23 @@ export function contentNodeToTableContent< const ret: TableContent = { type: "tableContent", columnWidths: [], + headerRows: undefined, + headerCols: undefined, rows: [], }; - contentNode.content.forEach((rowNode, _offset, index) => { + /** + * A matrix of boolean values indicating whether a cell is a header. + * The first index is the row index, the second index is the cell index. + */ + const headerMatrix: boolean[][] = []; + + contentNode.content.forEach((rowNode, _offset, rowIndex) => { const row: TableContent["rows"][0] = { cells: [], }; - if (index === 0) { + if (rowIndex === 0) { rowNode.content.forEach((cellNode) => { // TODO implement // The colwidth array should have multiple values when the colspan of a @@ -50,7 +58,13 @@ export function contentNodeToTableContent< }); } - row.cells = rowNode.content.content.map((cellNode) => { + row.cells = rowNode.content.content.map((cellNode, cellIndex) => { + if (!headerMatrix[rowIndex]) { + headerMatrix[rowIndex] = []; + } + // Mark the cell as a header if it is a tableHeader node. + headerMatrix[rowIndex][cellIndex] = cellNode.type.name === "tableHeader"; + return { type: "tableCell", content: contentNodeToInlineContent( @@ -71,6 +85,18 @@ export function contentNodeToTableContent< ret.rows.push(row); }); + for (let i = 0; i < headerMatrix.length; i++) { + if (headerMatrix[i].every((isHeader) => isHeader)) { + ret.headerRows = (ret.headerRows ?? 0) + 1; + } + } + + for (let i = 0; i < headerMatrix[0].length; i++) { + if (headerMatrix.every((row) => row[i])) { + ret.headerCols = (ret.headerCols ?? 0) + 1; + } + } + return ret; } diff --git a/packages/core/src/editor/editor.css b/packages/core/src/editor/editor.css index 797ffea0b2..27abebc140 100644 --- a/packages/core/src/editor/editor.css +++ b/packages/core/src/editor/editor.css @@ -156,7 +156,6 @@ Tippy popups that are appended to document.body directly .bn-editor [data-content-type="table"] th { font-weight: bold; - text-align: left; } /* tiptap uses colwidth instead of data-colwidth, se we need to adjust this style from prosemirror-tables */ diff --git a/packages/core/src/extensions/BackgroundColor/BackgroundColorExtension.ts b/packages/core/src/extensions/BackgroundColor/BackgroundColorExtension.ts index e9dfe16818..fca4922a1a 100644 --- a/packages/core/src/extensions/BackgroundColor/BackgroundColorExtension.ts +++ b/packages/core/src/extensions/BackgroundColor/BackgroundColorExtension.ts @@ -7,7 +7,7 @@ export const BackgroundColorExtension = Extension.create({ addGlobalAttributes() { return [ { - types: ["blockContainer", "tableCell"], + types: ["blockContainer", "tableCell", "tableHeader"], attributes: { backgroundColor: { default: defaultProps.backgroundColor.default, diff --git a/packages/core/src/extensions/TextAlignment/TextAlignmentExtension.ts b/packages/core/src/extensions/TextAlignment/TextAlignmentExtension.ts index 814b040322..cabde01b91 100644 --- a/packages/core/src/extensions/TextAlignment/TextAlignmentExtension.ts +++ b/packages/core/src/extensions/TextAlignment/TextAlignmentExtension.ts @@ -15,6 +15,7 @@ export const TextAlignmentExtension = Extension.create({ "numberedListItem", "checkListItem", "tableCell", + "tableHeader", ], attributes: { textAlignment: { diff --git a/packages/core/src/extensions/TextColor/TextColorExtension.ts b/packages/core/src/extensions/TextColor/TextColorExtension.ts index 1b949f20e1..4060fea6d6 100644 --- a/packages/core/src/extensions/TextColor/TextColorExtension.ts +++ b/packages/core/src/extensions/TextColor/TextColorExtension.ts @@ -7,7 +7,7 @@ export const TextColorExtension = Extension.create({ addGlobalAttributes() { return [ { - types: ["blockContainer", "tableCell"], + types: ["blockContainer", "tableCell", "tableHeader"], attributes: { textColor: { default: defaultProps.textColor.default, diff --git a/packages/core/src/i18n/locales/en.ts b/packages/core/src/i18n/locales/en.ts index 90ac64d603..27a863bc61 100644 --- a/packages/core/src/i18n/locales/en.ts +++ b/packages/core/src/i18n/locales/en.ts @@ -152,6 +152,8 @@ export const en = { drag_handle: { delete_menuitem: "Delete", colors_menuitem: "Colors", + header_row_menuitem: "Header row", + header_column_menuitem: "Header column", }, table_handle: { delete_column_menuitem: "Delete column", diff --git a/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx b/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx new file mode 100644 index 0000000000..ecc9e8caba --- /dev/null +++ b/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx @@ -0,0 +1,102 @@ +import { + BlockSchema, + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + SpecificBlock, + StyleSchema, +} from "@blocknote/core"; +import { ReactNode } from "react"; + +import { useComponentsContext } from "../../../../editor/ComponentsContext.js"; +import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor.js"; +import { DragHandleMenuProps } from "../DragHandleMenuProps.js"; + +export const TableRowHeaderItem = < + BSchema extends BlockSchema = DefaultBlockSchema, + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: Omit, "block"> & { + block: SpecificBlock<{ table: DefaultBlockSchema["table"] }, "table", I, S>; + children: ReactNode; + } +) => { + const Components = useComponentsContext()!; + + const editor = useBlockNoteEditor< + { table: DefaultBlockSchema["table"] }, + I, + S + >(); + + if (props.block.type !== "table") { + return null; + } + + // TODO only support 1 header row for now + const isHeaderRow = Boolean(props.block.content.headerRows); + + return ( + { + editor.updateBlock(props.block, { + type: "table", + content: { + ...props.block.content, + type: "tableContent", + headerRows: isHeaderRow ? undefined : 1, + }, + }); + }}> + {props.children} + + ); +}; + +export const TableColumnHeaderItem = < + BSchema extends BlockSchema = DefaultBlockSchema, + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: Omit, "block"> & { + block: SpecificBlock<{ table: DefaultBlockSchema["table"] }, "table", I, S>; + children: ReactNode; + } +) => { + const Components = useComponentsContext()!; + + const editor = useBlockNoteEditor< + { table: DefaultBlockSchema["table"] }, + I, + S + >(); + + if (props.block.type !== "table") { + return null; + } + + // TODO only support 1 header column for now + const isHeaderColumn = Boolean(props.block.content.headerCols); + + return ( + { + editor.updateBlock(props.block, { + type: "table", + content: { + ...props.block.content, + type: "tableContent", + headerCols: isHeaderColumn ? undefined : 1, + }, + }); + }}> + {props.children} + + ); +}; diff --git a/packages/react/src/components/SideMenu/DragHandleMenu/DragHandleMenu.tsx b/packages/react/src/components/SideMenu/DragHandleMenu/DragHandleMenu.tsx index f28b907cd5..53910b69a7 100644 --- a/packages/react/src/components/SideMenu/DragHandleMenu/DragHandleMenu.tsx +++ b/packages/react/src/components/SideMenu/DragHandleMenu/DragHandleMenu.tsx @@ -13,6 +13,10 @@ import { useDictionary } from "../../../i18n/dictionary.js"; import { BlockColorsItem } from "./DefaultItems/BlockColorsItem.js"; import { RemoveBlockItem } from "./DefaultItems/RemoveBlockItem.js"; import { DragHandleMenuProps } from "./DragHandleMenuProps.js"; +import { + TableColumnHeaderItem, + TableRowHeaderItem, +} from "./DefaultItems/TableHeadersItem.js"; /** * By default, the DragHandleMenu component will render with default items. @@ -43,6 +47,12 @@ export const DragHandleMenu = < {dict.drag_handle.colors_menuitem} + + {dict.drag_handle.header_row_menuitem} + + + {dict.drag_handle.header_column_menuitem} + )} From 539d8bcc09dcc0c3cf357962d2bda51a9d0411cb Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Tue, 18 Feb 2025 15:58:56 +0100 Subject: [PATCH 11/72] fix: disallow dragging columns with rowspans or colspans more than 1 --- .../TableHandles/TableHandlesPlugin.ts | 6 ++--- packages/mantine/src/style.css | 4 +++ .../components/TableHandles/TableHandle.tsx | 27 ++++++++++++++++--- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 31020955c3..cd1ad3c51f 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -847,13 +847,13 @@ export class TableHandlesProsemirrorPlugin< resolveRelativeTableCellIndices = ( relativeCellIndices: { row: number; col: number }, - block: Block<{ table: DefaultBlockSchema["table"] }, any, any> + block: BlockFromConfigNoChildren ) => { return resolveRelativeTableCellIndices(relativeCellIndices, block); }; getRow = ( - block: Block<{ table: DefaultBlockSchema["table"] }, any, any>, + block: BlockFromConfigNoChildren, relativeRowIndex: number ) => { return getRow(block, relativeRowIndex); @@ -863,7 +863,7 @@ export class TableHandlesProsemirrorPlugin< * Get all the cells in a column of the table block. */ getColumn = ( - block: Block<{ table: DefaultBlockSchema["table"] }, any, any>, + block: BlockFromConfigNoChildren, relativeColumnIndex: number ) => { return getColumn(block, relativeColumnIndex); diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css index 0028eb8435..3fdb22a7db 100644 --- a/packages/mantine/src/style.css +++ b/packages/mantine/src/style.css @@ -514,6 +514,10 @@ margin-inline: -4px; } +.bn-mantine .bn-table-handle-not-draggable { + cursor: default; +} + .bn-mantine .bn-table-handle:hover, .bn-mantine .bn-table-handle-dragging, .bn-mantine .bn-extend-button:hover, diff --git a/packages/react/src/components/TableHandles/TableHandle.tsx b/packages/react/src/components/TableHandles/TableHandle.tsx index 451de4b845..2eb37a5e3c 100644 --- a/packages/react/src/components/TableHandles/TableHandle.tsx +++ b/packages/react/src/components/TableHandles/TableHandle.tsx @@ -2,10 +2,11 @@ import { DefaultInlineContentSchema, DefaultStyleSchema, InlineContentSchema, + isTableCell, mergeCSSClasses, StyleSchema, } from "@blocknote/core"; -import { ReactNode, useState } from "react"; +import { ReactNode, useMemo, useState } from "react"; import { createPortal } from "react-dom"; import { MdDragIndicator } from "react-icons/md"; @@ -29,6 +30,25 @@ export const TableHandle = < const Component = props.tableHandleMenu || TableHandleMenu; + const isDraggable = useMemo(() => { + const tableHandles = props.editor.tableHandles; + if (!tableHandles) { + return false; + } + + if (props.orientation === "column") { + return tableHandles + .getColumn(props.block, props.index) + .every(({ cell }) => + isTableCell(cell) ? (cell.props.colspan ?? 1) <= 1 : true + ); + } + + return tableHandles + .getRow(props.block, props.index) + .every(({ cell }) => isTableCell(cell) && (cell.props.rowspan ?? 1) <= 1); + }, [props.block, props.editor.tableHandles, props.index, props.orientation]); + return ( { @@ -46,9 +66,10 @@ export const TableHandle = < { setIsDragging(true); props.dragStart(e); From f3f88a145f77f69ca6dbd1cd766efe3e38a09678 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Tue, 18 Feb 2025 15:59:50 +0100 Subject: [PATCH 12/72] refactor: add getColspan and getRowspan helpers --- .../api/blockManipulation/tables/tables.ts | 21 +++++++++------- packages/core/src/schema/blocks/types.ts | 24 +++++++++++++++++++ .../DefaultButtons/SplitButton.tsx | 5 ++-- .../components/TableHandles/TableHandle.tsx | 9 ++++--- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index ca05a57f6f..8a4ad5b52c 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -1,7 +1,8 @@ import { DefaultBlockSchema } from "../../../blocks/defaultBlocks.js"; import { BlockFromConfigNoChildren, - isTableCell, + getColspan, + getRowspan, TableContent, } from "../../../schema/blocks/types.js"; @@ -25,14 +26,14 @@ export function resolveRelativeTableCellIndices( let colIndex = 0; for (let i = 0; i < relativeCellIndices.col; i++) { const cell = block.content.rows[relativeCellIndices.row].cells[i]; - colIndex += isTableCell(cell) ? cell.props?.colspan ?? 1 : 1; + colIndex += getColspan(cell); } // Calculate row index by summing rowspan values of cells in previous rows let rowIndex = 0; for (let i = 0; i < relativeCellIndices.row; i++) { const cell = block.content.rows[i].cells[relativeCellIndices.col]; - rowIndex += isTableCell(cell) ? cell.props?.rowspan ?? 1 : 1; + rowIndex += getRowspan(cell); } return { row: rowIndex, col: colIndex }; @@ -51,7 +52,7 @@ export function getDimensionsOfTable( return Math.max( acc, cells.reduce((acc, cell) => { - return acc + (isTableCell(cell) ? cell.props?.colspan ?? 1 : 1); + return acc + getColspan(cell); }, 0) ); }, 0); @@ -110,8 +111,8 @@ export function resolveAbsoluteTableCellIndices( for (let row = 0; row < block.content.rows.length; row++) { for (let col = 0; col < block.content.rows[row].cells.length; col++) { const cell = block.content.rows[row].cells[col]; - const rowspan = isTableCell(cell) ? cell.props?.rowspan ?? 1 : 1; - const colspan = isTableCell(cell) ? cell.props?.colspan ?? 1 : 1; + const rowspan = getRowspan(cell); + const colspan = getColspan(cell); // Rowspan and colspan complicate things, by taking up multiple cells in the grid // We need to iterate over the cells that the rowspan and colspan take up @@ -176,7 +177,9 @@ export function getRow( // Filter out duplicates based on row and col properties return cells.filter((cell, index) => { return ( - cells.findIndex((c) => c.row === cell.row && c.col === cell.col) === index + cell && + cells.findIndex((c) => c && c.row === cell.row && c.col === cell.col) === + index ); }); } @@ -205,7 +208,9 @@ export function getColumn( // Filter out duplicates based on row and col properties return cells.filter((cell, index) => { return ( - cells.findIndex((c) => c.row === cell.row && c.col === cell.col) === index + cell && + cells.findIndex((c) => c && c.row === cell.row && c.col === cell.col) === + index ); }); } diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index 0f0769bd77..62ef7f483e 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -395,3 +395,27 @@ export function isTableCell< content.content !== undefined ); } + +export function getColspan( + cell: + | TableCell + | PartialTableCell + | PartialInlineContent +): number { + if (isTableCell(cell)) { + return cell.props.colspan ?? 1; + } + return 1; +} + +export function getRowspan( + cell: + | TableCell + | PartialTableCell + | PartialInlineContent +): number { + if (isTableCell(cell)) { + return cell.props.rowspan || 1; + } + return 1; +} diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx index 6c08ddaf42..cf4de400ee 100644 --- a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx +++ b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx @@ -2,6 +2,8 @@ import { DefaultBlockSchema, DefaultInlineContentSchema, DefaultStyleSchema, + getColspan, + getRowspan, InlineContentSchema, isTableCell, StyleSchema, @@ -32,8 +34,7 @@ export const SplitButton = < if ( !currentCell || !isTableCell(currentCell) || - ((currentCell.props.rowspan ?? 1) === 1 && - (currentCell.props.colspan ?? 1) === 1) + (getRowspan(currentCell) === 1 && getColspan(currentCell) === 1) ) { return null; } diff --git a/packages/react/src/components/TableHandles/TableHandle.tsx b/packages/react/src/components/TableHandles/TableHandle.tsx index 2eb37a5e3c..92cbe08592 100644 --- a/packages/react/src/components/TableHandles/TableHandle.tsx +++ b/packages/react/src/components/TableHandles/TableHandle.tsx @@ -1,8 +1,9 @@ import { DefaultInlineContentSchema, DefaultStyleSchema, + getColspan, + getRowspan, InlineContentSchema, - isTableCell, mergeCSSClasses, StyleSchema, } from "@blocknote/core"; @@ -39,14 +40,12 @@ export const TableHandle = < if (props.orientation === "column") { return tableHandles .getColumn(props.block, props.index) - .every(({ cell }) => - isTableCell(cell) ? (cell.props.colspan ?? 1) <= 1 : true - ); + .every(({ cell }) => getColspan(cell) === 1); } return tableHandles .getRow(props.block, props.index) - .every(({ cell }) => isTableCell(cell) && (cell.props.rowspan ?? 1) <= 1); + .every(({ cell }) => getRowspan(cell) === 1); }, [props.block, props.editor.tableHandles, props.index, props.orientation]); return ( From 895f4f4edf763be72d6d6342954628f6557845e8 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Tue, 18 Feb 2025 16:00:23 +0100 Subject: [PATCH 13/72] fix: better handling of dragging to be row and colspan aware --- .../TableHandles/TableHandlesPlugin.ts | 55 +++++++++++++++---- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index cd1ad3c51f..619cd15723 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -10,12 +10,15 @@ import { import { nodeToBlock } from "../../api/nodeConversions/nodeToBlock.js"; import { getNodeById } from "../../api/nodeUtil.js"; import { checkBlockIsDefaultType } from "../../blocks/defaultBlockTypeGuards.js"; -import { Block, DefaultBlockSchema } from "../../blocks/defaultBlocks.js"; +import { DefaultBlockSchema } from "../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { BlockFromConfigNoChildren, BlockSchemaWithBlock, + getColspan, + getRowspan, InlineContentSchema, + mapTableCell, StyleSchema, } from "../../schema/index.js"; import { EventEmitter } from "../../util/EventEmitter.js"; @@ -451,18 +454,50 @@ export class TableHandlesView< const { draggingState, colIndex, rowIndex } = this.state; - const rows = this.state.block.content.rows; + const newTable = this.state.block.content.rows.map((row) => { + return { + ...row, + cells: row.cells.map((cell) => mapTableCell(cell)), + }; + }); - // TODO need to handle rowspan and colspan, by resolving relative indices if (draggingState.draggedCellOrientation === "row") { - const rowToMove = rows[draggingState.originalIndex]; - rows.splice(draggingState.originalIndex, 1); - rows.splice(rowIndex, 0, rowToMove); + const row = getRow(this.state.block, rowIndex); + // TODO need to work on this logic + if ( + row.some((cell) => { + const rowspan = getRowspan(cell.cell); + if (rowspan === 1) { + return false; + } + return cell.row <= rowIndex && rowIndex <= cell.row + rowspan - 1; + }) + ) { + // If the row has cells with different row indices, don't move the row + return false; + } + const rowToMove = newTable[draggingState.originalIndex]; + newTable.splice(draggingState.originalIndex, 1); + newTable.splice(rowIndex, 0, rowToMove); } else { - const cellsToMove = rows.map( + const col = getColumn(this.state.block, colIndex); + // TODO need to work on this logic + if ( + col.some((cell) => { + const colspan = getColspan(cell.cell); + if (colspan === 1) { + return false; + } + return cell.col <= colIndex && colIndex <= cell.col + colspan - 1; + }) + ) { + // If the column has cells with different col indices, don't move the column + return false; + } + const cellsToMove = newTable.map( (row) => row.cells[draggingState.originalIndex] ); - rows.forEach((row, rowIndex) => { + newTable.forEach((row, rowIndex) => { row.cells.splice(draggingState.originalIndex, 1); row.cells.splice(colIndex, 0, cellsToMove[rowIndex] as any); }); @@ -471,8 +506,8 @@ export class TableHandlesView< this.editor.updateBlock(this.state.block, { type: "table", content: { - type: "tableContent", - rows: rows, + ...this.state.block.content, + rows: newTable, }, }); From f2625e0b5d66ae6234a291a07387d231b347159c Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Tue, 18 Feb 2025 18:14:13 +0100 Subject: [PATCH 14/72] refactor: resolve columnWidth preservation --- .../api/blockManipulation/tables/tables.ts | 156 ++++++++++++++---- .../src/api/nodeConversions/blockToNode.ts | 52 ++++-- .../src/api/nodeConversions/nodeToBlock.ts | 10 +- .../TableHandles/TableHandlesPlugin.ts | 11 +- packages/core/src/schema/blocks/types.ts | 10 +- .../DefaultButtons/TextAlignButton.tsx | 13 +- 6 files changed, 190 insertions(+), 62 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 8a4ad5b52c..48abc21796 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -3,6 +3,8 @@ import { BlockFromConfigNoChildren, getColspan, getRowspan, + mapTableCell, + TableCell, TableContent, } from "../../../schema/blocks/types.js"; @@ -59,27 +61,41 @@ export function getDimensionsOfTable( return { height, width }; } - /** - * This will resolve the absolute cell indices within the table block to the relative cell indices within the table. - * Accounts for colspan and rowspan. - * - * @returns The relative cell indices (row and column). + * An occupancy grid is a grid of the occupied cells in the table. + * It is used to track the occupied cells in the table to know where to place the next cell. */ -export function resolveAbsoluteTableCellIndices( +type OccupancyGrid = { /** - * The absolute position of the cell in the table. + * The row index of the cell. */ - absoluteCellIndices: { row: number; col: number }, + row: number; /** - * The table block containing the cell. + * The column index of the cell. */ - block: BlockFromConfigNoChildren -): { - row: number; col: number; - cell: TableContent["rows"][number]["cells"][number]; -} | null { + /** + * The rowspan of the cell. + */ + rowspan: number; + /** + * The colspan of the cell. + */ + colspan: number; + /** + * The cell. + */ + cell: TableCell; +}[][]; + +/** + * This will return a grid of the occupied cells in the table. + * + * @returns The grid of occupied cells. + */ +export function getTableCellOccupancyGrid( + block: BlockFromConfigNoChildren +): OccupancyGrid | null { const { height, width } = getDimensionsOfTable(block); /* @@ -87,13 +103,15 @@ export function resolveAbsoluteTableCellIndices( * This is used because rowspans and colspans take up multiple spaces * So, we need to track the occupied cells in the grid to know where to place the next cell */ - const grid: boolean[][] = new Array(height) + const grid: ({ + row: number; + col: number; + rowspan: number; + colspan: number; + cell: TableCell; + } | null)[][] = new Array(height) .fill(false) - .map(() => new Array(width).fill(false)); - - if (absoluteCellIndices.row >= height || absoluteCellIndices.col >= width) { - return null; - } + .map(() => new Array(width).fill(null)); // Find the next unoccupied cell in the table, row-major order function findNextAvailable(row: number, col: number) { @@ -110,7 +128,7 @@ export function resolveAbsoluteTableCellIndices( // Build up the grid, trying to fill in the cells with the correct relative row and column indices for (let row = 0; row < block.content.rows.length; row++) { for (let col = 0; col < block.content.rows[row].cells.length; col++) { - const cell = block.content.rows[row].cells[col]; + const cell = mapTableCell(block.content.rows[row].cells[col]); const rowspan = getRowspan(cell); const colspan = getColspan(cell); @@ -134,23 +152,75 @@ export function resolveAbsoluteTableCellIndices( return null; } - grid[i][j] = true; - - if (i === absoluteCellIndices.row && j === absoluteCellIndices.col) { - // If the cell is occupied, return the relative cell indices - return { - row, - col, - cell, - }; - } + grid[i][j] = { + row, + col, + rowspan, + colspan, + cell, + }; } } } } - // We did not find the cell, so return null - return null; + function isOccupancyGrid(grid: any[][]): grid is OccupancyGrid { + return grid.every((row) => + row.every((cell) => cell !== null && typeof cell === "object") + ); + } + + if (!isOccupancyGrid(grid)) { + // The table is missing cells, so it is invalid + return null; + } + + return grid; +} + +/** + * This will resolve the absolute cell indices within the table block to the relative cell indices within the table. + * Accounts for colspan and rowspan. + * + * @returns The relative cell indices (row and column). + */ +export function resolveAbsoluteTableCellIndices( + /** + * The absolute position of the cell in the table. + */ + absoluteCellIndices: { row: number; col: number }, + /** + * The table block containing the cell. + */ + block: BlockFromConfigNoChildren, + /** + * The occupancy grid of the table. + */ + occupancyGrid: OccupancyGrid | null = getTableCellOccupancyGrid(block) +): { + row: number; + col: number; + cell: TableContent["rows"][number]["cells"][number]; +} | null { + if (!occupancyGrid) { + // The table is missing cells, so it is invalid + return null; + } + + const occupancyCell = + occupancyGrid[absoluteCellIndices.row]?.[absoluteCellIndices.col]; + + // Double check that the cell can be accessed + if (!occupancyCell) { + // The cell is not occupied, so it is invalid + return null; + } + + return { + row: occupancyCell.row, + col: occupancyCell.col, + cell: occupancyCell.cell, + }; } /** @@ -162,6 +232,10 @@ export function getRow( block: BlockFromConfigNoChildren, relativeRowIndex: number ) { + const occupancyGrid = getTableCellOccupancyGrid(block); + if (!occupancyGrid) { + throw new Error("Table is malformed"); + } const { width } = getDimensionsOfTable(block); // First need to resolve the relative row index to an absolute row index const { row: absoluteRow } = resolveRelativeTableCellIndices( @@ -171,7 +245,11 @@ export function getRow( // Then for each column, get the cell at the absolute row index as a relative cell index const cells = new Array(width).fill(false).map((_v, col) => { - return resolveAbsoluteTableCellIndices({ row: absoluteRow, col }, block)!; + return resolveAbsoluteTableCellIndices( + { row: absoluteRow, col }, + block, + occupancyGrid + ); }); // Filter out duplicates based on row and col properties @@ -193,6 +271,10 @@ export function getColumn( block: BlockFromConfigNoChildren, relativeColumnIndex: number ) { + const occupancyGrid = getTableCellOccupancyGrid(block); + if (!occupancyGrid) { + throw new Error("Table is malformed"); + } const { height } = getDimensionsOfTable(block); // First need to resolve the relative column index to an absolute column index const { col: absoluteCol } = resolveRelativeTableCellIndices( @@ -202,7 +284,11 @@ export function getColumn( // Then for each row, get the cell at the absolute column index as a relative cell index const cells = new Array(height).fill(false).map((_v, row) => { - return resolveAbsoluteTableCellIndices({ row, col: absoluteCol }, block)!; + return resolveAbsoluteTableCellIndices( + { row, col: absoluteCol }, + block, + occupancyGrid + ); }); // Filter out duplicates based on row and col properties diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 95dc16d329..6e52523d16 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -12,12 +12,13 @@ import type { } from "../../schema"; import type { PartialBlock } from "../../blocks/defaultBlocks"; +import { isPartialTableCell } from "../../schema/blocks/types.js"; import { isPartialLinkInlineContent, isStyledTextInlineContent, } from "../../schema/inlineContent/types.js"; -import { isPartialTableCell } from "../../schema/blocks/types.js"; import { UnreachableCaseError } from "../../util/typescript.js"; +import { resolveRelativeTableCellIndices } from "../blockManipulation/tables/tables.js"; /** * Convert a StyledText inline element to a @@ -175,9 +176,14 @@ export function tableContentToNodes< styleSchema: StyleSchema ): Node[] { const rowNodes: Node[] = []; + // Header rows and columns are used to determine the type of the cell + // If headerRows is 1, then the first row is a header row const headerRows = new Array(tableContent.headerRows ?? 0).fill(true); + // If headerCols is 1, then the first column is a header column const headerCols = new Array(tableContent.headerCols ?? 0).fill(true); + const columnWidths: (number | null)[] = tableContent.columnWidths ?? []; + for (let rowIndex = 0; rowIndex < tableContent.rows.length; rowIndex++) { const row = tableContent.rows[rowIndex]; const columnNodes: Node[] = []; @@ -185,17 +191,45 @@ export function tableContentToNodes< for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex++) { const cell = row.cells[cellIndex]; const isHeaderCol = headerCols[cellIndex]; - let attrs: Attrs | null = null; + /** + * The attributes of the cell to apply to the node + */ + const attrs: Attrs | null = null; + /** + * The content of the cell to apply to the node + */ let content: Fragment | Node | readonly Node[] | null = null; - const marks: undefined | readonly Mark[] = undefined; + + // Colwidths are absolutely referenced to the table, so we need to resolve the relative cell index to the absolute cell index + const absoluteCellIndex = resolveRelativeTableCellIndices( + { + row: rowIndex, + col: cellIndex, + }, + { type: "table", content: tableContent } as any + ); + // Assume the column width is the width of the cell at the absolute cell index + let colwidth: (number | null)[] = [ + columnWidths[absoluteCellIndex.col] ?? null, + ]; + if (!cell) { - attrs = {}; + // No-op } else if (typeof cell === "string") { content = schema.text(cell); } else if (isPartialTableCell(cell)) { if (cell.content) { content = inlineContentToNodes(cell.content, schema, styleSchema); } + const colSpan = cell.props?.colspan; + + if (colSpan && colSpan > 1) { + // If the cell has a > 1 colspan, we need to get the column width for each cell in the span + colwidth = new Array(colSpan).fill(null).map((_, i) => { + // Starting from the absolute cell index, get the column width for each cell in the span + return columnWidths[absoluteCellIndex.col + i] ?? null; + }); + } } else { content = inlineContentToNodes(cell, schema, styleSchema); } @@ -204,16 +238,10 @@ export function tableContentToNodes< isHeaderCol || isHeaderRow ? "tableHeader" : "tableCell" ].createChecked( { - // TODO modify - // The colwidth array should have multiple values when the colspan of - // a cell is greater than 1. However, this is not yet implemented so - // we can always assume a length of 1. - colwidth: tableContent.columnWidths?.[cellIndex] - ? [tableContent.columnWidths[cellIndex]] - : null, ...(isPartialTableCell(cell) ? cell.props : {}), + colwidth, }, - schema.nodes["tableParagraph"].createChecked(attrs, content, marks) + schema.nodes["tableParagraph"].createChecked(attrs, content) ); columnNodes.push(cellNode); } diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index d9e0e29531..aa44078c65 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -50,11 +50,11 @@ export function contentNodeToTableContent< if (rowIndex === 0) { rowNode.content.forEach((cellNode) => { - // TODO implement - // The colwidth array should have multiple values when the colspan of a - // cell is greater than 1. However, this is not yet implemented so we - // can always assume a length of 1. - ret.columnWidths.push(cellNode.attrs.colwidth?.[0] || undefined); + let colWidth = cellNode.attrs.colwidth as null | number[]; + if (colWidth === null) { + colWidth = new Array(cellNode.attrs.colspan ?? 1).fill(null); + } + ret.columnWidths.push(...colWidth); }); } diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 619cd15723..e1135c12e5 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -170,7 +170,10 @@ export class TableHandlesView< "dragover", this.dragOverHandler as EventListener ); - pmView.root.addEventListener("drop", this.dropHandler as EventListener); + pmView.root.addEventListener( + "drop", + this.dropHandler as unknown as EventListener + ); } viewMousedownHandler = () => { @@ -438,7 +441,7 @@ export class TableHandlesView< dropHandler = (event: DragEvent) => { this.mouseState = "up"; if (this.state === undefined || this.state.draggingState === undefined) { - return; + return false; } if ( @@ -514,6 +517,8 @@ export class TableHandlesView< // Have to reset text cursor position to the block as `updateBlock` moves // the existing selection out of the block. this.editor.setTextCursorPosition(this.state.block.id); + + return true; }; // Updates drag handles when the table is modified or removed. update() { @@ -583,7 +588,7 @@ export class TableHandlesView< ); this.pmView.root.removeEventListener( "drop", - this.dropHandler as EventListener + this.dropHandler as unknown as EventListener ); } } diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index 62ef7f483e..f1886bb5f5 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -144,7 +144,7 @@ export type BlockSchemaWithBlock< [k in BType]: C; }; -export type TableItemProps = { +export type TableCellProps = { backgroundColor: string; textColor: string; textAlignment: "left" | "center" | "right" | "justify"; @@ -157,7 +157,7 @@ export type TableCell< S extends StyleSchema = StyleSchema > = { type: "tableCell"; - props: TableItemProps; + props: TableCellProps; content: InlineContent[]; }; @@ -166,7 +166,7 @@ export type TableContent< S extends StyleSchema = StyleSchema > = { type: "tableContent"; - columnWidths: (number | undefined)[]; + columnWidths: (number | null)[]; headerRows?: number; headerCols?: number; rows: { @@ -244,7 +244,7 @@ export type PartialTableCell< S extends StyleSchema = StyleSchema > = { type: "tableCell"; - props?: Partial; + props?: Partial; content?: PartialInlineContent; }; @@ -253,7 +253,7 @@ export type PartialTableContent< S extends StyleSchema = StyleSchema > = { type: "tableContent"; - columnWidths?: (number | undefined)[]; + columnWidths?: (number | null)[]; headerRows?: number; headerCols?: number; rows: { diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx index 0f14549029..7d7c04fd7f 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx @@ -64,20 +64,24 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { props: { textAlignment: textAlignment }, }); } else if (block.type === "table") { - // TODO document this, it is a mess + // Based on the current selection, find the table cells that are within the selected range const state = editor.prosemirrorState; const selection = state.selection; let $fromCell = selection.$from; let $toCell = selection.$to; if (isTableCellSelection(selection)) { + // When the selection is a table cell selection, we can find the + // from and to cells by iterating over the ranges in the selection const { ranges } = selection; ranges.forEach((range) => { $fromCell = range.$from.min($fromCell ?? range.$from); $toCell = range.$to.max($toCell ?? range.$to); }); } else { + // When the selection is a normal text selection // Assumes we are within a tableParagraph + // And find the from and to cells by resolving the positions $fromCell = state.doc.resolve( selection.$from.pos - selection.$from.parentOffset - 1 ); @@ -86,16 +90,20 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { ); } + // Find the row and table that the from and to cells are in const $fromRow = state.doc.resolve( $fromCell.pos - $fromCell.parentOffset - 1 ); const $toRow = state.doc.resolve( $toCell.pos - $toCell.parentOffset - 1 ); + + // Find the table const $table = state.doc.resolve( $fromRow.pos - $fromRow.parentOffset - 1 ); + // Find the column and row indices of the from and to cells const fromColIndex = $fromCell.index($fromRow.depth); const fromRowIndex = $fromRow.index($table.depth); const toColIndex = $toCell.index($toRow.depth); @@ -107,7 +115,7 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { ...row, cells: row.cells.map((cell, c) => { const mappedCell = mapTableCell(cell); - // If the cell is within the selected range, update the text alignment + // If the cell is within the selected range of cells, update the text alignment if ( fromRowIndex <= r && r <= toRowIndex && @@ -131,6 +139,7 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { editor.updateBlock(block, { type: "table", content: { + ...(block.content as TableContent), type: "tableContent", rows: newTable, } as any, From 97e02fabaff6a8b011fe866347a2c07140ad9924 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Tue, 18 Feb 2025 18:21:59 +0100 Subject: [PATCH 15/72] fix: filter out nulls --- packages/core/src/api/blockManipulation/tables/tables.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 48abc21796..32351de207 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -251,11 +251,10 @@ export function getRow( occupancyGrid ); }); - // Filter out duplicates based on row and col properties - return cells.filter((cell, index) => { + return cells.filter((cell, index): cell is NonNullable => { return ( - cell && + cell !== null && cells.findIndex((c) => c && c.row === cell.row && c.col === cell.col) === index ); @@ -292,9 +291,9 @@ export function getColumn( }); // Filter out duplicates based on row and col properties - return cells.filter((cell, index) => { + return cells.filter((cell, index): cell is NonNullable => { return ( - cell && + cell !== null && cells.findIndex((c) => c && c.row === cell.row && c.col === cell.col) === index ); From c60560c83f96d833e33d46ad606f90ed80fa21a6 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Tue, 18 Feb 2025 18:22:15 +0100 Subject: [PATCH 16/72] fix: accessing undefined --- .../core/src/extensions/TableHandles/TableHandlesPlugin.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index e1135c12e5..d3c385d202 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -298,11 +298,11 @@ export class TableHandlesView< referencePosTable: tableRect, block: tableBlock, widgetContainer, - colIndex: hideHandles ? undefined : this.state!.colIndex, - rowIndex: hideHandles ? undefined : this.state!.rowIndex, + colIndex: hideHandles ? undefined : this.state?.colIndex, + rowIndex: hideHandles ? undefined : this.state?.rowIndex, referencePosCell: hideHandles ? undefined - : this.state!.referencePosCell, + : this.state?.referencePosCell, }; } else { const colIndex = getChildIndex(target.domNode); From 3a590babe42465505ec6e0516045d6571b1b3212 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Tue, 18 Feb 2025 18:22:33 +0100 Subject: [PATCH 17/72] fix: when re-ordering columns, re-order column widths too --- .../core/src/extensions/TableHandles/TableHandlesPlugin.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index d3c385d202..d59fa590d7 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -463,6 +463,7 @@ export class TableHandlesView< cells: row.cells.map((cell) => mapTableCell(cell)), }; }); + const columnWidths = this.state.block.content.columnWidths; if (draggingState.draggedCellOrientation === "row") { const row = getRow(this.state.block, rowIndex); @@ -504,12 +505,15 @@ export class TableHandlesView< row.cells.splice(draggingState.originalIndex, 1); row.cells.splice(colIndex, 0, cellsToMove[rowIndex] as any); }); + const [columnWidth] = columnWidths.splice(draggingState.originalIndex, 1); + columnWidths.splice(colIndex, 0, columnWidth); } this.editor.updateBlock(this.state.block, { type: "table", content: { ...this.state.block.content, + columnWidths, rows: newTable, }, }); From 580d3bf839676ae3debd2e2f6140f7f20c82cda3 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Tue, 18 Feb 2025 19:00:14 +0100 Subject: [PATCH 18/72] fix: add translations and build fixes --- packages/core/src/api/nodeConversions/blockToNode.ts | 3 ++- packages/core/src/blocks/defaultBlockTypeGuards.ts | 4 +++- packages/core/src/i18n/locales/ar.ts | 8 ++++++++ packages/core/src/i18n/locales/fr.ts | 8 ++++++++ packages/core/src/i18n/locales/is.ts | 8 ++++++++ packages/core/src/i18n/locales/ja.ts | 8 ++++++++ packages/core/src/i18n/locales/ko.ts | 8 ++++++++ packages/core/src/i18n/locales/nl.ts | 8 ++++++++ packages/core/src/i18n/locales/pl.ts | 8 ++++++++ packages/core/src/i18n/locales/pt.ts | 8 ++++++++ packages/core/src/i18n/locales/ru.ts | 8 ++++++++ packages/core/src/i18n/locales/uk.ts | 8 ++++++++ packages/core/src/i18n/locales/vi.ts | 8 ++++++++ packages/core/src/i18n/locales/zh.ts | 8 ++++++++ packages/core/src/schema/blocks/types.ts | 4 ++-- packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx | 2 +- 16 files changed, 104 insertions(+), 5 deletions(-) diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 11926a80a4..cfaf49101b 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -182,7 +182,8 @@ export function tableContentToNodes< // If headerCols is 1, then the first column is a header column const headerCols = new Array(tableContent.headerCols ?? 0).fill(true); - const columnWidths: (number | null)[] = tableContent.columnWidths ?? []; + const columnWidths: (number | null | undefined)[] = + tableContent.columnWidths ?? []; for (let rowIndex = 0; rowIndex < tableContent.rows.length; rowIndex++) { const row = tableContent.rows[rowIndex]; diff --git a/packages/core/src/blocks/defaultBlockTypeGuards.ts b/packages/core/src/blocks/defaultBlockTypeGuards.ts index 15e7bf10cd..1704456045 100644 --- a/packages/core/src/blocks/defaultBlockTypeGuards.ts +++ b/packages/core/src/blocks/defaultBlockTypeGuards.ts @@ -162,6 +162,8 @@ export function checkBlockHasDefaultProp< return checkBlockTypeHasDefaultProp(prop, block.type, editor); } -export function isTableCellSelection(selection: Selection) { +export function isTableCellSelection( + selection: Selection +): selection is CellSelection { return selection instanceof CellSelection; } diff --git a/packages/core/src/i18n/locales/ar.ts b/packages/core/src/i18n/locales/ar.ts index 26bd7e41e8..814fc41a4d 100644 --- a/packages/core/src/i18n/locales/ar.ts +++ b/packages/core/src/i18n/locales/ar.ts @@ -138,6 +138,8 @@ export const ar: Dictionary = { drag_handle: { delete_menuitem: "حذف", colors_menuitem: "ألوان", + header_row_menuitem: "عنوان الصف", + header_column_menuitem: "عنوان العمود", }, table_handle: { delete_column_menuitem: "حذف عمود", @@ -146,6 +148,9 @@ export const ar: Dictionary = { add_right_menuitem: "إضافة عمود إلى اليمين", add_above_menuitem: "إضافة صف أعلى", add_below_menuitem: "إضافة صف أسفل", + split_cell_menuitem: "تقسيم الخلية", + merge_cells_menuitem: "جمع الخلايا", + background_color_menuitem: "لون الخلفية", }, suggestion_menu: { no_items_title: "لم يتم العثور على عناصر", @@ -261,6 +266,9 @@ export const ar: Dictionary = { align_justify: { tooltip: "ضبط النص", }, + table_cell_merge: { + tooltip: "جمع الخلايا", + }, comment: { tooltip: "إضافة ملاحظة", }, diff --git a/packages/core/src/i18n/locales/fr.ts b/packages/core/src/i18n/locales/fr.ts index 61ff162992..255813d0b0 100644 --- a/packages/core/src/i18n/locales/fr.ts +++ b/packages/core/src/i18n/locales/fr.ts @@ -177,6 +177,8 @@ export const fr: Dictionary = { drag_handle: { delete_menuitem: "Supprimer", colors_menuitem: "Couleurs", + header_row_menuitem: "En-tête de ligne", + header_column_menuitem: "En-tête de colonne", }, table_handle: { delete_column_menuitem: "Supprimer la colonne", @@ -185,6 +187,9 @@ export const fr: Dictionary = { add_right_menuitem: "Ajouter une colonne à droite", add_above_menuitem: "Ajouter une ligne au-dessus", add_below_menuitem: "Ajouter une ligne en dessous", + split_cell_menuitem: "Diviser la cellule", + merge_cells_menuitem: "Fusionner les cellules", + background_color_menuitem: "Couleur de fond", }, suggestion_menu: { no_items_title: "Aucun élément trouvé", @@ -300,6 +305,9 @@ export const fr: Dictionary = { align_justify: { tooltip: "Justifier le texte", }, + table_cell_merge: { + tooltip: "Fusionner les cellules", + }, comment: { tooltip: "Ajouter un commentaire", }, diff --git a/packages/core/src/i18n/locales/is.ts b/packages/core/src/i18n/locales/is.ts index c1d31720b0..80caf49b8a 100644 --- a/packages/core/src/i18n/locales/is.ts +++ b/packages/core/src/i18n/locales/is.ts @@ -145,6 +145,8 @@ export const is: Dictionary = { drag_handle: { delete_menuitem: "Eyða", colors_menuitem: "Litir", + header_row_menuitem: "Höfuðröð", + header_column_menuitem: "Höfuðdálkur", }, table_handle: { delete_column_menuitem: "Eyða dálki", @@ -153,6 +155,9 @@ export const is: Dictionary = { add_right_menuitem: "Bæta dálki við til hægri", add_above_menuitem: "Bæta röð við fyrir ofan", add_below_menuitem: "Bæta röð við fyrir neðan", + split_cell_menuitem: "Splita dálk", + merge_cells_menuitem: "Sameina dálka", + background_color_menuitem: "Breyta bakgrunni", }, suggestion_menu: { no_items_title: "Engir hlutir fundust", @@ -268,6 +273,9 @@ export const is: Dictionary = { align_justify: { tooltip: "Jafna texta", }, + table_cell_merge: { + tooltip: "Sameina dálka", + }, comment: { tooltip: "Bæta við athugun", }, diff --git a/packages/core/src/i18n/locales/ja.ts b/packages/core/src/i18n/locales/ja.ts index abb3b4b4d0..c723c2ca1f 100644 --- a/packages/core/src/i18n/locales/ja.ts +++ b/packages/core/src/i18n/locales/ja.ts @@ -173,6 +173,8 @@ export const ja: Dictionary = { drag_handle: { delete_menuitem: "削除", colors_menuitem: "色を変更", + header_row_menuitem: "行の見出し", + header_column_menuitem: "列の見出し", }, table_handle: { delete_column_menuitem: "列を削除", @@ -181,6 +183,9 @@ export const ja: Dictionary = { add_right_menuitem: "右に列を追加", add_above_menuitem: "上に行を追加", add_below_menuitem: "下に行を追加", + split_cell_menuitem: "セルを分割", + merge_cells_menuitem: "セルを結合", + background_color_menuitem: "背景色を変更", }, suggestion_menu: { no_items_title: "アイテムが見つかりません", @@ -296,6 +301,9 @@ export const ja: Dictionary = { align_justify: { tooltip: "両端揃え", }, + table_cell_merge: { + tooltip: "セルを結合", + }, comment: { tooltip: "コメントを追加", }, diff --git a/packages/core/src/i18n/locales/ko.ts b/packages/core/src/i18n/locales/ko.ts index 70a0f2d0c1..734c516e7c 100644 --- a/packages/core/src/i18n/locales/ko.ts +++ b/packages/core/src/i18n/locales/ko.ts @@ -166,6 +166,8 @@ export const ko: Dictionary = { drag_handle: { delete_menuitem: "삭제", colors_menuitem: "색깔", + header_row_menuitem: "행 제목", + header_column_menuitem: "열 제목", }, table_handle: { delete_column_menuitem: "열 1개 삭제", @@ -174,6 +176,9 @@ export const ko: Dictionary = { add_right_menuitem: "오른쪽에 열 1개 추가", add_above_menuitem: "위에 행 1개 추가", add_below_menuitem: "아래에 행 1개 추가", + split_cell_menuitem: "셀 분할", + merge_cells_menuitem: "셀 병합", + background_color_menuitem: "배경색 변경", }, suggestion_menu: { no_items_title: "항목을 찾을 수 없음", @@ -289,6 +294,9 @@ export const ko: Dictionary = { align_justify: { tooltip: "텍스트 양쪽 맞춤", }, + table_cell_merge: { + tooltip: "셀 병합", + }, comment: { tooltip: "코멘트 추가", }, diff --git a/packages/core/src/i18n/locales/nl.ts b/packages/core/src/i18n/locales/nl.ts index 07dcf6a09e..4b8566182f 100644 --- a/packages/core/src/i18n/locales/nl.ts +++ b/packages/core/src/i18n/locales/nl.ts @@ -153,6 +153,8 @@ export const nl: Dictionary = { drag_handle: { delete_menuitem: "Verwijder", colors_menuitem: "Kleuren", + header_row_menuitem: "Kopregel", + header_column_menuitem: "Kopkolom", }, table_handle: { delete_column_menuitem: "Verwijder kolom", @@ -161,6 +163,9 @@ export const nl: Dictionary = { add_right_menuitem: "Voeg kolom rechts toe", add_above_menuitem: "Voeg rij boven toe", add_below_menuitem: "Voeg rij onder toe", + split_cell_menuitem: "Splits cel", + merge_cells_menuitem: "Voeg cellen samen", + background_color_menuitem: "Achtergrondkleur wijzigen", }, suggestion_menu: { no_items_title: "Geen items gevonden", @@ -275,6 +280,9 @@ export const nl: Dictionary = { align_justify: { tooltip: "Tekst uitvullen", }, + table_cell_merge: { + tooltip: "Voeg cellen samen", + }, comment: { tooltip: "Commentaar toevoegen", }, diff --git a/packages/core/src/i18n/locales/pl.ts b/packages/core/src/i18n/locales/pl.ts index b8b9ad908d..8c5530d74a 100644 --- a/packages/core/src/i18n/locales/pl.ts +++ b/packages/core/src/i18n/locales/pl.ts @@ -137,6 +137,8 @@ export const pl: Dictionary = { drag_handle: { delete_menuitem: "Usuń", colors_menuitem: "Kolory", + header_row_menuitem: "Nagłówek wiersza", + header_column_menuitem: "Nagłówek kolumny", }, table_handle: { delete_column_menuitem: "Usuń kolumnę", @@ -145,6 +147,9 @@ export const pl: Dictionary = { add_right_menuitem: "Dodaj kolumnę po prawej", add_above_menuitem: "Dodaj wiersz powyżej", add_below_menuitem: "Dodaj wiersz poniżej", + split_cell_menuitem: "Podziel komórkę", + merge_cells_menuitem: "Połącz komórki", + background_color_menuitem: "Zmień kolor tła", }, suggestion_menu: { no_items_title: "Nie znaleziono elementów", @@ -260,6 +265,9 @@ export const pl: Dictionary = { align_justify: { tooltip: "Wyjustuj tekst", }, + table_cell_merge: { + tooltip: "Połącz komórki", + }, comment: { tooltip: "Dodaj komentarz", }, diff --git a/packages/core/src/i18n/locales/pt.ts b/packages/core/src/i18n/locales/pt.ts index 049119de0c..b827c258d1 100644 --- a/packages/core/src/i18n/locales/pt.ts +++ b/packages/core/src/i18n/locales/pt.ts @@ -145,6 +145,8 @@ export const pt: Dictionary = { drag_handle: { delete_menuitem: "Excluir", colors_menuitem: "Cores", + header_row_menuitem: "Cabeçalho de linha", + header_column_menuitem: "Cabeçalho de coluna", }, table_handle: { delete_column_menuitem: "Excluir coluna", @@ -153,6 +155,9 @@ export const pt: Dictionary = { add_right_menuitem: "Adicionar coluna à direita", add_above_menuitem: "Adicionar linha acima", add_below_menuitem: "Adicionar linha abaixo", + split_cell_menuitem: "Dividir célula", + merge_cells_menuitem: "Juntar células", + background_color_menuitem: "Alterar cor de fundo", }, suggestion_menu: { no_items_title: "Nenhum item encontrado", @@ -268,6 +273,9 @@ export const pt: Dictionary = { align_justify: { tooltip: "Justificar texto", }, + table_cell_merge: { + tooltip: "Juntar células", + }, comment: { tooltip: "Adicionar comentário", }, diff --git a/packages/core/src/i18n/locales/ru.ts b/packages/core/src/i18n/locales/ru.ts index 6445104cb8..6c1e4999ce 100644 --- a/packages/core/src/i18n/locales/ru.ts +++ b/packages/core/src/i18n/locales/ru.ts @@ -180,6 +180,8 @@ export const ru: Dictionary = { drag_handle: { delete_menuitem: "Удалить", colors_menuitem: "Цвета", + header_row_menuitem: "Заголовок строки", + header_column_menuitem: "Заголовок столбца", }, table_handle: { delete_column_menuitem: "Удалить столбец", @@ -188,6 +190,9 @@ export const ru: Dictionary = { add_right_menuitem: "Добавить столбец справа", add_above_menuitem: "Добавить строку выше", add_below_menuitem: "Добавить строку ниже", + split_cell_menuitem: "Разделить ячейку", + merge_cells_menuitem: "Объединить ячейки", + background_color_menuitem: "Цвет фона", }, suggestion_menu: { no_items_title: "ничего не найдено", @@ -303,6 +308,9 @@ export const ru: Dictionary = { align_justify: { tooltip: "По середине текст", }, + table_cell_merge: { + tooltip: "Объединить ячейки", + }, comment: { tooltip: "Добавить комментарий", }, diff --git a/packages/core/src/i18n/locales/uk.ts b/packages/core/src/i18n/locales/uk.ts index 28ee7cd93c..af594ec9ac 100644 --- a/packages/core/src/i18n/locales/uk.ts +++ b/packages/core/src/i18n/locales/uk.ts @@ -178,6 +178,8 @@ export const uk: Dictionary = { drag_handle: { delete_menuitem: "Видалити", colors_menuitem: "Кольори", + header_row_menuitem: "Заголовок рядка", + header_column_menuitem: "Заголовок стовпця", }, table_handle: { delete_column_menuitem: "Видалити стовпець", @@ -186,6 +188,9 @@ export const uk: Dictionary = { add_right_menuitem: "Додати стовпець справа", add_above_menuitem: "Додати рядок вище", add_below_menuitem: "Додати рядок нижче", + split_cell_menuitem: "Розділити клітинку", + merge_cells_menuitem: "Об'єднати клітинки", + background_color_menuitem: "Змінити колір фону", }, suggestion_menu: { no_items_title: "Нічого не знайдено", @@ -300,6 +305,9 @@ export const uk: Dictionary = { align_justify: { tooltip: "Вирівняти за шириною", }, + table_cell_merge: { + tooltip: "Об'єднати клітинки", + }, comment: { tooltip: "Додати коментар", }, diff --git a/packages/core/src/i18n/locales/vi.ts b/packages/core/src/i18n/locales/vi.ts index c9ae069214..919ba6aaed 100644 --- a/packages/core/src/i18n/locales/vi.ts +++ b/packages/core/src/i18n/locales/vi.ts @@ -152,6 +152,8 @@ export const vi: Dictionary = { drag_handle: { delete_menuitem: "Xóa", colors_menuitem: "Màu sắc", + header_row_menuitem: "Tiêu đề hàng", + header_column_menuitem: "Tiêu đề cột", }, table_handle: { delete_column_menuitem: "Xóa cột", @@ -160,6 +162,9 @@ export const vi: Dictionary = { add_right_menuitem: "Thêm cột bên phải", add_above_menuitem: "Thêm hàng phía trên", add_below_menuitem: "Thêm hàng phía dưới", + split_cell_menuitem: "Chia ô", + merge_cells_menuitem: "Gộp ô", + background_color_menuitem: "Màu nền", }, suggestion_menu: { no_items_title: "Không tìm thấy mục nào", @@ -275,6 +280,9 @@ export const vi: Dictionary = { align_justify: { tooltip: "Căn đều văn bản", }, + table_cell_merge: { + tooltip: "Gộp các ô", + }, comment: { tooltip: "Thêm bình luận", }, diff --git a/packages/core/src/i18n/locales/zh.ts b/packages/core/src/i18n/locales/zh.ts index e6f5f9334e..e1d4ce2861 100644 --- a/packages/core/src/i18n/locales/zh.ts +++ b/packages/core/src/i18n/locales/zh.ts @@ -186,6 +186,8 @@ export const zh: Dictionary = { drag_handle: { delete_menuitem: "删除", colors_menuitem: "颜色", + header_row_menuitem: "行标题", + header_column_menuitem: "列标题", }, table_handle: { delete_column_menuitem: "删除列", @@ -194,6 +196,9 @@ export const zh: Dictionary = { add_right_menuitem: "右侧添加列", add_above_menuitem: "上方添加行", add_below_menuitem: "下方添加行", + split_cell_menuitem: "拆分单元格", + merge_cells_menuitem: "合并单元格", + background_color_menuitem: "背景色", }, suggestion_menu: { no_items_title: "无匹配项", @@ -309,6 +314,9 @@ export const zh: Dictionary = { align_justify: { tooltip: "文本对齐", }, + table_cell_merge: { + tooltip: "合并单元格", + }, comment: { tooltip: "添加评论", }, diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index f1886bb5f5..3d0d8f1925 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -166,7 +166,7 @@ export type TableContent< S extends StyleSchema = StyleSchema > = { type: "tableContent"; - columnWidths: (number | null)[]; + columnWidths: (number | null | undefined)[]; headerRows?: number; headerCols?: number; rows: { @@ -253,7 +253,7 @@ export type PartialTableContent< S extends StyleSchema = StyleSchema > = { type: "tableContent"; - columnWidths?: (number | null)[]; + columnWidths?: (number | null | undefined)[]; headerRows?: number; headerCols?: number; rows: { diff --git a/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx b/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx index 6072cf5e82..aee2927973 100644 --- a/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx +++ b/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx @@ -63,7 +63,7 @@ export const Table = (props: { styles.cell, index === row.cells.length - 1 ? styles.rightCell : {}, props.data.columnWidths[index] - ? { width: props.data.columnWidths[index] } + ? { width: props.data.columnWidths[index] ?? undefined } : { flex: 1 }, ]} key={index}> From d4e8afe97560d15e44c78858bf1b746e964b1f75 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Tue, 18 Feb 2025 19:23:11 +0100 Subject: [PATCH 19/72] test: make columnWidths only be an array of undefined again --- .../__snapshots__/insertBlocks.test.ts.snap | 198 +++++++++++++----- .../__snapshots__/internal/basicBlocks.html | 2 +- .../internal/basicBlocksWithProps.html | 2 +- .../__snapshots__/internal/tableAllCells.html | 2 +- .../__snapshots__/internal/tableCell.html | 2 +- .../__snapshots__/internal/tableRow.html | 2 +- .../__snapshots__/table/basic/external.html | 2 +- .../__snapshots__/table/basic/internal.html | 2 +- .../table/mixedCellColors/external.html | 2 +- .../table/mixedCellColors/internal.html | 2 +- .../table/mixedColWidths/external.html | 2 +- .../table/mixedColWidths/internal.html | 2 +- .../nodeConversions.test.ts.snap | 60 ++++-- .../src/api/nodeConversions/blockToNode.ts | 11 +- .../src/api/nodeConversions/nodeToBlock.ts | 10 +- .../html/__snapshots__/parse-notion-html.json | 18 +- .../src/api/testUtil/partialBlockTestUtil.ts | 8 +- packages/core/src/schema/blocks/types.ts | 4 +- .../src/pdf/util/table/Table.tsx | 2 +- 19 files changed, 233 insertions(+), 100 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap index 70e328f2e7..2e301b409d 100644 --- a/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap @@ -2091,77 +2091,167 @@ exports[`Test insertBlocks > Insert single basic block before (without type) 1`] "rows": [ { "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, { "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", }, - ], + "type": "tableCell", + }, ], }, ], diff --git a/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocks.html b/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocks.html index 8c7757e46a..4a32d957d6 100644 --- a/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocks.html +++ b/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocks.html @@ -1 +1 @@ -

Paragraph

Heading

  1. Numbered List Item

  • Bullet List Item

  • Check List Item

console.log("Hello World");

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Add image

\ No newline at end of file +

Paragraph

Heading

  1. Numbered List Item

  • Bullet List Item

  • Check List Item

console.log("Hello World");

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Add image

\ No newline at end of file diff --git a/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html b/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html index 4397413824..2ae664f204 100644 --- a/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html +++ b/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html @@ -1 +1 @@ -

Paragraph

Heading

  1. Numbered List Item

  • Bullet List Item

  • Check List Item

console.log("Hello World");

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

1280px-Placeholder_view_vector.svg.png
Placeholder

\ No newline at end of file +

Paragraph

Heading

  1. Numbered List Item

  • Bullet List Item

  • Check List Item

console.log("Hello World");

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

1280px-Placeholder_view_vector.svg.png
Placeholder

\ No newline at end of file diff --git a/packages/core/src/api/clipboard/__snapshots__/internal/tableAllCells.html b/packages/core/src/api/clipboard/__snapshots__/internal/tableAllCells.html index 5a0ce46217..5ae1c65aba 100644 --- a/packages/core/src/api/clipboard/__snapshots__/internal/tableAllCells.html +++ b/packages/core/src/api/clipboard/__snapshots__/internal/tableAllCells.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/clipboard/__snapshots__/internal/tableCell.html b/packages/core/src/api/clipboard/__snapshots__/internal/tableCell.html index c4cc0e05d3..9d0fd3e5e8 100644 --- a/packages/core/src/api/clipboard/__snapshots__/internal/tableCell.html +++ b/packages/core/src/api/clipboard/__snapshots__/internal/tableCell.html @@ -1 +1 @@ -

Table Cell

\ No newline at end of file +

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/clipboard/__snapshots__/internal/tableRow.html b/packages/core/src/api/clipboard/__snapshots__/internal/tableRow.html index ffa15acbb9..9e4fb87c0f 100644 --- a/packages/core/src/api/clipboard/__snapshots__/internal/tableRow.html +++ b/packages/core/src/api/clipboard/__snapshots__/internal/tableRow.html @@ -1 +1 @@ -

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/basic/external.html b/packages/core/src/api/exporters/html/__snapshots__/table/basic/external.html index ce73c75aa8..626650c8cb 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/basic/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/basic/external.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/table/basic/internal.html index f00383b3d6..77b09fcac3 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/basic/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/basic/internal.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html b/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html index 780396996c..c1810a4a53 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html b/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html index 10e00b6852..45b36db1f7 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/external.html b/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/external.html index c7018c749b..8b596348ac 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/external.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/internal.html b/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/internal.html index b2b6765da5..5c53f11e4b 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/internal.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap index 614231e3b6..64178295fb 100644 --- a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +++ b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap @@ -2130,7 +2130,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2152,7 +2154,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2174,7 +2178,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2201,7 +2207,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2223,7 +2231,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2245,7 +2255,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2272,7 +2284,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2294,7 +2308,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2316,7 +2332,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2385,7 +2403,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "blue", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2460,7 +2480,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2535,7 +2557,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2628,7 +2652,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2703,7 +2729,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2778,7 +2806,9 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": null, + "colwidth": [ + undefined, + ], "rowspan": 1, "textAlignment": "left", "textColor": "default", diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index cfaf49101b..85e4190a25 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -182,8 +182,7 @@ export function tableContentToNodes< // If headerCols is 1, then the first column is a header column const headerCols = new Array(tableContent.headerCols ?? 0).fill(true); - const columnWidths: (number | null | undefined)[] = - tableContent.columnWidths ?? []; + const columnWidths: (number | undefined)[] = tableContent.columnWidths ?? []; for (let rowIndex = 0; rowIndex < tableContent.rows.length; rowIndex++) { const row = tableContent.rows[rowIndex]; @@ -210,8 +209,8 @@ export function tableContentToNodes< { type: "table", content: tableContent } as any ); // Assume the column width is the width of the cell at the absolute cell index - let colwidth: (number | null)[] = [ - columnWidths[absoluteCellIndex.col] ?? null, + let colwidth: (number | undefined)[] = [ + columnWidths[absoluteCellIndex.col] ?? undefined, ]; if (!cell) { @@ -226,9 +225,9 @@ export function tableContentToNodes< if (colSpan && colSpan > 1) { // If the cell has a > 1 colspan, we need to get the column width for each cell in the span - colwidth = new Array(colSpan).fill(null).map((_, i) => { + colwidth = new Array(colSpan).fill(false).map((_, i) => { // Starting from the absolute cell index, get the column width for each cell in the span - return columnWidths[absoluteCellIndex.col + i] ?? null; + return columnWidths[absoluteCellIndex.col + i] ?? undefined; }); } } else { diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 082fa64050..f6f493702e 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -32,8 +32,6 @@ export function contentNodeToTableContent< const ret: TableContent = { type: "tableContent", columnWidths: [], - headerRows: undefined, - headerCols: undefined, rows: [], }; @@ -50,9 +48,9 @@ export function contentNodeToTableContent< if (rowIndex === 0) { rowNode.content.forEach((cellNode) => { - let colWidth = cellNode.attrs.colwidth as null | number[]; - if (colWidth === null) { - colWidth = new Array(cellNode.attrs.colspan ?? 1).fill(null); + let colWidth = cellNode.attrs.colwidth as null | undefined | number[]; + if (colWidth === undefined || colWidth === null) { + colWidth = new Array(cellNode.attrs.colspan ?? 1).fill(undefined); } ret.columnWidths.push(...colWidth); }); @@ -91,7 +89,7 @@ export function contentNodeToTableContent< } } - for (let i = 0; i < headerMatrix[0].length; i++) { + for (let i = 0; i < headerMatrix[0]?.length; i++) { if (headerMatrix.every((row) => row[i])) { ret.headerCols = (ret.headerCols ?? 0) + 1; } diff --git a/packages/core/src/api/parsers/html/__snapshots__/parse-notion-html.json b/packages/core/src/api/parsers/html/__snapshots__/parse-notion-html.json index d99f504380..0ae9e143fd 100644 --- a/packages/core/src/api/parsers/html/__snapshots__/parse-notion-html.json +++ b/packages/core/src/api/parsers/html/__snapshots__/parse-notion-html.json @@ -388,7 +388,10 @@ ], "props": { "colspan": 1, - "rowspan": 1 + "rowspan": 1, + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" } }, { @@ -402,7 +405,10 @@ ], "props": { "colspan": 1, - "rowspan": 1 + "rowspan": 1, + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" } }, { @@ -416,7 +422,10 @@ ], "props": { "colspan": 1, - "rowspan": 1 + "rowspan": 1, + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" } } ] @@ -531,7 +540,8 @@ } ] } - ] + ], + "headerRows": 1 }, "children": [] }, diff --git a/packages/core/src/api/testUtil/partialBlockTestUtil.ts b/packages/core/src/api/testUtil/partialBlockTestUtil.ts index ecfeefece6..89e456a762 100644 --- a/packages/core/src/api/testUtil/partialBlockTestUtil.ts +++ b/packages/core/src/api/testUtil/partialBlockTestUtil.ts @@ -128,7 +128,13 @@ export function partialBlockToBlockForTesting< contentType === "inline" ? [] : contentType === "table" - ? { type: "tableContent", columnWidths: [], rows: [] } + ? { + type: "tableContent", + columnWidths: [undefined, undefined, undefined], + headerRows: undefined, + headerCols: undefined, + rows: [], + } : (undefined as any), children: [] as any, ...partialBlock, diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index 3d0d8f1925..ac275841a1 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -166,7 +166,7 @@ export type TableContent< S extends StyleSchema = StyleSchema > = { type: "tableContent"; - columnWidths: (number | null | undefined)[]; + columnWidths: (number | undefined)[]; headerRows?: number; headerCols?: number; rows: { @@ -253,7 +253,7 @@ export type PartialTableContent< S extends StyleSchema = StyleSchema > = { type: "tableContent"; - columnWidths?: (number | null | undefined)[]; + columnWidths?: (number | undefined)[]; headerRows?: number; headerCols?: number; rows: { diff --git a/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx b/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx index aee2927973..6072cf5e82 100644 --- a/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx +++ b/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx @@ -63,7 +63,7 @@ export const Table = (props: { styles.cell, index === row.cells.length - 1 ? styles.rightCell : {}, props.data.columnWidths[index] - ? { width: props.data.columnWidths[index] ?? undefined } + ? { width: props.data.columnWidths[index] } : { flex: 1 }, ]} key={index}> From eee7b08ba573c70ba898bcf555d3882695c1312b Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 19 Feb 2025 12:03:18 +0100 Subject: [PATCH 20/72] fix: use undefined --- packages/core/src/api/nodeConversions/blockToNode.ts | 2 +- .../DefaultButtons/TableCellMergeButton.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 85e4190a25..4611502572 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -194,7 +194,7 @@ export function tableContentToNodes< /** * The attributes of the cell to apply to the node */ - const attrs: Attrs | null = null; + const attrs: Attrs | undefined = undefined; /** * The content of the cell to apply to the node */ diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx index 69000ee631..1dc04c8433 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx @@ -34,7 +34,7 @@ function getMergeDirection( any > | undefined -): null | "horizontal" | "vertical" { +): undefined | "horizontal" | "vertical" { const tableHandles = editor.tableHandles; const cellSelection = isTableCellSelection( @@ -50,7 +50,7 @@ function getMergeDirection( // Only offer the merge button if there is more than one cell selected. cellSelection.ranges.length <= 1 ) { - return null; + return undefined; } const { $anchorCell, $headCell } = cellSelection; @@ -91,7 +91,7 @@ export const TableCellMergeButton = () => { const mergeDirection = useMemo(() => { // Checks if only one block is selected. if (selectedBlocks.length !== 1) { - return null; + return undefined; } const block = selectedBlocks[0]; @@ -100,14 +100,14 @@ export const TableCellMergeButton = () => { return getMergeDirection(editor, block); } - return null; + return undefined; }, [editor, selectedBlocks]); const onClick = useCallback(() => { editor.tableHandles?.mergeCells(); }, [editor]); - if (!editor.isEditable || mergeDirection === null) { + if (!editor.isEditable || mergeDirection === undefined) { return null; } From 87e3efbcb3947c7f6002f124cb8407c42ccce509 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 19 Feb 2025 16:47:51 +0100 Subject: [PATCH 21/72] fix: rm unused var --- .../core/src/blocks/TableBlockContent/TableBlockContent.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts index 20fddeb4a5..309b0b5068 100644 --- a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts +++ b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts @@ -13,18 +13,11 @@ import { mergeCSSClasses } from "../../util/browser.js"; import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js"; import { defaultProps } from "../defaultProps.js"; import { EMPTY_CELL_WIDTH, TableExtension } from "./TableExtension.js"; -import { PropSchema } from "../../schema/index.js"; export const tablePropSchema = { textColor: defaultProps.textColor, }; -export const tableCellPropSchema = { - ...defaultProps, - colspan: { default: 1 }, - rowspan: { default: 1 }, -} satisfies PropSchema; - export const TableBlockContent = createStronglyTypedTiptapNode({ name: "table", content: "tableRow+", From f1c4ad118cdb53d2bfd32ff35487b927df2efec3 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 19 Feb 2025 17:21:13 +0100 Subject: [PATCH 22/72] refactor: minor cleanup --- packages/core/src/api/nodeConversions/blockToNode.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 4611502572..6ccdf2d736 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -221,13 +221,13 @@ export function tableContentToNodes< if (cell.content) { content = inlineContentToNodes(cell.content, schema, styleSchema); } - const colSpan = cell.props?.colspan; + const colspan = getColspan(cell); - if (colSpan && colSpan > 1) { + if (colspan > 1) { // If the cell has a > 1 colspan, we need to get the column width for each cell in the span - colwidth = new Array(colSpan).fill(false).map((_, i) => { - // Starting from the absolute cell index, get the column width for each cell in the span - return columnWidths[absoluteCellIndex.col + i] ?? undefined; + colwidth = new Array(colspan).fill(false).map((_, i) => { + // Starting from the absolute column index, get the column width for each cell in the span + return columnWidths[absoluteColIndex + i] ?? undefined; }); } } else { From e2506e2185d1bf8d20aaa0748fa994ec97a141ad Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 19 Feb 2025 17:58:36 +0100 Subject: [PATCH 23/72] docs: better describe & expose table relative vs absolute positioning --- .../blockManipulation/tables/tables.test.ts | 554 +++++++++++++++--- .../api/blockManipulation/tables/tables.ts | 448 +++++++++----- .../src/api/nodeConversions/blockToNode.ts | 9 +- .../TableHandles/TableHandlesPlugin.ts | 102 +++- .../DefaultButtons/TableCellMergeButton.tsx | 66 +-- 5 files changed, 865 insertions(+), 314 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.test.ts b/packages/core/src/api/blockManipulation/tables/tables.test.ts index 4501f9324f..c0db675d5f 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.test.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.test.ts @@ -4,10 +4,19 @@ import { Block, DefaultBlockSchema } from "../../../blocks/defaultBlocks.js"; import { getColumn, getRow, - resolveAbsoluteTableCellIndices, - resolveRelativeTableCellIndices, + getRelativeTableCellIndices, + getAbsoluteTableCellIndices, } from "./tables.js"; +/** + * Normal table + * | 1-1 | 1-2 | 1-3 | + * | 2-1 | 2-2 | 2-3 | + * + * Table with colspan + * | 1-1 | 1-2 | 1-3 | + * | 2-1 | 2-2 | 2-3 | + */ const simpleTable = { type: "table", id: "table-0", @@ -81,6 +90,15 @@ const simpleTable = { any >; +/** + * Normal table + * | 1-1 | 1-2 | 1-3 | + * | 2-1 | 2-2 | 2-3 | + * + * Table with colspan + * | 1-1 | 1-1 | 1-2 | + * | 2-1 | 2-2 | 2-3 | + */ const tableWithColspan = { type: "table", id: "table-0", @@ -168,6 +186,17 @@ const tableWithColspan = { any >; +/** + * Normal table + * | 1-1 | 1-2 | 1-3 | + * | 2-1 | 2-2 | 2-3 | + * | 3-1 | 3-2 | 3-3 | + * + * Table with rowspan + * | 1-1 | 1-2 | 1-3 | + * | 1-1 | 2-1 | 2-2 | + * | 3-1 | 3-2 | 3-3 | + */ const tableWithRowspan = { type: "table", id: "table-0", @@ -229,7 +258,7 @@ const tableWithRowspan = { colspan: 1, rowspan: 1, }, - content: [{ type: "text", text: "3-2", styles: {} }], + content: [{ type: "text", text: "2-2", styles: {} }], }, { type: "tableCell", @@ -240,7 +269,7 @@ const tableWithRowspan = { colspan: 1, rowspan: 1, }, - content: [{ type: "text", text: "3-3", styles: {} }], + content: [{ type: "text", text: "2-3", styles: {} }], }, ], }, @@ -292,6 +321,17 @@ const tableWithRowspan = { any >; +/** + * Normal table + * | 1-1 | 1-2 | 1-3 | + * | 2-1 | 2-2 | 2-3 | + * | 3-1 | 3-2 | 3-3 | + * + * Table with colspan and rowspan + * | 1-1 | 1-2 | 1-3 | + * | 1-1 | 2-1 | 2-1 | + * | 3-1 | 3-2 | 3-3 | + */ const tableWithColspanAndRowspan = { type: "table", id: "table-0", @@ -408,6 +448,17 @@ const tableWithColspanAndRowspan = { any >; +/** + * Normal table + * | 1-1 | 1-2 | 1-3 | + * | 2-1 | 2-2 | 2-3 | + * | 3-1 | 3-2 | 3-3 | + * + * Table with colspans and rowspans + * | 1-1 | 1-2 | 1-2 | + * | 1-1 | 2-1 | 2-1 | + * | 3-1 | 2-1 | 2-1 | + */ const tableWithColspansAndRowspans = { type: "table", id: "table-0", @@ -496,122 +547,363 @@ const tableWithColspansAndRowspans = { any >; -describe("Test resolveRelativeTableCellIndices", () => { +/** + * Normal table + * | 1-1 | 1-2 | 1-3 | 1-4 | + * | 2-1 | 2-2 | 2-3 | 2-4 | + * | 3-1 | 3-2 | 3-3 | 3-4 | + * + * Table with complex rowspans and colspans + * | 1-1 | 1-1 | 1-2 | 1-3 | + * | 1-1 | 1-1 | 2-1 | 2-2 | + * | 3-1 | 3-2 | 2-1 | 3-3 | + */ +const tableWithComplexRowspansAndColspans = { + type: "table", + id: "table-0", + props: { + textColor: "default", + }, + content: { + type: "tableContent", + columnWidths: [100, 100], + rows: [ + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 2, + rowspan: 2, + }, + content: [ + { type: "text", text: "1-1", styles: {} }, + { type: "text", text: "1-2", styles: {} }, + { type: "text", text: "2-1", styles: {} }, + { type: "text", text: "2-2", styles: {} }, + ], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "1-3", styles: {} }], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "1-4", styles: {} }], + }, + ], + }, + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 2, + }, + content: [ + { type: "text", text: "2-3", styles: {} }, + { type: "text", text: "3-3", styles: {} }, + ], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "2-4", styles: {} }], + }, + ], + }, + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "3-1", styles: {} }], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "3-2", styles: {} }], + }, + { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "3-3", styles: {} }], + }, + ], + }, + ], + }, + children: [], +} satisfies Block< + { + table: DefaultBlockSchema["table"]; + }, + any, + any +>; + +describe("Test getAbsoluteTableCellIndices", () => { it("should resolve relative table cell indices to absolute table cell indices", () => { expect( - resolveRelativeTableCellIndices({ row: 0, col: 0 }, simpleTable) - ).toEqual({ row: 0, col: 0 }); + getAbsoluteTableCellIndices({ row: 0, col: 0 }, simpleTable) + ).toEqual({ row: 0, col: 0, cell: simpleTable.content.rows[0].cells[0] }); expect( - resolveRelativeTableCellIndices({ row: 0, col: 1 }, simpleTable) - ).toEqual({ row: 0, col: 1 }); + getAbsoluteTableCellIndices({ row: 0, col: 1 }, simpleTable) + ).toEqual({ row: 0, col: 1, cell: simpleTable.content.rows[0].cells[1] }); expect( - resolveRelativeTableCellIndices({ row: 1, col: 0 }, simpleTable) - ).toEqual({ row: 1, col: 0 }); + getAbsoluteTableCellIndices({ row: 1, col: 0 }, simpleTable) + ).toEqual({ row: 1, col: 0, cell: simpleTable.content.rows[1].cells[0] }); expect( - resolveRelativeTableCellIndices({ row: 1, col: 1 }, simpleTable) - ).toEqual({ row: 1, col: 1 }); + getAbsoluteTableCellIndices({ row: 1, col: 1 }, simpleTable) + ).toEqual({ row: 1, col: 1, cell: simpleTable.content.rows[1].cells[1] }); }); it("should resolve relative table cell indices to absolute table cell indices with colspan", () => { expect( - resolveRelativeTableCellIndices({ row: 0, col: 0 }, tableWithColspan) - ).toEqual({ row: 0, col: 0 }); + getAbsoluteTableCellIndices({ row: 0, col: 0 }, tableWithColspan) + ).toEqual({ + row: 0, + col: 0, + cell: tableWithColspan.content.rows[0].cells[0], + }); expect( - resolveRelativeTableCellIndices({ row: 0, col: 1 }, tableWithColspan) - ).toEqual({ row: 0, col: 2 }); + getAbsoluteTableCellIndices({ row: 0, col: 1 }, tableWithColspan) + ).toEqual({ + row: 0, + col: 2, + cell: tableWithColspan.content.rows[0].cells[1], + }); expect( - resolveRelativeTableCellIndices({ row: 1, col: 0 }, tableWithColspan) - ).toEqual({ row: 1, col: 0 }); + getAbsoluteTableCellIndices({ row: 1, col: 0 }, tableWithColspan) + ).toEqual({ + row: 1, + col: 0, + cell: tableWithColspan.content.rows[1].cells[0], + }); expect( - resolveRelativeTableCellIndices({ row: 1, col: 1 }, tableWithColspan) - ).toEqual({ row: 1, col: 1 }); + getAbsoluteTableCellIndices({ row: 1, col: 1 }, tableWithColspan) + ).toEqual({ + row: 1, + col: 1, + cell: tableWithColspan.content.rows[1].cells[1], + }); expect( - resolveRelativeTableCellIndices({ row: 1, col: 2 }, tableWithColspan) - ).toEqual({ row: 1, col: 2 }); + getAbsoluteTableCellIndices({ row: 1, col: 2 }, tableWithColspan) + ).toEqual({ + row: 1, + col: 2, + cell: tableWithColspan.content.rows[1].cells[2], + }); }); it("should resolve relative table cell indices to absolute table cell indices with rowspan", () => { expect( - resolveRelativeTableCellIndices({ row: 0, col: 0 }, tableWithRowspan) - ).toEqual({ row: 0, col: 0 }); + getAbsoluteTableCellIndices({ row: 0, col: 0 }, tableWithRowspan) + ).toEqual({ + row: 0, + col: 0, + cell: tableWithRowspan.content.rows[0].cells[0], + }); + expect( + getAbsoluteTableCellIndices({ row: 0, col: 1 }, tableWithRowspan) + ).toEqual({ + row: 0, + col: 1, + cell: tableWithRowspan.content.rows[0].cells[1], + }); + expect( + getAbsoluteTableCellIndices({ row: 0, col: 2 }, tableWithRowspan) + ).toEqual({ + row: 0, + col: 2, + cell: tableWithRowspan.content.rows[0].cells[2], + }); expect( - resolveRelativeTableCellIndices({ row: 0, col: 1 }, tableWithRowspan) - ).toEqual({ row: 0, col: 1 }); + getAbsoluteTableCellIndices({ row: 1, col: 0 }, tableWithRowspan) + ).toEqual({ + row: 2, + col: 0, + cell: tableWithRowspan.content.rows[2].cells[0], + }); + expect( + getAbsoluteTableCellIndices({ row: 1, col: 1 }, tableWithRowspan) + ).toEqual({ + row: 2, + col: 1, + cell: tableWithRowspan.content.rows[2].cells[1], + }); + expect( + getAbsoluteTableCellIndices({ row: 1, col: 2 }, tableWithRowspan) + ).toEqual({ + row: 2, + col: 2, + cell: tableWithRowspan.content.rows[2].cells[2], + }); + }); + + it("should resolve complex rowspans and colspans", () => { expect( - resolveRelativeTableCellIndices({ row: 0, col: 2 }, tableWithRowspan) - ).toEqual({ row: 0, col: 2 }); + getAbsoluteTableCellIndices( + { row: 0, col: 0 }, + tableWithComplexRowspansAndColspans + ) + ).toEqual({ + row: 0, + col: 0, + cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[0], + }); expect( - resolveRelativeTableCellIndices({ row: 1, col: 0 }, tableWithRowspan) - ).toEqual({ row: 2, col: 0 }); + getAbsoluteTableCellIndices( + { row: 0, col: 1 }, + tableWithComplexRowspansAndColspans + ) + ).toEqual({ + row: 0, + col: 2, + cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[1], + }); expect( - resolveRelativeTableCellIndices({ row: 1, col: 1 }, tableWithRowspan) - ).toEqual({ row: 1, col: 1 }); + getAbsoluteTableCellIndices( + { row: 0, col: 2 }, + tableWithComplexRowspansAndColspans + ) + ).toEqual({ + row: 0, + col: 3, + cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[2], + }); expect( - resolveRelativeTableCellIndices({ row: 1, col: 2 }, tableWithRowspan) - ).toEqual({ row: 1, col: 2 }); + getAbsoluteTableCellIndices( + { row: 1, col: 0 }, + tableWithComplexRowspansAndColspans + ) + ).toEqual({ + row: 2, + col: 0, + cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[0], + }); expect( - resolveRelativeTableCellIndices({ row: 2, col: 1 }, tableWithRowspan) - ).toEqual({ row: 2, col: 1 }); + getAbsoluteTableCellIndices( + { row: 1, col: 1 }, + tableWithComplexRowspansAndColspans + ) + ).toEqual({ + row: 2, + col: 1, + cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[1], + }); expect( - resolveRelativeTableCellIndices({ row: 2, col: 2 }, tableWithRowspan) - ).toEqual({ row: 2, col: 2 }); + getAbsoluteTableCellIndices( + { row: 1, col: 2 }, + tableWithComplexRowspansAndColspans + ) + ).toEqual({ + row: 2, + col: 2, + cell: tableWithComplexRowspansAndColspans.content.rows[1].cells[0], + }); }); }); -describe("Test resolveAbsoluteTableCellIndices", () => { +describe("Test getRelativeTableCellIndices", () => { it("should resolve absolute table cell indices to relative table cell indices", () => { expect( - resolveAbsoluteTableCellIndices({ row: 0, col: 0 }, simpleTable) + getRelativeTableCellIndices({ row: 0, col: 0 }, simpleTable) ).toEqual({ row: 0, col: 0, cell: simpleTable.content.rows[0].cells[0] }); expect( - resolveAbsoluteTableCellIndices({ row: 0, col: 1 }, simpleTable) + getRelativeTableCellIndices({ row: 0, col: 1 }, simpleTable) ).toEqual({ row: 0, col: 1, cell: simpleTable.content.rows[0].cells[1] }); expect( - resolveAbsoluteTableCellIndices({ row: 1, col: 0 }, simpleTable) + getRelativeTableCellIndices({ row: 1, col: 0 }, simpleTable) ).toEqual({ row: 1, col: 0, cell: simpleTable.content.rows[1].cells[0] }); expect( - resolveAbsoluteTableCellIndices({ row: 1, col: 1 }, simpleTable) + getRelativeTableCellIndices({ row: 1, col: 1 }, simpleTable) ).toEqual({ row: 1, col: 1, cell: simpleTable.content.rows[1].cells[1] }); }); it("should resolve absolute table cell indices to relative table cell indices with colspan", () => { expect( - resolveAbsoluteTableCellIndices({ row: 0, col: 0 }, tableWithColspan) + getRelativeTableCellIndices({ row: 0, col: 0 }, tableWithColspan) ).toEqual({ row: 0, col: 0, cell: tableWithColspan.content.rows[0].cells[0], }); expect( - resolveAbsoluteTableCellIndices({ row: 0, col: 1 }, tableWithColspan) + getRelativeTableCellIndices({ row: 0, col: 1 }, tableWithColspan) ).toEqual({ row: 0, col: 0, cell: tableWithColspan.content.rows[0].cells[0], }); expect( - resolveAbsoluteTableCellIndices({ row: 0, col: 2 }, tableWithColspan) + getRelativeTableCellIndices({ row: 0, col: 2 }, tableWithColspan) ).toEqual({ row: 0, col: 1, cell: tableWithColspan.content.rows[0].cells[1], }); expect( - resolveAbsoluteTableCellIndices({ row: 1, col: 0 }, tableWithColspan) + getRelativeTableCellIndices({ row: 1, col: 0 }, tableWithColspan) ).toEqual({ row: 1, col: 0, cell: tableWithColspan.content.rows[1].cells[0], }); expect( - resolveAbsoluteTableCellIndices({ row: 1, col: 1 }, tableWithColspan) + getRelativeTableCellIndices({ row: 1, col: 1 }, tableWithColspan) ).toEqual({ row: 1, col: 1, cell: tableWithColspan.content.rows[1].cells[1], }); expect( - resolveAbsoluteTableCellIndices({ row: 1, col: 2 }, tableWithColspan) + getRelativeTableCellIndices({ row: 1, col: 2 }, tableWithColspan) ).toEqual({ row: 1, col: 2, @@ -621,63 +913,63 @@ describe("Test resolveAbsoluteTableCellIndices", () => { it("should resolve absolute table cell indices to relative table cell indices with rowspan", () => { expect( - resolveAbsoluteTableCellIndices({ row: 0, col: 0 }, tableWithRowspan) + getRelativeTableCellIndices({ row: 0, col: 0 }, tableWithRowspan) ).toEqual({ row: 0, col: 0, cell: tableWithRowspan.content.rows[0].cells[0], }); expect( - resolveAbsoluteTableCellIndices({ row: 0, col: 1 }, tableWithRowspan) + getRelativeTableCellIndices({ row: 0, col: 1 }, tableWithRowspan) ).toEqual({ row: 0, col: 1, cell: tableWithRowspan.content.rows[0].cells[1], }); expect( - resolveAbsoluteTableCellIndices({ row: 0, col: 2 }, tableWithRowspan) + getRelativeTableCellIndices({ row: 0, col: 2 }, tableWithRowspan) ).toEqual({ row: 0, col: 2, cell: tableWithRowspan.content.rows[0].cells[2], }); expect( - resolveAbsoluteTableCellIndices({ row: 1, col: 0 }, tableWithRowspan) + getRelativeTableCellIndices({ row: 1, col: 0 }, tableWithRowspan) ).toEqual({ row: 0, col: 0, cell: tableWithRowspan.content.rows[0].cells[0], }); expect( - resolveAbsoluteTableCellIndices({ row: 1, col: 1 }, tableWithRowspan) + getRelativeTableCellIndices({ row: 1, col: 1 }, tableWithRowspan) ).toEqual({ row: 1, col: 0, cell: tableWithRowspan.content.rows[1].cells[0], }); expect( - resolveAbsoluteTableCellIndices({ row: 1, col: 2 }, tableWithRowspan) + getRelativeTableCellIndices({ row: 1, col: 2 }, tableWithRowspan) ).toEqual({ row: 1, col: 1, cell: tableWithRowspan.content.rows[1].cells[1], }); expect( - resolveAbsoluteTableCellIndices({ row: 2, col: 0 }, tableWithRowspan) + getRelativeTableCellIndices({ row: 2, col: 0 }, tableWithRowspan) ).toEqual({ row: 2, col: 0, cell: tableWithRowspan.content.rows[2].cells[0], }); expect( - resolveAbsoluteTableCellIndices({ row: 2, col: 1 }, tableWithRowspan) + getRelativeTableCellIndices({ row: 2, col: 1 }, tableWithRowspan) ).toEqual({ row: 2, col: 1, cell: tableWithRowspan.content.rows[2].cells[1], }); expect( - resolveAbsoluteTableCellIndices({ row: 2, col: 2 }, tableWithRowspan) + getRelativeTableCellIndices({ row: 2, col: 2 }, tableWithRowspan) ).toEqual({ row: 2, col: 2, @@ -687,7 +979,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { it("should resolve absolute table cell indices to relative table cell indices with colspan and rowspan", () => { expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 0, col: 0 }, tableWithColspanAndRowspan ) @@ -697,7 +989,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspanAndRowspan.content.rows[0].cells[0], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 0, col: 1 }, tableWithColspanAndRowspan ) @@ -707,7 +999,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspanAndRowspan.content.rows[0].cells[1], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 0, col: 2 }, tableWithColspanAndRowspan ) @@ -717,7 +1009,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspanAndRowspan.content.rows[0].cells[2], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 1, col: 0 }, tableWithColspanAndRowspan ) @@ -727,7 +1019,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspanAndRowspan.content.rows[0].cells[0], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 1, col: 1 }, tableWithColspanAndRowspan ) @@ -737,7 +1029,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspanAndRowspan.content.rows[1].cells[0], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 1, col: 2 }, tableWithColspanAndRowspan ) @@ -747,7 +1039,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspanAndRowspan.content.rows[1].cells[0], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 2, col: 0 }, tableWithColspanAndRowspan ) @@ -757,7 +1049,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspanAndRowspan.content.rows[2].cells[0], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 2, col: 1 }, tableWithColspanAndRowspan ) @@ -767,7 +1059,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspanAndRowspan.content.rows[2].cells[1], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 2, col: 2 }, tableWithColspanAndRowspan ) @@ -780,7 +1072,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { it("should resolve absolute table cell indices to relative table cell indices with colspans and rowspans", () => { expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 0, col: 0 }, tableWithColspansAndRowspans ) @@ -790,7 +1082,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspansAndRowspans.content.rows[0].cells[0], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 0, col: 1 }, tableWithColspansAndRowspans ) @@ -800,7 +1092,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspansAndRowspans.content.rows[0].cells[1], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 0, col: 2 }, tableWithColspansAndRowspans ) @@ -810,7 +1102,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspansAndRowspans.content.rows[0].cells[1], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 1, col: 0 }, tableWithColspansAndRowspans ) @@ -820,7 +1112,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspansAndRowspans.content.rows[0].cells[0], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 1, col: 1 }, tableWithColspansAndRowspans ) @@ -830,7 +1122,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspansAndRowspans.content.rows[1].cells[0], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 1, col: 2 }, tableWithColspansAndRowspans ) @@ -840,7 +1132,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspansAndRowspans.content.rows[1].cells[0], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 2, col: 0 }, tableWithColspansAndRowspans ) @@ -850,7 +1142,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspansAndRowspans.content.rows[2].cells[0], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 2, col: 1 }, tableWithColspansAndRowspans ) @@ -860,7 +1152,7 @@ describe("Test resolveAbsoluteTableCellIndices", () => { cell: tableWithColspansAndRowspans.content.rows[1].cells[0], }); expect( - resolveAbsoluteTableCellIndices( + getRelativeTableCellIndices( { row: 2, col: 2 }, tableWithColspansAndRowspans ) @@ -872,6 +1164,23 @@ describe("Test resolveAbsoluteTableCellIndices", () => { }); }); +describe("resolveAbsoluteTableCellIndices and resolveRelativeTableCellIndices should be inverse functions", () => { + it("should work for simple tables", () => { + expect( + getRelativeTableCellIndices( + getAbsoluteTableCellIndices({ row: 0, col: 0 }, simpleTable), + simpleTable + ) + ).toEqual({ row: 0, col: 0, cell: simpleTable.content.rows[0].cells[0] }); + expect( + getAbsoluteTableCellIndices( + getRelativeTableCellIndices({ row: 0, col: 1 }, simpleTable), + simpleTable + ) + ).toEqual({ row: 0, col: 1, cell: simpleTable.content.rows[0].cells[1] }); + }); +}); + describe("Test getRow", () => { it("should get the row of the table", () => { expect(getRow(simpleTable, 0)).toEqual( @@ -953,6 +1262,49 @@ describe("Test getRow", () => { }, ]); }); + + it("should get the row of the table with complex rowspans and colspans", () => { + expect(getRow(tableWithComplexRowspansAndColspans, 0)).toEqual([ + { + row: 0, + col: 0, + cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[0], + }, + { + row: 0, + col: 1, + cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[1], + }, + { + row: 0, + col: 2, + cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[2], + }, + ]); + expect(getRow(tableWithComplexRowspansAndColspans, 1)).toEqual([ + { + row: 2, + col: 0, + cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[0], + }, + { + row: 2, + col: 1, + cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[1], + }, + { + row: 1, + col: 0, + cell: tableWithComplexRowspansAndColspans.content.rows[1].cells[0], + }, + { + row: 2, + col: 2, + cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[2], + }, + ]); + expect(getRow(tableWithComplexRowspansAndColspans, 2)).toEqual([]); + }); }); describe("Test getColumn", () => { @@ -982,6 +1334,7 @@ describe("Test getColumn", () => { cell: simpleTable.content.rows[1].cells[1], }, ]); + expect(getColumn(simpleTable, 2)).toEqual([]); }); it("should get the column of the table with colspan", () => { @@ -1010,6 +1363,7 @@ describe("Test getColumn", () => { cell: tableWithColspan.content.rows[1].cells[2], }, ]); + expect(getColumn(tableWithColspan, 2)).toEqual([]); }); it("should get the column of the table with rowspan", () => { @@ -1061,5 +1415,51 @@ describe("Test getColumn", () => { cell: tableWithRowspan.content.rows[2].cells[2], }, ]); + expect(getColumn(tableWithRowspan, 3)).toEqual([]); + }); + + it("should get the column of the table with complex rowspans and colspans", () => { + expect(getColumn(tableWithComplexRowspansAndColspans, 0)).toEqual([ + { + row: 0, + col: 0, + cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[0], + }, + { + row: 2, + col: 0, + cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[0], + }, + ]); + expect(getColumn(tableWithComplexRowspansAndColspans, 1)).toEqual([ + { + row: 0, + col: 1, + cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[1], + }, + { + row: 1, + col: 0, + cell: tableWithComplexRowspansAndColspans.content.rows[1].cells[0], + }, + ]); + expect(getColumn(tableWithComplexRowspansAndColspans, 2)).toEqual([ + { + row: 0, + col: 2, + cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[2], + }, + { + row: 1, + col: 1, + cell: tableWithComplexRowspansAndColspans.content.rows[1].cells[1], + }, + { + row: 2, + col: 2, + cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[2], + }, + ]); + expect(getColumn(tableWithComplexRowspansAndColspans, 3)).toEqual([]); }); }); diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 32351de207..49a0216be6 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -9,71 +9,151 @@ import { } from "../../../schema/blocks/types.js"; /** - * This will resolve the relative cell indices within the table block to the absolute cell indices within the table. - * Accounts for colspan and rowspan. + * Here be dragons. * - * @returns The absolute cell indices (row and column). + * Tables are complex because of rowspan and colspan behavior. + * The majority of this file is concerned with translating between "relative" and "absolute" indices. + * + * The following diagram may help explain the relationship between the different indices: + * + * One-based indexing of rows and columns in a table: + * | 1-1 | 1-2 | 1-3 | + * | 2-1 | 2-2 | 2-3 | + * | 3-1 | 3-2 | 3-3 | + * + * A complicated table with colspans and rowspans: + * | 1-1 | 1-2 | 1-2 | + * | 2-1 | 2-1 | 2-2 | + * | 2-1 | 2-1 | 3-1 | + * + * You can see here that we have: + * - two cells that contain the value "1-2", because it has a colspan of 2. + * - four cells that contain the value "2-1", because it has a rowspan of 2 and a colspan of 2. + * + * This would be represented in block note json (roughly) as: + * [ + * { + * "cells": [ + * { + * "type": "tableCell", + * "content": ["1,1"], + * "props": { + * "colspan": 1, + * "rowspan": 1 + * }, + * }, + * { + * "type": "tableCell", + * "content": ["1,2"], + * "props": { + * "colspan": 2, + * "rowspan": 1 + * } + * } + * ], + * }, + * { + * "cells": [ + * { + * "type": "tableCell", + * "content": ["2,1"], + * "props": { + * "colspan": 2, + * "rowspan": 2 + * } + * }, + * { + * "type": "tableCell", + * "content": ["2,2"], + * "props": { + * "colspan": 1, + * "rowspan": 1 + * } + * ], + * }, + * { + * "cells": [ + * { + * "type": "tableCell", + * "content": ["3,1"], + * "props": { + * "colspan": 1, + * "rowspan": 1, + * } + * } + * ] + * } + * ] + * + * Which maps cleanly to the following HTML: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
1-11-2
2-12-2
3-1
+ * + * We have a problem though, from the block json, there is no way to tell that the cell "2-1" is the second cell in the second row. + * To resolve this, we created the occupancy grid, which is a grid of all the cells in the table, as though they were only 1x1 cells. + * + * TODO an example? */ -export function resolveRelativeTableCellIndices( - /** - * The relative position of the cell in the table. - */ - relativeCellIndices: { row: number; col: number }, - /** - * The table block containing the cell. - */ - block: BlockFromConfigNoChildren -): { row: number; col: number } { - // Calculate column index by summing colspan values of previous cells in the row - let colIndex = 0; - for (let i = 0; i < relativeCellIndices.col; i++) { - const cell = block.content.rows[relativeCellIndices.row].cells[i]; - colIndex += getColspan(cell); - } - // Calculate row index by summing rowspan values of cells in previous rows - let rowIndex = 0; - for (let i = 0; i < relativeCellIndices.row; i++) { - const cell = block.content.rows[i].cells[relativeCellIndices.col]; - rowIndex += getRowspan(cell); - } - - return { row: rowIndex, col: colIndex }; -} +/** + * Relative cell indices are relative to the table block's content. + * + * This is a sparse representation of the table and is how HTML and BlockNote JSON represent tables. + * + * For example, if we have a table with a rowspan of 2, the second row may only have 1 element in a 2x2 table. + * + * ``` + * // Visual representation of the table + * | 1-1 | 1-2 | // has 2 cells + * | 1-1 | 2-2 | // has only 1 cell + * // Relative cell indices + * [{ row: 1, col: 1, rowspan: 2 }, { row: 1, col: 2 }] // has 2 cells + * [{ row: 1, col: 2 }] // has only 1 cell + * ``` + */ +export type RelativeCellIndices = { + row: number; + col: number; +}; /** - * This will get the dimensions of the table block. + * Absolute cell indices are relative to the table's layout (it's {@link OccupancyGrid}). * - * @returns The height and width of the table. + * It is as though the table is a grid of 1x1 cells, and any colspan or rowspan results in multiple 1x1 cells being occupied. + * + * For example, if we have a table with a colspan of 2, it will occupy 2 cells in the layout grid. + * + * ``` + * // Visual representation of the table + * | 1-1 | 1-1 | // has 2 cells + * | 2-1 | 2-2 | // has 2 cell + * // Absolute cell indices + * [{ row: 1, col: 1, colspan: 2 }, { row: 1, col: 2, colspan: 2 }] // has 2 cells + * [{ row: 1, col: 1 }, { row: 1, col: 2 }] // has 2 cells + * ``` */ -export function getDimensionsOfTable( - block: BlockFromConfigNoChildren -) { - const height = block.content.rows.length; - const width = block.content.rows.reduce((acc, { cells }) => { - return Math.max( - acc, - cells.reduce((acc, cell) => { - return acc + getColspan(cell); - }, 0) - ); - }, 0); +export type AbsoluteCellIndices = { + row: number; + col: number; +}; - return { height, width }; -} /** * An occupancy grid is a grid of the occupied cells in the table. * It is used to track the occupied cells in the table to know where to place the next cell. */ -type OccupancyGrid = { - /** - * The row index of the cell. - */ - row: number; - /** - * The column index of the cell. - */ - col: number; +type OccupancyGrid = (AbsoluteCellIndices & { /** * The rowspan of the cell. */ @@ -86,7 +166,7 @@ type OccupancyGrid = { * The cell. */ cell: TableCell; -}[][]; +})[][]; /** * This will return a grid of the occupied cells in the table. @@ -95,26 +175,20 @@ type OccupancyGrid = { */ export function getTableCellOccupancyGrid( block: BlockFromConfigNoChildren -): OccupancyGrid | null { +): OccupancyGrid { const { height, width } = getDimensionsOfTable(block); - /* + /** * Create a grid to track occupied cells * This is used because rowspans and colspans take up multiple spaces * So, we need to track the occupied cells in the grid to know where to place the next cell */ - const grid: ({ - row: number; - col: number; - rowspan: number; - colspan: number; - cell: TableCell; - } | null)[][] = new Array(height) + const grid: OccupancyGrid = new Array(height) .fill(false) .map(() => new Array(width).fill(null)); // Find the next unoccupied cell in the table, row-major order - function findNextAvailable(row: number, col: number) { + const findNextAvailable = (row: number, col: number) => { for (let i = row; i < height; i++) { for (let j = col; j < width; j++) { if (!grid[i][j]) { @@ -122,8 +196,11 @@ export function getTableCellOccupancyGrid( } } } - return null; - } + + throw new Error( + "Unable to create occupancy grid for table, no more available cells" + ); + }; // Build up the grid, trying to fill in the cells with the correct relative row and column indices for (let row = 0; row < block.content.rows.length; row++) { @@ -135,21 +212,16 @@ export function getTableCellOccupancyGrid( // Rowspan and colspan complicate things, by taking up multiple cells in the grid // We need to iterate over the cells that the rowspan and colspan take up // and fill in the grid with the correct relative row and column indices - const nextAvailableCell = findNextAvailable(row, col); - - if (nextAvailableCell === null) { - // No more available cells in the table, the table is invalid - return null; - } - - const { row: startRow, col: startCol } = nextAvailableCell; + const { row: startRow, col: startCol } = findNextAvailable(row, col); // Fill in the rowspan X colspan cells, starting from the next available cell, with the correct relative row and column indices for (let i = startRow; i < startRow + rowspan; i++) { for (let j = startCol; j < startCol + colspan; j++) { if (grid[i][j]) { - // The cell is already occupied, the table is invalid - return null; + // The cell is already occupied, the table is malformed + throw new Error( + `Unable to create occupancy grid for table, cell at ${i},${j} is already occupied` + ); } grid[i][j] = { @@ -164,18 +236,105 @@ export function getTableCellOccupancyGrid( } } - function isOccupancyGrid(grid: any[][]): grid is OccupancyGrid { - return grid.every((row) => - row.every((cell) => cell !== null && typeof cell === "object") - ); + return grid; +} + +/** + * This will resolve the relative cell indices within the table block to the absolute cell indices within the table. + * Accounts for colspan and rowspan. + * + * @returns The absolute cell indices (row and column). + */ +export function getAbsoluteTableCellIndices( + /** + * The relative position of the cell in the table. + */ + relativeCellIndices: RelativeCellIndices, + /** + * The table block containing the cell. + */ + block: BlockFromConfigNoChildren, + /** + * The occupancy grid of the table. + */ + occupancyGrid: OccupancyGrid = getTableCellOccupancyGrid(block) +): AbsoluteCellIndices & { + cell: TableContent["rows"][number]["cells"][number]; +} { + let absoluteRow = 0; + + // Jump through the occupied cells ${relativeCellIndices.row} times to find the absolute row position + for (let i = 0; i < relativeCellIndices.row; i++) { + const cell = occupancyGrid[absoluteRow]?.[0]; + + if (!cell) { + // As a sanity check, if the cell is not occupied, we should throw an error + throw new Error( + `Unable to resolve relative table cell indices for table, cell at ${absoluteRow},0 is not occupied` + ); + } + + // Skip the cells that the rowspan takes up + absoluteRow += cell.rowspan; } - if (!isOccupancyGrid(grid)) { - // The table is missing cells, so it is invalid - return null; + let absoluteCol = 0; + + // Now that we've already resolved the absolute row position, we can jump through the occupied cells ${relativeCellIndices.col} times to find the absolute column position + for (let i = 0; i < relativeCellIndices.col; i++) { + const cell = occupancyGrid[absoluteRow]?.[absoluteCol]; + + if (!cell) { + // As a sanity check, if the cell is not occupied, we should throw an error + throw new Error( + `Unable to resolve relative table cell indices for table, cell at ${absoluteRow},${absoluteCol} is not occupied` + ); + } + + // Skip the cells that the colspan takes up + absoluteCol += cell.colspan; } - return grid; + const cell = occupancyGrid[absoluteRow]?.[absoluteCol]; + + if (!cell) { + throw new Error( + `Unable to resolve relative table cell indices for table, cell at ${absoluteRow},${absoluteCol} is not occupied` + ); + } + + return { + row: absoluteRow, + col: absoluteCol, + cell: cell.cell, + }; +} + +/** + * This will get the dimensions of the table block. + * + * @returns The height and width of the table. + */ +export function getDimensionsOfTable( + block: BlockFromConfigNoChildren +) { + // Due to the way we store the table, the height is always the number of rows + const height = block.content.rows.length; + + // Calculating the width is a bit more complex, as it is the maximum width of any row + let width = 0; + block.content.rows.forEach((row) => { + // Find the width of the row by summing the colspan of each cell + let rowWidth = 0; + row.cells.forEach((cell) => { + rowWidth += getColspan(cell); + }); + + // Update the width if the row is wider than the current width + width = Math.max(width, rowWidth); + }); + + return { height, width }; } /** @@ -184,11 +343,11 @@ export function getTableCellOccupancyGrid( * * @returns The relative cell indices (row and column). */ -export function resolveAbsoluteTableCellIndices( +export function getRelativeTableCellIndices( /** * The absolute position of the cell in the table. */ - absoluteCellIndices: { row: number; col: number }, + absoluteCellIndices: AbsoluteCellIndices, /** * The table block containing the cell. */ @@ -196,24 +355,19 @@ export function resolveAbsoluteTableCellIndices( /** * The occupancy grid of the table. */ - occupancyGrid: OccupancyGrid | null = getTableCellOccupancyGrid(block) -): { - row: number; - col: number; + occupancyGrid: OccupancyGrid = getTableCellOccupancyGrid(block) +): RelativeCellIndices & { cell: TableContent["rows"][number]["cells"][number]; -} | null { - if (!occupancyGrid) { - // The table is missing cells, so it is invalid - return null; - } - +} { const occupancyCell = occupancyGrid[absoluteCellIndices.row]?.[absoluteCellIndices.col]; // Double check that the cell can be accessed if (!occupancyCell) { // The cell is not occupied, so it is invalid - return null; + throw new Error( + `Unable to resolve absolute table cell indices for table, cell at ${absoluteCellIndices.row},${absoluteCellIndices.col} is not occupied` + ); } return { @@ -230,35 +384,38 @@ export function resolveAbsoluteTableCellIndices( */ export function getRow( block: BlockFromConfigNoChildren, - relativeRowIndex: number + relativeRowIndex: RelativeCellIndices["row"] ) { - const occupancyGrid = getTableCellOccupancyGrid(block); - if (!occupancyGrid) { - throw new Error("Table is malformed"); - } - const { width } = getDimensionsOfTable(block); - // First need to resolve the relative row index to an absolute row index - const { row: absoluteRow } = resolveRelativeTableCellIndices( - { row: relativeRowIndex, col: 0 }, - block - ); - - // Then for each column, get the cell at the absolute row index as a relative cell index - const cells = new Array(width).fill(false).map((_v, col) => { - return resolveAbsoluteTableCellIndices( - { row: absoluteRow, col }, + try { + const occupancyGrid = getTableCellOccupancyGrid(block); + const { row: absoluteRow } = getAbsoluteTableCellIndices( + { row: relativeRowIndex, col: 0 }, block, occupancyGrid ); - }); - // Filter out duplicates based on row and col properties - return cells.filter((cell, index): cell is NonNullable => { - return ( - cell !== null && - cells.findIndex((c) => c && c.row === cell.row && c.col === cell.col) === + + // Then for each column, get the cell at the absolute row index as a relative cell index + const cells = new Array(occupancyGrid[0].length) + .fill(false) + .map((_v, col) => { + return getRelativeTableCellIndices( + { row: absoluteRow, col }, + block, + occupancyGrid + ); + }); + + // Filter out duplicates based on row and col properties + return cells.filter((cell, index) => { + return ( + cells.findIndex((c) => c.row === cell.row && c.col === cell.col) === index - ); - }); + ); + }); + } catch (e) { + // In case of an invalid index, return an empty array + return []; + } } /** @@ -268,34 +425,35 @@ export function getRow( */ export function getColumn( block: BlockFromConfigNoChildren, - relativeColumnIndex: number + relativeColumnIndex: RelativeCellIndices["col"] ) { - const occupancyGrid = getTableCellOccupancyGrid(block); - if (!occupancyGrid) { - throw new Error("Table is malformed"); - } - const { height } = getDimensionsOfTable(block); - // First need to resolve the relative column index to an absolute column index - const { col: absoluteCol } = resolveRelativeTableCellIndices( - { row: 0, col: relativeColumnIndex }, - block - ); - - // Then for each row, get the cell at the absolute column index as a relative cell index - const cells = new Array(height).fill(false).map((_v, row) => { - return resolveAbsoluteTableCellIndices( - { row, col: absoluteCol }, + try { + const occupancyGrid = getTableCellOccupancyGrid(block); + // First need to resolve the relative column index to an absolute column index + const { col: absoluteCol } = getAbsoluteTableCellIndices( + { row: 0, col: relativeColumnIndex }, block, occupancyGrid ); - }); - // Filter out duplicates based on row and col properties - return cells.filter((cell, index): cell is NonNullable => { - return ( - cell !== null && - cells.findIndex((c) => c && c.row === cell.row && c.col === cell.col) === + // Then for each row, get the cell at the absolute column index as a relative cell index + const cells = new Array(occupancyGrid.length).fill(false).map((_v, row) => { + return getRelativeTableCellIndices( + { row, col: absoluteCol }, + block, + occupancyGrid + ); + }); + + // Filter out duplicates based on row and col properties + return cells.filter((cell, index) => { + return ( + cells.findIndex((c) => c.row === cell.row && c.col === cell.col) === index - ); - }); + ); + }); + } catch (e) { + // In case of an invalid index, return an empty array + return []; + } } diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 6ccdf2d736..c4cf08982e 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -12,13 +12,13 @@ import type { } from "../../schema"; import type { PartialBlock } from "../../blocks/defaultBlocks"; -import { isPartialTableCell } from "../../schema/blocks/types.js"; +import { getColspan, isPartialTableCell } from "../../schema/blocks/types.js"; import { isPartialLinkInlineContent, isStyledTextInlineContent, } from "../../schema/inlineContent/types.js"; import { UnreachableCaseError } from "../../util/typescript.js"; -import { resolveRelativeTableCellIndices } from "../blockManipulation/tables/tables.js"; +import { getAbsoluteTableCellIndices } from "../blockManipulation/tables/tables.js"; /** * Convert a StyledText inline element to a @@ -201,13 +201,14 @@ export function tableContentToNodes< let content: Fragment | Node | readonly Node[] | null = null; // Colwidths are absolutely referenced to the table, so we need to resolve the relative cell index to the absolute cell index - const absoluteCellIndex = resolveRelativeTableCellIndices( + const absoluteCellIndex = getAbsoluteTableCellIndices( { row: rowIndex, col: cellIndex, }, { type: "table", content: tableContent } as any ); + // Assume the column width is the width of the cell at the absolute cell index let colwidth: (number | undefined)[] = [ columnWidths[absoluteCellIndex.col] ?? undefined, @@ -227,7 +228,7 @@ export function tableContentToNodes< // If the cell has a > 1 colspan, we need to get the column width for each cell in the span colwidth = new Array(colspan).fill(false).map((_, i) => { // Starting from the absolute column index, get the column width for each cell in the span - return columnWidths[absoluteColIndex + i] ?? undefined; + return columnWidths[absoluteCellIndex.col + i] ?? undefined; }); } } else { diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index d59fa590d7..71ce72345b 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -5,11 +5,15 @@ import { getColumn, getDimensionsOfTable, getRow, - resolveRelativeTableCellIndices, + getAbsoluteTableCellIndices, + RelativeCellIndices, } from "../../api/blockManipulation/tables/tables.js"; import { nodeToBlock } from "../../api/nodeConversions/nodeToBlock.js"; import { getNodeById } from "../../api/nodeUtil.js"; -import { checkBlockIsDefaultType } from "../../blocks/defaultBlockTypeGuards.js"; +import { + checkBlockIsDefaultType, + isTableCellSelection, +} from "../../blocks/defaultBlockTypeGuards.js"; import { DefaultBlockSchema } from "../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { @@ -889,16 +893,9 @@ export class TableHandlesProsemirrorPlugin< this.view!.menuFrozen = false; }; - resolveRelativeTableCellIndices = ( - relativeCellIndices: { row: number; col: number }, - block: BlockFromConfigNoChildren - ) => { - return resolveRelativeTableCellIndices(relativeCellIndices, block); - }; - getRow = ( block: BlockFromConfigNoChildren, - relativeRowIndex: number + relativeRowIndex: RelativeCellIndices["row"] ) => { return getRow(block, relativeRowIndex); }; @@ -908,7 +905,7 @@ export class TableHandlesProsemirrorPlugin< */ getColumn = ( block: BlockFromConfigNoChildren, - relativeColumnIndex: number + relativeColumnIndex: RelativeCellIndices["col"] ) => { return getColumn(block, relativeColumnIndex); }; @@ -918,8 +915,8 @@ export class TableHandlesProsemirrorPlugin< * @returns The new state after the selection has been set. */ private setCellSelection = ( - startCell: { row: number; col: number }, - endCell: { row: number; col: number } = startCell + relativeStartCell: RelativeCellIndices, + relativeEndCell: RelativeCellIndices = relativeStartCell ) => { const view = this.view; @@ -930,18 +927,18 @@ export class TableHandlesProsemirrorPlugin< const state = this.editor.prosemirrorState; const tableResolvedPos = state.doc.resolve(view.tablePos! + 1); const startRowResolvedPos = state.doc.resolve( - tableResolvedPos.posAtIndex(startCell.row) + 1 + tableResolvedPos.posAtIndex(relativeStartCell.row) + 1 ); const startCellResolvedPos = state.doc.resolve( // No need for +1, since CellSelection expects the position before the cell - startRowResolvedPos.posAtIndex(startCell.col) + startRowResolvedPos.posAtIndex(relativeStartCell.col) ); const endRowResolvedPos = state.doc.resolve( - tableResolvedPos.posAtIndex(endCell.row) + 1 + tableResolvedPos.posAtIndex(relativeEndCell.row) + 1 ); const endCellResolvedPos = state.doc.resolve( // No need for +1, since CellSelection expects the position before the cell - endRowResolvedPos.posAtIndex(endCell.col) + endRowResolvedPos.posAtIndex(relativeEndCell.col) ); // Begin a new transaction to set the selection @@ -960,11 +957,14 @@ export class TableHandlesProsemirrorPlugin< * Merges the cells in the table block. */ mergeCells = (cellsToMerge?: { - start: { row: number; col: number }; - end: { row: number; col: number }; + relativeStartCell: RelativeCellIndices; + relativeEndCell: RelativeCellIndices; }) => { const state = cellsToMerge - ? this.setCellSelection(cellsToMerge.start, cellsToMerge.end) + ? this.setCellSelection( + cellsToMerge.relativeStartCell, + cellsToMerge.relativeEndCell + ) : this.editor.prosemirrorState; return mergeCells(state, this.editor.dispatch); @@ -974,11 +974,67 @@ export class TableHandlesProsemirrorPlugin< * Splits the cell in the table block. * If no cell is provided, the current cell selected will be split. */ - splitCell = (cellToSplit?: { row: number; col: number }) => { - const state = cellToSplit - ? this.setCellSelection(cellToSplit) + splitCell = (relativeCellToSplit?: RelativeCellIndices) => { + const state = relativeCellToSplit + ? this.setCellSelection(relativeCellToSplit) : this.editor.prosemirrorState; return splitCell(state, this.editor.dispatch); }; + + /** + * Gets the direction of the merge based on the current cell selection. + * + * Returns undefined when there is no cell selection, or the selection is not within a table. + */ + getMergeDirection = ( + block: + | BlockFromConfigNoChildren + | undefined + ) => { + const cellSelection = isTableCellSelection( + this.editor.prosemirrorState.selection + ) + ? this.editor.prosemirrorState.selection + : undefined; + + if ( + !cellSelection || + !block || + // Only offer the merge button if there is more than one cell selected. + cellSelection.ranges.length <= 1 + ) { + return undefined; + } + + const { $anchorCell, $headCell } = cellSelection; + // Table indices are relative to the table, so we need to resolve the absolute cell indices + const absoluteCellIndices = getAbsoluteTableCellIndices( + { + // row index within the table + row: $anchorCell.index($anchorCell.depth - 1), + // column index within the row + col: $anchorCell.index(), + }, + block + ); + + // Table indices are relative to the table, so we need to resolve the absolute cell indices + const headAbsoluteCellIndices = getAbsoluteTableCellIndices( + { + // row index within the table + row: $headCell.index($headCell.depth - 1), + // column index within the row + col: $headCell.index(), + }, + block + ); + + // Compare the column indices to determine the merge direction + if (absoluteCellIndices.col === headAbsoluteCellIndices.col) { + return "vertical"; + } + + return "horizontal"; + }; } diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx index 1dc04c8433..776d47f4e1 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx @@ -1,9 +1,6 @@ import { - Block, - BlockNoteEditor, DefaultBlockSchema, InlineContentSchema, - isTableCellSelection, StyleSchema, } from "@blocknote/core"; import { useCallback, useMemo } from "react"; @@ -14,67 +11,6 @@ import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js"; import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks.js"; import { useDictionary } from "../../../i18n/dictionary.js"; -/** - * Gets the direction of the merge. Based on the current cell selection if there is one. - */ -function getMergeDirection( - editor: BlockNoteEditor< - { - table: DefaultBlockSchema["table"]; - }, - any, - any - >, - block: - | Block< - { - table: DefaultBlockSchema["table"]; - }, - any, - any - > - | undefined -): undefined | "horizontal" | "vertical" { - const tableHandles = editor.tableHandles; - - const cellSelection = isTableCellSelection( - editor._tiptapEditor.state.selection - ) - ? editor._tiptapEditor.state.selection - : undefined; - - if ( - !cellSelection || - !block || - !tableHandles || - // Only offer the merge button if there is more than one cell selected. - cellSelection.ranges.length <= 1 - ) { - return undefined; - } - - const { $anchorCell, $headCell } = cellSelection; - const anchorRowIndex = $anchorCell.index($anchorCell.depth - 1); - const anchorColIndex = $anchorCell.index(); - const anchorResolved = tableHandles.resolveRelativeTableCellIndices( - { row: anchorRowIndex, col: anchorColIndex }, - block - ); - - const headRowIndex = $headCell.index($headCell.depth - 1); - const headColIndex = $headCell.index(); - const headResolved = tableHandles.resolveRelativeTableCellIndices( - { row: headRowIndex, col: headColIndex }, - block - ); - - if (anchorResolved.col === headResolved.col) { - return "vertical"; - } - - return "horizontal"; -} - export const TableCellMergeButton = () => { const dict = useDictionary(); const Components = useComponentsContext()!; @@ -97,7 +33,7 @@ export const TableCellMergeButton = () => { const block = selectedBlocks[0]; if (block.type === "table") { - return getMergeDirection(editor, block); + return editor.tableHandles?.getMergeDirection(block); } return undefined; From c9a6f4e6698f6424a922e214daa8917f8f7ab520 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 19 Feb 2025 18:06:05 +0100 Subject: [PATCH 24/72] refactor: mark undefined instead --- packages/core/src/api/testUtil/partialBlockTestUtil.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/api/testUtil/partialBlockTestUtil.ts b/packages/core/src/api/testUtil/partialBlockTestUtil.ts index 89e456a762..ef99f5ff71 100644 --- a/packages/core/src/api/testUtil/partialBlockTestUtil.ts +++ b/packages/core/src/api/testUtil/partialBlockTestUtil.ts @@ -130,7 +130,7 @@ export function partialBlockToBlockForTesting< : contentType === "table" ? { type: "tableContent", - columnWidths: [undefined, undefined, undefined], + columnWidths: undefined, headerRows: undefined, headerCols: undefined, rows: [], From 77f3e5fe36d57bf4c1e66d9d868e6bb5bde96689 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 19 Feb 2025 18:16:20 +0100 Subject: [PATCH 25/72] refactor: cleanup --- .../DefaultButtons/CreateLinkButton.tsx | 4 +- .../DefaultButtons/ColorPicker.tsx | 95 ++++++++----------- 2 files changed, 39 insertions(+), 60 deletions(-) diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx index 37ca7141a0..63ad8c08d1 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx @@ -93,12 +93,12 @@ export const CreateLinkButton = () => { } } - if (isTableCellSelection(editor._tiptapEditor.state.selection)) { + if (isTableCellSelection(editor.prosemirrorState.selection)) { return false; } return true; - }, [linkInSchema, selectedBlocks, editor._tiptapEditor.state.selection]); + }, [linkInSchema, selectedBlocks, editor.prosemirrorState.selection]); if ( !show || diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx index c55abba3cb..d406436ca1 100644 --- a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx +++ b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx @@ -31,6 +31,41 @@ export const ColorPickerButton = < S >(); + const updateColor = (color: string, type: "text" | "background") => { + editor.updateBlock(props.block, { + type: "table", + content: { + ...props.block.content, + rows: props.block.content.rows.map( + (row, rowIndex) => + ({ + ...row, + cells: + props.rowIndex === rowIndex + ? row.cells.map(mapTableCell).map((cell, cellIndex) => + cellIndex === props.colIndex + ? { + ...cell, + props: + type === "text" + ? { + ...cell.props, + textColor: color, + } + : { + ...cell.props, + backgroundColor: color, + }, + } + : cell + ) + : row.cells.map(mapTableCell), + } as any) + ), + }, + }); + }; + const currentCell = props.block.content.rows[props.rowIndex]?.cells?.[props.colIndex]; @@ -58,69 +93,13 @@ export const ColorPickerButton = < color: isPartialTableCell(currentCell) ? currentCell.props.textColor : "default", - setColor: (color) => - editor.updateBlock(props.block, { - type: "table", - content: { - ...props.block.content, - rows: props.block.content.rows.map( - (row, rowIndex) => - ({ - ...row, - cells: - props.rowIndex === rowIndex - ? row.cells - .map(mapTableCell) - .map((cell, cellIndex) => - cellIndex === props.colIndex - ? { - ...cell, - props: { - ...cell.props, - textColor: color, - }, - } - : cell - ) - : row.cells.map(mapTableCell), - } as any) - ), - }, - }), + setColor: (color) => updateColor(color, "text"), }} background={{ color: isPartialTableCell(currentCell) ? currentCell.props.backgroundColor : "default", - setColor: (color) => - editor.updateBlock(props.block, { - type: "table", - content: { - ...props.block.content, - rows: props.block.content.rows.map( - (row, rowIndex) => - ({ - ...row, - cells: - props.rowIndex === rowIndex - ? row.cells - .map(mapTableCell) - .map((cell, cellIndex) => - cellIndex === props.colIndex - ? { - ...cell, - props: { - ...cell.props, - backgroundColor: color, - }, - } - : cell - ) - : row.cells.map(mapTableCell), - } as any) - ), - }, - }), + setColor: (color) => updateColor(color, "background"), }} /> From 290b43cec54d02cd2619a76b58f544a3853abfa8 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 19 Feb 2025 18:27:52 +0100 Subject: [PATCH 26/72] refactor: cleanup --- packages/core/src/schema/blocks/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index ac275841a1..d8fbae86f6 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -415,7 +415,7 @@ export function getRowspan( | PartialInlineContent ): number { if (isTableCell(cell)) { - return cell.props.rowspan || 1; + return cell.props.rowspan ?? 1; } return 1; } From 9092fdddf5950a3f95d49c9387848c0e95e7f715 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 20 Feb 2025 11:08:03 +0100 Subject: [PATCH 27/72] refactor: update the names to be much clearer --- .../blockManipulation/tables/tables.test.ts | 313 +++++++++++++----- .../api/blockManipulation/tables/tables.ts | 200 +++++++---- .../TableHandles/TableHandlesPlugin.ts | 16 +- .../components/TableHandles/TableHandle.tsx | 4 +- .../DefaultButtons/ColorPicker.tsx | 4 +- 5 files changed, 368 insertions(+), 169 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.test.ts b/packages/core/src/api/blockManipulation/tables/tables.test.ts index c0db675d5f..29696205dc 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.test.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.test.ts @@ -2,8 +2,8 @@ import { describe, expect, it } from "vitest"; import { Block, DefaultBlockSchema } from "../../../blocks/defaultBlocks.js"; import { - getColumn, - getRow, + getCellsAtColumnHandle, + getCellsAtRowHandle, getRelativeTableCellIndices, getAbsoluteTableCellIndices, } from "./tables.js"; @@ -766,23 +766,30 @@ describe("Test getAbsoluteTableCellIndices", () => { expect( getAbsoluteTableCellIndices({ row: 1, col: 0 }, tableWithRowspan) ).toEqual({ - row: 2, - col: 0, - cell: tableWithRowspan.content.rows[2].cells[0], + row: 1, + col: 1, + cell: tableWithRowspan.content.rows[1].cells[0], }); expect( getAbsoluteTableCellIndices({ row: 1, col: 1 }, tableWithRowspan) + ).toEqual({ + row: 1, + col: 2, + cell: tableWithRowspan.content.rows[1].cells[1], + }); + expect( + getAbsoluteTableCellIndices({ row: 2, col: 0 }, tableWithRowspan) ).toEqual({ row: 2, - col: 1, - cell: tableWithRowspan.content.rows[2].cells[1], + col: 0, + cell: tableWithRowspan.content.rows[2].cells[0], }); expect( - getAbsoluteTableCellIndices({ row: 1, col: 2 }, tableWithRowspan) + getAbsoluteTableCellIndices({ row: 2, col: 1 }, tableWithRowspan) ).toEqual({ row: 2, - col: 2, - cell: tableWithRowspan.content.rows[2].cells[2], + col: 1, + cell: tableWithRowspan.content.rows[2].cells[1], }); }); @@ -822,6 +829,26 @@ describe("Test getAbsoluteTableCellIndices", () => { { row: 1, col: 0 }, tableWithComplexRowspansAndColspans ) + ).toEqual({ + row: 1, + col: 2, + cell: tableWithComplexRowspansAndColspans.content.rows[1].cells[0], + }); + expect( + getAbsoluteTableCellIndices( + { row: 1, col: 1 }, + tableWithComplexRowspansAndColspans + ) + ).toEqual({ + row: 1, + col: 3, + cell: tableWithComplexRowspansAndColspans.content.rows[1].cells[1], + }); + expect( + getAbsoluteTableCellIndices( + { row: 2, col: 0 }, + tableWithComplexRowspansAndColspans + ) ).toEqual({ row: 2, col: 0, @@ -829,7 +856,7 @@ describe("Test getAbsoluteTableCellIndices", () => { }); expect( getAbsoluteTableCellIndices( - { row: 1, col: 1 }, + { row: 2, col: 1 }, tableWithComplexRowspansAndColspans ) ).toEqual({ @@ -839,13 +866,13 @@ describe("Test getAbsoluteTableCellIndices", () => { }); expect( getAbsoluteTableCellIndices( - { row: 1, col: 2 }, + { row: 2, col: 2 }, tableWithComplexRowspansAndColspans ) ).toEqual({ row: 2, - col: 2, - cell: tableWithComplexRowspansAndColspans.content.rows[1].cells[0], + col: 3, + cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[2], }); }); }); @@ -1166,24 +1193,126 @@ describe("Test getRelativeTableCellIndices", () => { describe("resolveAbsoluteTableCellIndices and resolveRelativeTableCellIndices should be inverse functions", () => { it("should work for simple tables", () => { - expect( - getRelativeTableCellIndices( - getAbsoluteTableCellIndices({ row: 0, col: 0 }, simpleTable), - simpleTable - ) - ).toEqual({ row: 0, col: 0, cell: simpleTable.content.rows[0].cells[0] }); - expect( - getAbsoluteTableCellIndices( - getRelativeTableCellIndices({ row: 0, col: 1 }, simpleTable), - simpleTable - ) - ).toEqual({ row: 0, col: 1, cell: simpleTable.content.rows[0].cells[1] }); + for (let row = 0; row < simpleTable.content.rows.length; row++) { + for ( + let col = 0; + col < simpleTable.content.rows[row].cells.length; + col++ + ) { + expect( + getRelativeTableCellIndices( + getAbsoluteTableCellIndices({ row, col }, simpleTable), + simpleTable + ) + ).toEqual({ row, col, cell: simpleTable.content.rows[row].cells[col] }); + } + } + }); + it("should work for tables with colspans", () => { + for (let row = 0; row < tableWithColspan.content.rows.length; row++) { + for ( + let col = 0; + col < tableWithColspan.content.rows[row].cells.length; + col++ + ) { + expect( + getRelativeTableCellIndices( + getAbsoluteTableCellIndices({ row, col }, tableWithColspan), + tableWithColspan + ) + ).toEqual({ + row, + col, + cell: tableWithColspan.content.rows[row].cells[col], + }); + } + } + }); + + it("should work for tables with rowspans", () => { + for (let row = 0; row < tableWithRowspan.content.rows.length; row++) { + for ( + let col = 0; + col < tableWithRowspan.content.rows[row].cells.length; + col++ + ) { + expect( + getRelativeTableCellIndices( + getAbsoluteTableCellIndices({ row, col }, tableWithRowspan), + tableWithRowspan + ) + ).toEqual({ + row, + col, + cell: tableWithRowspan.content.rows[row].cells[col], + }); + } + } + }); + + it("should work for tables with colspans and rowspans", () => { + for ( + let row = 0; + row < tableWithColspanAndRowspan.content.rows.length; + row++ + ) { + for ( + let col = 0; + col < tableWithColspanAndRowspan.content.rows[row].cells.length; + col++ + ) { + expect( + getRelativeTableCellIndices( + getAbsoluteTableCellIndices( + { row, col }, + tableWithColspanAndRowspan + ), + tableWithColspanAndRowspan + ) + ).toEqual({ + row, + col, + cell: tableWithColspanAndRowspan.content.rows[row].cells[col], + }); + } + } + }); + + it("should work for tables with complex rowspans and colspans", () => { + for ( + let row = 0; + row < tableWithComplexRowspansAndColspans.content.rows.length; + row++ + ) { + for ( + let col = 0; + col < + tableWithComplexRowspansAndColspans.content.rows[row].cells.length; + col++ + ) { + expect( + getRelativeTableCellIndices( + getAbsoluteTableCellIndices( + { row, col }, + tableWithComplexRowspansAndColspans + ), + tableWithComplexRowspansAndColspans + ) + ).toEqual({ + row, + col, + cell: tableWithComplexRowspansAndColspans.content.rows[row].cells[ + col + ], + }); + } + } }); }); describe("Test getRow", () => { it("should get the row of the table", () => { - expect(getRow(simpleTable, 0)).toEqual( + expect(getCellsAtRowHandle(simpleTable, 0)).toEqual( simpleTable.content.rows[0].cells.map((cell, col) => ({ row: 0, col, @@ -1193,7 +1322,7 @@ describe("Test getRow", () => { }); it("should get the row of the table with colspan", () => { - expect(getRow(tableWithColspan, 0)).toEqual([ + expect(getCellsAtRowHandle(tableWithColspan, 0)).toEqual([ { row: 0, col: 0, @@ -1206,7 +1335,7 @@ describe("Test getRow", () => { }, ]); - expect(getRow(tableWithColspan, 1)).toEqual([ + expect(getCellsAtRowHandle(tableWithColspan, 1)).toEqual([ { row: 1, col: 0, @@ -1226,7 +1355,7 @@ describe("Test getRow", () => { }); it("should get the row of the table with rowspan", () => { - expect(getRow(tableWithRowspan, 0)).toEqual([ + expect(getCellsAtRowHandle(tableWithRowspan, 0)).toEqual([ { row: 0, col: 0, @@ -1244,7 +1373,7 @@ describe("Test getRow", () => { }, ]); - expect(getRow(tableWithRowspan, 1)).toEqual([ + expect(getCellsAtRowHandle(tableWithRowspan, 1)).toEqual([ { row: 2, col: 0, @@ -1264,52 +1393,58 @@ describe("Test getRow", () => { }); it("should get the row of the table with complex rowspans and colspans", () => { - expect(getRow(tableWithComplexRowspansAndColspans, 0)).toEqual([ - { - row: 0, - col: 0, - cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[0], - }, - { - row: 0, - col: 1, - cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[1], - }, - { - row: 0, - col: 2, - cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[2], - }, - ]); - expect(getRow(tableWithComplexRowspansAndColspans, 1)).toEqual([ - { - row: 2, - col: 0, - cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[0], - }, - { - row: 2, - col: 1, - cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[1], - }, - { - row: 1, - col: 0, - cell: tableWithComplexRowspansAndColspans.content.rows[1].cells[0], - }, - { - row: 2, - col: 2, - cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[2], - }, - ]); - expect(getRow(tableWithComplexRowspansAndColspans, 2)).toEqual([]); + expect(getCellsAtRowHandle(tableWithComplexRowspansAndColspans, 0)).toEqual( + [ + { + row: 0, + col: 0, + cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[0], + }, + { + row: 0, + col: 1, + cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[1], + }, + { + row: 0, + col: 2, + cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[2], + }, + ] + ); + expect(getCellsAtRowHandle(tableWithComplexRowspansAndColspans, 1)).toEqual( + [ + { + row: 2, + col: 0, + cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[0], + }, + { + row: 2, + col: 1, + cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[1], + }, + { + row: 1, + col: 0, + cell: tableWithComplexRowspansAndColspans.content.rows[1].cells[0], + }, + { + row: 2, + col: 2, + cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[2], + }, + ] + ); + expect(getCellsAtRowHandle(tableWithComplexRowspansAndColspans, 2)).toEqual( + [] + ); }); }); describe("Test getColumn", () => { it("should get the column of the table", () => { - expect(getColumn(simpleTable, 0)).toEqual([ + expect(getCellsAtColumnHandle(simpleTable, 0)).toEqual([ { row: 0, col: 0, @@ -1322,7 +1457,7 @@ describe("Test getColumn", () => { }, ]); - expect(getColumn(simpleTable, 1)).toEqual([ + expect(getCellsAtColumnHandle(simpleTable, 1)).toEqual([ { row: 0, col: 1, @@ -1334,11 +1469,11 @@ describe("Test getColumn", () => { cell: simpleTable.content.rows[1].cells[1], }, ]); - expect(getColumn(simpleTable, 2)).toEqual([]); + expect(getCellsAtColumnHandle(simpleTable, 2)).toEqual([]); }); it("should get the column of the table with colspan", () => { - expect(getColumn(tableWithColspan, 0)).toEqual([ + expect(getCellsAtColumnHandle(tableWithColspan, 0)).toEqual([ { row: 0, col: 0, @@ -1351,7 +1486,7 @@ describe("Test getColumn", () => { }, ]); - expect(getColumn(tableWithColspan, 1)).toEqual([ + expect(getCellsAtColumnHandle(tableWithColspan, 1)).toEqual([ { row: 0, col: 1, @@ -1363,11 +1498,11 @@ describe("Test getColumn", () => { cell: tableWithColspan.content.rows[1].cells[2], }, ]); - expect(getColumn(tableWithColspan, 2)).toEqual([]); + expect(getCellsAtColumnHandle(tableWithColspan, 2)).toEqual([]); }); it("should get the column of the table with rowspan", () => { - expect(getColumn(tableWithRowspan, 0)).toEqual([ + expect(getCellsAtColumnHandle(tableWithRowspan, 0)).toEqual([ { row: 0, col: 0, @@ -1380,7 +1515,7 @@ describe("Test getColumn", () => { }, ]); - expect(getColumn(tableWithRowspan, 1)).toEqual([ + expect(getCellsAtColumnHandle(tableWithRowspan, 1)).toEqual([ { row: 0, col: 1, @@ -1398,7 +1533,7 @@ describe("Test getColumn", () => { }, ]); - expect(getColumn(tableWithRowspan, 2)).toEqual([ + expect(getCellsAtColumnHandle(tableWithRowspan, 2)).toEqual([ { row: 0, col: 2, @@ -1415,11 +1550,13 @@ describe("Test getColumn", () => { cell: tableWithRowspan.content.rows[2].cells[2], }, ]); - expect(getColumn(tableWithRowspan, 3)).toEqual([]); + expect(getCellsAtColumnHandle(tableWithRowspan, 3)).toEqual([]); }); it("should get the column of the table with complex rowspans and colspans", () => { - expect(getColumn(tableWithComplexRowspansAndColspans, 0)).toEqual([ + expect( + getCellsAtColumnHandle(tableWithComplexRowspansAndColspans, 0) + ).toEqual([ { row: 0, col: 0, @@ -1431,7 +1568,9 @@ describe("Test getColumn", () => { cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[0], }, ]); - expect(getColumn(tableWithComplexRowspansAndColspans, 1)).toEqual([ + expect( + getCellsAtColumnHandle(tableWithComplexRowspansAndColspans, 1) + ).toEqual([ { row: 0, col: 1, @@ -1443,7 +1582,9 @@ describe("Test getColumn", () => { cell: tableWithComplexRowspansAndColspans.content.rows[1].cells[0], }, ]); - expect(getColumn(tableWithComplexRowspansAndColspans, 2)).toEqual([ + expect( + getCellsAtColumnHandle(tableWithComplexRowspansAndColspans, 2) + ).toEqual([ { row: 0, col: 2, @@ -1460,6 +1601,8 @@ describe("Test getColumn", () => { cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[2], }, ]); - expect(getColumn(tableWithComplexRowspansAndColspans, 3)).toEqual([]); + expect( + getCellsAtColumnHandle(tableWithComplexRowspansAndColspans, 3) + ).toEqual([]); }); }); diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 49a0216be6..befac12df8 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -103,8 +103,8 @@ import { * * We have a problem though, from the block json, there is no way to tell that the cell "2-1" is the second cell in the second row. * To resolve this, we created the occupancy grid, which is a grid of all the cells in the table, as though they were only 1x1 cells. + * See {@link OccupancyGrid} for more information. * - * TODO an example? */ /** @@ -152,6 +152,8 @@ export type AbsoluteCellIndices = { /** * An occupancy grid is a grid of the occupied cells in the table. * It is used to track the occupied cells in the table to know where to place the next cell. + * + * Since it allows us to resolve cell indices both {@link RelativeCellIndices} and {@link AbsoluteCellIndices}, it is the core data structure for table operations. */ type OccupancyGrid = (AbsoluteCellIndices & { /** @@ -169,9 +171,10 @@ type OccupancyGrid = (AbsoluteCellIndices & { })[][]; /** - * This will return a grid of the occupied cells in the table. + * This will return the {@link OccupancyGrid} of the table. + * By laying out the table as though it were a grid of 1x1 cells, we can easily track where the cells are located (both relatively and absolutely). * - * @returns The grid of occupied cells. + * @returns an {@link OccupancyGrid} */ export function getTableCellOccupancyGrid( block: BlockFromConfigNoChildren @@ -240,10 +243,11 @@ export function getTableCellOccupancyGrid( } /** - * This will resolve the relative cell indices within the table block to the absolute cell indices within the table. - * Accounts for colspan and rowspan. + * This will resolve the relative cell indices within the table block to the absolute cell indices within the table, accounting for colspan and rowspan. + * + * @note It will return only the first cell (i.e. top-left) that matches the relative cell indices. To find the other absolute cell indices this cell occupies, you can assume it is the rowspan and colspan number of cells away from the top-left cell. * - * @returns The absolute cell indices (row and column). + * @returns The {@link AbsoluteCellIndices} and the {@link TableCell} at the absolute position. */ export function getAbsoluteTableCellIndices( /** @@ -259,55 +263,23 @@ export function getAbsoluteTableCellIndices( */ occupancyGrid: OccupancyGrid = getTableCellOccupancyGrid(block) ): AbsoluteCellIndices & { - cell: TableContent["rows"][number]["cells"][number]; + cell: TableCell; } { - let absoluteRow = 0; - - // Jump through the occupied cells ${relativeCellIndices.row} times to find the absolute row position - for (let i = 0; i < relativeCellIndices.row; i++) { - const cell = occupancyGrid[absoluteRow]?.[0]; - - if (!cell) { - // As a sanity check, if the cell is not occupied, we should throw an error - throw new Error( - `Unable to resolve relative table cell indices for table, cell at ${absoluteRow},0 is not occupied` - ); - } - - // Skip the cells that the rowspan takes up - absoluteRow += cell.rowspan; - } - - let absoluteCol = 0; - - // Now that we've already resolved the absolute row position, we can jump through the occupied cells ${relativeCellIndices.col} times to find the absolute column position - for (let i = 0; i < relativeCellIndices.col; i++) { - const cell = occupancyGrid[absoluteRow]?.[absoluteCol]; - - if (!cell) { - // As a sanity check, if the cell is not occupied, we should throw an error - throw new Error( - `Unable to resolve relative table cell indices for table, cell at ${absoluteRow},${absoluteCol} is not occupied` - ); + for (let r = 0; r < occupancyGrid.length; r++) { + for (let c = 0; c < occupancyGrid[r].length; c++) { + const cell = occupancyGrid[r][c]; + if ( + cell.row === relativeCellIndices.row && + cell.col === relativeCellIndices.col + ) { + return { row: r, col: c, cell: cell.cell }; + } } - - // Skip the cells that the colspan takes up - absoluteCol += cell.colspan; - } - - const cell = occupancyGrid[absoluteRow]?.[absoluteCol]; - - if (!cell) { - throw new Error( - `Unable to resolve relative table cell indices for table, cell at ${absoluteRow},${absoluteCol} is not occupied` - ); } - return { - row: absoluteRow, - col: absoluteCol, - cell: cell.cell, - }; + throw new Error( + `Unable to resolve relative table cell indices for table, cell at ${relativeCellIndices.row},${relativeCellIndices.col} is not occupied` + ); } /** @@ -317,7 +289,16 @@ export function getAbsoluteTableCellIndices( */ export function getDimensionsOfTable( block: BlockFromConfigNoChildren -) { +): { + /** + * The number of rows in the table. + */ + height: number; + /** + * The number of columns in the table. + */ + width: number; +} { // Due to the way we store the table, the height is always the number of rows const height = block.content.rows.length; @@ -338,14 +319,13 @@ export function getDimensionsOfTable( } /** - * This will resolve the absolute cell indices within the table block to the relative cell indices within the table. - * Accounts for colspan and rowspan. + * This will resolve the absolute cell indices within the table block to the relative cell indices within the table, accounting for colspan and rowspan. * - * @returns The relative cell indices (row and column). + * @returns The {@link RelativeCellIndices} and the {@link TableCell} at the relative position. */ export function getRelativeTableCellIndices( /** - * The absolute position of the cell in the table. + * The {@link AbsoluteCellIndices} of the cell in the table. */ absoluteCellIndices: AbsoluteCellIndices, /** @@ -378,21 +358,59 @@ export function getRelativeTableCellIndices( } /** - * This will get all the cells in a row of the table block. + * This will get all the cells within a relative row of a table block. + * + * This method always starts the search for the row at the first column of the table. * - * @returns The row of the table. + * ``` + * // Visual representation of a table + * | A | B | C | + * | | D | E | + * | F | G | H | + * // "A" has a rowspan of 2 + * + * // getCellsAtRowHandle(0) + * // returns [ + * { row: 0, col: 0, cell: "A" }, + * { row: 0, col: 1, cell: "B" }, + * { row: 0, col: 2, cell: "C" }, + * ] + * + * // getCellsAtColumnHandle(1) + * // returns [ + * { row: 1, col: 0, cell: "F" }, + * { row: 1, col: 1, cell: "G" }, + * { row: 1, col: 2, cell: "H" }, + * ] + * ``` + * + * As you can see, you may not be able to retrieve all nodes given a relative row index, as cells can span multiple rows. + * + * @returns All of the cells associated with the relative row of the table. (All cells that have the same relative row index) */ -export function getRow( +export function getCellsAtRowHandle( block: BlockFromConfigNoChildren, relativeRowIndex: RelativeCellIndices["row"] ) { try { const occupancyGrid = getTableCellOccupancyGrid(block); - const { row: absoluteRow } = getAbsoluteTableCellIndices( - { row: relativeRowIndex, col: 0 }, - block, - occupancyGrid - ); + + let absoluteRow = 0; + + // Jump through the occupied cells ${relativeCellIndices.row} times to find the absolute row position + for (let i = 0; i < relativeRowIndex; i++) { + const cell = occupancyGrid[absoluteRow]?.[0]; + + if (!cell) { + // As a sanity check, if the cell is not occupied, we should throw an error + throw new Error( + `Unable to resolve relative table cell indices for table, cell at ${absoluteRow},0 is not occupied` + ); + } + + // Skip the cells that the rowspan takes up + absoluteRow += cell.rowspan; + } // Then for each column, get the cell at the absolute row index as a relative cell index const cells = new Array(occupancyGrid[0].length) @@ -419,22 +437,60 @@ export function getRow( } /** - * This will get all the cells in a column of the table block. + * This will get all the cells within a relative column of a table block. + * + * This method always starts the search for the column at the first row of the table. * - * @returns The column of the table. + * ``` + * // Visual representation of a table + * | A | B | + * | C | D | E | + * | F | G | H | + * // "A" has a colspan of 2 + * + * // getCellsAtColumnHandle(0) + * // returns [ + * { row: 0, col: 0, cell: "A" }, + * { row: 1, col: 0, cell: "C" }, + * { row: 2, col: 0, cell: "F" }, + * ] + * + * // getCellsAtColumnHandle(1) + * // returns [ + * { row: 0, col: 1, cell: "B" }, + * { row: 1, col: 2, cell: "E" }, + * { row: 2, col: 2, cell: "F" }, + * ] + * ``` + * + * As you can see, you may not be able to retrieve all nodes given a relative column index, as cells can span multiple columns. + * + * @returns All of the cells associated with the relative column of the table. (All cells that have the same relative column index) */ -export function getColumn( +export function getCellsAtColumnHandle( block: BlockFromConfigNoChildren, relativeColumnIndex: RelativeCellIndices["col"] ) { try { const occupancyGrid = getTableCellOccupancyGrid(block); // First need to resolve the relative column index to an absolute column index - const { col: absoluteCol } = getAbsoluteTableCellIndices( - { row: 0, col: relativeColumnIndex }, - block, - occupancyGrid - ); + + let absoluteCol = 0; + + // Now that we've already resolved the absolute row position, we can jump through the occupied cells ${relativeCellIndices.col} times to find the absolute column position + for (let i = 0; i < relativeColumnIndex; i++) { + const cell = occupancyGrid[0]?.[absoluteCol]; + + if (!cell) { + // As a sanity check, if the cell is not occupied, we should throw an error + throw new Error( + `Unable to resolve relative table cell indices for table, cell at 0,${absoluteCol} is not occupied` + ); + } + + // Skip the cells that the colspan takes up + absoluteCol += cell.colspan; + } // Then for each row, get the cell at the absolute column index as a relative cell index const cells = new Array(occupancyGrid.length).fill(false).map((_v, row) => { diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 71ce72345b..d9cf5b7757 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -2,9 +2,9 @@ import { Plugin, PluginKey, PluginView } from "prosemirror-state"; import { CellSelection, mergeCells, splitCell } from "prosemirror-tables"; import { Decoration, DecorationSet, EditorView } from "prosemirror-view"; import { - getColumn, + getCellsAtColumnHandle, getDimensionsOfTable, - getRow, + getCellsAtRowHandle, getAbsoluteTableCellIndices, RelativeCellIndices, } from "../../api/blockManipulation/tables/tables.js"; @@ -470,7 +470,7 @@ export class TableHandlesView< const columnWidths = this.state.block.content.columnWidths; if (draggingState.draggedCellOrientation === "row") { - const row = getRow(this.state.block, rowIndex); + const row = getCellsAtRowHandle(this.state.block, rowIndex); // TODO need to work on this logic if ( row.some((cell) => { @@ -488,7 +488,7 @@ export class TableHandlesView< newTable.splice(draggingState.originalIndex, 1); newTable.splice(rowIndex, 0, rowToMove); } else { - const col = getColumn(this.state.block, colIndex); + const col = getCellsAtColumnHandle(this.state.block, colIndex); // TODO need to work on this logic if ( col.some((cell) => { @@ -893,21 +893,21 @@ export class TableHandlesProsemirrorPlugin< this.view!.menuFrozen = false; }; - getRow = ( + getCellsAtRowHandle = ( block: BlockFromConfigNoChildren, relativeRowIndex: RelativeCellIndices["row"] ) => { - return getRow(block, relativeRowIndex); + return getCellsAtRowHandle(block, relativeRowIndex); }; /** * Get all the cells in a column of the table block. */ - getColumn = ( + getCellsAtColumnHandle = ( block: BlockFromConfigNoChildren, relativeColumnIndex: RelativeCellIndices["col"] ) => { - return getColumn(block, relativeColumnIndex); + return getCellsAtColumnHandle(block, relativeColumnIndex); }; /** diff --git a/packages/react/src/components/TableHandles/TableHandle.tsx b/packages/react/src/components/TableHandles/TableHandle.tsx index 92cbe08592..f5e31bc401 100644 --- a/packages/react/src/components/TableHandles/TableHandle.tsx +++ b/packages/react/src/components/TableHandles/TableHandle.tsx @@ -39,12 +39,12 @@ export const TableHandle = < if (props.orientation === "column") { return tableHandles - .getColumn(props.block, props.index) + .getCellsAtColumnHandle(props.block, props.index) .every(({ cell }) => getColspan(cell) === 1); } return tableHandles - .getRow(props.block, props.index) + .getCellsAtRowHandle(props.block, props.index) .every(({ cell }) => getRowspan(cell) === 1); }, [props.block, props.editor.tableHandles, props.index, props.orientation]); diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx index 08fbbb72a9..56fbe973c0 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx @@ -38,10 +38,10 @@ export const ColorPickerButton = < } if (props.orientation === "row") { - return tableHandles.getRow(props.block, props.index); + return tableHandles.getCellsAtRowHandle(props.block, props.index); } - return tableHandles.getColumn(props.block, props.index); + return tableHandles.getCellsAtColumnHandle(props.block, props.index); }, [props.block, props.index, props.orientation, tableHandles]); if (!currentCells || !tableHandles) { From 74345df79776202f1f88e4ef9a33ff6801893052 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 20 Feb 2025 13:20:13 +0100 Subject: [PATCH 28/72] refactor: add a break --- packages/core/src/api/nodeConversions/nodeToBlock.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index f6f493702e..358f1839fb 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -86,12 +86,16 @@ export function contentNodeToTableContent< for (let i = 0; i < headerMatrix.length; i++) { if (headerMatrix[i].every((isHeader) => isHeader)) { ret.headerRows = (ret.headerRows ?? 0) + 1; + } else { + break; } } for (let i = 0; i < headerMatrix[0]?.length; i++) { if (headerMatrix.every((row) => row[i])) { ret.headerCols = (ret.headerCols ?? 0) + 1; + } else { + break; } } From e2fb4983eef359b774bbc463ca6699f2c3714657 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 20 Feb 2025 13:20:29 +0100 Subject: [PATCH 29/72] refactor: simplify table cell selections --- .../TableHandles/TableHandlesPlugin.ts | 105 ++++++++++++++---- .../DefaultButtons/TextAlignButton.tsx | 91 +++++---------- 2 files changed, 111 insertions(+), 85 deletions(-) diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index d9cf5b7757..a18ca6d157 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -578,8 +578,12 @@ export class TableHandlesView< ) { const row = tableBody.children[this.state.rowIndex]; const cell = row.children[this.state.colIndex]; - - this.state.referencePosCell = cell.getBoundingClientRect(); + if (cell) { + this.state.referencePosCell = cell.getBoundingClientRect(); + } else { + this.state.rowIndex = undefined; + this.state.colIndex = undefined; + } } this.state.referencePosTable = tableBody.getBoundingClientRect(); @@ -982,6 +986,79 @@ export class TableHandlesProsemirrorPlugin< return splitCell(state, this.editor.dispatch); }; + /** + * Gets the start and end cells of the current cell selection. + * @returns The start and end cells of the current cell selection. + */ + getCellSelection = (): { + from: RelativeCellIndices; + to: RelativeCellIndices; + /** + * All of the cells that are within the selected range. + */ + cells: RelativeCellIndices[]; + } => { + // Based on the current selection, find the table cells that are within the selected range + const state = this.editor.prosemirrorState; + const selection = state.selection; + + let $fromCell = selection.$from; + let $toCell = selection.$to; + if (isTableCellSelection(selection)) { + // When the selection is a table cell selection, we can find the + // from and to cells by iterating over the ranges in the selection + const { ranges } = selection; + ranges.forEach((range) => { + $fromCell = range.$from.min($fromCell ?? range.$from); + $toCell = range.$to.max($toCell ?? range.$to); + }); + } else { + // When the selection is a normal text selection + // Assumes we are within a tableParagraph + // And find the from and to cells by resolving the positions + $fromCell = state.doc.resolve( + selection.$from.pos - selection.$from.parentOffset - 1 + ); + $toCell = state.doc.resolve( + selection.$to.pos - selection.$to.parentOffset - 1 + ); + } + + // Find the row and table that the from and to cells are in + const $fromRow = state.doc.resolve( + $fromCell.pos - $fromCell.parentOffset - 1 + ); + const $toRow = state.doc.resolve($toCell.pos - $toCell.parentOffset - 1); + + // Find the table + const $table = state.doc.resolve($fromRow.pos - $fromRow.parentOffset - 1); + + // Find the column and row indices of the from and to cells + const fromColIndex = $fromCell.index($fromRow.depth); + const fromRowIndex = $fromRow.index($table.depth); + const toColIndex = $toCell.index($toRow.depth); + const toRowIndex = $toRow.index($table.depth); + + const cells: RelativeCellIndices[] = []; + for (let row = fromRowIndex; row <= toRowIndex; row++) { + for (let col = fromColIndex; col <= toColIndex; col++) { + cells.push({ row, col }); + } + } + + return { + from: { + row: fromRowIndex, + col: fromColIndex, + }, + to: { + row: toRowIndex, + col: toColIndex, + }, + cells, + }; + }; + /** * Gets the direction of the merge based on the current cell selection. * @@ -1007,31 +1084,15 @@ export class TableHandlesProsemirrorPlugin< return undefined; } - const { $anchorCell, $headCell } = cellSelection; + const { from, to } = this.getCellSelection(); // Table indices are relative to the table, so we need to resolve the absolute cell indices - const absoluteCellIndices = getAbsoluteTableCellIndices( - { - // row index within the table - row: $anchorCell.index($anchorCell.depth - 1), - // column index within the row - col: $anchorCell.index(), - }, - block - ); + const anchorAbsoluteCellIndices = getAbsoluteTableCellIndices(from, block); // Table indices are relative to the table, so we need to resolve the absolute cell indices - const headAbsoluteCellIndices = getAbsoluteTableCellIndices( - { - // row index within the table - row: $headCell.index($headCell.depth - 1), - // column index within the row - col: $headCell.index(), - }, - block - ); + const headAbsoluteCellIndices = getAbsoluteTableCellIndices(to, block); // Compare the column indices to determine the merge direction - if (absoluteCellIndices.col === headAbsoluteCellIndices.col) { + if (anchorAbsoluteCellIndices.col === headAbsoluteCellIndices.col) { return "vertical"; } diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx index 7d7c04fd7f..413e4f52d8 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx @@ -4,7 +4,6 @@ import { checkBlockTypeHasDefaultProp, DefaultProps, InlineContentSchema, - isTableCellSelection, mapTableCell, StyleSchema, TableContent, @@ -50,6 +49,23 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { if (checkBlockHasDefaultProp("textAlignment", block, editor)) { return block.props.textAlignment; } + if (block.type === "table") { + const cellSelection = editor.tableHandles?.getCellSelection(); + if (!cellSelection) { + return; + } + const allCellsInTable = cellSelection.cells.map( + ({ row, col }) => + mapTableCell( + (block.content as TableContent).rows[row].cells[col] + ).props.textAlignment + ); + const firstAlignment = allCellsInTable[0]; + + if (allCellsInTable.every((alignment) => alignment === firstAlignment)) { + return firstAlignment; + } + } return; }, [editor, selectedBlocks]); @@ -64,78 +80,27 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { props: { textAlignment: textAlignment }, }); } else if (block.type === "table") { - // Based on the current selection, find the table cells that are within the selected range - const state = editor.prosemirrorState; - const selection = state.selection; - - let $fromCell = selection.$from; - let $toCell = selection.$to; - if (isTableCellSelection(selection)) { - // When the selection is a table cell selection, we can find the - // from and to cells by iterating over the ranges in the selection - const { ranges } = selection; - ranges.forEach((range) => { - $fromCell = range.$from.min($fromCell ?? range.$from); - $toCell = range.$to.max($toCell ?? range.$to); - }); - } else { - // When the selection is a normal text selection - // Assumes we are within a tableParagraph - // And find the from and to cells by resolving the positions - $fromCell = state.doc.resolve( - selection.$from.pos - selection.$from.parentOffset - 1 - ); - $toCell = state.doc.resolve( - selection.$to.pos - selection.$to.parentOffset - 1 - ); + const cellSelection = editor.tableHandles?.getCellSelection(); + if (!cellSelection) { + continue; } - // Find the row and table that the from and to cells are in - const $fromRow = state.doc.resolve( - $fromCell.pos - $fromCell.parentOffset - 1 - ); - const $toRow = state.doc.resolve( - $toCell.pos - $toCell.parentOffset - 1 - ); - - // Find the table - const $table = state.doc.resolve( - $fromRow.pos - $fromRow.parentOffset - 1 - ); - - // Find the column and row indices of the from and to cells - const fromColIndex = $fromCell.index($fromRow.depth); - const fromRowIndex = $fromRow.index($table.depth); - const toColIndex = $toCell.index($toRow.depth); - const toRowIndex = $toRow.index($table.depth); - const newTable = (block.content as TableContent).rows.map( - (row, r) => { + (row) => { return { ...row, - cells: row.cells.map((cell, c) => { - const mappedCell = mapTableCell(cell); - // If the cell is within the selected range of cells, update the text alignment - if ( - fromRowIndex <= r && - r <= toRowIndex && - fromColIndex <= c && - c <= toColIndex - ) { - return { - ...mappedCell, - props: { - ...mappedCell.props, - textAlignment: textAlignment, - }, - }; - } - return mappedCell; + cells: row.cells.map((cell) => { + return mapTableCell(cell); }), }; } ); + // Apply the text alignment to the cells that are within the selected range + cellSelection.cells.forEach(({ row, col }) => { + newTable[row].cells[col].props.textAlignment = textAlignment; + }); + editor.updateBlock(block, { type: "table", content: { From 9a34984df8f556eb3a0db09417f96e5768cf3b1d Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 20 Feb 2025 14:10:42 +0100 Subject: [PATCH 30/72] refactor: use isTableCell check --- .../TableHandleMenu/DefaultButtons/ColorPicker.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx index 56fbe973c0..7f03fa790d 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx @@ -3,7 +3,7 @@ import { DefaultInlineContentSchema, DefaultStyleSchema, InlineContentSchema, - isPartialTableCell, + isTableCell, mapTableCell, StyleSchema, } from "@blocknote/core"; @@ -70,7 +70,7 @@ export const ColorPickerButton = < // All cells have the same text color color: currentCells.every( ({ cell }) => - isPartialTableCell(cell) && + isTableCell(cell) && cell.props.textColor === firstCell.props.textColor ) ? firstCell.props.textColor @@ -99,7 +99,7 @@ export const ColorPickerButton = < background={{ color: currentCells.every( ({ cell }) => - isPartialTableCell(cell) && + isTableCell(cell) && cell.props.backgroundColor === firstCell.props.backgroundColor ) ? firstCell.props.backgroundColor From 3413fcad5f14f45ea5d175d92d5b237493bec9bc Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 20 Feb 2025 14:11:05 +0100 Subject: [PATCH 31/72] test: add tests to headers, rowspans and colspan --- .../__snapshots__/insertBlocks.test.ts.snap | 14 + .../__snapshots__/mergeBlocks.test.ts.snap | 10 + .../__snapshots__/moveBlocks.test.ts.snap | 40 + .../__snapshots__/removeBlocks.test.ts.snap | 6 + .../__snapshots__/replaceBlocks.test.ts.snap | 16 + .../__snapshots__/splitBlock.test.ts.snap | 12 + .../__snapshots__/updateBlock.test.ts.snap | 32 + .../__snapshots__/selection.test.ts.snap | 4 + .../external/pasteEndOfParagraph.html | 2 + .../external/pasteEndOfParagraphText.html | 2 + .../__snapshots__/external/pasteImage.html | 2 + .../external/pasteParagraphInCustomBlock.html | 2 + .../__snapshots__/external/pasteTable.html | 4 + .../external/pasteTableInExistingTable.html | 2 + .../table/headerCols/external.html | 1 + .../table/headerCols/internal.html | 1 + .../table/headerRows/external.html | 1 + .../table/headerRows/internal.html | 1 + .../table/headersRows/external.html | 1 + .../table/headersRows/internal.html | 1 + .../mixedRowspansAndColspans/external.html | 1 + .../mixedRowspansAndColspans/internal.html | 1 + .../table/headerCols/markdown.md | 4 + .../table/headerRows/markdown.md | 4 + .../mixedRowspansAndColspans/markdown.md | 5 + .../nodeConversions.test.ts.snap | 926 ++++++++++++++++++ .../src/api/nodeConversions/nodeToBlock.ts | 6 +- .../html/__snapshots__/parse-notion-html.json | 4 +- .../src/api/testUtil/cases/defaultSchema.ts | 342 +++++++ .../src/api/testUtil/partialBlockTestUtil.ts | 4 + .../src/pdf/__snapshots__/example.jsx | 4 + .../exampleWithHeaderAndFooter.jsx | 4 + 32 files changed, 1453 insertions(+), 6 deletions(-) create mode 100644 packages/core/src/api/exporters/html/__snapshots__/table/headerCols/external.html create mode 100644 packages/core/src/api/exporters/html/__snapshots__/table/headerCols/internal.html create mode 100644 packages/core/src/api/exporters/html/__snapshots__/table/headerRows/external.html create mode 100644 packages/core/src/api/exporters/html/__snapshots__/table/headerRows/internal.html create mode 100644 packages/core/src/api/exporters/html/__snapshots__/table/headersRows/external.html create mode 100644 packages/core/src/api/exporters/html/__snapshots__/table/headersRows/internal.html create mode 100644 packages/core/src/api/exporters/html/__snapshots__/table/mixedRowspansAndColspans/external.html create mode 100644 packages/core/src/api/exporters/html/__snapshots__/table/mixedRowspansAndColspans/internal.html create mode 100644 packages/core/src/api/exporters/markdown/__snapshots__/table/headerCols/markdown.md create mode 100644 packages/core/src/api/exporters/markdown/__snapshots__/table/headerRows/markdown.md create mode 100644 packages/core/src/api/exporters/markdown/__snapshots__/table/mixedRowspansAndColspans/markdown.md diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap index 2e301b409d..e1bca36762 100644 --- a/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap @@ -314,6 +314,8 @@ exports[`Test insertBlocks > Insert multiple blocks after 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -930,6 +932,8 @@ exports[`Test insertBlocks > Insert multiple blocks before 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -1506,6 +1510,8 @@ exports[`Test insertBlocks > Insert single basic block after 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -2088,6 +2094,8 @@ exports[`Test insertBlocks > Insert single basic block before (without type) 1`] undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -2664,6 +2672,8 @@ exports[`Test insertBlocks > Insert single basic block before 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -3297,6 +3307,8 @@ exports[`Test insertBlocks > Insert single complex block after 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -3930,6 +3942,8 @@ exports[`Test insertBlocks > Insert single complex block before 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap index e208fff975..f4e686823e 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap @@ -246,6 +246,8 @@ exports[`Test mergeBlocks > Basic 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -794,6 +796,8 @@ exports[`Test mergeBlocks > Blocks have different types 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -1342,6 +1346,8 @@ exports[`Test mergeBlocks > First block has children 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -1889,6 +1895,8 @@ exports[`Test mergeBlocks > Second block has children 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -2454,6 +2462,8 @@ exports[`Test mergeBlocks > Second block is empty 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ diff --git a/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap index a70ab4e070..b295b6357d 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap @@ -263,6 +263,8 @@ exports[`Test moveBlocksDown > Basic 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -828,6 +830,8 @@ exports[`Test moveBlocksDown > Into children 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -1393,6 +1397,8 @@ exports[`Test moveBlocksDown > Last block 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -1958,6 +1964,8 @@ exports[`Test moveBlocksDown > Multiple blocks 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -2523,6 +2531,8 @@ exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`] undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -3088,6 +3098,8 @@ exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -3663,6 +3675,8 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -4228,6 +4242,8 @@ exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1 undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -4792,6 +4808,8 @@ exports[`Test moveBlocksDown > Multiple blocks starting in nested block 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -5357,6 +5375,8 @@ exports[`Test moveBlocksDown > Out of children 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -5921,6 +5941,8 @@ exports[`Test moveBlocksUp > Basic 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -6486,6 +6508,8 @@ exports[`Test moveBlocksUp > First block 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -7051,6 +7075,8 @@ exports[`Test moveBlocksUp > Into children 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -7616,6 +7642,8 @@ exports[`Test moveBlocksUp > Multiple blocks 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -8181,6 +8209,8 @@ exports[`Test moveBlocksUp > Multiple blocks ending in block with children 1`] = undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -8746,6 +8776,8 @@ exports[`Test moveBlocksUp > Multiple blocks ending in nested block 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -9293,6 +9325,8 @@ exports[`Test moveBlocksUp > Multiple blocks starting and ending in nested block undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -9875,6 +9909,8 @@ exports[`Test moveBlocksUp > Multiple blocks starting in block with children 1`] undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -10439,6 +10475,8 @@ exports[`Test moveBlocksUp > Multiple blocks starting in nested block 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -11004,6 +11042,8 @@ exports[`Test moveBlocksUp > Out of children 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ diff --git a/packages/core/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap index 2b677b1e23..75d5602f2e 100644 --- a/packages/core/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap @@ -227,6 +227,8 @@ exports[`Test removeBlocks > Remove all child blocks 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -705,6 +707,8 @@ exports[`Test removeBlocks > Remove multiple consecutive blocks 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -1550,6 +1554,8 @@ exports[`Test removeBlocks > Remove single block 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap index e01f7bb5c3..8b16a1536d 100644 --- a/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap @@ -176,6 +176,8 @@ exports[`Test replaceBlocks > Remove multiple consecutive blocks 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -1021,6 +1023,8 @@ exports[`Test replaceBlocks > Remove single block 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -1550,6 +1554,8 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with multiple undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -2039,6 +2045,8 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single ba undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -2585,6 +2593,8 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single co undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -4205,6 +4215,8 @@ exports[`Test replaceBlocks > Replace single block with multiple 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -4764,6 +4776,8 @@ exports[`Test replaceBlocks > Replace single block with single basic 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -5380,6 +5394,8 @@ exports[`Test replaceBlocks > Replace single block with single complex 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap index c92733c5f7..f07442152c 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap @@ -280,6 +280,8 @@ exports[`Test splitBlocks > Basic 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -862,6 +864,8 @@ exports[`Test splitBlocks > Block has children 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -1444,6 +1448,8 @@ exports[`Test splitBlocks > Don't keep props 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -2026,6 +2032,8 @@ exports[`Test splitBlocks > Don't keep type 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -2602,6 +2610,8 @@ exports[`Test splitBlocks > End of content 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -3185,6 +3195,8 @@ exports[`Test splitBlocks > Keep type 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap index 7e20863ec9..dfdc926ac4 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap @@ -263,6 +263,8 @@ exports[`Test updateBlock > Revert all props 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -828,6 +830,8 @@ exports[`Test updateBlock > Revert single prop 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -1393,6 +1397,8 @@ exports[`Test updateBlock > Update all props 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -1958,6 +1964,8 @@ exports[`Test updateBlock > Update children 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -2521,6 +2529,8 @@ exports[`Test updateBlock > Update inline content to no content 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -2833,6 +2843,8 @@ exports[`Test updateBlock > Update inline content to table content 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -3252,6 +3264,8 @@ exports[`Test updateBlock > Update inline content to table content 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -3813,6 +3827,8 @@ exports[`Test updateBlock > Update no content to empty inline content 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -4374,6 +4390,8 @@ exports[`Test updateBlock > Update no content to empty table content 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -4941,6 +4959,8 @@ exports[`Test updateBlock > Update no content to inline content 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -5474,6 +5494,8 @@ exports[`Test updateBlock > Update no content to table content 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -5674,6 +5696,8 @@ exports[`Test updateBlock > Update no content to table content 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -6239,6 +6263,8 @@ exports[`Test updateBlock > Update single prop 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -7993,6 +8019,8 @@ exports[`Test updateBlock > Update type 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -8557,6 +8585,8 @@ exports[`Test updateBlock > Update with plain content 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -9108,6 +9138,8 @@ exports[`Test updateBlock > Update with styled content 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ diff --git a/packages/core/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap b/packages/core/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap index d62828d09b..b5bde19afc 100644 --- a/packages/core/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap +++ b/packages/core/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap @@ -317,6 +317,8 @@ exports[`Test getSelection & setSelection > Ends in table 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -643,6 +645,8 @@ exports[`Test getSelection & setSelection > Starts in table 1`] = ` undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ diff --git a/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraph.html b/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraph.html index 77b6ae34de..0a607ca98c 100644 --- a/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraph.html +++ b/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraph.html @@ -23,6 +23,8 @@ undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ diff --git a/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraphText.html b/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraphText.html index 77b6ae34de..0a607ca98c 100644 --- a/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraphText.html +++ b/packages/core/src/api/clipboard/__snapshots__/external/pasteEndOfParagraphText.html @@ -23,6 +23,8 @@ undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ diff --git a/packages/core/src/api/clipboard/__snapshots__/external/pasteImage.html b/packages/core/src/api/clipboard/__snapshots__/external/pasteImage.html index f66cb46c43..cab1cac6f1 100644 --- a/packages/core/src/api/clipboard/__snapshots__/external/pasteImage.html +++ b/packages/core/src/api/clipboard/__snapshots__/external/pasteImage.html @@ -38,6 +38,8 @@ undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ diff --git a/packages/core/src/api/clipboard/__snapshots__/external/pasteParagraphInCustomBlock.html b/packages/core/src/api/clipboard/__snapshots__/external/pasteParagraphInCustomBlock.html index e3f80b520b..9c4f9b7629 100644 --- a/packages/core/src/api/clipboard/__snapshots__/external/pasteParagraphInCustomBlock.html +++ b/packages/core/src/api/clipboard/__snapshots__/external/pasteParagraphInCustomBlock.html @@ -23,6 +23,8 @@ undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ diff --git a/packages/core/src/api/clipboard/__snapshots__/external/pasteTable.html b/packages/core/src/api/clipboard/__snapshots__/external/pasteTable.html index 9816c0fedd..b2724689d2 100644 --- a/packages/core/src/api/clipboard/__snapshots__/external/pasteTable.html +++ b/packages/core/src/api/clipboard/__snapshots__/external/pasteTable.html @@ -23,6 +23,8 @@ undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ @@ -116,6 +118,8 @@ undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ diff --git a/packages/core/src/api/clipboard/__snapshots__/external/pasteTableInExistingTable.html b/packages/core/src/api/clipboard/__snapshots__/external/pasteTableInExistingTable.html index bd9d13231b..034816337e 100644 --- a/packages/core/src/api/clipboard/__snapshots__/external/pasteTableInExistingTable.html +++ b/packages/core/src/api/clipboard/__snapshots__/external/pasteTableInExistingTable.html @@ -24,6 +24,8 @@ undefined, undefined, ], + "headerCols": undefined, + "headerRows": undefined, "rows": [ { "cells": [ diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/headerCols/external.html b/packages/core/src/api/exporters/html/__snapshots__/table/headerCols/external.html new file mode 100644 index 0000000000..0d1edf2ae0 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/table/headerCols/external.html @@ -0,0 +1 @@ +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/headerCols/internal.html b/packages/core/src/api/exporters/html/__snapshots__/table/headerCols/internal.html new file mode 100644 index 0000000000..c7bd12dc42 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/table/headerCols/internal.html @@ -0,0 +1 @@ +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/headerRows/external.html b/packages/core/src/api/exporters/html/__snapshots__/table/headerRows/external.html new file mode 100644 index 0000000000..4c1d3e7203 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/table/headerRows/external.html @@ -0,0 +1 @@ +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/headerRows/internal.html b/packages/core/src/api/exporters/html/__snapshots__/table/headerRows/internal.html new file mode 100644 index 0000000000..95f848187d --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/table/headerRows/internal.html @@ -0,0 +1 @@ +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/headersRows/external.html b/packages/core/src/api/exporters/html/__snapshots__/table/headersRows/external.html new file mode 100644 index 0000000000..4c1d3e7203 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/table/headersRows/external.html @@ -0,0 +1 @@ +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/headersRows/internal.html b/packages/core/src/api/exporters/html/__snapshots__/table/headersRows/internal.html new file mode 100644 index 0000000000..95f848187d --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/table/headersRows/internal.html @@ -0,0 +1 @@ +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/mixedRowspansAndColspans/external.html b/packages/core/src/api/exporters/html/__snapshots__/table/mixedRowspansAndColspans/external.html new file mode 100644 index 0000000000..1e0671ec07 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/table/mixedRowspansAndColspans/external.html @@ -0,0 +1 @@ +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/mixedRowspansAndColspans/internal.html b/packages/core/src/api/exporters/html/__snapshots__/table/mixedRowspansAndColspans/internal.html new file mode 100644 index 0000000000..49f80e058f --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/table/mixedRowspansAndColspans/internal.html @@ -0,0 +1 @@ +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/table/headerCols/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/table/headerCols/markdown.md new file mode 100644 index 0000000000..812aac2d02 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/table/headerCols/markdown.md @@ -0,0 +1,4 @@ +| Table Cell | Table Cell | Table Cell | +| ---------- | ---------- | ---------- | +| Table Cell | Table Cell | Table Cell | +| Table Cell | Table Cell | Table Cell | diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/table/headerRows/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/table/headerRows/markdown.md new file mode 100644 index 0000000000..812aac2d02 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/table/headerRows/markdown.md @@ -0,0 +1,4 @@ +| Table Cell | Table Cell | Table Cell | +| ---------- | ---------- | ---------- | +| Table Cell | Table Cell | Table Cell | +| Table Cell | Table Cell | Table Cell | diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/table/mixedRowspansAndColspans/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/table/mixedRowspansAndColspans/markdown.md new file mode 100644 index 0000000000..5f817e4d91 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/table/mixedRowspansAndColspans/markdown.md @@ -0,0 +1,5 @@ +| | | | +| ---------- | ---------- | ---------- | +| Table Cell | | Table Cell | +| Table Cell | Table Cell | | +| | Table Cell | Table Cell | diff --git a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap index 64178295fb..0180108f88 100644 --- a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +++ b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap @@ -2363,6 +2363,753 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert } `; +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert table/headerCols to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", +} +`; + +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert table/headerRows to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", +} +`; + +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert table/headers to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + undefined, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", +} +`; + exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert table/mixedCellColors to/from prosemirror 1`] = ` { "attrs": { @@ -2860,3 +3607,182 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "type": "blockContainer", } `; + +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert table/mixedRowspansAndColspans to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "red", + "colspan": 2, + "colwidth": [ + 100, + 200, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "blue", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "yellow", + "colspan": 1, + "colwidth": [ + 300, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "red", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 100, + ], + "rowspan": 2, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 2, + "colwidth": [ + 200, + 300, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 200, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 300, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Table Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", +} +`; diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 358f1839fb..3d90168824 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -32,6 +32,8 @@ export function contentNodeToTableContent< const ret: TableContent = { type: "tableContent", columnWidths: [], + headerRows: undefined, + headerCols: undefined, rows: [], }; @@ -86,16 +88,12 @@ export function contentNodeToTableContent< for (let i = 0; i < headerMatrix.length; i++) { if (headerMatrix[i].every((isHeader) => isHeader)) { ret.headerRows = (ret.headerRows ?? 0) + 1; - } else { - break; } } for (let i = 0; i < headerMatrix[0]?.length; i++) { if (headerMatrix.every((row) => row[i])) { ret.headerCols = (ret.headerCols ?? 0) + 1; - } else { - break; } } diff --git a/packages/core/src/api/parsers/html/__snapshots__/parse-notion-html.json b/packages/core/src/api/parsers/html/__snapshots__/parse-notion-html.json index 0ae9e143fd..b1ebb03e09 100644 --- a/packages/core/src/api/parsers/html/__snapshots__/parse-notion-html.json +++ b/packages/core/src/api/parsers/html/__snapshots__/parse-notion-html.json @@ -374,6 +374,7 @@ null, null ], + "headerRows": 1, "rows": [ { "cells": [ @@ -540,8 +541,7 @@ } ] } - ], - "headerRows": 1 + ] }, "children": [] }, diff --git a/packages/core/src/api/testUtil/cases/defaultSchema.ts b/packages/core/src/api/testUtil/cases/defaultSchema.ts index 72ca3b7b9b..2dbfaffb5c 100644 --- a/packages/core/src/api/testUtil/cases/defaultSchema.ts +++ b/packages/core/src/api/testUtil/cases/defaultSchema.ts @@ -881,6 +881,348 @@ export const defaultSchemaTestCases: EditorTestCases< }, ], }, + { + name: "table/mixedRowspansAndColspans", + blocks: [ + { + type: "table", + content: { + type: "tableContent", + columnWidths: [100, 200, 300], + rows: [ + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "red", + colspan: 2, + rowspan: 1, + textAlignment: "left", + textColor: "blue", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "yellow", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "red", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 2, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 2, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + ], + }, + }, + ], + }, + { + name: "table/headerRows", + blocks: [ + { + type: "table", + content: { + headerRows: 1, + type: "tableContent", + rows: [ + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + ], + }, + }, + ], + }, + { + name: "table/headerCols", + blocks: [ + { + type: "table", + content: { + headerCols: 1, + type: "tableContent", + rows: [ + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + ], + }, + }, + ], + }, { name: "link/basic", blocks: [ diff --git a/packages/core/src/api/testUtil/partialBlockTestUtil.ts b/packages/core/src/api/testUtil/partialBlockTestUtil.ts index ef99f5ff71..8c6a15f623 100644 --- a/packages/core/src/api/testUtil/partialBlockTestUtil.ts +++ b/packages/core/src/api/testUtil/partialBlockTestUtil.ts @@ -72,6 +72,8 @@ function partialContentToInlineContent( return { type: "tableContent", columnWidths: content.columnWidths, + headerRows: content.headerRows, + headerCols: content.headerCols, rows: content.rows.map((row) => ({ ...row, cells: row.cells.map( @@ -162,6 +164,8 @@ export function partialBlockToBlockForTesting< content?.columnWidths || content?.rows[0]?.cells.map(() => undefined) || [], + headerRows: content?.headerRows || undefined, + headerCols: content?.headerCols || undefined, rows: content?.rows.map((row) => ({ cells: row.cells.map((cell) => partialContentToInlineContent(cell)), diff --git a/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx b/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx index 74a79c1821..361cafe56b 100644 --- a/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx +++ b/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx @@ -424,6 +424,8 @@ undefined, undefined ], + headerCols: undefined, + headerRows: undefined, rows: [ { cells: [ @@ -802,6 +804,8 @@ undefined, undefined ], + headerCols: undefined, + headerRows: undefined, rows: [ { cells: [ diff --git a/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx b/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx index 3c6a68a2f7..173ae4b0c8 100644 --- a/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx +++ b/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx @@ -432,6 +432,8 @@ undefined, undefined ], + headerCols: undefined, + headerRows: undefined, rows: [ { cells: [ @@ -810,6 +812,8 @@ undefined, undefined ], + headerCols: undefined, + headerRows: undefined, rows: [ { cells: [ From db490a8e5ba2277fa2bd4b61e00643e6b5fc6acf Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 20 Feb 2025 14:35:24 +0100 Subject: [PATCH 32/72] fix: switch to dropdown arrow --- .../react/src/components/TableHandles/TableCellHandle.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react/src/components/TableHandles/TableCellHandle.tsx b/packages/react/src/components/TableHandles/TableCellHandle.tsx index 1cf36ee259..62ff99b84c 100644 --- a/packages/react/src/components/TableHandles/TableCellHandle.tsx +++ b/packages/react/src/components/TableHandles/TableCellHandle.tsx @@ -7,7 +7,7 @@ import { import { ReactNode } from "react"; import { createPortal } from "react-dom"; -import { MdSettings } from "react-icons/md"; +import { MdArrowDropDown } from "react-icons/md"; import { useComponentsContext } from "../../editor/ComponentsContext.js"; import { TableCellHandleProps } from "./TableCellHandleProps.js"; import { TableCellHandleMenu } from "./TableCellHandleMenu/TableCellHandleMenu.js"; @@ -40,7 +40,9 @@ export const TableCellHandle = < {/* TODO we should probably make a generic button (wait for comments PR to land) */}
- {props.children || } + {props.children || ( + + )}
{/* the menu can extend outside of the table, so we use a portal to prevent clipping */} From 55617d32d241c2ce03cd1b105251d9a8de7e43bd Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 20 Feb 2025 14:57:39 +0100 Subject: [PATCH 33/72] refactor: clean up color picker --- .../DefaultButtons/TextAlignButton.tsx | 4 ++ .../DefaultButtons/ColorPicker.tsx | 51 ++++++-------- .../DefaultButtons/ColorPicker.tsx | 69 +++++++++---------- 3 files changed, 57 insertions(+), 67 deletions(-) diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx index 413e4f52d8..e057a23179 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx @@ -109,6 +109,10 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { rows: newTable, } as any, }); + + // Have to reset text cursor position to the block as `updateBlock` + // moves the existing selection out of the block. + editor.setTextCursorPosition(block); } } }, diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx index d406436ca1..06cb687df5 100644 --- a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx +++ b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx @@ -3,7 +3,7 @@ import { DefaultInlineContentSchema, DefaultStyleSchema, InlineContentSchema, - isPartialTableCell, + isTableCell, mapTableCell, StyleSchema, } from "@blocknote/core"; @@ -32,38 +32,31 @@ export const ColorPickerButton = < >(); const updateColor = (color: string, type: "text" | "background") => { + const newTable = props.block.content.rows.map((row) => { + return { + ...row, + cells: row.cells.map((cell) => mapTableCell(cell)), + }; + }); + + if (type === "text") { + newTable[props.rowIndex].cells[props.colIndex].props.textColor = color; + } else { + newTable[props.rowIndex].cells[props.colIndex].props.backgroundColor = + color; + } + editor.updateBlock(props.block, { type: "table", content: { ...props.block.content, - rows: props.block.content.rows.map( - (row, rowIndex) => - ({ - ...row, - cells: - props.rowIndex === rowIndex - ? row.cells.map(mapTableCell).map((cell, cellIndex) => - cellIndex === props.colIndex - ? { - ...cell, - props: - type === "text" - ? { - ...cell.props, - textColor: color, - } - : { - ...cell.props, - backgroundColor: color, - }, - } - : cell - ) - : row.cells.map(mapTableCell), - } as any) - ), + rows: newTable, }, }); + + // Have to reset text cursor position to the block as `updateBlock` + // moves the existing selection out of the block. + editor.setTextCursorPosition(props.block); }; const currentCell = @@ -90,13 +83,13 @@ export const ColorPickerButton = < updateColor(color, "text"), }} background={{ - color: isPartialTableCell(currentCell) + color: isTableCell(currentCell) ? currentCell.props.backgroundColor : "default", setColor: (color) => updateColor(color, "background"), diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx index 7f03fa790d..d421b4c555 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx @@ -44,6 +44,35 @@ export const ColorPickerButton = < return tableHandles.getCellsAtColumnHandle(props.block, props.index); }, [props.block, props.index, props.orientation, tableHandles]); + const updateColor = (color: string, type: "text" | "background") => { + const newTable = props.block.content.rows.map((row) => { + return { + ...row, + cells: row.cells.map((cell) => mapTableCell(cell)), + }; + }); + + currentCells.forEach(({ row, col }) => { + if (type === "text") { + newTable[row].cells[col].props.textColor = color; + } else { + newTable[row].cells[col].props.backgroundColor = color; + } + }); + + editor.updateBlock(props.block, { + type: "table", + content: { + ...props.block.content, + rows: newTable, + }, + }); + + // Have to reset text cursor position to the block as `updateBlock` + // moves the existing selection out of the block. + editor.setTextCursorPosition(props.block); + }; + if (!currentCells || !tableHandles) { return null; } @@ -76,24 +105,7 @@ export const ColorPickerButton = < ? firstCell.props.textColor : "default", setColor: (color) => { - const newTable = props.block.content.rows.map((row) => { - return { - ...row, - cells: row.cells.map((cell) => mapTableCell(cell)), - }; - }); - - currentCells.forEach(({ row, col }) => { - newTable[row].cells[col].props.textColor = color; - }); - - return editor.updateBlock(props.block, { - type: "table", - content: { - ...props.block.content, - rows: newTable, - }, - }); + updateColor(color, "text"); }, }} background={{ @@ -104,26 +116,7 @@ export const ColorPickerButton = < ) ? firstCell.props.backgroundColor : "default", - setColor: (color) => { - const newTable = props.block.content.rows.map((row) => { - return { - ...row, - cells: row.cells.map((cell) => mapTableCell(cell)), - }; - }); - - currentCells.forEach(({ row, col }) => { - newTable[row].cells[col].props.backgroundColor = color; - }); - - return editor.updateBlock(props.block, { - type: "table", - content: { - ...props.block.content, - rows: newTable, - }, - }); - }, + setColor: (color) => updateColor(color, "background"), }} /> From 0bcd5a239abcfb26b7fd08ef7082218ae470b44c Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 20 Feb 2025 15:15:18 +0100 Subject: [PATCH 34/72] feat: leverage prosemirror-tables for adding/deleting columns & rows --- .../TableHandles/TableHandlesPlugin.ts | 63 ++++++++++- .../DefaultButtons/AddButton.tsx | 107 +++--------------- .../DefaultButtons/DeleteButton.tsx | 86 ++------------ 3 files changed, 85 insertions(+), 171 deletions(-) diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index a18ca6d157..984c243aee 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -1,11 +1,21 @@ import { Plugin, PluginKey, PluginView } from "prosemirror-state"; -import { CellSelection, mergeCells, splitCell } from "prosemirror-tables"; +import { + addColumnAfter, + addColumnBefore, + addRowAfter, + addRowBefore, + CellSelection, + deleteColumn, + deleteRow, + mergeCells, + splitCell, +} from "prosemirror-tables"; import { Decoration, DecorationSet, EditorView } from "prosemirror-view"; import { + getAbsoluteTableCellIndices, getCellsAtColumnHandle, - getDimensionsOfTable, getCellsAtRowHandle, - getAbsoluteTableCellIndices, + getDimensionsOfTable, RelativeCellIndices, } from "../../api/blockManipulation/tables/tables.js"; import { nodeToBlock } from "../../api/nodeConversions/nodeToBlock.js"; @@ -957,6 +967,53 @@ export class TableHandlesProsemirrorPlugin< return state.apply(tr); }; + /** + * Adds a row or column to the table using prosemirror-table commands + */ + addRowOrColumn = ( + index: RelativeCellIndices["row"] | RelativeCellIndices["col"], + direction: + | { orientation: "row"; side: "above" | "below" } + | { orientation: "column"; side: "left" | "right" } + ) => { + const state = this.setCellSelection( + direction.orientation === "row" + ? { row: index, col: 0 } + : { row: 0, col: index } + ); + if (direction.orientation === "row") { + if (direction.side === "above") { + return addRowBefore(state, this.editor.dispatch); + } else { + return addRowAfter(state, this.editor.dispatch); + } + } else { + if (direction.side === "left") { + return addColumnBefore(state, this.editor.dispatch); + } else { + return addColumnAfter(state, this.editor.dispatch); + } + } + }; + + /** + * Removes a row or column from the table using prosemirror-table commands + */ + removeRowOrColumn = ( + index: RelativeCellIndices["row"] | RelativeCellIndices["col"], + direction: "row" | "column" + ) => { + const state = this.setCellSelection( + direction === "row" ? { row: index, col: 0 } : { row: 0, col: index } + ); + + if (direction === "row") { + return deleteRow(state, this.editor.dispatch); + } else { + return deleteColumn(state, this.editor.dispatch); + } + }; + /** * Merges the cells in the table block. */ diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx index d4dc3704e9..9d764cd1a2 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx @@ -2,11 +2,8 @@ import { DefaultBlockSchema, DefaultInlineContentSchema, DefaultStyleSchema, - InlineContentFromConfig, InlineContentSchema, - PartialTableContent, StyleSchema, - TableCell, } from "@blocknote/core"; import { useComponentsContext } from "../../../../editor/ComponentsContext.js"; @@ -14,11 +11,15 @@ import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor.js"; import { useDictionary } from "../../../../i18n/dictionary.js"; import { TableHandleMenuProps } from "../TableHandleMenuProps.js"; -export const AddRowButton = < +export const AddButton = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >( - props: TableHandleMenuProps & { side: "above" | "below" } + props: TableHandleMenuProps & + ( + | { orientation: "row"; side: "above" | "below" } + | { orientation: "column"; side: "left" | "right" } + ) ) => { const Components = useComponentsContext()!; const dict = useDictionary(); @@ -29,101 +30,23 @@ export const AddRowButton = < S >(); - return ( - { - const emptyCol = props.block.content.rows[props.index].cells.map( - () => [] - ); - const rows = [...props.block.content.rows]; - rows.splice(props.index + (props.side === "below" ? 1 : 0), 0, { - cells: emptyCol, - }); - - editor.updateBlock(props.block, { - type: "table", - content: { - type: "tableContent", - columnWidths: props.block.content.columnWidths, - rows, - }, - }); - - // Have to reset text cursor position to the block as `updateBlock` - // moves the existing selection out of the block. - editor.setTextCursorPosition(props.block); - }}> - {dict.table_handle[`add_${props.side}_menuitem`]} - - ); -}; - -export const AddColumnButton = < - I extends InlineContentSchema = DefaultInlineContentSchema, - S extends StyleSchema = DefaultStyleSchema ->( - props: TableHandleMenuProps & { side: "left" | "right" } -) => { - const Components = useComponentsContext()!; - const dict = useDictionary(); + const tableHandles = editor.tableHandles; - const editor = useBlockNoteEditor< - { table: DefaultBlockSchema["table"] }, - I, - S - >(); + if (!tableHandles) { + return null; + } return ( { - const columnWidths = [...props.block.content.columnWidths]; - columnWidths.splice( - props.index + (props.side === "right" ? 1 : 0), - 0, - undefined + tableHandles.addRowOrColumn( + props.index, + props.orientation === "row" + ? { orientation: "row", side: props.side } + : { orientation: "column", side: props.side } ); - const content: PartialTableContent = { - type: "tableContent", - columnWidths, - rows: props.block.content.rows.map((row) => { - const cells = [...row.cells] as - | InlineContentFromConfig[] - | TableCell[]; - cells.splice( - props.index + (props.side === "right" ? 1 : 0), - 0, - [] as any - ); - return { cells }; - }), - }; - - editor.updateBlock(props.block, { - type: "table", - content: content, - }); - - // Have to reset text cursor position to the block as `updateBlock` - // moves the existing selection out of the block. - editor.setTextCursorPosition(props.block); }}> {dict.table_handle[`add_${props.side}_menuitem`]} ); }; - -export const AddButton = < - I extends InlineContentSchema = DefaultInlineContentSchema, - S extends StyleSchema = DefaultStyleSchema ->( - props: TableHandleMenuProps & - ( - | { orientation: "row"; side: "above" | "below" } - | { orientation: "column"; side: "left" | "right" } - ) -) => - props.orientation === "row" ? ( - - ) : ( - - ); diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/DeleteButton.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/DeleteButton.tsx index 955b9c9aab..bbcf616692 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/DeleteButton.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/DeleteButton.tsx @@ -2,11 +2,8 @@ import { DefaultBlockSchema, DefaultInlineContentSchema, DefaultStyleSchema, - InlineContentFromConfig, InlineContentSchema, - PartialTableContent, StyleSchema, - TableCell, } from "@blocknote/core"; import { useComponentsContext } from "../../../../editor/ComponentsContext.js"; @@ -14,11 +11,11 @@ import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor.js"; import { useDictionary } from "../../../../i18n/dictionary.js"; import { TableHandleMenuProps } from "../TableHandleMenuProps.js"; -export const DeleteRowButton = < +export const DeleteButton = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >( - props: TableHandleMenuProps + props: TableHandleMenuProps & { orientation: "row" | "column" } ) => { const Components = useComponentsContext()!; const dict = useDictionary(); @@ -28,83 +25,20 @@ export const DeleteRowButton = < S >(); - return ( - { - const content: PartialTableContent = { - type: "tableContent", - columnWidths: props.block.content.columnWidths, - rows: props.block.content.rows.filter( - (_, index) => index !== props.index - ), - }; - - editor.updateBlock(props.block, { - type: "table", - content, - }); - - // Have to reset text cursor position to the block as `updateBlock` - // moves the existing selection out of the block. - editor.setTextCursorPosition(props.block); - }}> - {dict.table_handle.delete_row_menuitem} - - ); -}; - -export const DeleteColumnButton = < - I extends InlineContentSchema = DefaultInlineContentSchema, - S extends StyleSchema = DefaultStyleSchema ->( - props: TableHandleMenuProps -) => { - const Components = useComponentsContext()!; - const dict = useDictionary(); + const tableHandles = editor.tableHandles; - const editor = useBlockNoteEditor< - { table: DefaultBlockSchema["table"] }, - I, - S - >(); + if (!tableHandles) { + return null; + } return ( { - const content: PartialTableContent = { - type: "tableContent", - columnWidths: props.block.content.columnWidths.filter( - (_, index) => index !== props.index - ), - rows: props.block.content.rows.map((row) => ({ - cells: row.cells.filter((_, index) => index !== props.index) as - | InlineContentFromConfig[] - | TableCell[], - })), - }; - - editor.updateBlock(props.block, { - type: "table", - content, - }); - - // Have to reset text cursor position to the block as `updateBlock` - // moves the existing selection out of the block. - editor.setTextCursorPosition(props.block); + tableHandles.removeRowOrColumn(props.index, props.orientation); }}> - {dict.table_handle.delete_column_menuitem} + {props.orientation === "row" + ? dict.table_handle.delete_row_menuitem + : dict.table_handle.delete_column_menuitem} ); }; - -export const DeleteButton = < - I extends InlineContentSchema = DefaultInlineContentSchema, - S extends StyleSchema = DefaultStyleSchema ->( - props: TableHandleMenuProps & { orientation: "row" | "column" } -) => - props.orientation === "row" ? ( - - ) : ( - - ); From 710d2e5820e765dc2c47775e3f93f2958c1c3d11 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Fri, 21 Feb 2025 08:25:26 +0100 Subject: [PATCH 35/72] fix: handle extending table errors --- .../components/TableHandles/ExtendButton/ExtendButton.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/react/src/components/TableHandles/ExtendButton/ExtendButton.tsx b/packages/react/src/components/TableHandles/ExtendButton/ExtendButton.tsx index b2b63a2baa..68bf4c7656 100644 --- a/packages/react/src/components/TableHandles/ExtendButton/ExtendButton.tsx +++ b/packages/react/src/components/TableHandles/ExtendButton/ExtendButton.tsx @@ -23,10 +23,13 @@ import { useComponentsContext } from "../../../editor/ComponentsContext.js"; import { ExtendButtonProps } from "./ExtendButtonProps.js"; function isCellEmpty( - cell: PartialTableContent["rows"][number]["cells"][number] + cell: PartialTableContent["rows"][number]["cells"][number] | undefined ): boolean { + if (!cell) { + return true; + } if (isPartialTableCell(cell)) { - return (cell.content?.length ?? 0) === 0; + return isCellEmpty(cell.content); } else { return cell.length === 0; } From b050510262976917ae784e827f2b23e575345f39 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Fri, 21 Feb 2025 08:31:51 +0100 Subject: [PATCH 36/72] fix(table): better handling of the dropzone for dragging rows and columns --- .../TableHandles/TableHandlesPlugin.ts | 121 +++++++++++++++--- 1 file changed, 101 insertions(+), 20 deletions(-) diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 984c243aee..cb9c5b8b1f 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -141,6 +141,76 @@ function hideElements(selector: string, rootEl: Document | ShadowRoot) { } } +/** + * Checks if a row can be safely dropped at the target row index without splitting merged cells. + */ +function canRowBeDraggedInto( + block: BlockFromConfigNoChildren, + draggingIndex: RelativeCellIndices["row"], + targetRowIndex: RelativeCellIndices["row"] +) { + // Check cells at the target row + const targetCells = getCellsAtRowHandle(block, targetRowIndex); + + // If no cells have rowspans > 1, dragging is always allowed + const hasMergedCells = targetCells.some((cell) => getRowspan(cell.cell) > 1); + if (!hasMergedCells) { + return true; + } + + let endRowIndex = targetRowIndex; + let startRowIndex = targetRowIndex; + targetCells.forEach((cell) => { + const rowspan = getRowspan(cell.cell); + endRowIndex = Math.max(endRowIndex, cell.row + rowspan - 1); + startRowIndex = Math.min(startRowIndex, cell.row); + }); + + // Check the direction of the drag + const isDraggingDown = draggingIndex < targetRowIndex; + + // Allow dragging only at the start/end of merged cells + // Otherwise, the target row was within a merged cell which we don't allow + return isDraggingDown + ? targetRowIndex === endRowIndex + : targetRowIndex === startRowIndex; +} + +/** + * Checks if a column can be safely dropped at the target column index without splitting merged cells. + */ +function canColumnBeDraggedInto( + block: BlockFromConfigNoChildren, + draggingIndex: RelativeCellIndices["col"], + targetColumnIndex: RelativeCellIndices["col"] +) { + // Check cells at the target column + const targetCells = getCellsAtColumnHandle(block, targetColumnIndex); + + // If no cells have colspans > 1, dragging is always allowed + const hasMergedCells = targetCells.some((cell) => getColspan(cell.cell) > 1); + if (!hasMergedCells) { + return true; + } + + let endColumnIndex = targetColumnIndex; + let startColumnIndex = targetColumnIndex; + targetCells.forEach((cell) => { + const colspan = getColspan(cell.cell); + endColumnIndex = Math.max(endColumnIndex, cell.col + colspan - 1); + startColumnIndex = Math.min(startColumnIndex, cell.col); + }); + + // Check the direction of the drag + const isDraggingRight = draggingIndex < targetColumnIndex; + + // Allow dragging only at the start/end of merged cells + // Otherwise, the target column was within a merged cell which we don't allow + return isDraggingRight + ? targetColumnIndex === endColumnIndex + : targetColumnIndex === startColumnIndex; +} + export class TableHandlesView< I extends InlineContentSchema, S extends StyleSchema @@ -480,41 +550,34 @@ export class TableHandlesView< const columnWidths = this.state.block.content.columnWidths; if (draggingState.draggedCellOrientation === "row") { - const row = getCellsAtRowHandle(this.state.block, rowIndex); - // TODO need to work on this logic if ( - row.some((cell) => { - const rowspan = getRowspan(cell.cell); - if (rowspan === 1) { - return false; - } - return cell.row <= rowIndex && rowIndex <= cell.row + rowspan - 1; - }) + !canRowBeDraggedInto( + this.state.block, + draggingState.originalIndex, + rowIndex + ) ) { - // If the row has cells with different row indices, don't move the row + // If the target row is invalid, don't move the row return false; } const rowToMove = newTable[draggingState.originalIndex]; newTable.splice(draggingState.originalIndex, 1); newTable.splice(rowIndex, 0, rowToMove); } else { - const col = getCellsAtColumnHandle(this.state.block, colIndex); - // TODO need to work on this logic if ( - col.some((cell) => { - const colspan = getColspan(cell.cell); - if (colspan === 1) { - return false; - } - return cell.col <= colIndex && colIndex <= cell.col + colspan - 1; - }) + !canColumnBeDraggedInto( + this.state.block, + draggingState.originalIndex, + colIndex + ) ) { - // If the column has cells with different col indices, don't move the column + // If the target column is invalid, don't move the column return false; } const cellsToMove = newTable.map( (row) => row.cells[draggingState.originalIndex] ); + // TODO moving cells around is more complex than this newTable.forEach((row, rowIndex) => { row.cells.splice(draggingState.originalIndex, 1); row.cells.splice(colIndex, 0, cellsToMove[rowIndex] as any); @@ -666,6 +729,24 @@ export class TableHandlesProsemirrorPlugin< if (newIndex === this.view.state.draggingState.originalIndex) { return DecorationSet.create(state.doc, decorations); + } else if ( + this.view.state.draggingState.draggedCellOrientation === "row" && + !canRowBeDraggedInto( + this.view.state.block, + this.view.state.draggingState.originalIndex, + newIndex + ) + ) { + return DecorationSet.create(state.doc, decorations); + } else if ( + this.view.state.draggingState.draggedCellOrientation === "col" && + !canColumnBeDraggedInto( + this.view.state.block, + this.view.state.draggingState.originalIndex, + newIndex + ) + ) { + return DecorationSet.create(state.doc, decorations); } // Gets the table to show the drop cursor in. From b3c08c20c9cae6c1360a3bb5c97aba0664543b82 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Fri, 21 Feb 2025 10:08:40 +0100 Subject: [PATCH 37/72] feat: dragging columns and rows now stably works --- .../api/blockManipulation/tables/tables.ts | 105 +++++++++++++++++- .../TableHandles/TableHandlesPlugin.ts | 52 ++++----- 2 files changed, 130 insertions(+), 27 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index befac12df8..1a50de6161 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -242,6 +242,34 @@ export function getTableCellOccupancyGrid( return grid; } +/** + * Given an {@link OccupancyGrid}, this will return the {@link TableContent} rows. + * + * @note This will remove duplicates from the occupancy grid. And does no bounds checking for validity of the occupancy grid. + */ +export function getTableRowsFromOccupancyGrid( + occupancyGrid: OccupancyGrid +): TableContent["rows"] { + // Because a cell can have a rowspan or colspan, it can occupy multiple cells in the occupancy grid + // So, we need to remove duplicates from the occupancy grid before we can return the table rows + const seen = new Set(); + + return occupancyGrid.map((row) => { + // Just read out the cells in the occupancy grid, removing duplicates + return { + cells: row + .map((cell) => { + if (seen.has(cell.row + ":" + cell.col)) { + return false; + } + seen.add(cell.row + ":" + cell.col); + return cell.cell; + }) + .filter((cell) => cell !== false), + }; + }); +} + /** * This will resolve the relative cell indices within the table block to the absolute cell indices within the table, accounting for colspan and rowspan. * @@ -395,6 +423,7 @@ export function getCellsAtRowHandle( try { const occupancyGrid = getTableCellOccupancyGrid(block); + // First need to resolve the relative row index to an absolute row index let absoluteRow = 0; // Jump through the occupied cells ${relativeCellIndices.row} times to find the absolute row position @@ -474,7 +503,6 @@ export function getCellsAtColumnHandle( try { const occupancyGrid = getTableCellOccupancyGrid(block); // First need to resolve the relative column index to an absolute column index - let absoluteCol = 0; // Now that we've already resolved the absolute row position, we can jump through the occupied cells ${relativeCellIndices.col} times to find the absolute column position @@ -513,3 +541,78 @@ export function getCellsAtColumnHandle( return []; } } + +/** + * This moves a column from one index to another. + * + * @note This is a destructive operation, it will modify the {@link OccupancyGrid} in place. + */ +export function moveColumn( + block: BlockFromConfigNoChildren, + fromColIndex: RelativeCellIndices["col"], + toColIndex: RelativeCellIndices["col"], + occupancyGrid: OccupancyGrid = getTableCellOccupancyGrid(block) +): TableContent["rows"] { + // To move cells in a column, we need to layout the whole table + // and then move the cells accordingly. + const { col: absoluteSourceCol } = getAbsoluteTableCellIndices( + { + row: 0, + col: fromColIndex, + }, + block, + occupancyGrid + ); + const { col: absoluteTargetCol } = getAbsoluteTableCellIndices( + { + row: 0, + col: toColIndex, + }, + block, + occupancyGrid + ); + + occupancyGrid.forEach((row) => { + // Move the cell to the target column + const [sourceCell] = row.splice(absoluteSourceCol, 1); + row.splice(absoluteTargetCol, 0, sourceCell); + }); + + return getTableRowsFromOccupancyGrid(occupancyGrid); +} + +/** + * This moves a row from one index to another. + * + * @note This is a destructive operation, it will modify the {@link OccupancyGrid} in place. + */ +export function moveRow( + block: BlockFromConfigNoChildren, + fromRowIndex: RelativeCellIndices["row"], + toRowIndex: RelativeCellIndices["row"], + occupancyGrid: OccupancyGrid = getTableCellOccupancyGrid(block) +): TableContent["rows"] { + // To move cells in a column, we need to layout the whole table + // and then move the cells accordingly. + const { row: absoluteSourceRow } = getAbsoluteTableCellIndices( + { + row: fromRowIndex, + col: 0, + }, + block, + occupancyGrid + ); + const { row: absoluteTargetRow } = getAbsoluteTableCellIndices( + { + row: toRowIndex, + col: 0, + }, + block, + occupancyGrid + ); + + const [sourceRow] = occupancyGrid.splice(absoluteSourceRow, 1); + occupancyGrid.splice(absoluteTargetRow, 0, sourceRow); + + return getTableRowsFromOccupancyGrid(occupancyGrid); +} diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index cb9c5b8b1f..a3004a5aa1 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -16,6 +16,8 @@ import { getCellsAtColumnHandle, getCellsAtRowHandle, getDimensionsOfTable, + moveColumn, + moveRow, RelativeCellIndices, } from "../../api/blockManipulation/tables/tables.js"; import { nodeToBlock } from "../../api/nodeConversions/nodeToBlock.js"; @@ -32,7 +34,6 @@ import { getColspan, getRowspan, InlineContentSchema, - mapTableCell, StyleSchema, } from "../../schema/index.js"; import { EventEmitter } from "../../util/EventEmitter.js"; @@ -541,12 +542,6 @@ export class TableHandlesView< const { draggingState, colIndex, rowIndex } = this.state; - const newTable = this.state.block.content.rows.map((row) => { - return { - ...row, - cells: row.cells.map((cell) => mapTableCell(cell)), - }; - }); const columnWidths = this.state.block.content.columnWidths; if (draggingState.draggedCellOrientation === "row") { @@ -560,9 +555,18 @@ export class TableHandlesView< // If the target row is invalid, don't move the row return false; } - const rowToMove = newTable[draggingState.originalIndex]; - newTable.splice(draggingState.originalIndex, 1); - newTable.splice(rowIndex, 0, rowToMove); + const newTable = moveRow( + this.state.block, + draggingState.originalIndex, + rowIndex + ); + this.editor.updateBlock(this.state.block, { + type: "table", + content: { + ...this.state.block.content, + rows: newTable as any, + }, + }); } else { if ( !canColumnBeDraggedInto( @@ -574,27 +578,23 @@ export class TableHandlesView< // If the target column is invalid, don't move the column return false; } - const cellsToMove = newTable.map( - (row) => row.cells[draggingState.originalIndex] + const newTable = moveColumn( + this.state.block, + draggingState.originalIndex, + colIndex ); - // TODO moving cells around is more complex than this - newTable.forEach((row, rowIndex) => { - row.cells.splice(draggingState.originalIndex, 1); - row.cells.splice(colIndex, 0, cellsToMove[rowIndex] as any); - }); const [columnWidth] = columnWidths.splice(draggingState.originalIndex, 1); columnWidths.splice(colIndex, 0, columnWidth); + this.editor.updateBlock(this.state.block, { + type: "table", + content: { + ...this.state.block.content, + columnWidths, + rows: newTable as any, + }, + }); } - this.editor.updateBlock(this.state.block, { - type: "table", - content: { - ...this.state.block.content, - columnWidths, - rows: newTable, - }, - }); - // Have to reset text cursor position to the block as `updateBlock` moves // the existing selection out of the block. this.editor.setTextCursorPosition(this.state.block.id); From 2111f2fdf0bbdf67273138f60302130aab78aab0 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Fri, 21 Feb 2025 10:54:33 +0100 Subject: [PATCH 38/72] fix: get build running again --- packages/core/src/api/blockManipulation/tables/tables.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 1a50de6161..8f2a0df26d 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -265,7 +265,7 @@ export function getTableRowsFromOccupancyGrid( seen.add(cell.row + ":" + cell.col); return cell.cell; }) - .filter((cell) => cell !== false), + .filter((cell): cell is TableCell => cell !== false), }; }); } From 33941a384df6699011c0848e963e818494e41c93 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Fri, 21 Feb 2025 11:08:48 +0100 Subject: [PATCH 39/72] fix(table): drop cursors for table row & col re-ordering works again --- .../TableHandles/TableHandlesPlugin.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index a3004a5aa1..eb54dfdc89 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -760,14 +760,22 @@ export class TableHandlesProsemirrorPlugin< ); const rowNode = rowResolvedPos.node(); + const cellsInRow = getCellsAtRowHandle( + this.view.state.block, + newIndex + ); + // Iterates over all cells in the row. for (let i = 0; i < rowNode.childCount; i++) { + if (!cellsInRow.some((cell) => cell.col === i)) { + // Skip columns that don't have a cell in that row (because of a rowspan) + continue; + } // Gets each cell in the row. const cellResolvedPos = state.doc.resolve( rowResolvedPos.posAtIndex(i) + 1 ); const cellNode = cellResolvedPos.node(); - // Creates a decoration at the start or end of each cell, // depending on whether the new index is before or after the // original index. @@ -802,8 +810,16 @@ export class TableHandlesProsemirrorPlugin< ); } } else { + const cellsInColumn = getCellsAtColumnHandle( + this.view.state.block, + newIndex + ); // Iterates over all rows in the table. for (let i = 0; i < tableNode.childCount; i++) { + if (!cellsInColumn.some((cell) => cell.row === i)) { + // Skip rows that don't have a cell in that column (because of a colspan) + continue; + } // Gets each row in the table. const rowResolvedPos = state.doc.resolve( tableResolvedPos.posAtIndex(i) + 1 From 21da581857120c70ac524fd0db70cba9553113e8 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Fri, 21 Feb 2025 13:31:02 +0100 Subject: [PATCH 40/72] fix: do not output a columnWidth if not defined --- .../__snapshots__/internal/basicBlocks.html | 2 +- .../internal/basicBlocksWithProps.html | 2 +- .../__snapshots__/internal/tableAllCells.html | 2 +- .../__snapshots__/internal/tableCell.html | 2 +- .../__snapshots__/internal/tableRow.html | 2 +- .../__snapshots__/table/basic/external.html | 2 +- .../__snapshots__/table/basic/internal.html | 2 +- .../table/headerCols/external.html | 2 +- .../table/headerCols/internal.html | 2 +- .../table/headerRows/external.html | 2 +- .../table/headerRows/internal.html | 2 +- .../table/mixedCellColors/external.html | 2 +- .../table/mixedCellColors/internal.html | 2 +- .../table/mixedColWidths/external.html | 2 +- .../table/mixedColWidths/internal.html | 2 +- .../nodeConversions.test.ts.snap | 381 ++---------------- .../src/api/nodeConversions/blockToNode.ts | 8 +- 17 files changed, 53 insertions(+), 366 deletions(-) diff --git a/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocks.html b/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocks.html index 4a32d957d6..8c7757e46a 100644 --- a/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocks.html +++ b/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocks.html @@ -1 +1 @@ -

Paragraph

Heading

  1. Numbered List Item

  • Bullet List Item

  • Check List Item

console.log("Hello World");

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Add image

\ No newline at end of file +

Paragraph

Heading

  1. Numbered List Item

  • Bullet List Item

  • Check List Item

console.log("Hello World");

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Add image

\ No newline at end of file diff --git a/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html b/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html index 2ae664f204..4397413824 100644 --- a/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html +++ b/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html @@ -1 +1 @@ -

Paragraph

Heading

  1. Numbered List Item

  • Bullet List Item

  • Check List Item

console.log("Hello World");

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

1280px-Placeholder_view_vector.svg.png
Placeholder

\ No newline at end of file +

Paragraph

Heading

  1. Numbered List Item

  • Bullet List Item

  • Check List Item

console.log("Hello World");

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

1280px-Placeholder_view_vector.svg.png
Placeholder

\ No newline at end of file diff --git a/packages/core/src/api/clipboard/__snapshots__/internal/tableAllCells.html b/packages/core/src/api/clipboard/__snapshots__/internal/tableAllCells.html index 5ae1c65aba..5a0ce46217 100644 --- a/packages/core/src/api/clipboard/__snapshots__/internal/tableAllCells.html +++ b/packages/core/src/api/clipboard/__snapshots__/internal/tableAllCells.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/clipboard/__snapshots__/internal/tableCell.html b/packages/core/src/api/clipboard/__snapshots__/internal/tableCell.html index 9d0fd3e5e8..c4cc0e05d3 100644 --- a/packages/core/src/api/clipboard/__snapshots__/internal/tableCell.html +++ b/packages/core/src/api/clipboard/__snapshots__/internal/tableCell.html @@ -1 +1 @@ -

Table Cell

\ No newline at end of file +

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/clipboard/__snapshots__/internal/tableRow.html b/packages/core/src/api/clipboard/__snapshots__/internal/tableRow.html index 9e4fb87c0f..ffa15acbb9 100644 --- a/packages/core/src/api/clipboard/__snapshots__/internal/tableRow.html +++ b/packages/core/src/api/clipboard/__snapshots__/internal/tableRow.html @@ -1 +1 @@ -

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/basic/external.html b/packages/core/src/api/exporters/html/__snapshots__/table/basic/external.html index 626650c8cb..ce73c75aa8 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/basic/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/basic/external.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/table/basic/internal.html index 77b09fcac3..f00383b3d6 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/basic/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/basic/internal.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/headerCols/external.html b/packages/core/src/api/exporters/html/__snapshots__/table/headerCols/external.html index 0d1edf2ae0..03ecb174b0 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/headerCols/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/headerCols/external.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/headerCols/internal.html b/packages/core/src/api/exporters/html/__snapshots__/table/headerCols/internal.html index c7bd12dc42..972eb90512 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/headerCols/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/headerCols/internal.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/headerRows/external.html b/packages/core/src/api/exporters/html/__snapshots__/table/headerRows/external.html index 4c1d3e7203..9306b01089 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/headerRows/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/headerRows/external.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/headerRows/internal.html b/packages/core/src/api/exporters/html/__snapshots__/table/headerRows/internal.html index 95f848187d..b07ff89563 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/headerRows/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/headerRows/internal.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html b/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html index c1810a4a53..780396996c 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html b/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html index 45b36db1f7..10e00b6852 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/external.html b/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/external.html index 8b596348ac..c7018c749b 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/external.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/internal.html b/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/internal.html index 5c53f11e4b..b2b6765da5 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/table/mixedColWidths/internal.html @@ -1 +1 @@ -

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file +

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

\ No newline at end of file diff --git a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap index 0180108f88..245ab3ba9c 100644 --- a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +++ b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap @@ -2130,9 +2130,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2154,9 +2152,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2178,9 +2174,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2207,9 +2201,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2231,9 +2223,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2255,9 +2245,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2284,9 +2272,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2308,9 +2294,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2332,9 +2316,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2379,9 +2361,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2403,9 +2383,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2427,9 +2405,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2456,9 +2432,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2480,9 +2454,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2504,9 +2476,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2533,9 +2503,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2557,9 +2525,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2581,9 +2547,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2628,9 +2592,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2652,9 +2614,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2676,9 +2636,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2705,9 +2663,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2729,9 +2685,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2753,9 +2707,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2782,9 +2734,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2806,9 +2756,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -2830,258 +2778,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "content": [ - { - "content": [ - { - "text": "Table Cell", - "type": "text", - }, - ], - "type": "tableParagraph", - }, - ], - "type": "tableCell", - }, - ], - "type": "tableRow", - }, - ], - "type": "table", - }, - ], - "type": "blockContainer", -} -`; - -exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert table/headers to/from prosemirror 1`] = ` -{ - "attrs": { - "backgroundColor": "default", - "id": "1", - "textColor": "default", - }, - "content": [ - { - "content": [ - { - "content": [ - { - "attrs": { - "backgroundColor": "default", - "colspan": 1, - "colwidth": [ - undefined, - ], - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "content": [ - { - "content": [ - { - "text": "Table Cell", - "type": "text", - }, - ], - "type": "tableParagraph", - }, - ], - "type": "tableHeader", - }, - { - "attrs": { - "backgroundColor": "default", - "colspan": 1, - "colwidth": [ - undefined, - ], - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "content": [ - { - "content": [ - { - "text": "Table Cell", - "type": "text", - }, - ], - "type": "tableParagraph", - }, - ], - "type": "tableHeader", - }, - { - "attrs": { - "backgroundColor": "default", - "colspan": 1, - "colwidth": [ - undefined, - ], - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "content": [ - { - "content": [ - { - "text": "Table Cell", - "type": "text", - }, - ], - "type": "tableParagraph", - }, - ], - "type": "tableHeader", - }, - ], - "type": "tableRow", - }, - { - "content": [ - { - "attrs": { - "backgroundColor": "default", - "colspan": 1, - "colwidth": [ - undefined, - ], - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "content": [ - { - "content": [ - { - "text": "Table Cell", - "type": "text", - }, - ], - "type": "tableParagraph", - }, - ], - "type": "tableCell", - }, - { - "attrs": { - "backgroundColor": "default", - "colspan": 1, - "colwidth": [ - undefined, - ], - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "content": [ - { - "content": [ - { - "text": "Table Cell", - "type": "text", - }, - ], - "type": "tableParagraph", - }, - ], - "type": "tableCell", - }, - { - "attrs": { - "backgroundColor": "default", - "colspan": 1, - "colwidth": [ - undefined, - ], - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "content": [ - { - "content": [ - { - "text": "Table Cell", - "type": "text", - }, - ], - "type": "tableParagraph", - }, - ], - "type": "tableCell", - }, - ], - "type": "tableRow", - }, - { - "content": [ - { - "attrs": { - "backgroundColor": "default", - "colspan": 1, - "colwidth": [ - undefined, - ], - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "content": [ - { - "content": [ - { - "text": "Table Cell", - "type": "text", - }, - ], - "type": "tableParagraph", - }, - ], - "type": "tableCell", - }, - { - "attrs": { - "backgroundColor": "default", - "colspan": 1, - "colwidth": [ - undefined, - ], - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "content": [ - { - "content": [ - { - "text": "Table Cell", - "type": "text", - }, - ], - "type": "tableParagraph", - }, - ], - "type": "tableCell", - }, - { - "attrs": { - "backgroundColor": "default", - "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -3150,9 +2847,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "blue", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -3227,9 +2922,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -3304,9 +2997,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -3399,9 +3090,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -3476,9 +3165,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", @@ -3553,9 +3240,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "attrs": { "backgroundColor": "default", "colspan": 1, - "colwidth": [ - undefined, - ], + "colwidth": null, "rowspan": 1, "textAlignment": "left", "textColor": "default", diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index c4cf08982e..b346924393 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -210,9 +210,11 @@ export function tableContentToNodes< ); // Assume the column width is the width of the cell at the absolute cell index - let colwidth: (number | undefined)[] = [ - columnWidths[absoluteCellIndex.col] ?? undefined, - ]; + let colwidth: (number | undefined)[] | null = columnWidths[ + absoluteCellIndex.col + ] + ? [columnWidths[absoluteCellIndex.col]] + : null; if (!cell) { // No-op From 5d406ddf09c05afafc20ce8464af90594060837b Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Fri, 21 Feb 2025 13:43:20 +0100 Subject: [PATCH 41/72] refactor: use the cells or rows in the table to decide which decos to show --- .../TableHandles/TableHandlesPlugin.ts | 58 +++++++------------ 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index eb54dfdc89..2bf660eac1 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -751,29 +751,23 @@ export class TableHandlesProsemirrorPlugin< // Gets the table to show the drop cursor in. const tableResolvedPos = state.doc.resolve(this.view.tablePos + 1); - const tableNode = tableResolvedPos.node(); + const originalIndex = this.view.state.draggingState.originalIndex; if (this.view.state.draggingState.draggedCellOrientation === "row") { - // Gets the row at the new index. - const rowResolvedPos = state.doc.resolve( - tableResolvedPos.posAtIndex(newIndex) + 1 - ); - const rowNode = rowResolvedPos.node(); - const cellsInRow = getCellsAtRowHandle( this.view.state.block, newIndex ); - // Iterates over all cells in the row. - for (let i = 0; i < rowNode.childCount; i++) { - if (!cellsInRow.some((cell) => cell.col === i)) { - // Skip columns that don't have a cell in that row (because of a rowspan) - continue; - } - // Gets each cell in the row. + cellsInRow.forEach(({ row, col }) => { + // Gets each row in the table. + const rowResolvedPos = state.doc.resolve( + tableResolvedPos.posAtIndex(row) + 1 + ); + + // Gets the cell within the row. const cellResolvedPos = state.doc.resolve( - rowResolvedPos.posAtIndex(i) + 1 + rowResolvedPos.posAtIndex(col) + 1 ); const cellNode = cellResolvedPos.node(); // Creates a decoration at the start or end of each cell, @@ -781,9 +775,7 @@ export class TableHandlesProsemirrorPlugin< // original index. const decorationPos = cellResolvedPos.pos + - (newIndex > this.view.state.draggingState.originalIndex - ? cellNode.nodeSize - 2 - : 0); + (newIndex > originalIndex ? cellNode.nodeSize - 2 : 0); decorations.push( // The widget is a small bar which spans the width of the cell. Decoration.widget(decorationPos, () => { @@ -796,9 +788,7 @@ export class TableHandlesProsemirrorPlugin< // table cells is an odd number of pixels. So this makes the // positioning slightly more consistent regardless of where // the row is being dropped. - if ( - newIndex > this.view!.state!.draggingState!.originalIndex - ) { + if (newIndex > originalIndex) { widget.style.bottom = "-2px"; } else { widget.style.top = "-3px"; @@ -808,26 +798,22 @@ export class TableHandlesProsemirrorPlugin< return widget; }) ); - } + }); } else { const cellsInColumn = getCellsAtColumnHandle( this.view.state.block, newIndex ); - // Iterates over all rows in the table. - for (let i = 0; i < tableNode.childCount; i++) { - if (!cellsInColumn.some((cell) => cell.row === i)) { - // Skip rows that don't have a cell in that column (because of a colspan) - continue; - } + + cellsInColumn.forEach(({ row, col }) => { // Gets each row in the table. const rowResolvedPos = state.doc.resolve( - tableResolvedPos.posAtIndex(i) + 1 + tableResolvedPos.posAtIndex(row) + 1 ); - // Gets the cell at the new index in the row. + // Gets the cell within the row. const cellResolvedPos = state.doc.resolve( - rowResolvedPos.posAtIndex(newIndex) + 1 + rowResolvedPos.posAtIndex(col) + 1 ); const cellNode = cellResolvedPos.node(); @@ -836,9 +822,7 @@ export class TableHandlesProsemirrorPlugin< // original index. const decorationPos = cellResolvedPos.pos + - (newIndex > this.view.state.draggingState.originalIndex - ? cellNode.nodeSize - 2 - : 0); + (newIndex > originalIndex ? cellNode.nodeSize - 2 : 0); decorations.push( // The widget is a small bar which spans the height of the cell. @@ -852,9 +836,7 @@ export class TableHandlesProsemirrorPlugin< // table cells is an odd number of pixels. So this makes the // positioning slightly more consistent regardless of where // the column is being dropped. - if ( - newIndex > this.view!.state!.draggingState!.originalIndex - ) { + if (newIndex > originalIndex) { widget.style.right = "-2px"; } else { widget.style.left = "-3px"; @@ -864,7 +846,7 @@ export class TableHandlesProsemirrorPlugin< return widget; }) ); - } + }); } return DecorationSet.create(state.doc, decorations); From a69803682a1036d6f9818b68d3c67f2bfc7fa8d9 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Fri, 21 Feb 2025 14:22:43 +0100 Subject: [PATCH 42/72] fix: make extend buttons more robust and colspan rowspan aware --- .../api/blockManipulation/tables/tables.ts | 113 +++++++++++ .../TableHandles/TableHandlesPlugin.ts | 17 ++ .../ExtendButton/ExtendButton.tsx | 176 +++++------------- 3 files changed, 172 insertions(+), 134 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 8f2a0df26d..51814a37d1 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -3,7 +3,9 @@ import { BlockFromConfigNoChildren, getColspan, getRowspan, + isPartialTableCell, mapTableCell, + PartialTableContent, TableCell, TableContent, } from "../../../schema/blocks/types.js"; @@ -616,3 +618,114 @@ export function moveRow( return getTableRowsFromOccupancyGrid(occupancyGrid); } + +/** + * This will check if a cell is empty. + * + * @returns True if the cell is empty, false otherwise. + */ +function isCellEmpty( + cell: + | PartialTableContent["rows"][number]["cells"][number] + | undefined +): boolean { + if (!cell) { + return true; + } + if (isPartialTableCell(cell)) { + return isCellEmpty(cell.content); + } else { + return cell.length === 0; + } +} + +/** + * This will remove empty rows or columns from the table. + * + * @note This is a destructive operation, it will modify the {@link OccupancyGrid} in place. + */ +export function cropEmptyRowsOrColumns( + block: BlockFromConfigNoChildren, + removeEmpty: "columns" | "rows", + occupancyGrid: OccupancyGrid = getTableCellOccupancyGrid(block) +): TableContent["rows"] { + let emptyColsOnRight = 0; + + if (removeEmpty === "columns") { + // strips empty columns to the right and empty rows at the bottom + for (let i = occupancyGrid[0].length - 1; i >= 0; i--) { + const isEmpty = occupancyGrid.every((row) => isCellEmpty(row[i].cell)); + if (!isEmpty) { + break; + } + + emptyColsOnRight++; + } + } + + for (let i = occupancyGrid.length - 1; i >= 0; i--) { + if (removeEmpty === "rows") { + if ( + // rows.length === 0 && + occupancyGrid[i].every((cell) => isCellEmpty(cell.cell)) + ) { + // empty row at bottom + continue; + } + } + + occupancyGrid[i] = occupancyGrid[i].slice( + 0, + occupancyGrid[i].length - emptyColsOnRight + ); + } + + return getTableRowsFromOccupancyGrid(occupancyGrid); +} + +/** + * This will add a specified number of rows or columns to the table (filling with empty cells). + * + * @note This is a destructive operation, it will modify the {@link OccupancyGrid} in place. + */ +export function addRowsOrColumns( + block: BlockFromConfigNoChildren, + addType: "columns" | "rows", + numToAdd: number, + occupancyGrid: OccupancyGrid = getTableCellOccupancyGrid(block) +): TableContent["rows"] { + if (numToAdd <= 0) { + return getTableRowsFromOccupancyGrid(occupancyGrid); + } + + const { width, height } = getDimensionsOfTable(block); + + if (addType === "columns") { + // Add empty columns to the right + occupancyGrid.forEach((row) => { + for (let i = 0; i < numToAdd; i++) { + row.push({ + row: row[0].row, + col: width + i, + rowspan: 1, + colspan: 1, + cell: mapTableCell(""), + }); + } + }); + } else { + // Add empty rows to the bottom + for (let i = 0; i < numToAdd; i++) { + const newRow = new Array(width).fill(null).map((_, colIndex) => ({ + row: height + i, + col: colIndex, + rowspan: 1, + colspan: 1, + cell: mapTableCell(""), + })); + occupancyGrid.push(newRow); + } + } + + return getTableRowsFromOccupancyGrid(occupancyGrid); +} diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 2bf660eac1..f56dcfe8ad 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -12,6 +12,8 @@ import { } from "prosemirror-tables"; import { Decoration, DecorationSet, EditorView } from "prosemirror-view"; import { + addRowsOrColumns, + cropEmptyRowsOrColumns, getAbsoluteTableCellIndices, getCellsAtColumnHandle, getCellsAtRowHandle, @@ -1234,4 +1236,19 @@ export class TableHandlesProsemirrorPlugin< return "horizontal"; }; + + cropEmptyRowsOrColumns = ( + block: BlockFromConfigNoChildren, + removeEmpty: "columns" | "rows" + ) => { + return cropEmptyRowsOrColumns(block, removeEmpty); + }; + + addRowsOrColumns = ( + block: BlockFromConfigNoChildren, + addType: "columns" | "rows", + numToAdd: number + ) => { + return addRowsOrColumns(block, addType, numToAdd); + }; } diff --git a/packages/react/src/components/TableHandles/ExtendButton/ExtendButton.tsx b/packages/react/src/components/TableHandles/ExtendButton/ExtendButton.tsx index 68bf4c7656..8ec1fafe4f 100644 --- a/packages/react/src/components/TableHandles/ExtendButton/ExtendButton.tsx +++ b/packages/react/src/components/TableHandles/ExtendButton/ExtendButton.tsx @@ -4,7 +4,6 @@ import { EMPTY_CELL_HEIGHT, EMPTY_CELL_WIDTH, InlineContentSchema, - isPartialTableCell, mergeCSSClasses, PartialTableContent, StyleSchema, @@ -22,65 +21,6 @@ import { RiAddFill } from "react-icons/ri"; import { useComponentsContext } from "../../../editor/ComponentsContext.js"; import { ExtendButtonProps } from "./ExtendButtonProps.js"; -function isCellEmpty( - cell: PartialTableContent["rows"][number]["cells"][number] | undefined -): boolean { - if (!cell) { - return true; - } - if (isPartialTableCell(cell)) { - return isCellEmpty(cell.content); - } else { - return cell.length === 0; - } -} - -function cropEmptyRowsOrColumns< - I extends InlineContentSchema, - S extends StyleSchema ->( - content: PartialTableContent, - removeEmpty: "columns" | "rows" -): PartialTableContent { - let emptyColsOnRight = 0; - - if (removeEmpty === "columns") { - // strips empty columns to the right and empty rows at the bottom - for (let i = content.rows[0].cells.length - 1; i >= 0; i--) { - const isEmpty = content.rows.every((row) => isCellEmpty(row.cells[i])); - if (!isEmpty) { - break; - } - - emptyColsOnRight++; - } - } - - const rows: PartialTableContent["rows"] = []; - for (let i = content.rows.length - 1; i >= 0; i--) { - if (removeEmpty === "rows") { - if ( - rows.length === 0 && - content.rows[i].cells.every((cell) => isCellEmpty(cell)) - ) { - // empty row at bottom - continue; - } - } - - rows.unshift({ - cells: content.rows[i].cells.slice( - 0, - content.rows[0].cells.length - emptyColsOnRight - ), - }); - } - - return { - ...content, - rows, - }; -} // Rounds a number up or down, depending on whether we're close (as defined by // `margin`) to the next integer. const marginRound = (num: number, margin = 0.3) => { @@ -96,56 +36,6 @@ const marginRound = (num: number, margin = 0.3) => { } }; -const getContentWithAddedRows = < - I extends InlineContentSchema, - S extends StyleSchema ->( - content: PartialTableContent, - rowsToAdd: number, - numCols: number -): PartialTableContent => { - const newRow: PartialTableContent["rows"][number] = { - cells: Array(numCols).fill([]), - }; - - const newRows: PartialTableContent["rows"] = []; - for (let i = 0; i < rowsToAdd; i++) { - newRows.push(newRow); - } - return { - type: "tableContent", - columnWidths: content.columnWidths, - rows: [...content.rows, ...newRows], - }; -}; - -const getContentWithAddedCols = < - I extends InlineContentSchema, - S extends StyleSchema ->( - content: PartialTableContent, - colsToAdd: number -): PartialTableContent => { - const newCell: PartialTableContent["rows"][number]["cells"][number] = - []; - const newCells: PartialTableContent["rows"][number]["cells"] = []; - for (let i = 0; i < colsToAdd; i++) { - // TODO: fix this - newCells.push(newCell as any); - } - - return { - type: "tableContent", - columnWidths: content.columnWidths - ? [...content.columnWidths, ...newCells.map(() => undefined)] - : undefined, - rows: content.rows.map((row) => ({ - // TODO: fix this - cells: [...row.cells, ...newCells] as any[], - })), - }; -}; - export const ExtendButton = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema @@ -173,10 +63,12 @@ export const ExtendButton = < props.onMouseDown(); setEditingState({ originalContent: props.block.content, - originalCroppedContent: cropEmptyRowsOrColumns( - props.block.content, - props.orientation === "addOrRemoveColumns" ? "columns" : "rows" - ), + originalCroppedContent: { + rows: props.editor.tableHandles!.cropEmptyRowsOrColumns( + props.block, + props.orientation === "addOrRemoveColumns" ? "columns" : "rows" + ), + } as PartialTableContent, startPos: props.orientation === "addOrRemoveColumns" ? event.clientX @@ -196,21 +88,27 @@ export const ExtendButton = < } props.editor.updateBlock(props.block, { type: "table", - content: - props.orientation === "addOrRemoveColumns" - ? getContentWithAddedCols(props.block.content, 1) - : getContentWithAddedRows( - props.block.content, - 1, - props.block.content.rows[0].cells.length - ), + content: { + ...props.block.content, + rows: + props.orientation === "addOrRemoveColumns" + ? props.editor.tableHandles!.addRowsOrColumns( + props.block, + "columns", + 1 + ) + : props.editor.tableHandles!.addRowsOrColumns( + props.block, + "rows", + 1 + ), + } as any, }); }, [props.block, props.orientation, props.editor]); // Extends columns/rows on when moving the mouse. useEffect(() => { const callback = (event: MouseEvent) => { - // console.log("callback", event); if (!editingState) { throw new Error("editingState is undefined"); } @@ -254,17 +152,27 @@ export const ExtendButton = < ) { props.editor.updateBlock(props.block, { type: "table", - content: - props.orientation === "addOrRemoveColumns" - ? getContentWithAddedCols( - editingState.originalCroppedContent, - newNumCells - numCroppedCells - ) - : getContentWithAddedRows( - editingState.originalCroppedContent, - newNumCells - numCroppedCells, - editingState.originalContent.rows[0].cells.length - ), + content: { + ...props.block.content, + rows: + props.orientation === "addOrRemoveColumns" + ? props.editor.tableHandles!.addRowsOrColumns( + { + type: "table", + content: editingState.originalCroppedContent, + } as any, + "columns", + newNumCells - numCroppedCells + ) + : props.editor.tableHandles!.addRowsOrColumns( + { + type: "table", + content: editingState.originalCroppedContent, + } as any, + "rows", + newNumCells - numCroppedCells + ), + } as any, }); // Edge case for updating block content as `updateBlock` causes the From c6af87e1a0a07125a05d92b559c45c557fe2cd8c Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Fri, 21 Feb 2025 14:32:27 +0100 Subject: [PATCH 43/72] fix: minor bug fix --- .../TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx index d421b4c555..8b6f2dd038 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx @@ -73,7 +73,7 @@ export const ColorPickerButton = < editor.setTextCursorPosition(props.block); }; - if (!currentCells || !tableHandles) { + if (!currentCells || !currentCells[0] || !tableHandles) { return null; } From 2365d4663030a11eed6290f8f7a7589672b7ee78 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Mon, 24 Feb 2025 11:10:23 +0100 Subject: [PATCH 44/72] fix(table): extending an empty table's columns needs at least 1 cell --- packages/core/src/api/blockManipulation/tables/tables.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 51814a37d1..488fa1ffa9 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -676,7 +676,7 @@ export function cropEmptyRowsOrColumns( occupancyGrid[i] = occupancyGrid[i].slice( 0, - occupancyGrid[i].length - emptyColsOnRight + Math.max(occupancyGrid[i].length - emptyColsOnRight, 1) ); } From e164fad09fb513d10db33c7c999d05a9ea5edb83 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Mon, 24 Feb 2025 11:15:22 +0100 Subject: [PATCH 45/72] refactor: minor cleanup --- packages/core/src/api/blockManipulation/tables/tables.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 488fa1ffa9..03eaa84237 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -665,10 +665,7 @@ export function cropEmptyRowsOrColumns( for (let i = occupancyGrid.length - 1; i >= 0; i--) { if (removeEmpty === "rows") { - if ( - // rows.length === 0 && - occupancyGrid[i].every((cell) => isCellEmpty(cell.cell)) - ) { + if (occupancyGrid[i].every((cell) => isCellEmpty(cell.cell))) { // empty row at bottom continue; } @@ -676,6 +673,7 @@ export function cropEmptyRowsOrColumns( occupancyGrid[i] = occupancyGrid[i].slice( 0, + // We maintain at least one cell, even if all the cells are empty Math.max(occupancyGrid[i].length - emptyColsOnRight, 1) ); } From cc7e6996ae2f295293b19e1dbb9c4957fd069884 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 26 Feb 2025 06:38:39 +0100 Subject: [PATCH 46/72] fix(tables): better handling of the expand table buttons --- .../blockManipulation/tables/tables.test.ts | 105 ++++++++++++++- .../api/blockManipulation/tables/tables.ts | 126 ++++++++++++------ 2 files changed, 187 insertions(+), 44 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.test.ts b/packages/core/src/api/blockManipulation/tables/tables.test.ts index 29696205dc..439218e739 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.test.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.test.ts @@ -6,14 +6,13 @@ import { getCellsAtRowHandle, getRelativeTableCellIndices, getAbsoluteTableCellIndices, + moveColumn, + moveRow, + cropEmptyRowsOrColumns, } from "./tables.js"; /** - * Normal table - * | 1-1 | 1-2 | 1-3 | - * | 2-1 | 2-2 | 2-3 | - * - * Table with colspan + * Simple table * | 1-1 | 1-2 | 1-3 | * | 2-1 | 2-2 | 2-3 | */ @@ -1606,3 +1605,99 @@ describe("Test getColumn", () => { ).toEqual([]); }); }); + +describe("Test moveColumn", () => { + it("should move the column of the table", () => { + expect(moveColumn(simpleTable, 0, 1)).toEqual([ + { + cells: [ + simpleTable.content.rows[0].cells[1], + simpleTable.content.rows[0].cells[0], + ], + }, + { + cells: [ + simpleTable.content.rows[1].cells[1], + simpleTable.content.rows[1].cells[0], + ], + }, + ]); + }); +}); + +describe("Test moveRow", () => { + it("should move the row of the table", () => { + expect(moveRow(simpleTable, 0, 1)).toEqual([ + { + cells: [ + simpleTable.content.rows[1].cells[0], + simpleTable.content.rows[1].cells[1], + ], + }, + { + cells: [ + simpleTable.content.rows[0].cells[0], + simpleTable.content.rows[0].cells[1], + ], + }, + ]); + }); +}); + +describe("Test cropEmptyRowsOrColumns", () => { + it("should crop the empty rows of the table", () => { + expect( + cropEmptyRowsOrColumns( + { + ...simpleTable, + content: { + ...simpleTable.content, + rows: simpleTable.content.rows.concat([ + { + cells: [ + { + type: "tableCell", + props: { + backgroundColor: "red", + textColor: "blue", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "", styles: {} }], + }, + + { + type: "tableCell", + props: { + backgroundColor: "red", + textColor: "blue", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text", text: "", styles: {} }], + }, + ], + }, + ]), + }, + }, + "rows" + ) + ).toEqual([ + { + cells: [ + simpleTable.content.rows[0].cells[0], + simpleTable.content.rows[0].cells[1], + ], + }, + { + cells: [ + simpleTable.content.rows[1].cells[0], + simpleTable.content.rows[1].cells[1], + ], + }, + ]); + }); +}); diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 03eaa84237..5095bf32af 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -9,6 +9,10 @@ import { TableCell, TableContent, } from "../../../schema/blocks/types.js"; +import { + isPartialLinkInlineContent, + isStyledTextInlineContent, +} from "../../../schema/index.js"; /** * Here be dragons. @@ -634,8 +638,22 @@ function isCellEmpty( } if (isPartialTableCell(cell)) { return isCellEmpty(cell.content); - } else { + } else if (typeof cell === "string") { return cell.length === 0; + } else if (Array.isArray(cell)) { + return cell.every((c) => + typeof c === "string" + ? c.length === 0 + : isStyledTextInlineContent(c) + ? c.text.length === 0 + : isPartialLinkInlineContent(c) + ? typeof c.content === "string" + ? c.content.length === 0 + : c.content.every((s) => s.text.length === 0) + : false + ); + } else { + return false; } } @@ -649,36 +667,55 @@ export function cropEmptyRowsOrColumns( removeEmpty: "columns" | "rows", occupancyGrid: OccupancyGrid = getTableCellOccupancyGrid(block) ): TableContent["rows"] { - let emptyColsOnRight = 0; - if (removeEmpty === "columns") { - // strips empty columns to the right and empty rows at the bottom - for (let i = occupancyGrid[0].length - 1; i >= 0; i--) { - const isEmpty = occupancyGrid.every((row) => isCellEmpty(row[i].cell)); + // strips empty columns on the right + let emptyColsOnRight = 0; + for ( + let cellIndex = occupancyGrid[0].length - 1; + cellIndex >= 0; + cellIndex-- + ) { + const isEmpty = occupancyGrid.every((row) => + isCellEmpty(row[cellIndex].cell) + ); if (!isEmpty) { break; } emptyColsOnRight++; } - } - for (let i = occupancyGrid.length - 1; i >= 0; i--) { - if (removeEmpty === "rows") { - if (occupancyGrid[i].every((cell) => isCellEmpty(cell.cell))) { - // empty row at bottom - continue; + for (let i = occupancyGrid.length - 1; i >= 0; i--) { + // We maintain at least one cell, even if all the cells are empty + const cellsToRemove = Math.max( + occupancyGrid[i].length - emptyColsOnRight, + 1 + ); + occupancyGrid[i] = occupancyGrid[i].slice(0, cellsToRemove); + } + + return getTableRowsFromOccupancyGrid(occupancyGrid); + } else { + // strips empty rows at the bottom + let emptyRowsOnBottom = 0; + for (let rowIndex = occupancyGrid.length - 1; rowIndex >= 0; rowIndex--) { + const isEmpty = occupancyGrid[rowIndex].every((cell) => + isCellEmpty(cell.cell) + ); + if (!isEmpty) { + break; } + + emptyRowsOnBottom++; } - occupancyGrid[i] = occupancyGrid[i].slice( - 0, - // We maintain at least one cell, even if all the cells are empty - Math.max(occupancyGrid[i].length - emptyColsOnRight, 1) - ); - } + // We maintain at least one row, even if all the rows are empty + const rowsToRemove = Math.min(emptyRowsOnBottom, occupancyGrid.length - 1); - return getTableRowsFromOccupancyGrid(occupancyGrid); + occupancyGrid.splice(occupancyGrid.length - rowsToRemove, rowsToRemove); + + return getTableRowsFromOccupancyGrid(occupancyGrid); + } } /** @@ -689,39 +726,50 @@ export function cropEmptyRowsOrColumns( export function addRowsOrColumns( block: BlockFromConfigNoChildren, addType: "columns" | "rows", + /** + * The number of rows or columns to add. + * + * @note if negative, it will remove rows or columns. + */ numToAdd: number, occupancyGrid: OccupancyGrid = getTableCellOccupancyGrid(block) ): TableContent["rows"] { - if (numToAdd <= 0) { - return getTableRowsFromOccupancyGrid(occupancyGrid); - } - const { width, height } = getDimensionsOfTable(block); if (addType === "columns") { // Add empty columns to the right occupancyGrid.forEach((row) => { + if (numToAdd >= 0) { + for (let i = 0; i < numToAdd; i++) { + row.push({ + row: row[0].row, + col: width + i, + rowspan: 1, + colspan: 1, + cell: mapTableCell(""), + }); + } + } else { + // Remove columns on the right + row.splice(width + numToAdd, -1 * numToAdd); + } + }); + } else { + if (numToAdd > 0) { + // Add empty rows to the bottom for (let i = 0; i < numToAdd; i++) { - row.push({ - row: row[0].row, - col: width + i, + const newRow = new Array(width).fill(null).map((_, colIndex) => ({ + row: height + i, + col: colIndex, rowspan: 1, colspan: 1, cell: mapTableCell(""), - }); + })); + occupancyGrid.push(newRow); } - }); - } else { - // Add empty rows to the bottom - for (let i = 0; i < numToAdd; i++) { - const newRow = new Array(width).fill(null).map((_, colIndex) => ({ - row: height + i, - col: colIndex, - rowspan: 1, - colspan: 1, - cell: mapTableCell(""), - })); - occupancyGrid.push(newRow); + } else if (numToAdd < 0) { + // Remove rows at the bottom + occupancyGrid.splice(height + numToAdd, -1 * numToAdd); } } From d43dbebde6e8d6b790e64155cd33b28483c6c07c Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 26 Feb 2025 06:42:11 +0100 Subject: [PATCH 47/72] refactor: rename internal fns --- .../blockManipulation/tables/tables.test.ts | 541 ++++++++---------- .../api/blockManipulation/tables/tables.ts | 16 +- .../src/api/nodeConversions/blockToNode.ts | 4 +- .../TableHandles/TableHandlesPlugin.ts | 6 +- 4 files changed, 263 insertions(+), 304 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.test.ts b/packages/core/src/api/blockManipulation/tables/tables.test.ts index 439218e739..a1946bcf36 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.test.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.test.ts @@ -4,8 +4,8 @@ import { Block, DefaultBlockSchema } from "../../../blocks/defaultBlocks.js"; import { getCellsAtColumnHandle, getCellsAtRowHandle, - getRelativeTableCellIndices, - getAbsoluteTableCellIndices, + getRelativeTableCells, + getAbsoluteTableCells, moveColumn, moveRow, cropEmptyRowsOrColumns, @@ -688,113 +688,121 @@ const tableWithComplexRowspansAndColspans = { describe("Test getAbsoluteTableCellIndices", () => { it("should resolve relative table cell indices to absolute table cell indices", () => { - expect( - getAbsoluteTableCellIndices({ row: 0, col: 0 }, simpleTable) - ).toEqual({ row: 0, col: 0, cell: simpleTable.content.rows[0].cells[0] }); - expect( - getAbsoluteTableCellIndices({ row: 0, col: 1 }, simpleTable) - ).toEqual({ row: 0, col: 1, cell: simpleTable.content.rows[0].cells[1] }); - expect( - getAbsoluteTableCellIndices({ row: 1, col: 0 }, simpleTable) - ).toEqual({ row: 1, col: 0, cell: simpleTable.content.rows[1].cells[0] }); - expect( - getAbsoluteTableCellIndices({ row: 1, col: 1 }, simpleTable) - ).toEqual({ row: 1, col: 1, cell: simpleTable.content.rows[1].cells[1] }); - }); - - it("should resolve relative table cell indices to absolute table cell indices with colspan", () => { - expect( - getAbsoluteTableCellIndices({ row: 0, col: 0 }, tableWithColspan) - ).toEqual({ + expect(getAbsoluteTableCells({ row: 0, col: 0 }, simpleTable)).toEqual({ row: 0, col: 0, - cell: tableWithColspan.content.rows[0].cells[0], + cell: simpleTable.content.rows[0].cells[0], }); - expect( - getAbsoluteTableCellIndices({ row: 0, col: 1 }, tableWithColspan) - ).toEqual({ + expect(getAbsoluteTableCells({ row: 0, col: 1 }, simpleTable)).toEqual({ row: 0, - col: 2, - cell: tableWithColspan.content.rows[0].cells[1], + col: 1, + cell: simpleTable.content.rows[0].cells[1], }); - expect( - getAbsoluteTableCellIndices({ row: 1, col: 0 }, tableWithColspan) - ).toEqual({ + expect(getAbsoluteTableCells({ row: 1, col: 0 }, simpleTable)).toEqual({ row: 1, col: 0, - cell: tableWithColspan.content.rows[1].cells[0], + cell: simpleTable.content.rows[1].cells[0], }); - expect( - getAbsoluteTableCellIndices({ row: 1, col: 1 }, tableWithColspan) - ).toEqual({ + expect(getAbsoluteTableCells({ row: 1, col: 1 }, simpleTable)).toEqual({ row: 1, col: 1, - cell: tableWithColspan.content.rows[1].cells[1], - }); - expect( - getAbsoluteTableCellIndices({ row: 1, col: 2 }, tableWithColspan) - ).toEqual({ - row: 1, - col: 2, - cell: tableWithColspan.content.rows[1].cells[2], + cell: simpleTable.content.rows[1].cells[1], }); }); + it("should resolve relative table cell indices to absolute table cell indices with colspan", () => { + expect(getAbsoluteTableCells({ row: 0, col: 0 }, tableWithColspan)).toEqual( + { + row: 0, + col: 0, + cell: tableWithColspan.content.rows[0].cells[0], + } + ); + expect(getAbsoluteTableCells({ row: 0, col: 1 }, tableWithColspan)).toEqual( + { + row: 0, + col: 2, + cell: tableWithColspan.content.rows[0].cells[1], + } + ); + expect(getAbsoluteTableCells({ row: 1, col: 0 }, tableWithColspan)).toEqual( + { + row: 1, + col: 0, + cell: tableWithColspan.content.rows[1].cells[0], + } + ); + expect(getAbsoluteTableCells({ row: 1, col: 1 }, tableWithColspan)).toEqual( + { + row: 1, + col: 1, + cell: tableWithColspan.content.rows[1].cells[1], + } + ); + expect(getAbsoluteTableCells({ row: 1, col: 2 }, tableWithColspan)).toEqual( + { + row: 1, + col: 2, + cell: tableWithColspan.content.rows[1].cells[2], + } + ); + }); + it("should resolve relative table cell indices to absolute table cell indices with rowspan", () => { - expect( - getAbsoluteTableCellIndices({ row: 0, col: 0 }, tableWithRowspan) - ).toEqual({ - row: 0, - col: 0, - cell: tableWithRowspan.content.rows[0].cells[0], - }); - expect( - getAbsoluteTableCellIndices({ row: 0, col: 1 }, tableWithRowspan) - ).toEqual({ - row: 0, - col: 1, - cell: tableWithRowspan.content.rows[0].cells[1], - }); - expect( - getAbsoluteTableCellIndices({ row: 0, col: 2 }, tableWithRowspan) - ).toEqual({ - row: 0, - col: 2, - cell: tableWithRowspan.content.rows[0].cells[2], - }); - expect( - getAbsoluteTableCellIndices({ row: 1, col: 0 }, tableWithRowspan) - ).toEqual({ - row: 1, - col: 1, - cell: tableWithRowspan.content.rows[1].cells[0], - }); - expect( - getAbsoluteTableCellIndices({ row: 1, col: 1 }, tableWithRowspan) - ).toEqual({ - row: 1, - col: 2, - cell: tableWithRowspan.content.rows[1].cells[1], - }); - expect( - getAbsoluteTableCellIndices({ row: 2, col: 0 }, tableWithRowspan) - ).toEqual({ - row: 2, - col: 0, - cell: tableWithRowspan.content.rows[2].cells[0], - }); - expect( - getAbsoluteTableCellIndices({ row: 2, col: 1 }, tableWithRowspan) - ).toEqual({ - row: 2, - col: 1, - cell: tableWithRowspan.content.rows[2].cells[1], - }); + expect(getAbsoluteTableCells({ row: 0, col: 0 }, tableWithRowspan)).toEqual( + { + row: 0, + col: 0, + cell: tableWithRowspan.content.rows[0].cells[0], + } + ); + expect(getAbsoluteTableCells({ row: 0, col: 1 }, tableWithRowspan)).toEqual( + { + row: 0, + col: 1, + cell: tableWithRowspan.content.rows[0].cells[1], + } + ); + expect(getAbsoluteTableCells({ row: 0, col: 2 }, tableWithRowspan)).toEqual( + { + row: 0, + col: 2, + cell: tableWithRowspan.content.rows[0].cells[2], + } + ); + expect(getAbsoluteTableCells({ row: 1, col: 0 }, tableWithRowspan)).toEqual( + { + row: 1, + col: 1, + cell: tableWithRowspan.content.rows[1].cells[0], + } + ); + expect(getAbsoluteTableCells({ row: 1, col: 1 }, tableWithRowspan)).toEqual( + { + row: 1, + col: 2, + cell: tableWithRowspan.content.rows[1].cells[1], + } + ); + expect(getAbsoluteTableCells({ row: 2, col: 0 }, tableWithRowspan)).toEqual( + { + row: 2, + col: 0, + cell: tableWithRowspan.content.rows[2].cells[0], + } + ); + expect(getAbsoluteTableCells({ row: 2, col: 1 }, tableWithRowspan)).toEqual( + { + row: 2, + col: 1, + cell: tableWithRowspan.content.rows[2].cells[1], + } + ); }); it("should resolve complex rowspans and colspans", () => { expect( - getAbsoluteTableCellIndices( + getAbsoluteTableCells( { row: 0, col: 0 }, tableWithComplexRowspansAndColspans ) @@ -804,7 +812,7 @@ describe("Test getAbsoluteTableCellIndices", () => { cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[0], }); expect( - getAbsoluteTableCellIndices( + getAbsoluteTableCells( { row: 0, col: 1 }, tableWithComplexRowspansAndColspans ) @@ -814,7 +822,7 @@ describe("Test getAbsoluteTableCellIndices", () => { cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[1], }); expect( - getAbsoluteTableCellIndices( + getAbsoluteTableCells( { row: 0, col: 2 }, tableWithComplexRowspansAndColspans ) @@ -824,7 +832,7 @@ describe("Test getAbsoluteTableCellIndices", () => { cell: tableWithComplexRowspansAndColspans.content.rows[0].cells[2], }); expect( - getAbsoluteTableCellIndices( + getAbsoluteTableCells( { row: 1, col: 0 }, tableWithComplexRowspansAndColspans ) @@ -834,7 +842,7 @@ describe("Test getAbsoluteTableCellIndices", () => { cell: tableWithComplexRowspansAndColspans.content.rows[1].cells[0], }); expect( - getAbsoluteTableCellIndices( + getAbsoluteTableCells( { row: 1, col: 1 }, tableWithComplexRowspansAndColspans ) @@ -844,7 +852,7 @@ describe("Test getAbsoluteTableCellIndices", () => { cell: tableWithComplexRowspansAndColspans.content.rows[1].cells[1], }); expect( - getAbsoluteTableCellIndices( + getAbsoluteTableCells( { row: 2, col: 0 }, tableWithComplexRowspansAndColspans ) @@ -854,7 +862,7 @@ describe("Test getAbsoluteTableCellIndices", () => { cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[0], }); expect( - getAbsoluteTableCellIndices( + getAbsoluteTableCells( { row: 2, col: 1 }, tableWithComplexRowspansAndColspans ) @@ -864,7 +872,7 @@ describe("Test getAbsoluteTableCellIndices", () => { cell: tableWithComplexRowspansAndColspans.content.rows[2].cells[1], }); expect( - getAbsoluteTableCellIndices( + getAbsoluteTableCells( { row: 2, col: 2 }, tableWithComplexRowspansAndColspans ) @@ -878,217 +886,198 @@ describe("Test getAbsoluteTableCellIndices", () => { describe("Test getRelativeTableCellIndices", () => { it("should resolve absolute table cell indices to relative table cell indices", () => { - expect( - getRelativeTableCellIndices({ row: 0, col: 0 }, simpleTable) - ).toEqual({ row: 0, col: 0, cell: simpleTable.content.rows[0].cells[0] }); - expect( - getRelativeTableCellIndices({ row: 0, col: 1 }, simpleTable) - ).toEqual({ row: 0, col: 1, cell: simpleTable.content.rows[0].cells[1] }); - expect( - getRelativeTableCellIndices({ row: 1, col: 0 }, simpleTable) - ).toEqual({ row: 1, col: 0, cell: simpleTable.content.rows[1].cells[0] }); - expect( - getRelativeTableCellIndices({ row: 1, col: 1 }, simpleTable) - ).toEqual({ row: 1, col: 1, cell: simpleTable.content.rows[1].cells[1] }); - }); - - it("should resolve absolute table cell indices to relative table cell indices with colspan", () => { - expect( - getRelativeTableCellIndices({ row: 0, col: 0 }, tableWithColspan) - ).toEqual({ + expect(getRelativeTableCells({ row: 0, col: 0 }, simpleTable)).toEqual({ row: 0, col: 0, - cell: tableWithColspan.content.rows[0].cells[0], + cell: simpleTable.content.rows[0].cells[0], }); - expect( - getRelativeTableCellIndices({ row: 0, col: 1 }, tableWithColspan) - ).toEqual({ - row: 0, - col: 0, - cell: tableWithColspan.content.rows[0].cells[0], - }); - expect( - getRelativeTableCellIndices({ row: 0, col: 2 }, tableWithColspan) - ).toEqual({ + expect(getRelativeTableCells({ row: 0, col: 1 }, simpleTable)).toEqual({ row: 0, col: 1, - cell: tableWithColspan.content.rows[0].cells[1], + cell: simpleTable.content.rows[0].cells[1], }); - expect( - getRelativeTableCellIndices({ row: 1, col: 0 }, tableWithColspan) - ).toEqual({ + expect(getRelativeTableCells({ row: 1, col: 0 }, simpleTable)).toEqual({ row: 1, col: 0, - cell: tableWithColspan.content.rows[1].cells[0], + cell: simpleTable.content.rows[1].cells[0], }); - expect( - getRelativeTableCellIndices({ row: 1, col: 1 }, tableWithColspan) - ).toEqual({ + expect(getRelativeTableCells({ row: 1, col: 1 }, simpleTable)).toEqual({ row: 1, col: 1, - cell: tableWithColspan.content.rows[1].cells[1], - }); - expect( - getRelativeTableCellIndices({ row: 1, col: 2 }, tableWithColspan) - ).toEqual({ - row: 1, - col: 2, - cell: tableWithColspan.content.rows[1].cells[2], + cell: simpleTable.content.rows[1].cells[1], }); }); + it("should resolve absolute table cell indices to relative table cell indices with colspan", () => { + expect(getRelativeTableCells({ row: 0, col: 0 }, tableWithColspan)).toEqual( + { + row: 0, + col: 0, + cell: tableWithColspan.content.rows[0].cells[0], + } + ); + expect(getRelativeTableCells({ row: 0, col: 1 }, tableWithColspan)).toEqual( + { + row: 0, + col: 0, + cell: tableWithColspan.content.rows[0].cells[0], + } + ); + expect(getRelativeTableCells({ row: 0, col: 2 }, tableWithColspan)).toEqual( + { + row: 0, + col: 1, + cell: tableWithColspan.content.rows[0].cells[1], + } + ); + expect(getRelativeTableCells({ row: 1, col: 0 }, tableWithColspan)).toEqual( + { + row: 1, + col: 0, + cell: tableWithColspan.content.rows[1].cells[0], + } + ); + expect(getRelativeTableCells({ row: 1, col: 1 }, tableWithColspan)).toEqual( + { + row: 1, + col: 1, + cell: tableWithColspan.content.rows[1].cells[1], + } + ); + expect(getRelativeTableCells({ row: 1, col: 2 }, tableWithColspan)).toEqual( + { + row: 1, + col: 2, + cell: tableWithColspan.content.rows[1].cells[2], + } + ); + }); + it("should resolve absolute table cell indices to relative table cell indices with rowspan", () => { - expect( - getRelativeTableCellIndices({ row: 0, col: 0 }, tableWithRowspan) - ).toEqual({ - row: 0, - col: 0, - cell: tableWithRowspan.content.rows[0].cells[0], - }); - expect( - getRelativeTableCellIndices({ row: 0, col: 1 }, tableWithRowspan) - ).toEqual({ - row: 0, - col: 1, - cell: tableWithRowspan.content.rows[0].cells[1], - }); - expect( - getRelativeTableCellIndices({ row: 0, col: 2 }, tableWithRowspan) - ).toEqual({ - row: 0, - col: 2, - cell: tableWithRowspan.content.rows[0].cells[2], - }); - expect( - getRelativeTableCellIndices({ row: 1, col: 0 }, tableWithRowspan) - ).toEqual({ - row: 0, - col: 0, - cell: tableWithRowspan.content.rows[0].cells[0], - }); - expect( - getRelativeTableCellIndices({ row: 1, col: 1 }, tableWithRowspan) - ).toEqual({ - row: 1, - col: 0, - cell: tableWithRowspan.content.rows[1].cells[0], - }); - expect( - getRelativeTableCellIndices({ row: 1, col: 2 }, tableWithRowspan) - ).toEqual({ - row: 1, - col: 1, - cell: tableWithRowspan.content.rows[1].cells[1], - }); - expect( - getRelativeTableCellIndices({ row: 2, col: 0 }, tableWithRowspan) - ).toEqual({ - row: 2, - col: 0, - cell: tableWithRowspan.content.rows[2].cells[0], - }); - expect( - getRelativeTableCellIndices({ row: 2, col: 1 }, tableWithRowspan) - ).toEqual({ - row: 2, - col: 1, - cell: tableWithRowspan.content.rows[2].cells[1], - }); - expect( - getRelativeTableCellIndices({ row: 2, col: 2 }, tableWithRowspan) - ).toEqual({ - row: 2, - col: 2, - cell: tableWithRowspan.content.rows[2].cells[2], - }); + expect(getRelativeTableCells({ row: 0, col: 0 }, tableWithRowspan)).toEqual( + { + row: 0, + col: 0, + cell: tableWithRowspan.content.rows[0].cells[0], + } + ); + expect(getRelativeTableCells({ row: 0, col: 1 }, tableWithRowspan)).toEqual( + { + row: 0, + col: 1, + cell: tableWithRowspan.content.rows[0].cells[1], + } + ); + expect(getRelativeTableCells({ row: 0, col: 2 }, tableWithRowspan)).toEqual( + { + row: 0, + col: 2, + cell: tableWithRowspan.content.rows[0].cells[2], + } + ); + expect(getRelativeTableCells({ row: 1, col: 0 }, tableWithRowspan)).toEqual( + { + row: 0, + col: 0, + cell: tableWithRowspan.content.rows[0].cells[0], + } + ); + expect(getRelativeTableCells({ row: 1, col: 1 }, tableWithRowspan)).toEqual( + { + row: 1, + col: 0, + cell: tableWithRowspan.content.rows[1].cells[0], + } + ); + expect(getRelativeTableCells({ row: 1, col: 2 }, tableWithRowspan)).toEqual( + { + row: 1, + col: 1, + cell: tableWithRowspan.content.rows[1].cells[1], + } + ); + expect(getRelativeTableCells({ row: 2, col: 0 }, tableWithRowspan)).toEqual( + { + row: 2, + col: 0, + cell: tableWithRowspan.content.rows[2].cells[0], + } + ); + expect(getRelativeTableCells({ row: 2, col: 1 }, tableWithRowspan)).toEqual( + { + row: 2, + col: 1, + cell: tableWithRowspan.content.rows[2].cells[1], + } + ); + expect(getRelativeTableCells({ row: 2, col: 2 }, tableWithRowspan)).toEqual( + { + row: 2, + col: 2, + cell: tableWithRowspan.content.rows[2].cells[2], + } + ); }); it("should resolve absolute table cell indices to relative table cell indices with colspan and rowspan", () => { expect( - getRelativeTableCellIndices( - { row: 0, col: 0 }, - tableWithColspanAndRowspan - ) + getRelativeTableCells({ row: 0, col: 0 }, tableWithColspanAndRowspan) ).toEqual({ row: 0, col: 0, cell: tableWithColspanAndRowspan.content.rows[0].cells[0], }); expect( - getRelativeTableCellIndices( - { row: 0, col: 1 }, - tableWithColspanAndRowspan - ) + getRelativeTableCells({ row: 0, col: 1 }, tableWithColspanAndRowspan) ).toEqual({ row: 0, col: 1, cell: tableWithColspanAndRowspan.content.rows[0].cells[1], }); expect( - getRelativeTableCellIndices( - { row: 0, col: 2 }, - tableWithColspanAndRowspan - ) + getRelativeTableCells({ row: 0, col: 2 }, tableWithColspanAndRowspan) ).toEqual({ row: 0, col: 2, cell: tableWithColspanAndRowspan.content.rows[0].cells[2], }); expect( - getRelativeTableCellIndices( - { row: 1, col: 0 }, - tableWithColspanAndRowspan - ) + getRelativeTableCells({ row: 1, col: 0 }, tableWithColspanAndRowspan) ).toEqual({ row: 0, col: 0, cell: tableWithColspanAndRowspan.content.rows[0].cells[0], }); expect( - getRelativeTableCellIndices( - { row: 1, col: 1 }, - tableWithColspanAndRowspan - ) + getRelativeTableCells({ row: 1, col: 1 }, tableWithColspanAndRowspan) ).toEqual({ row: 1, col: 0, cell: tableWithColspanAndRowspan.content.rows[1].cells[0], }); expect( - getRelativeTableCellIndices( - { row: 1, col: 2 }, - tableWithColspanAndRowspan - ) + getRelativeTableCells({ row: 1, col: 2 }, tableWithColspanAndRowspan) ).toEqual({ row: 1, col: 0, cell: tableWithColspanAndRowspan.content.rows[1].cells[0], }); expect( - getRelativeTableCellIndices( - { row: 2, col: 0 }, - tableWithColspanAndRowspan - ) + getRelativeTableCells({ row: 2, col: 0 }, tableWithColspanAndRowspan) ).toEqual({ row: 2, col: 0, cell: tableWithColspanAndRowspan.content.rows[2].cells[0], }); expect( - getRelativeTableCellIndices( - { row: 2, col: 1 }, - tableWithColspanAndRowspan - ) + getRelativeTableCells({ row: 2, col: 1 }, tableWithColspanAndRowspan) ).toEqual({ row: 2, col: 1, cell: tableWithColspanAndRowspan.content.rows[2].cells[1], }); expect( - getRelativeTableCellIndices( - { row: 2, col: 2 }, - tableWithColspanAndRowspan - ) + getRelativeTableCells({ row: 2, col: 2 }, tableWithColspanAndRowspan) ).toEqual({ row: 2, col: 2, @@ -1098,90 +1087,63 @@ describe("Test getRelativeTableCellIndices", () => { it("should resolve absolute table cell indices to relative table cell indices with colspans and rowspans", () => { expect( - getRelativeTableCellIndices( - { row: 0, col: 0 }, - tableWithColspansAndRowspans - ) + getRelativeTableCells({ row: 0, col: 0 }, tableWithColspansAndRowspans) ).toEqual({ row: 0, col: 0, cell: tableWithColspansAndRowspans.content.rows[0].cells[0], }); expect( - getRelativeTableCellIndices( - { row: 0, col: 1 }, - tableWithColspansAndRowspans - ) + getRelativeTableCells({ row: 0, col: 1 }, tableWithColspansAndRowspans) ).toEqual({ row: 0, col: 1, cell: tableWithColspansAndRowspans.content.rows[0].cells[1], }); expect( - getRelativeTableCellIndices( - { row: 0, col: 2 }, - tableWithColspansAndRowspans - ) + getRelativeTableCells({ row: 0, col: 2 }, tableWithColspansAndRowspans) ).toEqual({ row: 0, col: 1, cell: tableWithColspansAndRowspans.content.rows[0].cells[1], }); expect( - getRelativeTableCellIndices( - { row: 1, col: 0 }, - tableWithColspansAndRowspans - ) + getRelativeTableCells({ row: 1, col: 0 }, tableWithColspansAndRowspans) ).toEqual({ row: 0, col: 0, cell: tableWithColspansAndRowspans.content.rows[0].cells[0], }); expect( - getRelativeTableCellIndices( - { row: 1, col: 1 }, - tableWithColspansAndRowspans - ) + getRelativeTableCells({ row: 1, col: 1 }, tableWithColspansAndRowspans) ).toEqual({ row: 1, col: 0, cell: tableWithColspansAndRowspans.content.rows[1].cells[0], }); expect( - getRelativeTableCellIndices( - { row: 1, col: 2 }, - tableWithColspansAndRowspans - ) + getRelativeTableCells({ row: 1, col: 2 }, tableWithColspansAndRowspans) ).toEqual({ row: 1, col: 0, cell: tableWithColspansAndRowspans.content.rows[1].cells[0], }); expect( - getRelativeTableCellIndices( - { row: 2, col: 0 }, - tableWithColspansAndRowspans - ) + getRelativeTableCells({ row: 2, col: 0 }, tableWithColspansAndRowspans) ).toEqual({ row: 2, col: 0, cell: tableWithColspansAndRowspans.content.rows[2].cells[0], }); expect( - getRelativeTableCellIndices( - { row: 2, col: 1 }, - tableWithColspansAndRowspans - ) + getRelativeTableCells({ row: 2, col: 1 }, tableWithColspansAndRowspans) ).toEqual({ row: 1, col: 0, cell: tableWithColspansAndRowspans.content.rows[1].cells[0], }); expect( - getRelativeTableCellIndices( - { row: 2, col: 2 }, - tableWithColspansAndRowspans - ) + getRelativeTableCells({ row: 2, col: 2 }, tableWithColspansAndRowspans) ).toEqual({ row: 1, col: 0, @@ -1199,8 +1161,8 @@ describe("resolveAbsoluteTableCellIndices and resolveRelativeTableCellIndices sh col++ ) { expect( - getRelativeTableCellIndices( - getAbsoluteTableCellIndices({ row, col }, simpleTable), + getRelativeTableCells( + getAbsoluteTableCells({ row, col }, simpleTable), simpleTable ) ).toEqual({ row, col, cell: simpleTable.content.rows[row].cells[col] }); @@ -1215,8 +1177,8 @@ describe("resolveAbsoluteTableCellIndices and resolveRelativeTableCellIndices sh col++ ) { expect( - getRelativeTableCellIndices( - getAbsoluteTableCellIndices({ row, col }, tableWithColspan), + getRelativeTableCells( + getAbsoluteTableCells({ row, col }, tableWithColspan), tableWithColspan ) ).toEqual({ @@ -1236,8 +1198,8 @@ describe("resolveAbsoluteTableCellIndices and resolveRelativeTableCellIndices sh col++ ) { expect( - getRelativeTableCellIndices( - getAbsoluteTableCellIndices({ row, col }, tableWithRowspan), + getRelativeTableCells( + getAbsoluteTableCells({ row, col }, tableWithRowspan), tableWithRowspan ) ).toEqual({ @@ -1261,11 +1223,8 @@ describe("resolveAbsoluteTableCellIndices and resolveRelativeTableCellIndices sh col++ ) { expect( - getRelativeTableCellIndices( - getAbsoluteTableCellIndices( - { row, col }, - tableWithColspanAndRowspan - ), + getRelativeTableCells( + getAbsoluteTableCells({ row, col }, tableWithColspanAndRowspan), tableWithColspanAndRowspan ) ).toEqual({ @@ -1290,8 +1249,8 @@ describe("resolveAbsoluteTableCellIndices and resolveRelativeTableCellIndices sh col++ ) { expect( - getRelativeTableCellIndices( - getAbsoluteTableCellIndices( + getRelativeTableCells( + getAbsoluteTableCells( { row, col }, tableWithComplexRowspansAndColspans ), diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 5095bf32af..08f7f27e02 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -283,7 +283,7 @@ export function getTableRowsFromOccupancyGrid( * * @returns The {@link AbsoluteCellIndices} and the {@link TableCell} at the absolute position. */ -export function getAbsoluteTableCellIndices( +export function getAbsoluteTableCells( /** * The relative position of the cell in the table. */ @@ -357,7 +357,7 @@ export function getDimensionsOfTable( * * @returns The {@link RelativeCellIndices} and the {@link TableCell} at the relative position. */ -export function getRelativeTableCellIndices( +export function getRelativeTableCells( /** * The {@link AbsoluteCellIndices} of the cell in the table. */ @@ -451,7 +451,7 @@ export function getCellsAtRowHandle( const cells = new Array(occupancyGrid[0].length) .fill(false) .map((_v, col) => { - return getRelativeTableCellIndices( + return getRelativeTableCells( { row: absoluteRow, col }, block, occupancyGrid @@ -528,7 +528,7 @@ export function getCellsAtColumnHandle( // Then for each row, get the cell at the absolute column index as a relative cell index const cells = new Array(occupancyGrid.length).fill(false).map((_v, row) => { - return getRelativeTableCellIndices( + return getRelativeTableCells( { row, col: absoluteCol }, block, occupancyGrid @@ -561,7 +561,7 @@ export function moveColumn( ): TableContent["rows"] { // To move cells in a column, we need to layout the whole table // and then move the cells accordingly. - const { col: absoluteSourceCol } = getAbsoluteTableCellIndices( + const { col: absoluteSourceCol } = getAbsoluteTableCells( { row: 0, col: fromColIndex, @@ -569,7 +569,7 @@ export function moveColumn( block, occupancyGrid ); - const { col: absoluteTargetCol } = getAbsoluteTableCellIndices( + const { col: absoluteTargetCol } = getAbsoluteTableCells( { row: 0, col: toColIndex, @@ -600,7 +600,7 @@ export function moveRow( ): TableContent["rows"] { // To move cells in a column, we need to layout the whole table // and then move the cells accordingly. - const { row: absoluteSourceRow } = getAbsoluteTableCellIndices( + const { row: absoluteSourceRow } = getAbsoluteTableCells( { row: fromRowIndex, col: 0, @@ -608,7 +608,7 @@ export function moveRow( block, occupancyGrid ); - const { row: absoluteTargetRow } = getAbsoluteTableCellIndices( + const { row: absoluteTargetRow } = getAbsoluteTableCells( { row: toRowIndex, col: 0, diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index b346924393..9e00af5c50 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -18,7 +18,7 @@ import { isStyledTextInlineContent, } from "../../schema/inlineContent/types.js"; import { UnreachableCaseError } from "../../util/typescript.js"; -import { getAbsoluteTableCellIndices } from "../blockManipulation/tables/tables.js"; +import { getAbsoluteTableCells } from "../blockManipulation/tables/tables.js"; /** * Convert a StyledText inline element to a @@ -201,7 +201,7 @@ export function tableContentToNodes< let content: Fragment | Node | readonly Node[] | null = null; // Colwidths are absolutely referenced to the table, so we need to resolve the relative cell index to the absolute cell index - const absoluteCellIndex = getAbsoluteTableCellIndices( + const absoluteCellIndex = getAbsoluteTableCells( { row: rowIndex, col: cellIndex, diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index f56dcfe8ad..4c391ecef1 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -14,7 +14,7 @@ import { Decoration, DecorationSet, EditorView } from "prosemirror-view"; import { addRowsOrColumns, cropEmptyRowsOrColumns, - getAbsoluteTableCellIndices, + getAbsoluteTableCells, getCellsAtColumnHandle, getCellsAtRowHandle, getDimensionsOfTable, @@ -1224,10 +1224,10 @@ export class TableHandlesProsemirrorPlugin< const { from, to } = this.getCellSelection(); // Table indices are relative to the table, so we need to resolve the absolute cell indices - const anchorAbsoluteCellIndices = getAbsoluteTableCellIndices(from, block); + const anchorAbsoluteCellIndices = getAbsoluteTableCells(from, block); // Table indices are relative to the table, so we need to resolve the absolute cell indices - const headAbsoluteCellIndices = getAbsoluteTableCellIndices(to, block); + const headAbsoluteCellIndices = getAbsoluteTableCells(to, block); // Compare the column indices to determine the merge direction if (anchorAbsoluteCellIndices.col === headAbsoluteCellIndices.col) { From 1c333d1d926fc65490461788fec66a6095dbac48 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 26 Feb 2025 06:50:20 +0100 Subject: [PATCH 48/72] refactor(tables): move the canColumnBeDraggedInto logic and clarify moveColumn implementation --- .../api/blockManipulation/tables/tables.ts | 82 ++++++++++++++++++- .../TableHandles/TableHandlesPlugin.ts | 72 +--------------- 2 files changed, 83 insertions(+), 71 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 08f7f27e02..b8fc19f3a9 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -551,7 +551,7 @@ export function getCellsAtColumnHandle( /** * This moves a column from one index to another. * - * @note This is a destructive operation, it will modify the {@link OccupancyGrid} in place. + * @note This is a destructive operation, it will modify the provided {@link OccupancyGrid} in place. */ export function moveColumn( block: BlockFromConfigNoChildren, @@ -578,6 +578,11 @@ export function moveColumn( occupancyGrid ); + /** + * Currently, this function assumes that the caller has already checked that the source and target columns are valid. + * Such as by using {@link canColumnBeDraggedInto}. In the future, we may want to have the move logic be smarter + * and handle invalid column indices in some way. + */ occupancyGrid.forEach((row) => { // Move the cell to the target column const [sourceCell] = row.splice(absoluteSourceCol, 1); @@ -617,6 +622,11 @@ export function moveRow( occupancyGrid ); + /** + * Currently, this function assumes that the caller has already checked that the source and target rows are valid. + * Such as by using {@link canRowBeDraggedInto}. In the future, we may want to have the move logic be smarter + * and handle invalid row indices in some way. + */ const [sourceRow] = occupancyGrid.splice(absoluteSourceRow, 1); occupancyGrid.splice(absoluteTargetRow, 0, sourceRow); @@ -775,3 +785,73 @@ export function addRowsOrColumns( return getTableRowsFromOccupancyGrid(occupancyGrid); } + +/** + * Checks if a row can be safely dropped at the target row index without splitting merged cells. + */ +export function canRowBeDraggedInto( + block: BlockFromConfigNoChildren, + draggingIndex: RelativeCellIndices["row"], + targetRowIndex: RelativeCellIndices["row"] +) { + // Check cells at the target row + const targetCells = getCellsAtRowHandle(block, targetRowIndex); + + // If no cells have rowspans > 1, dragging is always allowed + const hasMergedCells = targetCells.some((cell) => getRowspan(cell.cell) > 1); + if (!hasMergedCells) { + return true; + } + + let endRowIndex = targetRowIndex; + let startRowIndex = targetRowIndex; + targetCells.forEach((cell) => { + const rowspan = getRowspan(cell.cell); + endRowIndex = Math.max(endRowIndex, cell.row + rowspan - 1); + startRowIndex = Math.min(startRowIndex, cell.row); + }); + + // Check the direction of the drag + const isDraggingDown = draggingIndex < targetRowIndex; + + // Allow dragging only at the start/end of merged cells + // Otherwise, the target row was within a merged cell which we don't allow + return isDraggingDown + ? targetRowIndex === endRowIndex + : targetRowIndex === startRowIndex; +} + +/** + * Checks if a column can be safely dropped at the target column index without splitting merged cells. + */ +export function canColumnBeDraggedInto( + block: BlockFromConfigNoChildren, + draggingIndex: RelativeCellIndices["col"], + targetColumnIndex: RelativeCellIndices["col"] +) { + // Check cells at the target column + const targetCells = getCellsAtColumnHandle(block, targetColumnIndex); + + // If no cells have colspans > 1, dragging is always allowed + const hasMergedCells = targetCells.some((cell) => getColspan(cell.cell) > 1); + if (!hasMergedCells) { + return true; + } + + let endColumnIndex = targetColumnIndex; + let startColumnIndex = targetColumnIndex; + targetCells.forEach((cell) => { + const colspan = getColspan(cell.cell); + endColumnIndex = Math.max(endColumnIndex, cell.col + colspan - 1); + startColumnIndex = Math.min(startColumnIndex, cell.col); + }); + + // Check the direction of the drag + const isDraggingRight = draggingIndex < targetColumnIndex; + + // Allow dragging only at the start/end of merged cells + // Otherwise, the target column was within a merged cell which we don't allow + return isDraggingRight + ? targetColumnIndex === endColumnIndex + : targetColumnIndex === startColumnIndex; +} diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 4c391ecef1..5a9a0ba475 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -13,6 +13,8 @@ import { import { Decoration, DecorationSet, EditorView } from "prosemirror-view"; import { addRowsOrColumns, + canColumnBeDraggedInto, + canRowBeDraggedInto, cropEmptyRowsOrColumns, getAbsoluteTableCells, getCellsAtColumnHandle, @@ -144,76 +146,6 @@ function hideElements(selector: string, rootEl: Document | ShadowRoot) { } } -/** - * Checks if a row can be safely dropped at the target row index without splitting merged cells. - */ -function canRowBeDraggedInto( - block: BlockFromConfigNoChildren, - draggingIndex: RelativeCellIndices["row"], - targetRowIndex: RelativeCellIndices["row"] -) { - // Check cells at the target row - const targetCells = getCellsAtRowHandle(block, targetRowIndex); - - // If no cells have rowspans > 1, dragging is always allowed - const hasMergedCells = targetCells.some((cell) => getRowspan(cell.cell) > 1); - if (!hasMergedCells) { - return true; - } - - let endRowIndex = targetRowIndex; - let startRowIndex = targetRowIndex; - targetCells.forEach((cell) => { - const rowspan = getRowspan(cell.cell); - endRowIndex = Math.max(endRowIndex, cell.row + rowspan - 1); - startRowIndex = Math.min(startRowIndex, cell.row); - }); - - // Check the direction of the drag - const isDraggingDown = draggingIndex < targetRowIndex; - - // Allow dragging only at the start/end of merged cells - // Otherwise, the target row was within a merged cell which we don't allow - return isDraggingDown - ? targetRowIndex === endRowIndex - : targetRowIndex === startRowIndex; -} - -/** - * Checks if a column can be safely dropped at the target column index without splitting merged cells. - */ -function canColumnBeDraggedInto( - block: BlockFromConfigNoChildren, - draggingIndex: RelativeCellIndices["col"], - targetColumnIndex: RelativeCellIndices["col"] -) { - // Check cells at the target column - const targetCells = getCellsAtColumnHandle(block, targetColumnIndex); - - // If no cells have colspans > 1, dragging is always allowed - const hasMergedCells = targetCells.some((cell) => getColspan(cell.cell) > 1); - if (!hasMergedCells) { - return true; - } - - let endColumnIndex = targetColumnIndex; - let startColumnIndex = targetColumnIndex; - targetCells.forEach((cell) => { - const colspan = getColspan(cell.cell); - endColumnIndex = Math.max(endColumnIndex, cell.col + colspan - 1); - startColumnIndex = Math.min(startColumnIndex, cell.col); - }); - - // Check the direction of the drag - const isDraggingRight = draggingIndex < targetColumnIndex; - - // Allow dragging only at the start/end of merged cells - // Otherwise, the target column was within a merged cell which we don't allow - return isDraggingRight - ? targetColumnIndex === endColumnIndex - : targetColumnIndex === startColumnIndex; -} - export class TableHandlesView< I extends InlineContentSchema, S extends StyleSchema From f0695b53b4dcab71923fffa4d73422b9810d8e11 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 26 Feb 2025 06:54:33 +0100 Subject: [PATCH 49/72] refactor: clearer bounds checking --- .../api/blockManipulation/tables/tables.ts | 140 +++++++++--------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index b8fc19f3a9..0dd982b138 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -426,49 +426,47 @@ export function getCellsAtRowHandle( block: BlockFromConfigNoChildren, relativeRowIndex: RelativeCellIndices["row"] ) { - try { - const occupancyGrid = getTableCellOccupancyGrid(block); + const occupancyGrid = getTableCellOccupancyGrid(block); - // First need to resolve the relative row index to an absolute row index - let absoluteRow = 0; + if (relativeRowIndex < 0 || relativeRowIndex >= occupancyGrid.length) { + return []; + } - // Jump through the occupied cells ${relativeCellIndices.row} times to find the absolute row position - for (let i = 0; i < relativeRowIndex; i++) { - const cell = occupancyGrid[absoluteRow]?.[0]; + // First need to resolve the relative row index to an absolute row index + let absoluteRow = 0; - if (!cell) { - // As a sanity check, if the cell is not occupied, we should throw an error - throw new Error( - `Unable to resolve relative table cell indices for table, cell at ${absoluteRow},0 is not occupied` - ); - } + // Jump through the occupied cells ${relativeCellIndices.row} times to find the absolute row position + for (let i = 0; i < relativeRowIndex; i++) { + const cell = occupancyGrid[absoluteRow]?.[0]; - // Skip the cells that the rowspan takes up - absoluteRow += cell.rowspan; + if (!cell) { + // As a sanity check, if the cell is not occupied, we should throw an error + throw new Error( + `Unable to resolve relative table cell indices for table, cell at ${absoluteRow},0 is not occupied` + ); } - // Then for each column, get the cell at the absolute row index as a relative cell index - const cells = new Array(occupancyGrid[0].length) - .fill(false) - .map((_v, col) => { - return getRelativeTableCells( - { row: absoluteRow, col }, - block, - occupancyGrid - ); - }); - - // Filter out duplicates based on row and col properties - return cells.filter((cell, index) => { - return ( - cells.findIndex((c) => c.row === cell.row && c.col === cell.col) === - index + // Skip the cells that the rowspan takes up + absoluteRow += cell.rowspan; + } + + // Then for each column, get the cell at the absolute row index as a relative cell index + const cells = new Array(occupancyGrid[0].length) + .fill(false) + .map((_v, col) => { + return getRelativeTableCells( + { row: absoluteRow, col }, + block, + occupancyGrid ); }); - } catch (e) { - // In case of an invalid index, return an empty array - return []; - } + + // Filter out duplicates based on row and col properties + return cells.filter((cell, index) => { + return ( + cells.findIndex((c) => c.row === cell.row && c.col === cell.col) === index + ); + }); } /** @@ -506,46 +504,48 @@ export function getCellsAtColumnHandle( block: BlockFromConfigNoChildren, relativeColumnIndex: RelativeCellIndices["col"] ) { - try { - const occupancyGrid = getTableCellOccupancyGrid(block); - // First need to resolve the relative column index to an absolute column index - let absoluteCol = 0; - - // Now that we've already resolved the absolute row position, we can jump through the occupied cells ${relativeCellIndices.col} times to find the absolute column position - for (let i = 0; i < relativeColumnIndex; i++) { - const cell = occupancyGrid[0]?.[absoluteCol]; - - if (!cell) { - // As a sanity check, if the cell is not occupied, we should throw an error - throw new Error( - `Unable to resolve relative table cell indices for table, cell at 0,${absoluteCol} is not occupied` - ); - } + const occupancyGrid = getTableCellOccupancyGrid(block); - // Skip the cells that the colspan takes up - absoluteCol += cell.colspan; - } + if ( + relativeColumnIndex < 0 || + relativeColumnIndex >= occupancyGrid[0].length + ) { + return []; + } - // Then for each row, get the cell at the absolute column index as a relative cell index - const cells = new Array(occupancyGrid.length).fill(false).map((_v, row) => { - return getRelativeTableCells( - { row, col: absoluteCol }, - block, - occupancyGrid - ); - }); + // First need to resolve the relative column index to an absolute column index + let absoluteCol = 0; + + // Now that we've already resolved the absolute row position, we can jump through the occupied cells ${relativeCellIndices.col} times to find the absolute column position + for (let i = 0; i < relativeColumnIndex; i++) { + const cell = occupancyGrid[0]?.[absoluteCol]; - // Filter out duplicates based on row and col properties - return cells.filter((cell, index) => { - return ( - cells.findIndex((c) => c.row === cell.row && c.col === cell.col) === - index + if (!cell) { + // As a sanity check, if the cell is not occupied, we should throw an error + throw new Error( + `Unable to resolve relative table cell indices for table, cell at 0,${absoluteCol} is not occupied` ); - }); - } catch (e) { - // In case of an invalid index, return an empty array - return []; + } + + // Skip the cells that the colspan takes up + absoluteCol += cell.colspan; } + + // Then for each row, get the cell at the absolute column index as a relative cell index + const cells = new Array(occupancyGrid.length).fill(false).map((_v, row) => { + return getRelativeTableCells( + { row, col: absoluteCol }, + block, + occupancyGrid + ); + }); + + // Filter out duplicates based on row and col properties + return cells.filter((cell, index) => { + return ( + cells.findIndex((c) => c.row === cell.row && c.col === cell.col) === index + ); + }); } /** From 244410a7598d5a8fbcb82b390337c5a74e5df5a9 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 26 Feb 2025 07:30:08 +0100 Subject: [PATCH 50/72] fix: unused import --- packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 5a9a0ba475..2d69f1df3a 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -35,8 +35,6 @@ import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { BlockFromConfigNoChildren, BlockSchemaWithBlock, - getColspan, - getRowspan, InlineContentSchema, StyleSchema, } from "../../schema/index.js"; From 00a3bdb947e702a6cbfca915e2d83373af59ed4e Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 26 Feb 2025 07:36:17 +0100 Subject: [PATCH 51/72] refactor: do not expose absolute positioning --- .../api/blockManipulation/tables/tables.ts | 20 +++++++++++++++++++ .../TableHandles/TableHandlesPlugin.ts | 10 ++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 0dd982b138..2b9c80fdf0 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -855,3 +855,23 @@ export function canColumnBeDraggedInto( ? targetColumnIndex === endColumnIndex : targetColumnIndex === startColumnIndex; } + +/** + * Checks if two cells are in the same column. + * + * @returns True if the cells are in the same column, false otherwise. + */ +export function areInSameColumn( + from: RelativeCellIndices, + to: RelativeCellIndices, + block: BlockFromConfigNoChildren +) { + // Table indices are relative to the table, so we need to resolve the absolute cell indices + const anchorAbsoluteCellIndices = getAbsoluteTableCells(from, block); + + // Table indices are relative to the table, so we need to resolve the absolute cell indices + const headAbsoluteCellIndices = getAbsoluteTableCells(to, block); + + // Compare the column indices to determine the merge direction + return anchorAbsoluteCellIndices.col === headAbsoluteCellIndices.col; +} diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 2d69f1df3a..11944fa146 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -13,10 +13,10 @@ import { import { Decoration, DecorationSet, EditorView } from "prosemirror-view"; import { addRowsOrColumns, + areInSameColumn, canColumnBeDraggedInto, canRowBeDraggedInto, cropEmptyRowsOrColumns, - getAbsoluteTableCells, getCellsAtColumnHandle, getCellsAtRowHandle, getDimensionsOfTable, @@ -1153,14 +1153,8 @@ export class TableHandlesProsemirrorPlugin< } const { from, to } = this.getCellSelection(); - // Table indices are relative to the table, so we need to resolve the absolute cell indices - const anchorAbsoluteCellIndices = getAbsoluteTableCells(from, block); - // Table indices are relative to the table, so we need to resolve the absolute cell indices - const headAbsoluteCellIndices = getAbsoluteTableCells(to, block); - - // Compare the column indices to determine the merge direction - if (anchorAbsoluteCellIndices.col === headAbsoluteCellIndices.col) { + if (areInSameColumn(from, to, block)) { return "vertical"; } From 4ab7a72f5d16f7c0de2c46ca5aeac65d1ee67ba9 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 26 Feb 2025 08:06:25 +0100 Subject: [PATCH 52/72] fix: return an empty array if out of bounds --- .../api/blockManipulation/tables/tables.ts | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 2b9c80fdf0..5e3a181bfe 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -161,7 +161,7 @@ export type AbsoluteCellIndices = { * * Since it allows us to resolve cell indices both {@link RelativeCellIndices} and {@link AbsoluteCellIndices}, it is the core data structure for table operations. */ -type OccupancyGrid = (AbsoluteCellIndices & { +type OccupancyGrid = (RelativeCellIndices & { /** * The rowspan of the cell. */ @@ -370,18 +370,18 @@ export function getRelativeTableCells( * The occupancy grid of the table. */ occupancyGrid: OccupancyGrid = getTableCellOccupancyGrid(block) -): RelativeCellIndices & { - cell: TableContent["rows"][number]["cells"][number]; -} { +): + | (RelativeCellIndices & { + cell: TableContent["rows"][number]["cells"][number]; + }) + | undefined { const occupancyCell = occupancyGrid[absoluteCellIndices.row]?.[absoluteCellIndices.col]; // Double check that the cell can be accessed if (!occupancyCell) { // The cell is not occupied, so it is invalid - throw new Error( - `Unable to resolve absolute table cell indices for table, cell at ${absoluteCellIndices.row},${absoluteCellIndices.col} is not occupied` - ); + return undefined; } return { @@ -440,10 +440,7 @@ export function getCellsAtRowHandle( const cell = occupancyGrid[absoluteRow]?.[0]; if (!cell) { - // As a sanity check, if the cell is not occupied, we should throw an error - throw new Error( - `Unable to resolve relative table cell indices for table, cell at ${absoluteRow},0 is not occupied` - ); + return []; } // Skip the cells that the rowspan takes up @@ -459,7 +456,8 @@ export function getCellsAtRowHandle( block, occupancyGrid ); - }); + }) + .filter((a) => a !== undefined); // Filter out duplicates based on row and col properties return cells.filter((cell, index) => { @@ -521,10 +519,7 @@ export function getCellsAtColumnHandle( const cell = occupancyGrid[0]?.[absoluteCol]; if (!cell) { - // As a sanity check, if the cell is not occupied, we should throw an error - throw new Error( - `Unable to resolve relative table cell indices for table, cell at 0,${absoluteCol} is not occupied` - ); + return []; } // Skip the cells that the colspan takes up @@ -532,13 +527,16 @@ export function getCellsAtColumnHandle( } // Then for each row, get the cell at the absolute column index as a relative cell index - const cells = new Array(occupancyGrid.length).fill(false).map((_v, row) => { - return getRelativeTableCells( - { row, col: absoluteCol }, - block, - occupancyGrid - ); - }); + const cells = new Array(occupancyGrid.length) + .fill(false) + .map((_v, row) => { + return getRelativeTableCells( + { row, col: absoluteCol }, + block, + occupancyGrid + ); + }) + .filter((a) => a !== undefined); // Filter out duplicates based on row and col properties return cells.filter((cell, index) => { From ce3675daec7848c1190cd5e9d366c902592c0734 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 26 Feb 2025 12:49:33 +0100 Subject: [PATCH 53/72] fix(tables): fix a bug with block content being out of date when applying heading --- .../commands/updateBlock/updateBlock.ts | 2 +- packages/core/src/schema/blocks/types.ts | 4 ++-- .../DragHandleMenu/DefaultItems/TableHeadersItem.tsx | 12 ++++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 9581e16a36..a1cd756a3c 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -222,7 +222,7 @@ function updateChildren< editor: BlockNoteEditor, blockInfo: BlockInfo ) { - if (block.children !== undefined) { + if (block.children !== undefined && block.children.length > 0) { const childNodes = block.children.map((child) => { return blockToNode(child, state.schema, editor.schema.styleSchema); }); diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index d8fbae86f6..4141aed25b 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -336,6 +336,7 @@ export function mapTableCell< : isPartialTableCell(content) ? { type: "tableCell", + content: ([] as InlineContent[]).concat(content.content as any), props: { backgroundColor: content.props?.backgroundColor ?? "default", textColor: content.props?.textColor ?? "default", @@ -343,10 +344,10 @@ export function mapTableCell< colspan: content.props?.colspan ?? 1, rowspan: content.props?.rowspan ?? 1, }, - content: ([] as InlineContent[]).concat(content.content as any), } : { type: "tableCell", + content: ([] as InlineContent[]).concat(content as any), props: { backgroundColor: "default", textColor: "default", @@ -354,7 +355,6 @@ export function mapTableCell< colspan: 1, rowspan: 1, }, - content: ([] as InlineContent[]).concat(content as any), }; } diff --git a/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx b/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx index ecc9e8caba..53874aae4d 100644 --- a/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx +++ b/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx @@ -43,11 +43,15 @@ export const TableRowHeaderItem = < className={"bn-menu-item"} checked={isHeaderRow} onClick={() => { - editor.updateBlock(props.block, { - type: "table", + // The block may have been modified and out of date, so we get the latest block + const block = editor.getBlock(props.block.id); + if (!block) { + return; + } + editor.updateBlock(block, { + ...block, content: { - ...props.block.content, - type: "tableContent", + ...block.content, headerRows: isHeaderRow ? undefined : 1, }, }); From c37917e871a97b46326aa5208d2f1d9555358008 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 26 Feb 2025 12:59:13 +0100 Subject: [PATCH 54/72] chore: fix build --- .../core/src/api/blockManipulation/tables/tables.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 5e3a181bfe..f478448fd2 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -457,7 +457,10 @@ export function getCellsAtRowHandle( occupancyGrid ); }) - .filter((a) => a !== undefined); + .filter( + (a): a is RelativeCellIndices & { cell: TableCell } => + a !== undefined + ); // Filter out duplicates based on row and col properties return cells.filter((cell, index) => { @@ -536,7 +539,10 @@ export function getCellsAtColumnHandle( occupancyGrid ); }) - .filter((a) => a !== undefined); + .filter( + (a): a is RelativeCellIndices & { cell: TableCell } => + a !== undefined + ); // Filter out duplicates based on row and col properties return cells.filter((cell, index) => { From 55ce7df07fe498dd4dddb6ccabfa010ee98e3a1b Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 26 Feb 2025 14:34:58 +0100 Subject: [PATCH 55/72] feat: implement table cell handle button --- packages/ariakit/src/components.ts | 2 + packages/ariakit/src/menu/Button.tsx | 44 ++++++++++++++ packages/ariakit/src/style.css | 7 ++- packages/mantine/src/components.tsx | 2 + packages/mantine/src/menu/Button.tsx | 60 +++++++++++++++++++ packages/mantine/src/style.css | 10 +++- .../TableHandles/TableCellHandle.tsx | 9 ++- .../hooks/useTableHandlesPositioning.ts | 8 ++- .../react/src/editor/ComponentsContext.tsx | 14 +++++ packages/shadcn/src/components.ts | 2 + packages/shadcn/src/menu/Button.tsx | 45 ++++++++++++++ packages/shadcn/src/style.css | 5 ++ 12 files changed, 198 insertions(+), 10 deletions(-) create mode 100644 packages/ariakit/src/menu/Button.tsx create mode 100644 packages/mantine/src/menu/Button.tsx create mode 100644 packages/shadcn/src/menu/Button.tsx diff --git a/packages/ariakit/src/components.ts b/packages/ariakit/src/components.ts index e8cb91f1c3..f40a63ac8b 100644 --- a/packages/ariakit/src/components.ts +++ b/packages/ariakit/src/components.ts @@ -10,6 +10,7 @@ import { MenuLabel, MenuTrigger, } from "./menu/Menu.js"; +import { MenuButton } from "./menu/Button.js"; import { Panel } from "./panel/Panel.js"; import { PanelButton } from "./panel/PanelButton.js"; import { PanelFileInput } from "./panel/PanelFileInput.js"; @@ -87,6 +88,7 @@ export const components: Components = { Divider: MenuDivider, Label: MenuLabel, Item: MenuItem, + Button: MenuButton, }, Popover: { Root: Popover, diff --git a/packages/ariakit/src/menu/Button.tsx b/packages/ariakit/src/menu/Button.tsx new file mode 100644 index 0000000000..04ad11500a --- /dev/null +++ b/packages/ariakit/src/menu/Button.tsx @@ -0,0 +1,44 @@ +import { Button as AriakitButton } from "@ariakit/react"; + +import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; +import { ComponentProps } from "@blocknote/react"; +import { forwardRef } from "react"; + +export const MenuButton = forwardRef< + HTMLButtonElement, + ComponentProps["Generic"]["Menu"]["Button"] +>((props, ref) => { + const { + className, + children, + icon, + onClick, + label, + onDragEnd, + onDragStart, + draggable, + ...rest + } = props; + + // false, because rest props can be added by ariakit when button is used as a trigger + // assertEmpty in this case is only used at typescript level, not runtime level + assertEmpty(rest, false); + + return ( + + {icon} + {children} + + ); +}); diff --git a/packages/ariakit/src/style.css b/packages/ariakit/src/style.css index eb05443137..0858857862 100644 --- a/packages/ariakit/src/style.css +++ b/packages/ariakit/src/style.css @@ -157,15 +157,18 @@ box-shadow: none; } -.bn-ariakit .bn-table-handle { +.bn-ariakit .bn-table-handle, +.bn-ariakit .bn-table-cell-handle { height: fit-content; padding: 0; width: fit-content; + cursor: pointer; } .bn-ariakit .bn-side-menu, .bn-ariakit .bn-table-handle, -.bn-ariakit .bn-extend-button { +.bn-ariakit .bn-extend-button, +.bn-ariakit .bn-table-cell-handle { color: gray; } diff --git a/packages/mantine/src/components.tsx b/packages/mantine/src/components.tsx index 4fb3279d86..31806e0431 100644 --- a/packages/mantine/src/components.tsx +++ b/packages/mantine/src/components.tsx @@ -9,6 +9,7 @@ import { MenuLabel, MenuTrigger, } from "./menu/Menu.js"; +import { Button } from "./menu/Button.js"; import { Panel } from "./panel/Panel.js"; import { PanelButton } from "./panel/PanelButton.js"; import { PanelFileInput } from "./panel/PanelFileInput.js"; @@ -87,6 +88,7 @@ export const components: Components = { Divider: MenuDivider, Label: MenuLabel, Item: MenuItem, + Button: Button, }, Popover: { Root: Popover, diff --git a/packages/mantine/src/menu/Button.tsx b/packages/mantine/src/menu/Button.tsx new file mode 100644 index 0000000000..9c431557b2 --- /dev/null +++ b/packages/mantine/src/menu/Button.tsx @@ -0,0 +1,60 @@ +import { + ActionIcon as MantineActionIcon, + Button as MantineButton, +} from "@mantine/core"; + +import { assertEmpty } from "@blocknote/core"; +import { ComponentProps } from "@blocknote/react"; +import { forwardRef } from "react"; + +export const Button = forwardRef< + HTMLButtonElement, + ComponentProps["Generic"]["Menu"]["Button"] +>((props, ref) => { + const { + className, + children, + icon, + onClick, + onDragEnd, + onDragStart, + draggable, + label, + ...rest + } = props; + + // false, because rest props can be added by mantine when button is used as a trigger + // assertEmpty in this case is only used at typescript level, not runtime level + assertEmpty(rest, false); + + if (icon) { + return ( + + {icon} + + ); + } + + return ( + + {children} + + ); +}); diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css index 3fdb22a7db..043546d669 100644 --- a/packages/mantine/src/style.css +++ b/packages/mantine/src/style.css @@ -495,7 +495,8 @@ /* Table Handle styling */ .bn-mantine .bn-table-handle, -.bn-mantine .bn-extend-button { +.bn-mantine .bn-extend-button, +.bn-mantine .bn-table-cell-handle { align-items: center; background-color: var(--bn-colors-menu-background); border: var(--bn-border); @@ -510,6 +511,10 @@ padding: 0; } +.bn-mantine .bn-table-cell-handle { + padding: 0 4px; +} + .bn-mantine .bn-table-handle svg { margin-inline: -4px; } @@ -521,7 +526,8 @@ .bn-mantine .bn-table-handle:hover, .bn-mantine .bn-table-handle-dragging, .bn-mantine .bn-extend-button:hover, -.bn-mantine .bn-extend-button-editing { +.bn-mantine .bn-extend-button-editing, +.bn-mantine .bn-table-cell-handle:hover { background-color: var(--bn-colors-hovered-background); } diff --git a/packages/react/src/components/TableHandles/TableCellHandle.tsx b/packages/react/src/components/TableHandles/TableCellHandle.tsx index 62ff99b84c..279b39dee5 100644 --- a/packages/react/src/components/TableHandles/TableCellHandle.tsx +++ b/packages/react/src/components/TableHandles/TableCellHandle.tsx @@ -13,7 +13,7 @@ import { TableCellHandleProps } from "./TableCellHandleProps.js"; import { TableCellHandleMenu } from "./TableCellHandleMenu/TableCellHandleMenu.js"; /** - * By default, the TableHandle component will render with the default icon. + * By default, the TableCellHandle component will render with the default icon. * However, you can override the icon to render by passing children. */ export const TableCellHandle = < @@ -38,12 +38,11 @@ export const TableCellHandle = < }} position={"right"}> - {/* TODO we should probably make a generic button (wait for comments PR to land) */} -
+ {props.children || ( - + )} -
+
{/* the menu can extend outside of the table, so we use a portal to prevent clipping */} {createPortal( diff --git a/packages/react/src/components/TableHandles/hooks/useTableHandlesPositioning.ts b/packages/react/src/components/TableHandles/hooks/useTableHandlesPositioning.ts index cb66856973..c45b4281a4 100644 --- a/packages/react/src/components/TableHandles/hooks/useTableHandlesPositioning.ts +++ b/packages/react/src/components/TableHandles/hooks/useTableHandlesPositioning.ts @@ -79,7 +79,13 @@ function useTableHandlePosition( ? "top" : "bottom-end", middleware: [ - offset(orientation === "row" ? -10 : orientation === "col" ? -12 : 0), + offset( + orientation === "row" + ? -10 + : orientation === "col" + ? -12 + : { mainAxis: 1, crossAxis: -1 } + ), ], }); diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index ba594f6acc..37f4e21e07 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -31,6 +31,19 @@ type ToolbarButtonType = { | { children: ReactNode; label?: string } | { children?: undefined; label: string } ); + +type ButtonType = { + className?: string; + onClick?: (e: MouseEvent) => void; + icon?: ReactNode; + onDragStart?: (e: React.DragEvent) => void; + onDragEnd?: (e: React.DragEvent) => void; + draggable?: boolean; +} & ( + | { children: ReactNode; label?: string } + | { children?: undefined; label: string } +); + export type ComponentProps = { FormattingToolbar: { Root: ToolbarRootType; @@ -230,6 +243,7 @@ export type ComponentProps = { children?: ReactNode; sub?: boolean; }; + Button: ButtonType; }; Popover: { Root: { diff --git a/packages/shadcn/src/components.ts b/packages/shadcn/src/components.ts index 334cce4463..54947cec8e 100644 --- a/packages/shadcn/src/components.ts +++ b/packages/shadcn/src/components.ts @@ -10,6 +10,7 @@ import { MenuLabel, MenuTrigger, } from "./menu/Menu.js"; +import { MenuButton } from "./menu/Button.js"; import { Panel } from "./panel/Panel.js"; import { PanelTab } from "./panel/PanelTab.js"; import { PanelTextInput } from "./panel/PanelTextInput.js"; @@ -87,6 +88,7 @@ export const components: Components = { Divider: MenuDivider, Label: MenuLabel, Item: MenuItem, + Button: MenuButton, }, Popover: { Root: Popover, diff --git a/packages/shadcn/src/menu/Button.tsx b/packages/shadcn/src/menu/Button.tsx new file mode 100644 index 0000000000..1ac021e553 --- /dev/null +++ b/packages/shadcn/src/menu/Button.tsx @@ -0,0 +1,45 @@ +import { assertEmpty } from "@blocknote/core"; +import { ComponentProps } from "@blocknote/react"; +import { forwardRef } from "react"; + +import { cn } from "../lib/utils.js"; +import { useShadCNComponentsContext } from "../ShadCNComponentsContext.js"; + +export const MenuButton = forwardRef< + HTMLButtonElement, + ComponentProps["Generic"]["Menu"]["Button"] +>((props, ref) => { + const { + className, + children, + icon, + onClick, + onDragEnd, + onDragStart, + draggable, + label, + ...rest + } = props; + + // false, because rest props can be added by ariakit when button is used as a trigger + // assertEmpty in this case is only used at typescript level, not runtime level + assertEmpty(rest, false); + + const ShadCNComponents = useShadCNComponentsContext()!; + + return ( + + {icon} + {children} + + ); +}); diff --git a/packages/shadcn/src/style.css b/packages/shadcn/src/style.css index 468753f44b..9e645a236f 100644 --- a/packages/shadcn/src/style.css +++ b/packages/shadcn/src/style.css @@ -173,3 +173,8 @@ overflow-x: auto; max-width: 100vw; } + +.bn-shadcn .bn-table-cell-handle { + padding: 0 4px; + height: 12px; +} From 4420d0966ccc75e3aac138261fba9d6f5e489f1b Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 26 Feb 2025 14:37:37 +0100 Subject: [PATCH 56/72] fix: move button cursor style --- packages/ariakit/src/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ariakit/src/style.css b/packages/ariakit/src/style.css index 0858857862..41347e50ae 100644 --- a/packages/ariakit/src/style.css +++ b/packages/ariakit/src/style.css @@ -45,6 +45,7 @@ .bn-ak-button { outline-style: none; + cursor: pointer; } .bn-ak-menu-item[aria-selected="true"], @@ -162,7 +163,6 @@ height: fit-content; padding: 0; width: fit-content; - cursor: pointer; } .bn-ariakit .bn-side-menu, From 95bd4b35650bc5677cb227e402014a7a079ac84e Mon Sep 17 00:00:00 2001 From: Nick Perez Date: Wed, 26 Feb 2025 15:36:12 +0100 Subject: [PATCH 57/72] fix(table): table cell merging (#1446) * fix: table cell merging * fix: better merging of styled text in table cells --- .../src/api/nodeConversions/nodeToBlock.ts | 36 ++++++++++++++++--- .../TableBlockContent/TableBlockContent.ts | 4 +-- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 3d90168824..5d25d1ea58 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -64,14 +64,40 @@ export function contentNodeToTableContent< } // Mark the cell as a header if it is a tableHeader node. headerMatrix[rowIndex][cellIndex] = cellNode.type.name === "tableHeader"; + // Convert cell content to inline content and merge adjacent styled text nodes + const content = cellNode.content.content + .map((child) => + contentNodeToInlineContent(child, inlineContentSchema, styleSchema) + ) + // The reason that we merge this content is that we allow table cells to contain multiple tableParagraph nodes + // So that we can leverage prosemirror-tables native merging + // If the schema only allowed a single tableParagraph node, then the merging would not work and cause prosemirror to fit the content into a new cell + .reduce((acc, contentPartial) => { + if (!acc.length) { + return contentPartial; + } + + const last = acc[acc.length - 1]; + const first = contentPartial[0]; + + // Only merge if the last and first content are both styled text nodes and have the same styles + if ( + isStyledTextInlineContent(last) && + isStyledTextInlineContent(first) && + JSON.stringify(last.styles) === JSON.stringify(first.styles) + ) { + // Join them together if they have the same styles + last.text += "\n" + first.text; + acc.push(...contentPartial.slice(1)); + return acc; + } + acc.push(...contentPartial); + return acc; + }, [] as InlineContent[]); return { type: "tableCell", - content: contentNodeToInlineContent( - cellNode.firstChild!, - inlineContentSchema, - styleSchema - ), + content, props: { colspan: cellNode.attrs.colspan, rowspan: cellNode.attrs.rowspan, diff --git a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts index 309b0b5068..be59382502 100644 --- a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts +++ b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts @@ -159,10 +159,10 @@ export const Table = createBlockSpecFromStronglyTypedTiptapNode( TableExtension, TableParagraph, TableHeader.extend({ - content: "tableContent", + content: "tableContent*", }), TableCell.extend({ - content: "tableContent", + content: "tableContent*", }), TableRow, ] From bc5153a29e49755917ab379f1a0f569640140017 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 26 Feb 2025 17:02:23 +0100 Subject: [PATCH 58/72] fix: handle edge case of colspan or rowspan on an empty row --- .../blockManipulation/tables/tables.test.ts | 369 ++++++++++++++++-- .../api/blockManipulation/tables/tables.ts | 46 ++- 2 files changed, 372 insertions(+), 43 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.test.ts b/packages/core/src/api/blockManipulation/tables/tables.test.ts index a1946bcf36..91b8a45dd0 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.test.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.test.ts @@ -1604,6 +1604,17 @@ describe("Test moveRow", () => { }); describe("Test cropEmptyRowsOrColumns", () => { + const emptyCell = { + type: "tableCell" as const, + props: { + backgroundColor: "red", + textColor: "blue", + textAlignment: "left" as const, + colspan: 1, + rowspan: 1, + }, + content: [{ type: "text" as const, text: "", styles: {} }], + }; it("should crop the empty rows of the table", () => { expect( cropEmptyRowsOrColumns( @@ -1612,50 +1623,364 @@ describe("Test cropEmptyRowsOrColumns", () => { content: { ...simpleTable.content, rows: simpleTable.content.rows.concat([ + { + cells: [emptyCell, emptyCell], + }, + ]), + }, + }, + "rows" + ) + ).toEqual([ + { + cells: [ + simpleTable.content.rows[0].cells[0], + simpleTable.content.rows[0].cells[1], + ], + }, + { + cells: [ + simpleTable.content.rows[1].cells[0], + simpleTable.content.rows[1].cells[1], + ], + }, + ]); + }); + + it("should crop the empty rows of a table with colspan", () => { + expect( + cropEmptyRowsOrColumns( + { + ...tableWithColspan, + content: { + ...tableWithColspan.content, + rows: tableWithColspan.content.rows.concat([ + { + cells: [emptyCell, emptyCell, emptyCell], + }, + ]), + }, + }, + "rows" + ) + ).toEqual(tableWithColspan.content.rows); + }); + + it("should crop the empty columns of a table with colspan", () => { + expect( + cropEmptyRowsOrColumns( + { + ...tableWithColspan, + content: { + ...tableWithColspan.content, + rows: [ + { + cells: [...tableWithColspan.content.rows[0].cells, emptyCell], + }, + { + cells: [...tableWithColspan.content.rows[1].cells, emptyCell], + }, + ], + }, + }, + "columns" + ) + ).toEqual(tableWithColspan.content.rows); + }); + + it("should crop the empty rows of a table with rowspan", () => { + expect( + cropEmptyRowsOrColumns( + { + ...tableWithRowspan, + content: { + ...tableWithRowspan.content, + rows: tableWithRowspan.content.rows.concat([ + { + cells: [emptyCell, emptyCell, emptyCell], + }, + ]), + }, + }, + "rows" + ) + ).toEqual(tableWithRowspan.content.rows); + }); + + it("should crop the empty columns of a table with rowspan", () => { + expect( + cropEmptyRowsOrColumns( + { + ...tableWithRowspan, + content: { + ...tableWithRowspan.content, + rows: [ + { + cells: [...tableWithRowspan.content.rows[0].cells, emptyCell], + }, + { + cells: [...tableWithRowspan.content.rows[1].cells, emptyCell], + }, + { + cells: [...tableWithRowspan.content.rows[2].cells, emptyCell], + }, + ], + }, + }, + "columns" + ) + ).toEqual(tableWithRowspan.content.rows); + }); + + it("should crop the empty rows of a table with colspan and rowspan", () => { + expect( + cropEmptyRowsOrColumns( + { + ...tableWithColspanAndRowspan, + content: { + ...tableWithColspanAndRowspan.content, + rows: tableWithColspanAndRowspan.content.rows.concat([ + { + cells: [emptyCell, emptyCell, emptyCell], + }, + ]), + }, + }, + "rows" + ) + ).toEqual(tableWithColspanAndRowspan.content.rows); + }); + + it("should crop the empty columns of a table with colspan and rowspan", () => { + expect( + cropEmptyRowsOrColumns( + { + ...tableWithColspanAndRowspan, + content: { + ...tableWithColspanAndRowspan.content, + rows: [ + { + cells: [ + ...tableWithColspanAndRowspan.content.rows[0].cells, + emptyCell, + ], + }, + { + cells: [ + ...tableWithColspanAndRowspan.content.rows[1].cells, + emptyCell, + ], + }, + { + cells: [ + ...tableWithColspanAndRowspan.content.rows[2].cells, + emptyCell, + ], + }, + ], + }, + }, + "columns" + ) + ).toEqual(tableWithColspanAndRowspan.content.rows); + }); + + it("should crop the empty rows of a table with complex rowspans and colspans", () => { + expect( + cropEmptyRowsOrColumns( + { + ...tableWithComplexRowspansAndColspans, + content: { + ...tableWithComplexRowspansAndColspans.content, + rows: tableWithComplexRowspansAndColspans.content.rows.concat([ + { + cells: [emptyCell, emptyCell, emptyCell, emptyCell], + }, + ]), + }, + }, + "rows" + ) + ).toEqual(tableWithComplexRowspansAndColspans.content.rows); + }); + + it("should crop the empty columns of a table with complex rowspans and colspans", () => { + expect( + cropEmptyRowsOrColumns( + { + ...tableWithComplexRowspansAndColspans, + content: { + ...tableWithComplexRowspansAndColspans.content, + rows: [ + { + cells: [ + ...tableWithComplexRowspansAndColspans.content.rows[0].cells, + emptyCell, + ], + }, + { + cells: [ + ...tableWithComplexRowspansAndColspans.content.rows[1].cells, + emptyCell, + ], + }, + { + cells: [ + ...tableWithComplexRowspansAndColspans.content.rows[2].cells, + emptyCell, + ], + }, + ], + }, + }, + "columns" + ) + ).toEqual(tableWithComplexRowspansAndColspans.content.rows); + }); + + it("should not crop out the last row of a table", () => { + expect( + cropEmptyRowsOrColumns( + { + ...simpleTable, + content: { + ...simpleTable.content, + rows: [ + { + cells: [emptyCell, emptyCell], + }, + { + cells: [emptyCell, emptyCell], + }, + ], + }, + }, + "rows" + ) + ).toEqual([ + { + cells: [emptyCell, emptyCell], + }, + ]); + }); + + it("should not crop out the last column of a table", () => { + expect( + cropEmptyRowsOrColumns( + { + ...simpleTable, + content: { + ...simpleTable.content, + rows: [ + { + cells: [emptyCell, emptyCell], + }, + { + cells: [emptyCell, emptyCell], + }, + ], + }, + }, + "columns" + ) + ).toEqual([ + { + cells: [emptyCell], + }, + { + cells: [emptyCell], + }, + ]); + }); + + it("should preserve any colspan or rowspan on that last row", () => { + expect( + cropEmptyRowsOrColumns( + { + ...simpleTable, + content: { + ...simpleTable.content, + rows: [ { cells: [ { - type: "tableCell", + ...emptyCell, props: { - backgroundColor: "red", - textColor: "blue", - textAlignment: "left", - colspan: 1, - rowspan: 1, + ...emptyCell.props, + rowspan: 2, }, - content: [{ type: "text", text: "", styles: {} }], }, + emptyCell, + ], + }, + { + cells: [emptyCell], + }, + ], + }, + }, + "rows" + ) + ).toEqual([ + { + cells: [ + { + ...emptyCell, + props: { + ...emptyCell.props, + rowspan: 2, + }, + }, + emptyCell, + ], + }, + { + cells: [emptyCell], + }, + ]); + }); + it("should preserve any colspan or rowspan on that last column", () => { + expect( + cropEmptyRowsOrColumns( + { + ...simpleTable, + content: { + ...simpleTable.content, + rows: [ + { + cells: [ { - type: "tableCell", + ...emptyCell, props: { - backgroundColor: "red", - textColor: "blue", - textAlignment: "left", - colspan: 1, - rowspan: 1, + ...emptyCell.props, + colspan: 2, }, - content: [{ type: "text", text: "", styles: {} }], }, ], }, - ]), + { + cells: [emptyCell, emptyCell], + }, + ], }, }, - "rows" + "columns" ) ).toEqual([ { cells: [ - simpleTable.content.rows[0].cells[0], - simpleTable.content.rows[0].cells[1], + { + ...emptyCell, + props: { + ...emptyCell.props, + colspan: 2, + }, + }, ], }, { - cells: [ - simpleTable.content.rows[1].cells[0], - simpleTable.content.rows[1].cells[1], - ], + cells: [emptyCell, emptyCell], }, ]); }); diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index f478448fd2..161d3114ce 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -245,6 +245,8 @@ export function getTableCellOccupancyGrid( } } + // console.log(grid); + return grid; } @@ -301,6 +303,7 @@ export function getAbsoluteTableCells( } { for (let r = 0; r < occupancyGrid.length; r++) { for (let c = 0; c < occupancyGrid[r].length; c++) { + // console.log(r, c, occupancyGrid); const cell = occupancyGrid[r][c]; if ( cell.row === relativeCellIndices.row && @@ -689,8 +692,9 @@ export function cropEmptyRowsOrColumns( cellIndex >= 0; cellIndex-- ) { - const isEmpty = occupancyGrid.every((row) => - isCellEmpty(row[cellIndex].cell) + const isEmpty = occupancyGrid.every( + (row) => + isCellEmpty(row[cellIndex].cell) && row[cellIndex].colspan === 1 ); if (!isEmpty) { break; @@ -709,27 +713,27 @@ export function cropEmptyRowsOrColumns( } return getTableRowsFromOccupancyGrid(occupancyGrid); - } else { - // strips empty rows at the bottom - let emptyRowsOnBottom = 0; - for (let rowIndex = occupancyGrid.length - 1; rowIndex >= 0; rowIndex--) { - const isEmpty = occupancyGrid[rowIndex].every((cell) => - isCellEmpty(cell.cell) - ); - if (!isEmpty) { - break; - } + } - emptyRowsOnBottom++; + // strips empty rows at the bottom + let emptyRowsOnBottom = 0; + for (let rowIndex = occupancyGrid.length - 1; rowIndex >= 0; rowIndex--) { + const isEmpty = occupancyGrid[rowIndex].every( + (cell) => isCellEmpty(cell.cell) && cell.rowspan === 1 + ); + if (!isEmpty) { + break; } - // We maintain at least one row, even if all the rows are empty - const rowsToRemove = Math.min(emptyRowsOnBottom, occupancyGrid.length - 1); + emptyRowsOnBottom++; + } - occupancyGrid.splice(occupancyGrid.length - rowsToRemove, rowsToRemove); + // We maintain at least one row, even if all the rows are empty + const rowsToRemove = Math.min(emptyRowsOnBottom, occupancyGrid.length - 1); - return getTableRowsFromOccupancyGrid(occupancyGrid); - } + occupancyGrid.splice(occupancyGrid.length - rowsToRemove, rowsToRemove); + + return getTableRowsFromOccupancyGrid(occupancyGrid); } /** @@ -752,12 +756,12 @@ export function addRowsOrColumns( if (addType === "columns") { // Add empty columns to the right - occupancyGrid.forEach((row) => { + occupancyGrid.forEach((row, rowIndex) => { if (numToAdd >= 0) { for (let i = 0; i < numToAdd; i++) { row.push({ - row: row[0].row, - col: width + i, + row: rowIndex, + col: Math.max(...row.map((r) => r.col)) + 1, rowspan: 1, colspan: 1, cell: mapTableCell(""), From 10dcb5c622d1736506e04495504c70dcab10e26d Mon Sep 17 00:00:00 2001 From: Nick Perez Date: Wed, 26 Feb 2025 22:39:56 +0100 Subject: [PATCH 59/72] fix(table): table cell merging (#1446) * fix: table cell merging * fix: better merging of styled text in table cells From 10d3bc5c74448ceed0d4590f5e83453b542fb47b Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 27 Feb 2025 13:22:34 +0100 Subject: [PATCH 60/72] docs: minor comment of explanation --- .../core/src/blocks/TableBlockContent/TableBlockContent.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts index be59382502..143d2370c0 100644 --- a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts +++ b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts @@ -159,6 +159,13 @@ export const Table = createBlockSpecFromStronglyTypedTiptapNode( TableExtension, TableParagraph, TableHeader.extend({ + /** + * We allow table headers and cells to have multiple tableContent nodes because + * when merging cells, prosemirror-tables will concat the contents of the cells naively. + * This would cause that content to overflow into other cells when prosemirror tries to enforce the cell structure. + * + * So, we manually fix this up when reading back in the `nodeToBlock` and only ever place a single tableContent back into the cell. + */ content: "tableContent*", }), TableCell.extend({ From 26f90c7f03a3a4fc7cc076abfaa1f07f5037643d Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 27 Feb 2025 13:32:55 +0100 Subject: [PATCH 61/72] refactor: move table helpers to utils folder --- .../api/blockManipulation/tables/tables.ts | 10 +- .../src/api/nodeConversions/blockToNode.ts | 2 +- packages/core/src/index.ts | 1 + packages/core/src/schema/blocks/types.ts | 100 ---------------- packages/core/src/util/table.ts | 107 ++++++++++++++++++ 5 files changed, 115 insertions(+), 105 deletions(-) create mode 100644 packages/core/src/util/table.ts diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 161d3114ce..a84e62351c 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -1,10 +1,6 @@ import { DefaultBlockSchema } from "../../../blocks/defaultBlocks.js"; import { BlockFromConfigNoChildren, - getColspan, - getRowspan, - isPartialTableCell, - mapTableCell, PartialTableContent, TableCell, TableContent, @@ -13,6 +9,12 @@ import { isPartialLinkInlineContent, isStyledTextInlineContent, } from "../../../schema/index.js"; +import { + getColspan, + getRowspan, + isPartialTableCell, + mapTableCell, +} from "../../../util/table.js"; /** * Here be dragons. diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 9e00af5c50..e689ef2578 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -12,11 +12,11 @@ import type { } from "../../schema"; import type { PartialBlock } from "../../blocks/defaultBlocks"; -import { getColspan, isPartialTableCell } from "../../schema/blocks/types.js"; import { isPartialLinkInlineContent, isStyledTextInlineContent, } from "../../schema/inlineContent/types.js"; +import { getColspan, isPartialTableCell } from "../../util/table.js"; import { UnreachableCaseError } from "../../util/typescript.js"; import { getAbsoluteTableCells } from "../blockManipulation/tables/tables.js"; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 072d375bc5..44870bc742 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -52,6 +52,7 @@ export * from "./schema/index.js"; export * from "./util/browser.js"; export * from "./util/combineByGroup.js"; export * from "./util/esmDependencies.js"; +export * from "./util/table.js"; export * from "./util/string.js"; export * from "./util/typescript.js"; export { UnreachableCaseError, assertEmpty } from "./util/typescript.js"; diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index 4141aed25b..dac8f5c9e9 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -319,103 +319,3 @@ export type PartialBlockFromConfig< }; export type BlockIdentifier = { id: string } | string; - -/** - * This will map a table cell to a TableCell object. - * This is useful for when we want to get the full table cell object from a partial table cell. - * It is guaranteed to return a new TableCell object. - */ -export function mapTableCell< - T extends InlineContentSchema, - S extends StyleSchema ->( - content: PartialInlineContent | PartialTableCell | TableCell -): TableCell { - return isTableCell(content) - ? { ...content } - : isPartialTableCell(content) - ? { - type: "tableCell", - content: ([] as InlineContent[]).concat(content.content as any), - props: { - backgroundColor: content.props?.backgroundColor ?? "default", - textColor: content.props?.textColor ?? "default", - textAlignment: content.props?.textAlignment ?? "left", - colspan: content.props?.colspan ?? 1, - rowspan: content.props?.rowspan ?? 1, - }, - } - : { - type: "tableCell", - content: ([] as InlineContent[]).concat(content as any), - props: { - backgroundColor: "default", - textColor: "default", - textAlignment: "left", - colspan: 1, - rowspan: 1, - }, - }; -} - -export function isPartialTableCell< - T extends InlineContentSchema, - S extends StyleSchema ->( - content: - | TableCell - | PartialInlineContent - | PartialTableCell - | undefined - | null -): content is PartialTableCell { - return ( - content !== undefined && - content !== null && - typeof content !== "string" && - !Array.isArray(content) && - content.type === "tableCell" - ); -} - -export function isTableCell< - T extends InlineContentSchema, - S extends StyleSchema ->( - content: - | TableCell - | PartialInlineContent - | PartialTableCell - | undefined - | null -): content is TableCell { - return ( - isPartialTableCell(content) && - content.props !== undefined && - content.content !== undefined - ); -} - -export function getColspan( - cell: - | TableCell - | PartialTableCell - | PartialInlineContent -): number { - if (isTableCell(cell)) { - return cell.props.colspan ?? 1; - } - return 1; -} - -export function getRowspan( - cell: - | TableCell - | PartialTableCell - | PartialInlineContent -): number { - if (isTableCell(cell)) { - return cell.props.rowspan ?? 1; - } - return 1; -} diff --git a/packages/core/src/util/table.ts b/packages/core/src/util/table.ts new file mode 100644 index 0000000000..e06aca7b1e --- /dev/null +++ b/packages/core/src/util/table.ts @@ -0,0 +1,107 @@ +import type { + InlineContentSchema, + StyleSchema, + PartialInlineContent, + InlineContent, +} from "../schema"; +import { PartialTableCell, TableCell } from "../schema/blocks/types.js"; + +/** + * This will map a table cell to a TableCell object. + * This is useful for when we want to get the full table cell object from a partial table cell. + * It is guaranteed to return a new TableCell object. + */ +export function mapTableCell< + T extends InlineContentSchema, + S extends StyleSchema +>( + content: PartialInlineContent | PartialTableCell | TableCell +): TableCell { + return isTableCell(content) + ? { ...content } + : isPartialTableCell(content) + ? { + type: "tableCell", + content: ([] as InlineContent[]).concat(content.content as any), + props: { + backgroundColor: content.props?.backgroundColor ?? "default", + textColor: content.props?.textColor ?? "default", + textAlignment: content.props?.textAlignment ?? "left", + colspan: content.props?.colspan ?? 1, + rowspan: content.props?.rowspan ?? 1, + }, + } + : { + type: "tableCell", + content: ([] as InlineContent[]).concat(content as any), + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + }; +} + +export function isPartialTableCell< + T extends InlineContentSchema, + S extends StyleSchema +>( + content: + | TableCell + | PartialInlineContent + | PartialTableCell + | undefined + | null +): content is PartialTableCell { + return ( + content !== undefined && + content !== null && + typeof content !== "string" && + !Array.isArray(content) && + content.type === "tableCell" + ); +} + +export function isTableCell< + T extends InlineContentSchema, + S extends StyleSchema +>( + content: + | TableCell + | PartialInlineContent + | PartialTableCell + | undefined + | null +): content is TableCell { + return ( + isPartialTableCell(content) && + content.props !== undefined && + content.content !== undefined + ); +} + +export function getColspan( + cell: + | TableCell + | PartialTableCell + | PartialInlineContent +): number { + if (isTableCell(cell)) { + return cell.props.colspan ?? 1; + } + return 1; +} + +export function getRowspan( + cell: + | TableCell + | PartialTableCell + | PartialInlineContent +): number { + if (isTableCell(cell)) { + return cell.props.rowspan ?? 1; + } + return 1; +} From a8317819cac65dfd398fc71757470f98ec001529 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 27 Feb 2025 13:35:51 +0100 Subject: [PATCH 62/72] style: switch cursors --- packages/mantine/src/style.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css index 043546d669..ee3dff4774 100644 --- a/packages/mantine/src/style.css +++ b/packages/mantine/src/style.css @@ -503,7 +503,7 @@ border-radius: var(--bn-border-radius-small); box-shadow: var(--bn-shadow-light); color: var(--bn-colors-side-menu); - cursor: pointer; + cursor: grab; display: flex; height: fit-content; justify-content: center; @@ -520,7 +520,7 @@ } .bn-mantine .bn-table-handle-not-draggable { - cursor: default; + cursor: pointer; } .bn-mantine .bn-table-handle:hover, From 31408ea6bc5930a5260c961273742fab3035d940 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 27 Feb 2025 14:03:11 +0100 Subject: [PATCH 63/72] feat: allow toggling the header row/col via the table row/col handle menu --- .../DefaultButtons/TableHeaderButton.tsx | 104 ++++++++++++++++++ .../TableHandleMenu/TableHandleMenu.tsx | 12 ++ 2 files changed, 116 insertions(+) create mode 100644 packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/TableHeaderButton.tsx diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/TableHeaderButton.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/TableHeaderButton.tsx new file mode 100644 index 0000000000..2efad1b0ae --- /dev/null +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/TableHeaderButton.tsx @@ -0,0 +1,104 @@ +import { + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; + +import { useComponentsContext } from "../../../../editor/ComponentsContext.js"; +import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor.js"; +import { TableHandleMenuProps } from "../TableHandleMenuProps.js"; +import { useDictionary } from "../../../../i18n/dictionary.js"; + +export const TableHeaderRowButton = < + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: TableHandleMenuProps & { orientation: "row" | "column" } +) => { + const Components = useComponentsContext()!; + const dict = useDictionary(); + + const editor = useBlockNoteEditor< + { table: DefaultBlockSchema["table"] }, + I, + S + >(); + const tableHandles = editor.tableHandles; + + if (!tableHandles || props.index !== 0 || props.orientation !== "row") { + return null; + } + + // We only support 1 header row for now + const isHeaderRow = Boolean(props.block.content.headerRows); + + return ( + { + // The block may have been modified and out of date, so we get the latest block + const block = editor.getBlock(props.block.id); + if (!block) { + return; + } + editor.updateBlock(block, { + ...block, + content: { + ...block.content, + headerRows: isHeaderRow ? undefined : 1, + }, + }); + }}> + {dict.drag_handle.header_row_menuitem} + + ); +}; + +export const TableHeaderColumnButton = < + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: TableHandleMenuProps & { orientation: "row" | "column" } +) => { + const Components = useComponentsContext()!; + const dict = useDictionary(); + + const editor = useBlockNoteEditor< + { table: DefaultBlockSchema["table"] }, + I, + S + >(); + const tableHandles = editor.tableHandles; + + if (!tableHandles || props.index !== 0 || props.orientation !== "column") { + return null; + } + + // We only support 1 header column for now + const isHeaderColumn = Boolean(props.block.content.headerCols); + + return ( + { + // The block may have been modified and out of date, so we get the latest block + const block = editor.getBlock(props.block.id); + if (!block) { + return; + } + editor.updateBlock(block, { + ...block, + content: { + ...block.content, + headerCols: isHeaderColumn ? undefined : 1, + }, + }); + }}> + {dict.drag_handle.header_column_menuitem} + + ); +}; diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenu.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenu.tsx index 75804155f7..b9710d6e06 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenu.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenu.tsx @@ -11,6 +11,8 @@ import { AddButton } from "./DefaultButtons/AddButton.js"; import { DeleteButton } from "./DefaultButtons/DeleteButton.js"; import { TableHandleMenuProps } from "./TableHandleMenuProps.js"; import { ColorPickerButton } from "./DefaultButtons/ColorPicker.js"; +import { TableHeaderColumnButton } from "./DefaultButtons/TableHeaderButton.js"; +import { TableHeaderRowButton } from "./DefaultButtons/TableHeaderButton.js"; export const TableHandleMenu = < I extends InlineContentSchema = DefaultInlineContentSchema, @@ -41,6 +43,16 @@ export const TableHandleMenu = < index={props.index} side={props.orientation === "row" ? "below" : ("right" as any)} /> + + Date: Thu, 27 Feb 2025 14:13:46 +0100 Subject: [PATCH 64/72] refactor: rename tablecellhandle to TableCellButton --- .../{TableCellHandle.tsx => TableCellButton.tsx} | 10 +++++----- ...TableCellHandleProps.ts => TableCellButtonProps.ts} | 6 +++--- .../TableCellHandleMenu/DefaultButtons/ColorPicker.tsx | 4 ++-- .../TableCellHandleMenu/DefaultButtons/SplitButton.tsx | 4 ++-- .../{TableCellHandleMenu.tsx => TableCellMenu.tsx} | 6 +++--- ...bleCellHandleMenuProps.ts => TableCellMenuProps.ts} | 2 +- .../components/TableHandles/TableHandlesController.tsx | 8 ++++---- 7 files changed, 20 insertions(+), 20 deletions(-) rename packages/react/src/components/TableHandles/{TableCellHandle.tsx => TableCellButton.tsx} (83%) rename packages/react/src/components/TableHandles/{TableCellHandleProps.ts => TableCellButtonProps.ts} (80%) rename packages/react/src/components/TableHandles/TableCellHandleMenu/{TableCellHandleMenu.tsx => TableCellMenu.tsx} (85%) rename packages/react/src/components/TableHandles/TableCellHandleMenu/{TableCellHandleMenuProps.ts => TableCellMenuProps.ts} (91%) diff --git a/packages/react/src/components/TableHandles/TableCellHandle.tsx b/packages/react/src/components/TableHandles/TableCellButton.tsx similarity index 83% rename from packages/react/src/components/TableHandles/TableCellHandle.tsx rename to packages/react/src/components/TableHandles/TableCellButton.tsx index 279b39dee5..1b3da61d51 100644 --- a/packages/react/src/components/TableHandles/TableCellHandle.tsx +++ b/packages/react/src/components/TableHandles/TableCellButton.tsx @@ -9,22 +9,22 @@ import { ReactNode } from "react"; import { createPortal } from "react-dom"; import { MdArrowDropDown } from "react-icons/md"; import { useComponentsContext } from "../../editor/ComponentsContext.js"; -import { TableCellHandleProps } from "./TableCellHandleProps.js"; -import { TableCellHandleMenu } from "./TableCellHandleMenu/TableCellHandleMenu.js"; +import { TableCellButtonProps } from "./TableCellButtonProps.js"; +import { TableCellMenu } from "./TableCellHandleMenu/TableCellMenu.js"; /** * By default, the TableCellHandle component will render with the default icon. * However, you can override the icon to render by passing children. */ -export const TableCellHandle = < +export const TableCellButton = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >( - props: TableCellHandleProps & { children?: ReactNode } + props: TableCellButtonProps & { children?: ReactNode } ) => { const Components = useComponentsContext()!; - const Component = props.tableCellHandleMenu || TableCellHandleMenu; + const Component = props.tableCellMenu || TableCellMenu; return ( = { @@ -24,7 +24,7 @@ export type TableCellHandleProps< rowIndex: number; colIndex: number; menuContainer: HTMLDivElement; - tableCellHandleMenu?: FC>; + tableCellMenu?: FC>; } & Pick, "block"> & Pick< Exclude< diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx index 06cb687df5..c9f21f85e9 100644 --- a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx +++ b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx @@ -12,14 +12,14 @@ import { useComponentsContext } from "../../../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor.js"; import { useDictionary } from "../../../../i18n/dictionary.js"; import { ColorPicker } from "../../../ColorPicker/ColorPicker.js"; -import { TableCellHandleMenuProps } from "../TableCellHandleMenuProps.js"; +import { TableCellMenuProps } from "../TableCellMenuProps.js"; import { ReactNode } from "react"; export const ColorPickerButton = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >( - props: TableCellHandleMenuProps & { + props: TableCellMenuProps & { children?: ReactNode; } ) => { diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx index cf4de400ee..9d05c0ed65 100644 --- a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx +++ b/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx @@ -12,13 +12,13 @@ import { import { useComponentsContext } from "../../../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor.js"; import { useDictionary } from "../../../../i18n/dictionary.js"; -import { TableCellHandleMenuProps } from "../TableCellHandleMenuProps.js"; +import { TableCellMenuProps } from "../TableCellMenuProps.js"; export const SplitButton = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >( - props: TableCellHandleMenuProps + props: TableCellMenuProps ) => { const Components = useComponentsContext()!; const dict = useDictionary(); diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellHandleMenu.tsx b/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellMenu.tsx similarity index 85% rename from packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellHandleMenu.tsx rename to packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellMenu.tsx index c7ad1bd00c..91987b428a 100644 --- a/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellHandleMenu.tsx +++ b/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellMenu.tsx @@ -9,13 +9,13 @@ import { ReactNode } from "react"; import { useComponentsContext } from "../../../editor/ComponentsContext.js"; import { ColorPickerButton } from "./DefaultButtons/ColorPicker.js"; import { SplitButton } from "./DefaultButtons/SplitButton.js"; -import { TableCellHandleMenuProps } from "./TableCellHandleMenuProps.js"; +import { TableCellMenuProps } from "./TableCellMenuProps.js"; -export const TableCellHandleMenu = < +export const TableCellMenu = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >( - props: TableCellHandleMenuProps & { children?: ReactNode } + props: TableCellMenuProps & { children?: ReactNode } ) => { const Components = useComponentsContext()!; diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellHandleMenuProps.ts b/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellMenuProps.ts similarity index 91% rename from packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellHandleMenuProps.ts rename to packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellMenuProps.ts index 7b268f242a..8147e2ac25 100644 --- a/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellHandleMenuProps.ts +++ b/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellMenuProps.ts @@ -7,7 +7,7 @@ import { StyleSchema, } from "@blocknote/core"; -export type TableCellHandleMenuProps< +export type TableCellMenuProps< I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema > = { diff --git a/packages/react/src/components/TableHandles/TableHandlesController.tsx b/packages/react/src/components/TableHandles/TableHandlesController.tsx index 09ca27cda9..6e69f23463 100644 --- a/packages/react/src/components/TableHandles/TableHandlesController.tsx +++ b/packages/react/src/components/TableHandles/TableHandlesController.tsx @@ -16,14 +16,14 @@ import { TableHandle } from "./TableHandle.js"; import { TableHandleProps } from "./TableHandleProps.js"; import { useExtendButtonsPositioning } from "./hooks/useExtendButtonsPositioning.js"; import { useTableHandlesPositioning } from "./hooks/useTableHandlesPositioning.js"; -import { TableCellHandle } from "./TableCellHandle.js"; -import { TableCellHandleProps } from "./TableCellHandleProps.js"; +import { TableCellButton } from "./TableCellButton.js"; +import { TableCellButtonProps } from "./TableCellButtonProps.js"; export const TableHandlesController = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >(props: { - tableCellHandle?: FC>; + tableCellHandle?: FC>; tableHandle?: FC>; extendButton?: FC>; }) => { @@ -101,7 +101,7 @@ export const TableHandlesController = < const TableHandleComponent = props.tableHandle || TableHandle; const ExtendButtonComponent = props.extendButton || ExtendButton; - const TableCellHandleComponent = props.tableCellHandle || TableCellHandle; + const TableCellHandleComponent = props.tableCellHandle || TableCellButton; return ( <> From 7fe382432822545d937e9c86732d8cd6eb5a2c3f Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 27 Feb 2025 14:15:32 +0100 Subject: [PATCH 65/72] refactor: rename to menubutton --- packages/react/src/editor/ComponentsContext.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index 37f4e21e07..15d6f86cbb 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -32,7 +32,7 @@ type ToolbarButtonType = { | { children?: undefined; label: string } ); -type ButtonType = { +type MenuButtonType = { className?: string; onClick?: (e: MouseEvent) => void; icon?: ReactNode; @@ -243,7 +243,7 @@ export type ComponentProps = { children?: ReactNode; sub?: boolean; }; - Button: ButtonType; + Button: MenuButtonType; }; Popover: { Root: { From df2b218b08d2705bcbd50ffeca1c6b55ef5a9238 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 27 Feb 2025 14:16:54 +0100 Subject: [PATCH 66/72] fix: pdf transformer for table cells --- packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx b/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx index 6072cf5e82..56991d330d 100644 --- a/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx +++ b/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx @@ -1,6 +1,7 @@ import { Exporter, InlineContentSchema, + mapTableCell, StyleSchema, TableContent, } from "@blocknote/core"; @@ -67,8 +68,9 @@ export const Table = (props: { : { flex: 1 }, ]} key={index}> - {/* TODO: fix this */} - {props.transformer.transformInlineContent(cell as any)} + {props.transformer.transformInlineContent( + mapTableCell(cell).content + )} ))} From 77117a5bbd1006bff7ed8caa59b5230a2ccc8af0 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Thu, 27 Feb 2025 14:42:46 +0100 Subject: [PATCH 67/72] fix: allow table blocks to be dragged around --- .../TableBlockContent/TableBlockContent.ts | 4 +- .../TableHandles/TableHandlesPlugin.ts | 37 ++++++++++++------- .../components/TableHandles/TableHandle.tsx | 2 +- .../DefaultButtons/ColorPicker.tsx | 2 +- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts index 143d2370c0..c9830f227d 100644 --- a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts +++ b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts @@ -166,10 +166,10 @@ export const Table = createBlockSpecFromStronglyTypedTiptapNode( * * So, we manually fix this up when reading back in the `nodeToBlock` and only ever place a single tableContent back into the cell. */ - content: "tableContent*", + content: "tableContent+", }), TableCell.extend({ - content: "tableContent*", + content: "tableContent+", }), TableRow, ] diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 11944fa146..c79ceeaf37 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -1058,14 +1058,16 @@ export class TableHandlesProsemirrorPlugin< * Gets the start and end cells of the current cell selection. * @returns The start and end cells of the current cell selection. */ - getCellSelection = (): { - from: RelativeCellIndices; - to: RelativeCellIndices; - /** - * All of the cells that are within the selected range. - */ - cells: RelativeCellIndices[]; - } => { + getCellSelection = (): + | undefined + | { + from: RelativeCellIndices; + to: RelativeCellIndices; + /** + * All of the cells that are within the selected range. + */ + cells: RelativeCellIndices[]; + } => { // Based on the current selection, find the table cells that are within the selected range const state = this.editor.prosemirrorState; const selection = state.selection; @@ -1092,6 +1094,11 @@ export class TableHandlesProsemirrorPlugin< ); } + // Opt-out when the selection is not over a range of cells + if ($fromCell.pos === $toCell.pos) { + return undefined; + } + // Find the row and table that the from and to cells are in const $fromRow = state.doc.resolve( $fromCell.pos - $fromCell.parentOffset - 1 @@ -1137,24 +1144,28 @@ export class TableHandlesProsemirrorPlugin< | BlockFromConfigNoChildren | undefined ) => { - const cellSelection = isTableCellSelection( + const isSelectingTableCells = isTableCellSelection( this.editor.prosemirrorState.selection ) ? this.editor.prosemirrorState.selection : undefined; if ( - !cellSelection || + !isSelectingTableCells || !block || // Only offer the merge button if there is more than one cell selected. - cellSelection.ranges.length <= 1 + isSelectingTableCells.ranges.length <= 1 ) { return undefined; } - const { from, to } = this.getCellSelection(); + const cellSelection = this.getCellSelection(); + + if (!cellSelection) { + return undefined; + } - if (areInSameColumn(from, to, block)) { + if (areInSameColumn(cellSelection.from, cellSelection.to, block)) { return "vertical"; } diff --git a/packages/react/src/components/TableHandles/TableHandle.tsx b/packages/react/src/components/TableHandles/TableHandle.tsx index f5e31bc401..8ceb450569 100644 --- a/packages/react/src/components/TableHandles/TableHandle.tsx +++ b/packages/react/src/components/TableHandles/TableHandle.tsx @@ -33,7 +33,7 @@ export const TableHandle = < const isDraggable = useMemo(() => { const tableHandles = props.editor.tableHandles; - if (!tableHandles) { + if (!tableHandles || !props.block) { return false; } diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx index 8b6f2dd038..0b615a146e 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx @@ -33,7 +33,7 @@ export const ColorPickerButton = < const tableHandles = editor.tableHandles; const currentCells = useMemo(() => { - if (!tableHandles) { + if (!tableHandles || !props.block) { return []; } From a767959005b148b4d94ea09a018c76d931a63394 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 28 Feb 2025 11:57:16 +0100 Subject: [PATCH 68/72] Fixed ShadCN checked menu item styles --- packages/shadcn/src/menu/Menu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shadcn/src/menu/Menu.tsx b/packages/shadcn/src/menu/Menu.tsx index a62a4cd6ca..ae50db8860 100644 --- a/packages/shadcn/src/menu/Menu.tsx +++ b/packages/shadcn/src/menu/Menu.tsx @@ -147,7 +147,7 @@ export const MenuItem = forwardRef< if (checked !== undefined) { return ( Date: Fri, 28 Feb 2025 14:25:28 +0100 Subject: [PATCH 69/72] fix: tableHeader text alignment --- packages/core/src/editor/Block.css | 16 ++++++++-------- packages/core/src/editor/editor.css | 1 + .../TableHandles/TableHandlesPlugin.ts | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index 67db567702..95479b75fd 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -499,23 +499,23 @@ NESTED BLOCKS /* TEXT ALIGNMENT */ [data-text-alignment="left"] { - justify-content: flex-start; - text-align: left; + justify-content: flex-start !important; + text-align: left !important; } [data-text-alignment="center"] { - justify-content: center; - text-align: center; + justify-content: center !important; + text-align: center !important; } [data-text-alignment="right"] { - justify-content: flex-end; - text-align: right; + justify-content: flex-end !important; + text-align: right !important; } [data-text-alignment="justify"] { - justify-content: flex-start; - text-align: justify; + justify-content: flex-start !important; + text-align: justify !important; } .bn-block-column-list { diff --git a/packages/core/src/editor/editor.css b/packages/core/src/editor/editor.css index 27abebc140..797ffea0b2 100644 --- a/packages/core/src/editor/editor.css +++ b/packages/core/src/editor/editor.css @@ -156,6 +156,7 @@ Tippy popups that are appended to document.body directly .bn-editor [data-content-type="table"] th { font-weight: bold; + text-align: left; } /* tiptap uses colwidth instead of data-colwidth, se we need to adjust this style from prosemirror-tables */ diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index c79ceeaf37..7b35776b41 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -1092,11 +1092,11 @@ export class TableHandlesProsemirrorPlugin< $toCell = state.doc.resolve( selection.$to.pos - selection.$to.parentOffset - 1 ); - } - // Opt-out when the selection is not over a range of cells - if ($fromCell.pos === $toCell.pos) { - return undefined; + // Opt-out when the selection is not pointing into cells + if ($fromCell.pos === 0 || $toCell.pos === 0) { + return undefined; + } } // Find the row and table that the from and to cells are in From 6b3438edf04b6e2e0c0461e1b9e0677a774e7070 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Fri, 28 Feb 2025 14:36:28 +0100 Subject: [PATCH 70/72] fix: rename dir --- packages/react/src/components/TableHandles/TableCellButton.tsx | 2 +- .../react/src/components/TableHandles/TableCellButtonProps.ts | 2 +- .../DefaultButtons/ColorPicker.tsx | 0 .../DefaultButtons/SplitButton.tsx | 0 .../{TableCellHandleMenu => TableCellMenu}/TableCellMenu.tsx | 0 .../TableCellMenuProps.ts | 0 6 files changed, 2 insertions(+), 2 deletions(-) rename packages/react/src/components/TableHandles/{TableCellHandleMenu => TableCellMenu}/DefaultButtons/ColorPicker.tsx (100%) rename packages/react/src/components/TableHandles/{TableCellHandleMenu => TableCellMenu}/DefaultButtons/SplitButton.tsx (100%) rename packages/react/src/components/TableHandles/{TableCellHandleMenu => TableCellMenu}/TableCellMenu.tsx (100%) rename packages/react/src/components/TableHandles/{TableCellHandleMenu => TableCellMenu}/TableCellMenuProps.ts (100%) diff --git a/packages/react/src/components/TableHandles/TableCellButton.tsx b/packages/react/src/components/TableHandles/TableCellButton.tsx index 1b3da61d51..f1ba77f211 100644 --- a/packages/react/src/components/TableHandles/TableCellButton.tsx +++ b/packages/react/src/components/TableHandles/TableCellButton.tsx @@ -10,7 +10,7 @@ import { createPortal } from "react-dom"; import { MdArrowDropDown } from "react-icons/md"; import { useComponentsContext } from "../../editor/ComponentsContext.js"; import { TableCellButtonProps } from "./TableCellButtonProps.js"; -import { TableCellMenu } from "./TableCellHandleMenu/TableCellMenu.js"; +import { TableCellMenu } from "./TableCellMenu/TableCellMenu.js"; /** * By default, the TableCellHandle component will render with the default icon. diff --git a/packages/react/src/components/TableHandles/TableCellButtonProps.ts b/packages/react/src/components/TableHandles/TableCellButtonProps.ts index ba2eabf99e..bfaca01810 100644 --- a/packages/react/src/components/TableHandles/TableCellButtonProps.ts +++ b/packages/react/src/components/TableHandles/TableCellButtonProps.ts @@ -7,7 +7,7 @@ import { StyleSchema, TableHandlesState, } from "@blocknote/core"; -import { TableCellMenuProps } from "./TableCellHandleMenu/TableCellMenuProps.js"; +import { TableCellMenuProps } from "./TableCellMenu/TableCellMenuProps.js"; import { FC } from "react"; export type TableCellButtonProps< diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx similarity index 100% rename from packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/ColorPicker.tsx rename to packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx b/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/SplitButton.tsx similarity index 100% rename from packages/react/src/components/TableHandles/TableCellHandleMenu/DefaultButtons/SplitButton.tsx rename to packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/SplitButton.tsx diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellMenu.tsx b/packages/react/src/components/TableHandles/TableCellMenu/TableCellMenu.tsx similarity index 100% rename from packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellMenu.tsx rename to packages/react/src/components/TableHandles/TableCellMenu/TableCellMenu.tsx diff --git a/packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellMenuProps.ts b/packages/react/src/components/TableHandles/TableCellMenu/TableCellMenuProps.ts similarity index 100% rename from packages/react/src/components/TableHandles/TableCellHandleMenu/TableCellMenuProps.ts rename to packages/react/src/components/TableHandles/TableCellMenu/TableCellMenuProps.ts From 52cd0355febee2e7d6ba5906a94ff593223ffb5d Mon Sep 17 00:00:00 2001 From: Nick Perez Date: Fri, 28 Feb 2025 16:48:16 +0100 Subject: [PATCH 71/72] feat(tables): add the ability to enable/disable table features (#1470) --- .../15-advanced-tables/.bnexample.json | 6 + .../15-advanced-tables/App.tsx | 305 ++++++++++++++++++ .../15-advanced-tables/README.md | 13 + .../15-advanced-tables/index.html | 14 + .../15-advanced-tables/main.tsx | 11 + .../15-advanced-tables/package.json | 37 +++ .../15-advanced-tables/tsconfig.json | 36 +++ .../15-advanced-tables/vite.config.ts | 32 ++ packages/core/src/editor/BlockNoteEditor.ts | 61 +++- .../DefaultButtons/TableCellMergeButton.tsx | 6 +- .../DefaultItems/TableHeadersItem.tsx | 4 +- .../TableHandles/TableCellButton.tsx | 9 + .../DefaultButtons/ColorPicker.tsx | 39 ++- .../DefaultButtons/SplitButton.tsx | 3 +- .../DefaultButtons/ColorPicker.tsx | 63 ++-- .../DefaultButtons/TableHeaderButton.tsx | 14 +- playground/src/examples.gen.tsx | 21 ++ 17 files changed, 624 insertions(+), 50 deletions(-) create mode 100644 examples/03-ui-components/15-advanced-tables/.bnexample.json create mode 100644 examples/03-ui-components/15-advanced-tables/App.tsx create mode 100644 examples/03-ui-components/15-advanced-tables/README.md create mode 100644 examples/03-ui-components/15-advanced-tables/index.html create mode 100644 examples/03-ui-components/15-advanced-tables/main.tsx create mode 100644 examples/03-ui-components/15-advanced-tables/package.json create mode 100644 examples/03-ui-components/15-advanced-tables/tsconfig.json create mode 100644 examples/03-ui-components/15-advanced-tables/vite.config.ts diff --git a/examples/03-ui-components/15-advanced-tables/.bnexample.json b/examples/03-ui-components/15-advanced-tables/.bnexample.json new file mode 100644 index 0000000000..9c4787320e --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": ["Intermediate", "UI Components", "Tables", "Appearance & Styling"] +} diff --git a/examples/03-ui-components/15-advanced-tables/App.tsx b/examples/03-ui-components/15-advanced-tables/App.tsx new file mode 100644 index 0000000000..dfe89812a9 --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/App.tsx @@ -0,0 +1,305 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + // This enables the advanced table features + tables: { + splitCells: true, + cellBackgroundColor: true, + cellTextColor: true, + headers: true, + }, + initialContent: [ + { + id: "7e498b3d-d42e-4ade-9be0-054b292715ea", + type: "heading", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + level: 2, + }, + content: [ + { + type: "text", + text: "Advanced Tables", + styles: {}, + }, + ], + children: [], + }, + { + id: "cbf287c6-770b-413a-bff5-ad490a0b562a", + type: "table", + props: { + textColor: "default", + }, + content: { + type: "tableContent", + columnWidths: [199, 148, 201], + headerRows: 1, + rows: [ + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "This row has headers", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "This is ", + styles: {}, + }, + { + type: "text", + text: "RED", + styles: { + bold: true, + }, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "red", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "Text is Blue", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "blue", + textAlignment: "center", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "This spans 2 columns\nand 2 rows", + styles: {}, + }, + ], + props: { + colspan: 2, + rowspan: 2, + backgroundColor: "yellow", + textColor: "default", + textAlignment: "left", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "Sooo many features", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "gray", + textColor: "default", + textAlignment: "left", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: [], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "gray", + textColor: "purple", + textAlignment: "left", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "A cell", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "Another Cell", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "right", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "Aligned center", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "center", + }, + }, + ], + }, + ], + }, + children: [], + }, + { + id: "16e76a94-74e5-42e2-b461-fc9da9f381f7", + type: "paragraph", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [ + { + type: "text", + text: "Featuring:", + styles: {}, + }, + ], + children: [ + { + id: "785fc9f7-8554-47f4-a4df-8fe2f1438cac", + type: "bulletListItem", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [ + { + type: "text", + text: "Cell background & foreground coloring", + styles: {}, + }, + ], + children: [], + }, + { + id: "1d0adf08-1b42-421a-b9ea-b3125dcc96d9", + type: "bulletListItem", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [ + { + type: "text", + text: "Splitting & merging cells", + styles: {}, + }, + ], + children: [], + }, + { + id: "99991aa7-9d86-4d06-9073-b1a9c0329062", + type: "bulletListItem", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [ + { + type: "text", + text: "Header row & column", + styles: {}, + }, + ], + children: [], + }, + ], + }, + { + id: "c7bf2a7c-8972-44f1-acd8-cf60fa734068", + type: "paragraph", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [], + children: [], + }, + ], + }); + + // Renders the editor instance using a React component. + return ; +} diff --git a/examples/03-ui-components/15-advanced-tables/README.md b/examples/03-ui-components/15-advanced-tables/README.md new file mode 100644 index 0000000000..0eb9e78c32 --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/README.md @@ -0,0 +1,13 @@ +# Advanced Tables + +This example enables the following features in tables: + +- Split cells +- Cell background color +- Cell text color +- Table row and column headers + +**Relevant Docs:** + +- [Tables](/docs/editor-basics/tables) +- [Editor Setup](/docs/editor-basics/setup) diff --git a/examples/03-ui-components/15-advanced-tables/index.html b/examples/03-ui-components/15-advanced-tables/index.html new file mode 100644 index 0000000000..b4bd86e618 --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/index.html @@ -0,0 +1,14 @@ + + + + + + Advanced Tables + + +
+ + + diff --git a/examples/03-ui-components/15-advanced-tables/main.tsx b/examples/03-ui-components/15-advanced-tables/main.tsx new file mode 100644 index 0000000000..f88b490fbd --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + +); diff --git a/examples/03-ui-components/15-advanced-tables/package.json b/examples/03-ui-components/15-advanced-tables/package.json new file mode 100644 index 0000000000..9d7b623424 --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/package.json @@ -0,0 +1,37 @@ +{ + "name": "@blocknote/example-advanced-tables", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --max-warnings 0" + }, + "dependencies": { + "@blocknote/core": "latest", + "@blocknote/react": "latest", + "@blocknote/ariakit": "latest", + "@blocknote/mantine": "latest", + "@blocknote/shadcn": "latest", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.0.25", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^8.10.0", + "vite": "^5.3.4" + }, + "eslintConfig": { + "extends": [ + "../../../.eslintrc.js" + ] + }, + "eslintIgnore": [ + "dist" + ] +} \ No newline at end of file diff --git a/examples/03-ui-components/15-advanced-tables/tsconfig.json b/examples/03-ui-components/15-advanced-tables/tsconfig.json new file mode 100644 index 0000000000..1bd8ab3c57 --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": [ + "." + ], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} \ No newline at end of file diff --git a/examples/03-ui-components/15-advanced-tables/vite.config.ts b/examples/03-ui-components/15-advanced-tables/vite.config.ts new file mode 100644 index 0000000000..f62ab20bc2 --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/vite.config.ts @@ -0,0 +1,32 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite"; +// import eslintPlugin from "vite-plugin-eslint"; +// https://vitejs.dev/config/ +export default defineConfig((conf) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/" + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/" + ), + } as any), + }, +})); diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 51255ce302..efadeade12 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -268,6 +268,36 @@ export type BlockNoteEditorOptions< * @default "viewport" */ sideMenuDetection: "viewport" | "editor"; + + /** + * Allows enabling / disabling features of tables. + */ + tables?: { + /** + * Whether to allow splitting and merging cells within a table. + * + * @default false + */ + splitCells?: boolean; + /** + * Whether to allow changing the background color of cells. + * + * @default false + */ + cellBackgroundColor?: boolean; + /** + * Whether to allow changing the text color of cells. + * + * @default false + */ + cellTextColor?: boolean; + /** + * Whether to allow changing cells into headers. + * + * @default false + */ + headers?: boolean; + }; }; const blockNoteTipTapOptions = { @@ -281,7 +311,10 @@ export class BlockNoteEditor< ISchema extends InlineContentSchema = DefaultInlineContentSchema, SSchema extends StyleSchema = DefaultStyleSchema > { - private readonly _pmSchema: Schema; + /** + * The underlying prosemirror schema + */ + public readonly pmSchema: Schema; /** * extensions that are added to the editor, can be tiptap extensions or prosemirror plugins @@ -371,9 +404,17 @@ export class BlockNoteEditor< public readonly resolveFileUrl?: (url: string) => Promise; - public get pmSchema() { - return this._pmSchema; - } + /** + * Editor settings + */ + public readonly settings: { + tables: { + splitCells: boolean; + cellBackgroundColor: boolean; + cellTextColor: boolean; + headers: boolean; + }; + }; public static create< BSchema extends BlockSchema = DefaultBlockSchema, @@ -412,6 +453,14 @@ export class BlockNoteEditor< } this.dictionary = options.dictionary || en; + this.settings = { + tables: { + splitCells: options?.tables?.splitCells ?? false, + cellBackgroundColor: options?.tables?.cellBackgroundColor ?? false, + cellTextColor: options?.tables?.cellTextColor ?? false, + headers: options?.tables?.headers ?? false, + }, + }; // apply defaults const newOptions = { @@ -579,11 +628,11 @@ export class BlockNoteEditor< view: any; contentComponent: any; }; - this._pmSchema = this._tiptapEditor.schema; + this.pmSchema = this._tiptapEditor.schema; } else { // In headless mode, we don't instantiate an underlying TipTap editor, // but we still need the schema - this._pmSchema = getSchema(tiptapOptions.extensions!); + this.pmSchema = getSchema(tiptapOptions.extensions!); } } diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx index 776d47f4e1..bb11326a08 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx @@ -43,7 +43,11 @@ export const TableCellMergeButton = () => { editor.tableHandles?.mergeCells(); }, [editor]); - if (!editor.isEditable || mergeDirection === undefined) { + if ( + !editor.isEditable || + mergeDirection === undefined || + !editor.settings.tables.splitCells + ) { return null; } diff --git a/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx b/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx index 53874aae4d..7f2e88fd86 100644 --- a/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx +++ b/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx @@ -31,7 +31,7 @@ export const TableRowHeaderItem = < S >(); - if (props.block.type !== "table") { + if (props.block.type !== "table" || !editor.settings.tables.headers) { return null; } @@ -79,7 +79,7 @@ export const TableColumnHeaderItem = < S >(); - if (props.block.type !== "table") { + if (props.block.type !== "table" || !editor.settings.tables.headers) { return null; } diff --git a/packages/react/src/components/TableHandles/TableCellButton.tsx b/packages/react/src/components/TableHandles/TableCellButton.tsx index f1ba77f211..00ef9f3a21 100644 --- a/packages/react/src/components/TableHandles/TableCellButton.tsx +++ b/packages/react/src/components/TableHandles/TableCellButton.tsx @@ -26,6 +26,15 @@ export const TableCellButton = < const Component = props.tableCellMenu || TableCellMenu; + if ( + !props.editor.settings.tables.splitCells && + !props.editor.settings.tables.cellBackgroundColor && + !props.editor.settings.tables.cellTextColor + ) { + // Hide the button altogether if all table cell settings are disabled + return null; + } + return ( { diff --git a/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx index c9f21f85e9..91b0951ec0 100644 --- a/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx +++ b/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx @@ -62,7 +62,11 @@ export const ColorPickerButton = < const currentCell = props.block.content.rows[props.rowIndex]?.cells?.[props.colIndex]; - if (!currentCell) { + if ( + !currentCell || + (editor.settings.tables.cellTextColor === false && + editor.settings.tables.cellBackgroundColor === false) + ) { return null; } @@ -72,7 +76,6 @@ export const ColorPickerButton = < - {/* TODO should I be using the dictionary here? */} {props.children || dict.drag_handle.colors_menuitem} @@ -82,18 +85,26 @@ export const ColorPickerButton = < className={"bn-menu-dropdown bn-color-picker-dropdown"}> updateColor(color, "text"), - }} - background={{ - color: isTableCell(currentCell) - ? currentCell.props.backgroundColor - : "default", - setColor: (color) => updateColor(color, "background"), - }} + text={ + editor.settings.tables.cellTextColor + ? { + color: isTableCell(currentCell) + ? currentCell.props.textColor + : "default", + setColor: (color) => updateColor(color, "text"), + } + : undefined + } + background={ + editor.settings.tables.cellBackgroundColor + ? { + color: isTableCell(currentCell) + ? currentCell.props.backgroundColor + : "default", + setColor: (color) => updateColor(color, "background"), + } + : undefined + } /> diff --git a/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/SplitButton.tsx b/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/SplitButton.tsx index 9d05c0ed65..1e8f5c9ab0 100644 --- a/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/SplitButton.tsx +++ b/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/SplitButton.tsx @@ -34,7 +34,8 @@ export const SplitButton = < if ( !currentCell || !isTableCell(currentCell) || - (getRowspan(currentCell) === 1 && getColspan(currentCell) === 1) + (getRowspan(currentCell) === 1 && getColspan(currentCell) === 1) || + !editor.settings.tables.splitCells ) { return null; } diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx index 0b615a146e..1010b5c34d 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx @@ -73,7 +73,13 @@ export const ColorPickerButton = < editor.setTextCursorPosition(props.block); }; - if (!currentCells || !currentCells[0] || !tableHandles) { + if ( + !currentCells || + !currentCells[0] || + !tableHandles || + (editor.settings.tables.cellTextColor === false && + editor.settings.tables.cellBackgroundColor === false) + ) { return null; } @@ -95,29 +101,38 @@ export const ColorPickerButton = < className={"bn-menu-dropdown bn-color-picker-dropdown"}> - isTableCell(cell) && - cell.props.textColor === firstCell.props.textColor - ) - ? firstCell.props.textColor - : "default", - setColor: (color) => { - updateColor(color, "text"); - }, - }} - background={{ - color: currentCells.every( - ({ cell }) => - isTableCell(cell) && - cell.props.backgroundColor === firstCell.props.backgroundColor - ) - ? firstCell.props.backgroundColor - : "default", - setColor: (color) => updateColor(color, "background"), - }} + text={ + editor.settings.tables.cellTextColor + ? { + // All cells have the same text color + color: currentCells.every( + ({ cell }) => + isTableCell(cell) && + cell.props.textColor === firstCell.props.textColor + ) + ? firstCell.props.textColor + : "default", + setColor: (color) => { + updateColor(color, "text"); + }, + } + : undefined + } + background={ + editor.settings.tables.cellBackgroundColor + ? { + color: currentCells.every( + ({ cell }) => + isTableCell(cell) && + cell.props.backgroundColor === + firstCell.props.backgroundColor + ) + ? firstCell.props.backgroundColor + : "default", + setColor: (color) => updateColor(color, "background"), + } + : undefined + } />
diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/TableHeaderButton.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/TableHeaderButton.tsx index 2efad1b0ae..1860127f64 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/TableHeaderButton.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/TableHeaderButton.tsx @@ -27,7 +27,12 @@ export const TableHeaderRowButton = < >(); const tableHandles = editor.tableHandles; - if (!tableHandles || props.index !== 0 || props.orientation !== "row") { + if ( + !tableHandles || + props.index !== 0 || + props.orientation !== "row" || + !editor.settings.tables.headers + ) { return null; } @@ -73,7 +78,12 @@ export const TableHeaderColumnButton = < >(); const tableHandles = editor.tableHandles; - if (!tableHandles || props.index !== 0 || props.orientation !== "column") { + if ( + !tableHandles || + props.index !== 0 || + props.orientation !== "column" || + !editor.settings.tables.headers + ) { return null; } diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index 1aa17a29dc..1952dca6da 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -657,6 +657,27 @@ "slug": "ui-components" } }, + { + "projectSlug": "advanced-tables", + "fullSlug": "ui-components/advanced-tables", + "pathFromRoot": "examples/03-ui-components/15-advanced-tables", + "config": { + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": [ + "Intermediate", + "UI Components", + "Tables", + "Appearance & Styling" + ] + }, + "title": "Advanced Tables", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" + } + }, { "projectSlug": "link-toolbar-buttons", "fullSlug": "ui-components/link-toolbar-buttons", From a46113ba55e032d04e61bd55e0d36a3d1cbd0e9d Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Fri, 28 Feb 2025 17:21:05 +0100 Subject: [PATCH 72/72] chore: add back translation --- packages/core/src/i18n/locales/no.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/core/src/i18n/locales/no.ts b/packages/core/src/i18n/locales/no.ts index 7c660e2955..105c0bd996 100644 --- a/packages/core/src/i18n/locales/no.ts +++ b/packages/core/src/i18n/locales/no.ts @@ -153,6 +153,8 @@ export const no: Dictionary = { drag_handle: { delete_menuitem: "Slett", colors_menuitem: "Farger", + header_row_menuitem: "Rad overskrift", + header_column_menuitem: "Kolonne overskrift", }, table_handle: { delete_column_menuitem: "Slett kolonne", @@ -161,6 +163,9 @@ export const no: Dictionary = { add_right_menuitem: "Legg til kolonne til høyre", add_above_menuitem: "Legg til rad over", add_below_menuitem: "Legg til rad under", + split_cell_menuitem: "Del celle", + merge_cells_menuitem: "Slå sammen celler", + background_color_menuitem: "Bakgrunnsfarge", }, suggestion_menu: { no_items_title: "Ingen elementer funnet", @@ -278,6 +283,9 @@ export const no: Dictionary = { comment: { tooltip: "Legg til kommentar", }, + table_cell_merge: { + tooltip: "Slå sammen celler", + }, }, file_panel: { upload: {