Skip to content

Commit 6fcc777

Browse files
committed
working ish
1 parent a26db91 commit 6fcc777

File tree

10 files changed

+179
-3
lines changed

10 files changed

+179
-3
lines changed

components/block-table.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
import React from 'react';
3+
4+
export default function BlockTable({ value }: { value: { rows: { cells: string[] }[] } }) {
5+
const { rows } = value;
6+
if (!rows) {
7+
return null;
8+
}
9+
return (
10+
<table>
11+
<tbody>
12+
{rows.map((row, rowIndex) => (
13+
<tr key={rowIndex}>
14+
{row.cells.map((cell, cellIndex) => (
15+
<td key={cellIndex}>{cell}</td>
16+
))}
17+
</tr>
18+
))}
19+
</tbody>
20+
</table>
21+
);
22+
}

components/portable-text.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const CodePenEmbed = dynamic(() => import("@/components/codepen-embed"));
2323
const CodeSandboxEmbed = dynamic(() => import("./codesandbox-embed"));
2424
const HTMLEmbed = dynamic(() => import("@/components/html-embed"));
2525
const QuoteEmbed = dynamic(() => import("@/components/quote-embed"));
26+
const BlockTable = dynamic(() => import("@/components/block-table"));
2627

2728
export default function CustomPortableText({
2829
className,
@@ -41,6 +42,7 @@ export default function CustomPortableText({
4142
twitter: ({ value }) => <TwitterEmbed {...value} />,
4243
htmlBlock: ({ value }) => <HTMLEmbed {...value} />,
4344
quote: ({ value }) => <QuoteEmbed {...value} />,
45+
table: ({ value }) => <BlockTable value={value} />,
4446
},
4547
block: {
4648
h5: ({ children }) => (

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"jwt-decode": "^4.0.0",
7676
"lucide-react": "^0.544.0",
7777
"micromark": "^4.0.2",
78+
"micromark-extension-gfm-table": "^2.1.1",
7879
"next": "^15.5.3",
7980
"next-cloudinary": "^6.16.0",
8081
"next-sanity": "^11.1.0",

pnpm-lock.yaml

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sanity.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import settings from "@/sanity/schemas/singletons/settings";
4545
import sponsor from "@/sanity/schemas/documents/sponsor";
4646
import sponsorshipRequest from "@/sanity/schemas/documents/sponsorshipRequest";
4747
import youtubeUpdateTask from "@/sanity/schemas/documents/youtubeUpdateTask";
48+
import table from "@/sanity/schemas/objects/table";
4849
import { resolveHref } from "@/sanity/lib/utils";
4950

5051
const homeLocation = {
@@ -142,6 +143,7 @@ export default defineConfig({
142143
youtubeUpdateTask,
143144
previewSession,
144145
sponsorshipRequest,
146+
table,
145147
],
146148
},
147149
document: {

sanity/components/pastehandler.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { htmlToBlocks } from "@portabletext/block-tools";
22
import { micromark } from "micromark";
3+
import { gfmTable, gfmTableHtml } from "micromark-extension-gfm-table";
34

45
interface Input {
56
event: ClipboardEvent;
@@ -31,7 +32,10 @@ export async function handlePaste(
3132
const json = event.clipboardData?.getData("application/json");
3233

3334
if (text && !json) {
34-
const html = micromark(text);
35+
const html = micromark(text, {
36+
extensions: [gfmTable()],
37+
htmlExtensions: [gfmTableHtml()],
38+
});
3539
return html
3640
? convertHtmlToSanityPortableTextPatch(html, schemaTypes, path)
3741
: undefined;
@@ -45,18 +49,32 @@ function convertHtmlToSanityPortableTextPatch(
4549
schemaTypes: SchemaTypes,
4650
path: Array<any>,
4751
): InsertPatch | undefined {
48-
if (!isCodeTypeAvailable(schemaTypes) || !html) return undefined;
52+
if (!isCodeTypeAvailable(schemaTypes) || !isTableTypeAvailable(schemaTypes) || !html) return undefined;
4953

5054
const blocks = htmlToBlocks(html, schemaTypes.portableText, {
5155
rules: [
5256
// @ts-ignore
5357
{ deserialize: deserializeCodeBlockElement },
58+
// @ts-ignore
59+
{ deserialize: deserializeTableElement },
5460
],
5561
});
5662

5763
return blocks ? { insert: blocks, path } : undefined;
5864
}
5965

66+
function isTableTypeAvailable(schemaTypes: SchemaTypes): boolean {
67+
const hasTableType = schemaTypes.blockObjects.some(
68+
(type) => type.name === "table",
69+
);
70+
if (!hasTableType) {
71+
console.warn(
72+
'A table type is not defined in the schema. This is required to paste tables.',
73+
);
74+
}
75+
return hasTableType;
76+
}
77+
6078
function isCodeTypeAvailable(schemaTypes: SchemaTypes): boolean {
6179
const hasCodeType = schemaTypes.blockObjects.some(
6280
(type) => type.name === "code",
@@ -125,3 +143,26 @@ function mapLanguageAliasToActualLanguage(languageAlias: string): string {
125143
(languageAlias as string)
126144
);
127145
}
146+
147+
function deserializeTableElement(
148+
el: Element,
149+
next: any,
150+
block: (block: any) => any,
151+
) {
152+
if (el?.tagName?.toLowerCase() !== 'table') {
153+
return undefined;
154+
}
155+
156+
const rows = Array.from(el.querySelectorAll('tr')).map((tr) => {
157+
const cells = Array.from(tr.querySelectorAll('th, td')).map((td) => td.textContent);
158+
return {
159+
_type: 'row',
160+
cells,
161+
};
162+
});
163+
164+
return block({
165+
_type: 'table',
166+
rows,
167+
});
168+
}

sanity/extract.json

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,60 @@
11
[
2+
{
3+
"name": "table",
4+
"type": "type",
5+
"value": {
6+
"type": "object",
7+
"attributes": {
8+
"_type": {
9+
"type": "objectAttribute",
10+
"value": {
11+
"type": "string",
12+
"value": "table"
13+
}
14+
},
15+
"rows": {
16+
"type": "objectAttribute",
17+
"value": {
18+
"type": "array",
19+
"of": {
20+
"type": "object",
21+
"attributes": {
22+
"cells": {
23+
"type": "objectAttribute",
24+
"value": {
25+
"type": "array",
26+
"of": {
27+
"type": "string"
28+
}
29+
},
30+
"optional": true
31+
},
32+
"_type": {
33+
"type": "objectAttribute",
34+
"value": {
35+
"type": "string",
36+
"value": "row"
37+
}
38+
}
39+
},
40+
"rest": {
41+
"type": "object",
42+
"attributes": {
43+
"_key": {
44+
"type": "objectAttribute",
45+
"value": {
46+
"type": "string"
47+
}
48+
}
49+
}
50+
}
51+
}
52+
},
53+
"optional": true
54+
}
55+
}
56+
}
57+
},
258
{
359
"name": "sponsorshipRequest",
460
"type": "document",

sanity/schemas/objects/table.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
import { defineType, defineField } from 'sanity'
3+
4+
export default defineType({
5+
name: 'table',
6+
title: 'Table',
7+
type: 'object',
8+
fields: [
9+
defineField({
10+
name: 'rows',
11+
title: 'Rows',
12+
type: 'array',
13+
of: [
14+
{
15+
type: 'object',
16+
name: 'row',
17+
fields: [
18+
{
19+
type: 'array',
20+
name: 'cells',
21+
of: [{ type: 'string' }],
22+
},
23+
],
24+
},
25+
],
26+
}),
27+
],
28+
})

sanity/schemas/partials/base.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ const baseType = defineType({
9898
defineArrayMember(twitter),
9999
defineArrayMember(html),
100100
defineArrayMember(quote),
101+
defineArrayMember({ type: 'table' }),
101102
],
102103
}),
103104
],

sanity/types.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@
1313
*/
1414

1515
// Source: schema.json
16+
export type Table = {
17+
_type: "table";
18+
rows?: Array<{
19+
cells?: Array<string>;
20+
_type: "row";
21+
_key: string;
22+
}>;
23+
};
24+
1625
export type SponsorshipRequest = {
1726
_id: string;
1827
_type: "sponsorshipRequest";
@@ -1766,7 +1775,7 @@ export type SanityAssetSourceData = {
17661775
url?: string;
17671776
};
17681777

1769-
export type AllSanitySchemaTypes = SponsorshipRequest | PreviewSession | YoutubeUpdateTask | Sponsor | Lesson | Author | Post | Podcast | Guest | PodcastType | Course | Page | Settings | PodcastRssEpisode | Code | CloudinaryAssetContextCustom | CloudinaryAssetDerived | CloudinaryAsset | CloudinaryAssetContext | SanityAssistInstructionTask | SanityAssistTaskStatus | SanityAssistSchemaTypeAnnotations | SanityAssistOutputType | SanityAssistOutputField | SanityAssistInstructionContext | AssistInstructionContext | SanityAssistInstructionUserInput | SanityAssistInstructionPrompt | SanityAssistInstructionFieldRef | SanityAssistInstruction | SanityAssistSchemaTypeField | SanityImagePaletteSwatch | SanityImagePalette | SanityImageDimensions | SanityImageHotspot | SanityImageCrop | SanityFileAsset | SanityImageAsset | SanityImageMetadata | Geopoint | Slug | SanityAssetSourceData;
1778+
export type AllSanitySchemaTypes = Table | SponsorshipRequest | PreviewSession | YoutubeUpdateTask | Sponsor | Lesson | Author | Post | Podcast | Guest | PodcastType | Course | Page | Settings | PodcastRssEpisode | Code | CloudinaryAssetContextCustom | CloudinaryAssetDerived | CloudinaryAsset | CloudinaryAssetContext | SanityAssistInstructionTask | SanityAssistTaskStatus | SanityAssistSchemaTypeAnnotations | SanityAssistOutputType | SanityAssistOutputField | SanityAssistInstructionContext | AssistInstructionContext | SanityAssistInstructionUserInput | SanityAssistInstructionPrompt | SanityAssistInstructionFieldRef | SanityAssistInstruction | SanityAssistSchemaTypeField | SanityImagePaletteSwatch | SanityImagePalette | SanityImageDimensions | SanityImageHotspot | SanityImageCrop | SanityFileAsset | SanityImageAsset | SanityImageMetadata | Geopoint | Slug | SanityAssetSourceData;
17701779
export declare const internalGroqTypeReferenceTo: unique symbol;
17711780
// Source: sanity/lib/queries.ts
17721781
// Variable: docCount

0 commit comments

Comments
 (0)