Skip to content

Commit f80a670

Browse files
author
Chris Sloop
authoredMay 4, 2021
Merge pull request #223 from contentful/SHE-10/update-slatejs-adapter
feat: [] update slate.js adapter
2 parents 6d28c01 + 33a3b6d commit f80a670

9 files changed

+26644
-4196
lines changed
 

‎packages/contentful-slatejs-adapter/package-lock.json

+26,344-3,848
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/contentful-slatejs-adapter/package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,17 @@
8888
"tslint": "^5.8.0",
8989
"tslint-config-prettier": "^1.1.0",
9090
"tslint-config-standard": "^7.0.0",
91-
"typescript": "^2.6.2"
91+
"typescript": "^4.2.4"
9292
},
9393
"publishConfig": {
9494
"access": "restricted"
9595
},
9696
"dependencies": {
9797
"@contentful/rich-text-types": "^5.0.0",
98+
"jest": "^26.6.3",
9899
"lodash.flatmap": "^4.5.0",
99100
"lodash.get": "^4.4.2",
100-
"lodash.omit": "^4.5.0"
101+
"lodash.omit": "^4.5.0",
102+
"typescript": "^4.2.4"
101103
}
102104
}

‎packages/contentful-slatejs-adapter/src/__test__/contentful-to-slatejs-adapter.test.ts

+169-164
Large diffs are not rendered by default.

‎packages/contentful-slatejs-adapter/src/__test__/slate-helpers.ts

-59
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import flatmap from 'lodash.flatmap';
22

3+
import { getDataOrDefault } from './helpers';
4+
5+
import { fromJSON, Schema, SchemaJSON } from './schema';
36
import * as Contentful from '@contentful/rich-text-types';
4-
import { ContentfulNode, SlateNode, ContentfulNonTextNodes } from './types';
5-
import { getDataOfDefault } from './helpers';
6-
import { SchemaJSON, fromJSON, Schema } from './schema';
7+
import {
8+
ContentfulNode,
9+
ContentfulElementNode,
10+
SlateNode,
11+
SlateElement,
12+
SlateText,
13+
SlateMarks,
14+
} from './types';
715

816
export interface ToSlatejsDocumentProperties {
917
document: Contentful.Document;
@@ -13,93 +21,50 @@ export interface ToSlatejsDocumentProperties {
1321
export default function toSlatejsDocument({
1422
document,
1523
schema,
16-
}: ToSlatejsDocumentProperties): Slate.Document {
17-
return {
18-
object: 'document',
19-
data: getDataOfDefault(document.data),
20-
nodes: flatmap(document.content, node => convertNode(node, fromJSON(schema))) as Slate.Block[],
21-
};
24+
}: ToSlatejsDocumentProperties): SlateNode[] {
25+
// TODO:
26+
// We allow adding data to the root document node, but Slate >v0.5.0
27+
// has no concept of a root document node. We should determine whether
28+
// this will be a compatibility problem for existing users.
29+
return flatmap(document.content, node => convertNode(node, fromJSON(schema)));
2230
}
2331

24-
function convertNode(node: ContentfulNode, schema: Schema): SlateNode[] {
25-
const nodes: SlateNode[] = [];
26-
32+
function convertNode(node: ContentfulNode, schema: Schema): SlateNode {
2733
if (node.nodeType === 'text') {
28-
const slateText = convertTextNode(node);
29-
30-
nodes.push(slateText);
34+
return convertTextNode(node as Contentful.Text);
3135
} else {
32-
const contentfulNode = node as ContentfulNonTextNodes;
36+
const contentfulNode = node as ContentfulElementNode;
3337
const childNodes = flatmap(contentfulNode.content, childNode => convertNode(childNode, schema));
34-
35-
const object = getSlateNodeObjectValue(contentfulNode.nodeType);
36-
let slateNode: SlateNode;
37-
if (object === 'inline') {
38-
slateNode = createInlineNode(contentfulNode, childNodes, schema);
39-
} else if (object === 'block') {
40-
slateNode = createBlockNode(contentfulNode, childNodes, schema);
41-
} else {
42-
throw new Error(`Unexpected slate object '${object}'`);
43-
}
44-
nodes.push(slateNode);
38+
const slateNode = convertElementNode(contentfulNode, childNodes, schema);
39+
return slateNode;
4540
}
46-
return nodes;
4741
}
4842

49-
function createBlockNode(
50-
contentfulBlock: ContentfulNonTextNodes,
51-
childNodes: SlateNode[],
43+
function convertElementNode(
44+
contentfulBlock: ContentfulElementNode,
45+
children: SlateNode[],
5246
schema: Schema,
53-
): Slate.Block {
47+
): SlateElement {
5448
return {
55-
object: 'block',
5649
type: contentfulBlock.nodeType,
57-
nodes: childNodes,
50+
children,
5851
isVoid: schema.isVoid(contentfulBlock),
59-
data: getDataOfDefault(contentfulBlock.data),
60-
} as Slate.Block;
52+
data: getDataOrDefault(contentfulBlock.data),
53+
};
6154
}
6255

63-
function createInlineNode(
64-
contentfulBlock: ContentfulNonTextNodes,
65-
childNodes: SlateNode[],
66-
schema: Schema,
67-
): Slate.Inline {
56+
function convertTextNode(node: Contentful.Text): SlateText {
6857
return {
69-
object: 'inline',
70-
type: contentfulBlock.nodeType,
71-
nodes: childNodes,
72-
isVoid: schema.isVoid(contentfulBlock),
73-
data: getDataOfDefault(contentfulBlock.data),
74-
} as Slate.Inline;
75-
}
76-
77-
function convertTextNode(node: ContentfulNode): Slate.Text {
78-
const { marks = [], value, data } = node as Contentful.Text;
79-
const slateText: Slate.Text = {
80-
object: 'text',
81-
leaves: [
82-
{
83-
object: 'leaf',
84-
text: value,
85-
marks: marks.map(mark => ({
86-
...mark,
87-
data: {},
88-
object: 'mark',
89-
})),
90-
} as Slate.TextLeaf,
91-
],
92-
data: getDataOfDefault(data),
58+
text: node.value,
59+
data: getDataOrDefault(node.data),
60+
...convertTextMarks(node)
9361
};
94-
return slateText;
9562
}
9663

97-
function getSlateNodeObjectValue(nodeType: string): 'inline' | 'block' {
98-
if (Object.values(Contentful.BLOCKS).includes(nodeType)) {
99-
return 'block';
100-
} else if (Object.values(Contentful.INLINES).includes(nodeType)) {
101-
return 'inline';
102-
} else {
103-
throw new Error(`Unexpected contentful nodeType '${nodeType}'`);
64+
function convertTextMarks(node: Contentful.Text): SlateMarks {
65+
const marks: SlateMarks = {};
66+
for (const mark of node.marks) {
67+
marks[mark.type as keyof SlateMarks] = true;
10468
}
69+
return marks;
10570
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/**
22
* Ensures that data defaults to an empty object.
33
*/
4-
export const getDataOfDefault = (value?: Record<string, any>) => value || {};
4+
export const getDataOrDefault = (value?: Record<string, any>) => value || {};

‎packages/contentful-slatejs-adapter/src/schema.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import get from 'lodash.get';
22
import * as Contentful from '@contentful/rich-text-types';
3-
import { ContentfulNonTextNodes } from './types';
3+
import { ContentfulElementNode } from './types';
44

55
const defaultSchema: SchemaJSON = {};
66

@@ -15,7 +15,7 @@ export interface SchemaJSON {
1515
inlines?: Record<string, SchemaValue>;
1616
}
1717
export interface Schema extends SchemaJSON {
18-
isVoid(node: ContentfulNonTextNodes): boolean;
18+
isVoid(node: ContentfulElementNode): boolean;
1919
}
2020

2121
export interface SchemaValue {
@@ -36,10 +36,10 @@ export function fromJSON(schema: SchemaJSON = defaultSchema): Schema {
3636
/**
3737
* Check if a `node` is void based on the schema rules.
3838
*
39-
* @param {ContentfulNonTextNodes} node
39+
* @param {ContentfulElementNode} node
4040
* @returns
4141
*/
42-
isVoid(node: ContentfulNonTextNodes) {
42+
isVoid(node: ContentfulElementNode) {
4343
const root = Object.values(Contentful.BLOCKS).includes(node.nodeType) ? 'blocks' : 'inlines';
4444
return get(schema, [root, node.nodeType as string, 'isVoid'], false);
4545
},
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,80 @@
11
import flatMap from 'lodash.flatmap';
2-
import * as Contentful from '@contentful/rich-text-types';
3-
import { ContentfulNode, SlateNode } from './types';
4-
import { getDataOfDefault } from './helpers';
2+
import { getDataOrDefault } from './helpers';
53
import { SchemaJSON, Schema, fromJSON } from './schema';
64

5+
import * as Contentful from '@contentful/rich-text-types';
6+
import {
7+
ContentfulNode,
8+
ContentfulElementNode,
9+
SlateNode,
10+
SlateElement,
11+
SlateText,
12+
SlateMarks,
13+
} from './types';
14+
715
export interface ToContentfulDocumentProperties {
8-
document: Slate.Document;
16+
document: SlateNode[];
917
schema?: SchemaJSON;
1018
}
1119

1220
export default function toContentfulDocument({
1321
document,
1422
schema,
1523
}: ToContentfulDocumentProperties): Contentful.Document {
24+
// TODO:
25+
// We allow adding data to the root document node, but Slate >v0.5.0
26+
// has no concept of a root document node. We should determine whether
27+
// this will be a compatibility problem for existing users.
1628
return {
1729
nodeType: Contentful.BLOCKS.DOCUMENT,
18-
data: getDataOfDefault(document.data),
30+
data: {},
1931
content: flatMap(
20-
document.nodes,
32+
document,
2133
node => convertNode(node, fromJSON(schema)) as Contentful.Block[],
2234
),
2335
};
2436
}
2537

26-
function convertNode(node: SlateNode, schema: Schema): ContentfulNode[] {
38+
function convertNode(
39+
node: SlateNode,
40+
schema: Schema
41+
): ContentfulNode[] {
2742
const nodes: ContentfulNode[] = [];
28-
switch (node.object) {
29-
case 'block':
30-
case 'inline':
31-
const slateNode = node as Slate.Block;
32-
const content = flatMap(slateNode.nodes, childNode => convertNode(childNode, schema));
33-
if (!slateNode.type) {
34-
throw new Error(`Unexpected slate node ${JSON.stringify(slateNode)}`);
35-
}
36-
37-
const contentfulBlock: Contentful.Block = {
38-
nodeType: slateNode.type,
39-
content: [],
40-
data: getDataOfDefault(slateNode.data),
41-
};
42-
43-
if (!schema.isVoid(contentfulBlock)) {
44-
contentfulBlock.content = content;
45-
}
46-
nodes.push(contentfulBlock);
47-
break;
48-
case 'text':
49-
convertText(node as Slate.Text).forEach(childNode => nodes.push(childNode));
50-
break;
51-
default:
52-
assertUnreachable(node);
53-
break;
43+
if (isSlateElement(node)) {
44+
const contentfulElement: ContentfulElementNode = {
45+
nodeType: node.type,
46+
data: getDataOrDefault(node.data),
47+
content: [],
48+
};
49+
if (!schema.isVoid(contentfulElement)) {
50+
contentfulElement.content = flatMap(node.children, childNode => convertNode(childNode, schema));
51+
}
52+
nodes.push(contentfulElement);
53+
} else {
54+
const contentfulText = convertText(node);
55+
nodes.push(contentfulText);
5456
}
55-
5657
return nodes;
5758
}
5859

59-
function convertText(node: Slate.Text): Contentful.Text[] {
60-
return node.leaves.map<Contentful.Text>(leaf => ({
60+
function convertText(node: SlateText): Contentful.Text {
61+
const { text, data, ...marks } = node;
62+
return {
6163
nodeType: 'text',
62-
value: leaf.text,
63-
marks: leaf.marks ? leaf.marks.map(mark => ({ type: mark.type })) : [],
64-
data: getDataOfDefault(node.data),
65-
}));
64+
value: node.text,
65+
marks: getMarkList(marks),
66+
data: getDataOrDefault(node.data),
67+
};
68+
}
69+
70+
function getMarkList(marks: SlateMarks): Contentful.Mark[] {
71+
const contentfulMarks: Contentful.Mark[] = [];
72+
for (const mark of Object.keys(marks)) {
73+
contentfulMarks.push({ type: mark });
74+
}
75+
return contentfulMarks;
6676
}
6777

68-
function assertUnreachable(object: never): never {
69-
throw new Error(`Unexpected slate object ${object}`);
78+
function isSlateElement(node: SlateNode): node is SlateElement {
79+
return 'type' in node;
7080
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
11
import * as Contentful from '@contentful/rich-text-types';
22

3-
export type ContentfulNode = Contentful.Block | Contentful.Inline | Contentful.Text;
4-
export type SlateNode = Slate.Block | Slate.Inline | Slate.Text;
5-
export type ContentfulNonTextNodes = Contentful.Block | Contentful.Inline;
3+
export type SlateMarks = {
4+
// This is a workaround for TypeScript's limitations around
5+
// index property exclusion. Ideally we'd join the above properties
6+
// with something like
7+
//
8+
// & { [mark: string]: string }
9+
//
10+
// but TypeScript doesn't allow us to create such objects, only
11+
// work around inconsistencies in existing JavaScript.
12+
//
13+
// In reality Slate's node marks are arbitrary, but for this library
14+
// denoting marks used by the tests as optional should be okay.
15+
bold?: boolean;
16+
italic?: boolean;
17+
underline?: boolean;
18+
};
19+
20+
export type SlateText = SlateMarks & {
21+
text: string;
22+
data: object;
23+
};
24+
25+
export type SlateElement = {
26+
type: string;
27+
data: object;
28+
isVoid: boolean;
29+
children: SlateNode[];
30+
};
31+
32+
export type ContentfulElementNode = Contentful.Block | Contentful.Inline;
33+
export type ContentfulNode = ContentfulElementNode | Contentful.Text;
34+
export type SlateNode = SlateElement | SlateText;

0 commit comments

Comments
 (0)
Please sign in to comment.