Skip to content

Commit ffee96d

Browse files
Merge pull request #14 from moveyourdigital/improve-typescript-typing
Improve typescript typing
2 parents 151b5d4 + 0b27de0 commit ffee96d

File tree

13 files changed

+136
-66
lines changed

13 files changed

+136
-66
lines changed

.github/CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,6 @@ The core team is monitoring for pull requests. We will review your pull request
3333
1) Fork the repository and create your branch from the correct branch/tag.
3434
2) Run `npm install` in the repository root.
3535
3) Add or change tests as needed.
36-
4) Ensure the test suite passes with `npm test`. Tip: `npm run test:watch` development.
36+
4) Ensure the test suite passes with `npm test`. Tip: run `npm run test:watch` during development.
3737
5) Run `npm test -- -u` to update the jest snapshots and commit these changes as well (if there are any updates).
3838
6) Run `npm run list && npm run format` and commit any changes.

README.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ Blocks are independent and you can import only a set of them and use them diretl
3434
```jsx
3535
import { Header } from 'editorjs-react-renderer';
3636

37-
const dataHeader = {
38-
"text": "Heading 2",
39-
"level": 2
37+
const dataHeader: HeaderBlockData = {
38+
text: "Heading 2",
39+
level: 2
4040
}
4141

42-
export const Heading () => <Header data={dataHeader} />;
42+
export const Heading () => <Header data={dataHeader} className="text-xl" />;
4343
```
4444

4545
## Internal blocks
@@ -144,17 +144,16 @@ So, in theory, any CSS framework (such as Bootstrap) can work seamlessly with th
144144
You can provide your own custom renderers or replace the default ones by passing a `renderers` object to the `Blocks`.
145145

146146
```tsx
147-
const Checklist = ({
147+
const Checklist: RenderFn<{
148+
items: string[]
149+
}> = ({
148150
data, className = ""
149-
}: {
150-
data: {[s:string]: any}
151-
className?: string
152151
}) => {
153152

154153
return (
155154
<>
156155
{data?.items.map((item, i) => (
157-
<p key={i}>
156+
<p key={i} className={className}>
158157
<label>
159158
<input type="checkbox" /> {HTMLReactParser(item)}
160159
</label>

src/index.test.tsx

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { create } from 'react-test-renderer';
3-
import Blocks, { DataProp } from '.';
3+
import Blocks, { ConfigProp, DataProp, RenderersProp, RenderFn } from '.';
44

55
describe('<Block />', () => {
66
describe('when receives an EditorJS blocks data', () => {
@@ -118,4 +118,63 @@ describe('<Block />', () => {
118118
expect(create(<Blocks data={data} />).toJSON()).toMatchSnapshot();
119119
});
120120
});
121+
122+
describe('when receives a Renderers object', () => {
123+
const data: DataProp = {
124+
time: 1610632160642,
125+
blocks: [
126+
{
127+
type: 'paragraph',
128+
data: {
129+
text: 'Mollit deserunt culpa fugiat ea do laboris minim ex do. Elit cillum qui aute sint irure aliqua excepteur minim. Eiusmod velit cupidatat ea culpa magna magna id consectetur enim irure ex excepteur tempor quis. Veniam incididunt ullamco adipisicing dolor ex proident ex amet dolor. Nisi in adipisicing non quis id Lorem consectetur.',
130+
},
131+
},
132+
{
133+
type: 'code',
134+
data: {
135+
code: 'const foo = new Bar();',
136+
lang: 'text/javascript',
137+
},
138+
},
139+
],
140+
version: '2.19.0',
141+
};
142+
143+
const CustomRenderCode: RenderFn<{
144+
code: string | number
145+
lang: "text/javascript" | "text/typescript"
146+
}> = ({ data: d, className }) => (
147+
<div>
148+
<pre>
149+
{d?.code && (
150+
<code lang={d.lang} className={className}>{`${d.code}`}</code>
151+
)}
152+
</pre>
153+
<p>Warning: do not run this code in production</p>
154+
</div>
155+
)
156+
157+
const CustomRenderHeader = ({
158+
data: d,
159+
className,
160+
}: {
161+
data: {[s:string]: any},
162+
className?: string
163+
}) => (
164+
<h1></h1>
165+
)
166+
167+
const renderers: RenderersProp = {
168+
code: CustomRenderCode,
169+
header: CustomRenderHeader,
170+
}
171+
172+
it('uses the provided renderer for the specified block', () => {
173+
expect(create(<Blocks data={data} renderers={renderers} />).toJSON()).toMatchSnapshot();
174+
});
175+
176+
it('must maintan backward compatibility with 0.1.x format', () => {
177+
expect(create(<Blocks data={data} renderers={renderers} />).toJSON()).toMatchSnapshot();
178+
});
179+
});
121180
});

src/index.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,24 @@ import Paragraph from './renderers/paragraph';
99
import Quote from './renderers/quote';
1010
import Table from './renderers/table';
1111

12-
export interface ConfigProp {
13-
[s: string]: {
14-
[s: string]: any;
15-
};
16-
}
12+
export type ConfigProp = Record<string, RenderConfig>
1713

18-
export interface RenderersProp {
19-
[s: string]: any;
20-
}
14+
export type RenderConfig = Record<string, any>
15+
16+
export type RenderFn<T = undefined, K = Record<string, any> | undefined> = (_: {
17+
data: T
18+
className?: string
19+
} & K) => JSX.Element;
20+
21+
export type RenderFnWithoutData<K = Record<string, any> | undefined> = (_: {
22+
className?: string
23+
} & K) => JSX.Element;
24+
25+
export type RenderersProp = Record<string, RenderFn<any>>
2126

2227
export interface Block {
2328
type: string;
24-
data: {
25-
[s: string]: any;
26-
};
29+
data: Record<string, any>;
2730
}
2831

2932
export interface DataProp {

src/renderers/code/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import React from 'react';
1+
import React, { FC } from 'react';
2+
import { RenderFn } from '../..';
23

34
export interface CodeBlockData {
45
code: string;
56
lang?: string;
67
}
78

8-
const Code = ({ data, className = '' }: { data: CodeBlockData; className?: string }) => {
9+
const Code: RenderFn<CodeBlockData> = ({ data, className = '' }) => {
910
const props: {
1011
[s: string]: string;
1112
} = {};

src/renderers/delimiter/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
2+
import { RenderFnWithoutData } from '../..';
23

3-
const Delimiter = ({ className = '' }: { className?: string }) => {
4+
const Delimiter: RenderFnWithoutData = ({ className = '' }) => {
45
const props: {
56
[s: string]: string;
67
} = {};

src/renderers/embed/index.tsx

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import HTMLReactParser from 'html-react-parser';
3+
import { RenderFn } from '../..';
34

45
export interface EmbedBlockData {
56
service: string;
@@ -10,20 +11,20 @@ export interface EmbedBlockData {
1011
caption?: string;
1112
}
1213

13-
const Embed = ({
14+
export interface EmbedBlockConfig {
15+
rel?: string;
16+
sandbox?: string | null;
17+
}
18+
19+
const Embed: RenderFn<EmbedBlockData, EmbedBlockConfig> = ({
1420
data,
1521
className = '',
1622
rel = 'noreferer nofollower external',
1723
sandbox,
18-
}: {
19-
data: EmbedBlockData;
20-
className?: string;
21-
rel?: string;
22-
sandbox?: string | null;
2324
}) => {
2425
const classNames: string[] = [];
2526
if (className) classNames.push(className);
26-
classNames.push(`embed-block-service-${data.service}`);
27+
classNames.push(`embed-block-service-${data?.service}`);
2728

2829
const figureprops: {
2930
[s: string]: string;
@@ -33,11 +34,11 @@ const Embed = ({
3334
figureprops.className = classNames.join(' ');
3435
}
3536

36-
if (data.width) {
37+
if (data?.width) {
3738
figureprops.width = data.width.toString();
3839
}
3940

40-
if (data.height) {
41+
if (data?.height) {
4142
figureprops.height = data.height.toString();
4243
}
4344

@@ -47,14 +48,14 @@ const Embed = ({
4748

4849
return (
4950
<figure>
50-
{data.embed ? (
51+
{data?.embed ? (
5152
<iframe src={data.embed} {...figureprops} frameBorder="0" data-src={data.source}></iframe>
5253
) : (
53-
<a href={data.source} target="_blank" rel={rel}>
54-
{data.source}
54+
<a href={data?.source} target="_blank" rel={rel}>
55+
{data?.source}
5556
</a>
5657
)}
57-
{data.caption && <figcaption>{HTMLReactParser(data.caption)}</figcaption>}
58+
{data?.caption && <figcaption>{HTMLReactParser(data.caption)}</figcaption>}
5859
</figure>
5960
);
6061
};

src/renderers/header/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import React from 'react';
22
import HTMLReactParser from 'html-react-parser';
3+
import { RenderFn } from '../..';
34

45
export interface HeaderBlockData {
56
text: string;
67
level: number;
78
}
89

9-
const Header = ({ data, className = '' }: { data: HeaderBlockData; className?: string }) => {
10+
const Header: RenderFn<HeaderBlockData> = ({ data, className = '' }) => {
1011
const props: {
1112
[s: string]: string;
1213
} = {};
@@ -15,7 +16,7 @@ const Header = ({ data, className = '' }: { data: HeaderBlockData; className?: s
1516
props.className = className;
1617
}
1718

18-
const Tag = `h${data.level || 1}` as keyof JSX.IntrinsicElements;
19+
const Tag = `h${data?.level || 1}` as keyof JSX.IntrinsicElements;
1920
return <Tag {...props}>{data?.text && HTMLReactParser(data.text)}</Tag>;
2021
};
2122

src/renderers/image/index.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import HTMLReactParser from 'html-react-parser';
3+
import { RenderFn } from '../..';
34

45
export interface ImageBlockData {
56
file: {
@@ -13,26 +14,26 @@ export interface ImageBlockData {
1314
[s: string]: any;
1415
}
1516

16-
const Image = ({
17+
export interface ImageBlockConfig {
18+
actionsClassNames?: {
19+
[s: string]: string;
20+
}
21+
}
22+
23+
const Image: RenderFn<ImageBlockData, ImageBlockConfig> = ({
1724
data,
1825
className = '',
1926
actionsClassNames = {
2027
stretched: 'image-block--stretched',
2128
withBorder: 'image-block--with-border',
2229
withBackground: 'image-block--with-background',
2330
},
24-
}: {
25-
data: ImageBlockData;
26-
className?: string;
27-
actionsClassNames?: {
28-
[s: string]: string;
29-
};
3031
}) => {
3132
const classNames: string[] = [];
3233
if (className) classNames.push(className);
3334

3435
Object.keys(actionsClassNames).forEach((actionName) => {
35-
if (data[actionName] === true && actionName in actionsClassNames) {
36+
if (data && data[actionName] === true && actionName in actionsClassNames) {
3637
// @ts-ignore
3738
classNames.push(actionsClassNames[actionName]);
3839
}
@@ -49,7 +50,7 @@ const Image = ({
4950
return (
5051
<figure {...figureprops}>
5152
{data?.file?.url && <img src={data.file.url} alt={data.caption || data.file.name} />}
52-
{data.caption && <figcaption>{HTMLReactParser(data.caption)}</figcaption>}
53+
{data?.caption && <figcaption>{HTMLReactParser(data.caption)}</figcaption>}
5354
</figure>
5455
);
5556
};

src/renderers/list/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import React from 'react';
22
import HTMLReactParser from 'html-react-parser';
3+
import { RenderFn } from '../..';
34

45
export interface ListBlockData {
56
style: 'ordered' | 'unordered';
67
items: string[];
78
}
89

9-
const List = ({ data, className = '' }: { data: ListBlockData; className?: string }) => {
10+
const List: RenderFn<ListBlockData> = ({ data, className = '' }) => {
1011
const props: {
1112
[s: string]: string;
1213
} = {};

0 commit comments

Comments
 (0)