hero-editor 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hero-editor",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {
@@ -15,6 +15,10 @@ const svgIcons = {
15
15
  '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14h-2V9h-2V7h4v10z"/></svg>',
16
16
  looks_two:
17
17
  '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 8c0 1.11-.9 2-2 2h-2v2h4v2H9v-4c0-1.11.9-2 2-2h2V9H9V7h4c1.1 0 2 .89 2 2v2z"/></svg>',
18
+ looks_three:
19
+ '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M360-280h160q33 0 56.5-23.5T600-360v-60q0-26-17-43t-43-17q26 0 43-17t17-43v-60q0-33-23.5-56.5T520-680H360v80h160v80h-80v80h80v80H360v80ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Z"/></svg>',
20
+ looks_four:
21
+ '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M520-280h80v-400h-80v160h-80v-160h-80v240h160v160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Z"/></svg>',
18
22
  link: '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M13.723 18.654l-3.61 3.609c-2.316 2.315-6.063 2.315-8.378 0-1.12-1.118-1.735-2.606-1.735-4.188 0-1.582.615-3.07 1.734-4.189l4.866-4.865c2.355-2.355 6.114-2.262 8.377 0 .453.453.81.973 1.089 1.527l-1.593 1.592c-.18-.613-.5-1.189-.964-1.652-1.448-1.448-3.93-1.51-5.439-.001l-.001.002-4.867 4.865c-1.5 1.499-1.5 3.941 0 5.44 1.517 1.517 3.958 1.488 5.442 0l2.425-2.424c.993.284 1.791.335 2.654.284zm.161-16.918l-3.574 3.576c.847-.05 1.655 0 2.653.283l2.393-2.389c1.498-1.502 3.94-1.5 5.44-.001 1.517 1.518 1.486 3.959 0 5.442l-4.831 4.831-.003.002c-1.438 1.437-3.886 1.552-5.439-.002-.473-.474-.785-1.042-.956-1.643l-.084.068-1.517 1.515c.28.556.635 1.075 1.088 1.528 2.245 2.245 6.004 2.374 8.378 0l4.832-4.831c2.314-2.316 2.316-6.062-.001-8.377-2.317-2.321-6.067-2.313-8.379-.002z"/></svg>',
19
23
  icon: '<svg width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6.75 0C10.0637 0 12.75 2.68629 12.75 6C12.75 9.31371 10.0637 12 6.75 12C3.43629 12 0.75 9.31371 0.75 6C0.75 2.68629 3.43629 0 6.75 0ZM6.75 0.84375C3.90228 0.84375 1.59375 3.15228 1.59375 6C1.59375 8.84772 3.90228 11.1562 6.75 11.1562C9.59772 11.1562 11.9062 8.84772 11.9062 6C11.9062 3.15228 9.59772 0.84375 6.75 0.84375ZM8.83711 7.61484C9.04551 7.71904 9.12998 7.97246 9.02578 8.18086C8.53481 9.16281 7.75947 9.67969 6.75 9.67969C5.74053 9.67969 4.96519 9.16281 4.47422 8.18086C4.37002 7.97246 4.45449 7.71904 4.66289 7.61484C4.87129 7.51064 5.12471 7.59512 5.22891 7.80352C5.58168 8.50907 6.07197 8.83594 6.75 8.83594C7.42803 8.83594 7.91832 8.50907 8.27109 7.80352C8.37529 7.59512 8.62871 7.51064 8.83711 7.61484ZM4.3125 3.76172C4.83027 3.76172 5.25 4.18146 5.25 4.69922C5.25 5.21699 4.83027 5.63672 4.3125 5.63672C3.79473 5.63672 3.375 5.21699 3.375 4.69922C3.375 4.18146 3.79473 3.76172 4.3125 3.76172ZM9.15234 3.76172C9.67011 3.76172 10.0898 4.18146 10.0898 4.69922C10.0898 5.21699 9.67011 5.63672 9.15234 5.63672C8.63458 5.63672 8.21484 5.21699 8.21484 4.69922C8.21484 4.18146 8.63458 3.76172 9.15234 3.76172Z" fill="black"/></svg>',
20
24
  };
@@ -27,6 +27,18 @@ const styles = {
27
27
  margin: '10px 5px',
28
28
  borderRight: 'solid thin #eaeaea',
29
29
  },
30
+ select: {
31
+ margin: 5,
32
+ height: 28,
33
+ border: 'solid thin silver',
34
+ borderRadius: 4,
35
+ padding: '0 4px',
36
+ cursor: 'pointer',
37
+ background: 'transparent',
38
+ fontSize: 12,
39
+ outline: 'none',
40
+ maxWidth: 120,
41
+ },
30
42
  };
31
43
 
32
44
  const Toolbar = ({ children }) => <div style={styles.toolbar}>{children}</div>;
@@ -44,5 +56,22 @@ Toolbar.Button = forwardRef(({ active, ...props }, ref) => (
44
56
  ));
45
57
 
46
58
  Toolbar.Separator = () => <div style={styles.separator} />;
59
+ Toolbar.Dropdown = forwardRef(
60
+ ({ options = [], currentValue, onChange, ...props }, ref) => (
61
+ <select
62
+ ref={ref}
63
+ onChange={onChange}
64
+ style={styles.select}
65
+ value={currentValue}
66
+ {...props}
67
+ >
68
+ {options.map((option) => (
69
+ <option key={option.value} value={option.value}>
70
+ {option.label}
71
+ </option>
72
+ ))}
73
+ </select>
74
+ ),
75
+ );
47
76
 
48
77
  export default Toolbar;
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import Toolbar from '../Toolbar';
4
+
5
+ const OPTIONS = [
6
+ { value: 'normal', label: 'Normal text' },
7
+ { value: 'heading-one', label: 'Heading 1' },
8
+ ];
9
+
10
+ describe('Toolbar.Dropdown', () => {
11
+ it('renders a select with the correct options', () => {
12
+ const { getAllByRole } = render(
13
+ <Toolbar.Dropdown
14
+ options={OPTIONS}
15
+ currentValue="normal"
16
+ onChange={() => {}}
17
+ />,
18
+ );
19
+ const options = getAllByRole('option');
20
+ expect(options).toHaveLength(2);
21
+ expect(options[0].value).toBe('normal');
22
+ expect(options[1].value).toBe('heading-one');
23
+ });
24
+
25
+ it('reflects the currentValue as the selected option', () => {
26
+ const { getByRole } = render(
27
+ <Toolbar.Dropdown
28
+ options={OPTIONS}
29
+ currentValue="heading-one"
30
+ onChange={() => {}}
31
+ />,
32
+ );
33
+ expect(getByRole('combobox').value).toBe('heading-one');
34
+ });
35
+
36
+ it('applies a numeric margin style to the select element', () => {
37
+ const { getByRole } = render(
38
+ <Toolbar.Dropdown
39
+ options={OPTIONS}
40
+ currentValue="normal"
41
+ onChange={() => {}}
42
+ />,
43
+ );
44
+ expect(getByRole('combobox').style.margin).toBe('5px');
45
+ });
46
+
47
+ it('renders with no options', () => {
48
+ const { getByRole } = render(
49
+ <Toolbar.Dropdown
50
+ options={[]}
51
+ currentValue={undefined}
52
+ onChange={() => {}}
53
+ />,
54
+ );
55
+ expect(getByRole('combobox')).toBeTruthy();
56
+ });
57
+ });
58
+
59
+ describe('Toolbar.Button', () => {
60
+ it('renders a button', () => {
61
+ const { getByRole } = render(<Toolbar.Button>Click</Toolbar.Button>);
62
+ expect(getByRole('button')).toBeTruthy();
63
+ });
64
+
65
+ it('applies active style when active is true', () => {
66
+ const { getByRole } = render(<Toolbar.Button active>Click</Toolbar.Button>);
67
+ expect(getByRole('button').style.background).toBe('rgb(245, 245, 245)');
68
+ });
69
+
70
+ it('does not apply active style when active is false', () => {
71
+ const { getByRole } = render(
72
+ <Toolbar.Button active={false}>Click</Toolbar.Button>,
73
+ );
74
+ expect(getByRole('button').style.background).toBe('transparent');
75
+ });
76
+ });
77
+
78
+ describe('Toolbar.Separator', () => {
79
+ it('renders a div', () => {
80
+ const { container } = render(<Toolbar.Separator />);
81
+ expect(container.firstChild.tagName).toBe('DIV');
82
+ });
83
+ });
package/src/constants.js CHANGED
@@ -7,6 +7,8 @@ const EMOJI = 'emoji';
7
7
  const LINK = 'link';
8
8
  const HEADING_ONE = 'heading-one';
9
9
  const HEADING_TWO = 'heading-two';
10
+ const HEADING_THREE = 'heading-three';
11
+ const HEADING_FOUR = 'heading-four';
10
12
  const PARAGRAPH = 'paragraph';
11
13
 
12
14
  // Marks
@@ -49,6 +51,8 @@ export {
49
51
  LINK,
50
52
  HEADING_ONE,
51
53
  HEADING_TWO,
54
+ HEADING_THREE,
55
+ HEADING_FOUR,
52
56
  PARAGRAPH,
53
57
  BOLD,
54
58
  ITALIC,
package/src/lib.js CHANGED
@@ -199,5 +199,12 @@ export { default as plainSerializer } from './serializers/plain';
199
199
  export { default as makeReactTransformer } from './transformers/react';
200
200
  export { defaultReactTransformer } from './transformers/react';
201
201
  export { isEmptyContent, getUrl, isUrl, getUrlFromNode } from './helpers';
202
- export { EMPTY_VALUE } from './constants';
202
+ export {
203
+ EMPTY_VALUE,
204
+ PARAGRAPH,
205
+ HEADING_ONE,
206
+ HEADING_TWO,
207
+ HEADING_THREE,
208
+ HEADING_FOUR,
209
+ } from './constants';
203
210
  export default HeroEditor;
@@ -0,0 +1,11 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`headingFour plugin renders h4 element 1`] = `
4
+ <body>
5
+ <div>
6
+ <h4>
7
+ Heading four
8
+ </h4>
9
+ </div>
10
+ </body>
11
+ `;
@@ -0,0 +1,11 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`headingThree plugin renders h3 element 1`] = `
4
+ <body>
5
+ <div>
6
+ <h3>
7
+ Heading three
8
+ </h3>
9
+ </div>
10
+ </body>
11
+ `;
@@ -0,0 +1,160 @@
1
+ import { createEditor } from 'slate';
2
+ import { render, fireEvent, waitFor } from '@testing-library/react';
3
+ import React from 'react';
4
+ import dropdown from '../dropdown';
5
+ import { createPlugin, postMessage, withId } from '../../helpers';
6
+ import { PARAGRAPH, HEADING_ONE, HEADING_TWO } from '../../constants';
7
+
8
+ jest.mock('slate-react', () => ({
9
+ useSlate: jest.fn(),
10
+ }));
11
+
12
+ const { useSlate } = require('slate-react');
13
+
14
+ const OPTIONS = [
15
+ { value: PARAGRAPH, label: 'Normal text' },
16
+ { value: HEADING_ONE, label: 'Heading 1' },
17
+ { value: HEADING_TWO, label: 'Heading 2' },
18
+ ];
19
+
20
+ const renderToolbarButton = (plugin) => {
21
+ const ToolbarButton = plugin.ToolbarButton;
22
+ return render(<ToolbarButton />);
23
+ };
24
+
25
+ describe('dropdown plugin', () => {
26
+ beforeEach(() => {
27
+ const editor = withId('sample')(createEditor());
28
+ editor.children = [{ type: PARAGRAPH, children: [{ text: '' }] }];
29
+ editor.selection = null;
30
+ useSlate.mockReturnValue(editor);
31
+ });
32
+
33
+ it('is a plugin object', () => {
34
+ const plugin = dropdown({ name: 'test-dropdown', options: OPTIONS });
35
+ for (const prop in createPlugin()) {
36
+ expect(plugin[prop]).toBeDefined();
37
+ }
38
+ });
39
+
40
+ it('handles dropdown message and sets block type', async () => {
41
+ const editor = withId('sample')(createEditor());
42
+ dropdown({ name: 'test-dropdown', options: OPTIONS }).handleMessage(editor);
43
+ editor.insertNode({
44
+ type: PARAGRAPH,
45
+ children: [{ text: 'Some text' }],
46
+ });
47
+
48
+ postMessage('test-dropdown', { type: HEADING_ONE }, editor);
49
+
50
+ await waitFor(() => {
51
+ expect(editor.children[0].type).toBe(HEADING_ONE);
52
+ });
53
+ });
54
+
55
+ it('ignores message when data.type is missing', async () => {
56
+ const editor = withId('sample')(createEditor());
57
+ dropdown({ name: 'test-dropdown', options: OPTIONS }).handleMessage(editor);
58
+ editor.insertNode({
59
+ type: PARAGRAPH,
60
+ children: [{ text: 'Some text' }],
61
+ });
62
+
63
+ postMessage('test-dropdown', {}, editor);
64
+
65
+ await waitFor(() => {
66
+ expect(editor.children[0].type).toBe(PARAGRAPH);
67
+ });
68
+ });
69
+
70
+ it('provides a ToolbarButton', () => {
71
+ const plugin = dropdown({ name: 'test-dropdown', options: OPTIONS });
72
+ expect(plugin.ToolbarButton).toBeDefined();
73
+ });
74
+
75
+ it('provides a ToolbarButton even with empty options', () => {
76
+ const plugin = dropdown({ name: 'test-dropdown', options: [] });
77
+ expect(plugin.ToolbarButton).toBeDefined();
78
+ });
79
+
80
+ it('renders a separator before the dropdown when leftSeparator is true', () => {
81
+ const { container } = renderToolbarButton(
82
+ dropdown({
83
+ name: 'test-dropdown',
84
+ options: OPTIONS,
85
+ leftSeparator: true,
86
+ }),
87
+ );
88
+ const children = container.childNodes;
89
+ expect(children[0].tagName).toBe('DIV');
90
+ expect(children[1].tagName).toBe('SELECT');
91
+ });
92
+
93
+ it('renders a separator after the dropdown when rightSeparator is true', () => {
94
+ const { container } = renderToolbarButton(
95
+ dropdown({
96
+ name: 'test-dropdown',
97
+ options: OPTIONS,
98
+ rightSeparator: true,
99
+ }),
100
+ );
101
+ const children = container.childNodes;
102
+ expect(children[0].tagName).toBe('SELECT');
103
+ expect(children[1].tagName).toBe('DIV');
104
+ });
105
+
106
+ it('renders separators on both sides when both leftSeparator and rightSeparator are true', () => {
107
+ const { container } = renderToolbarButton(
108
+ dropdown({
109
+ name: 'test-dropdown',
110
+ options: OPTIONS,
111
+ leftSeparator: true,
112
+ rightSeparator: true,
113
+ }),
114
+ );
115
+ const children = container.childNodes;
116
+ expect(children[0].tagName).toBe('DIV');
117
+ expect(children[1].tagName).toBe('SELECT');
118
+ expect(children[2].tagName).toBe('DIV');
119
+ });
120
+
121
+ it('renders only the dropdown when both separators are false', () => {
122
+ const { container } = renderToolbarButton(
123
+ dropdown({
124
+ name: 'test-dropdown',
125
+ options: OPTIONS,
126
+ leftSeparator: false,
127
+ rightSeparator: false,
128
+ }),
129
+ );
130
+ const children = container.childNodes;
131
+ expect(children).toHaveLength(1);
132
+ expect(children[0].tagName).toBe('SELECT');
133
+ });
134
+
135
+ it('returns null when options is empty', () => {
136
+ const { container } = renderToolbarButton(
137
+ dropdown({ name: 'test-dropdown', options: [] }),
138
+ );
139
+ expect(container.firstChild).toBeNull();
140
+ });
141
+
142
+ it('calls postMessage with the selected value when onChange fires', async () => {
143
+ const editor = withId('onChange-test')(createEditor());
144
+ useSlate.mockReturnValue(editor);
145
+
146
+ dropdown({ name: 'test-dropdown', options: OPTIONS }).handleMessage(editor);
147
+
148
+ editor.insertNode({ type: PARAGRAPH, children: [{ text: 'Some text' }] });
149
+
150
+ const { getByRole } = renderToolbarButton(
151
+ dropdown({ name: 'test-dropdown', options: OPTIONS }),
152
+ );
153
+
154
+ fireEvent.change(getByRole('combobox'), { target: { value: HEADING_ONE } });
155
+
156
+ await waitFor(() => {
157
+ expect(editor.children[0].type).toBe(HEADING_ONE);
158
+ });
159
+ });
160
+ });
@@ -0,0 +1,27 @@
1
+ import { render } from '@testing-library/react';
2
+ import headingFour from '../headingFour';
3
+ import { createPlugin, renderDefaultElement } from '../../helpers';
4
+
5
+ describe('headingFour plugin', () => {
6
+ it('is a plugin object', () => {
7
+ for (const prop in createPlugin()) {
8
+ expect(headingFour()[prop]).toBeDefined();
9
+ }
10
+ });
11
+
12
+ it('renders h4 element', () => {
13
+ const { baseElement } = render(
14
+ headingFour().renderElement(renderDefaultElement)({
15
+ element: { type: 'heading-four' },
16
+ children: 'Heading four',
17
+ }),
18
+ );
19
+ expect(baseElement).toMatchSnapshot();
20
+ });
21
+
22
+ it('still provides ToolbarButton when showToolbarButton is false', () => {
23
+ expect(
24
+ headingFour({ showToolbarButton: false }).ToolbarButton,
25
+ ).toBeDefined();
26
+ });
27
+ });
@@ -18,4 +18,10 @@ describe('headingOne plugin', () => {
18
18
  );
19
19
  expect(baseElement).toMatchSnapshot();
20
20
  });
21
+
22
+ it('still provides ToolbarButton when showToolbarButton is false', () => {
23
+ expect(
24
+ headingOne({ showToolbarButton: false }).ToolbarButton,
25
+ ).toBeDefined();
26
+ });
21
27
  });
@@ -0,0 +1,27 @@
1
+ import { render } from '@testing-library/react';
2
+ import headingThree from '../headingThree';
3
+ import { createPlugin, renderDefaultElement } from '../../helpers';
4
+
5
+ describe('headingThree plugin', () => {
6
+ it('is a plugin object', () => {
7
+ for (const prop in createPlugin()) {
8
+ expect(headingThree()[prop]).toBeDefined();
9
+ }
10
+ });
11
+
12
+ it('renders h3 element', () => {
13
+ const { baseElement } = render(
14
+ headingThree().renderElement(renderDefaultElement)({
15
+ element: { type: 'heading-three' },
16
+ children: 'Heading three',
17
+ }),
18
+ );
19
+ expect(baseElement).toMatchSnapshot();
20
+ });
21
+
22
+ it('still provides ToolbarButton when showToolbarButton is false', () => {
23
+ expect(
24
+ headingThree({ showToolbarButton: false }).ToolbarButton,
25
+ ).toBeDefined();
26
+ });
27
+ });
@@ -18,4 +18,10 @@ describe('headingTwo plugin', () => {
18
18
  );
19
19
  expect(baseElement).toMatchSnapshot();
20
20
  });
21
+
22
+ it('still provides ToolbarButton when showToolbarButton is false', () => {
23
+ expect(
24
+ headingTwo({ showToolbarButton: false }).ToolbarButton,
25
+ ).toBeDefined();
26
+ });
21
27
  });
@@ -0,0 +1,64 @@
1
+ import React from 'react';
2
+ import { Transforms } from 'slate';
3
+ import { useSlate } from 'slate-react';
4
+ import {
5
+ createPlugin,
6
+ postMessage,
7
+ addMessageListener,
8
+ isBlockActive,
9
+ isList,
10
+ } from '../helpers';
11
+ import Toolbar from '../components/Toolbar';
12
+
13
+ const makeToolbarButton =
14
+ ({ name, options, leftSeparator, rightSeparator }) =>
15
+ () => {
16
+ const editor = useSlate();
17
+ if (options.length === 0) {
18
+ return null;
19
+ }
20
+ const currentValue =
21
+ options.find(({ value }) => isBlockActive(editor, value))?.value ??
22
+ options[0]?.value;
23
+
24
+ return (
25
+ <>
26
+ {leftSeparator === true && <Toolbar.Separator />}
27
+ <Toolbar.Dropdown
28
+ options={options}
29
+ currentValue={currentValue}
30
+ onChange={(event) => {
31
+ event.preventDefault();
32
+ postMessage(name, { type: event.target.value }, editor);
33
+ }}
34
+ />
35
+ {rightSeparator === true && <Toolbar.Separator />}
36
+ </>
37
+ );
38
+ };
39
+
40
+ export default ({
41
+ name,
42
+ options = [],
43
+ leftSeparator = false,
44
+ rightSeparator = false,
45
+ }) =>
46
+ createPlugin({
47
+ name,
48
+ ToolbarButton: makeToolbarButton({
49
+ name,
50
+ options,
51
+ leftSeparator,
52
+ rightSeparator,
53
+ }),
54
+ handleMessage: addMessageListener(name, ({ editor, data }) => {
55
+ if (!data?.type) {
56
+ return;
57
+ }
58
+ Transforms.unwrapNodes(editor, {
59
+ match: isList,
60
+ split: true,
61
+ });
62
+ Transforms.setNodes(editor, { type: data.type });
63
+ }),
64
+ });
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import { Transforms } from 'slate';
3
+ import { useSlate } from 'slate-react';
4
+ import {
5
+ createPlugin,
6
+ isBlockActive,
7
+ makeRenderElement,
8
+ addMessageListener,
9
+ postMessage,
10
+ isList,
11
+ } from '../helpers';
12
+ import Toolbar from '../components/Toolbar';
13
+ import Icon from '../components/Icon';
14
+ import { HEADING_FOUR } from '../constants';
15
+
16
+ const renderElement = makeRenderElement(
17
+ HEADING_FOUR,
18
+ ({ attributes, children }) => <h4 {...attributes}>{children}</h4>,
19
+ );
20
+
21
+ const handleMessage = addMessageListener(HEADING_FOUR, ({ editor }) => {
22
+ const isActive = isBlockActive(editor, HEADING_FOUR);
23
+
24
+ // Unwrap all the items of any list
25
+ Transforms.unwrapNodes(editor, {
26
+ match: isList,
27
+ split: true,
28
+ });
29
+
30
+ Transforms.setNodes(editor, {
31
+ type: isActive ? 'paragraph' : HEADING_FOUR,
32
+ });
33
+ });
34
+
35
+ const ToolbarButton = ({ showToolbarButton }) => {
36
+ const editor = useSlate();
37
+
38
+ if (!showToolbarButton) return null;
39
+
40
+ return (
41
+ <Toolbar.Button
42
+ active={isBlockActive(editor, HEADING_FOUR)}
43
+ onMouseDown={(event) => {
44
+ event.preventDefault();
45
+ postMessage(HEADING_FOUR, {}, editor);
46
+ }}
47
+ >
48
+ <Icon>looks_four</Icon>
49
+ </Toolbar.Button>
50
+ );
51
+ };
52
+
53
+ export default ({ showToolbarButton = true } = {}) =>
54
+ createPlugin({
55
+ name: HEADING_FOUR,
56
+ renderElement,
57
+ handleMessage,
58
+ ToolbarButton: () => (
59
+ <ToolbarButton showToolbarButton={showToolbarButton} />
60
+ ),
61
+ isActive: (editor) => isBlockActive(editor, HEADING_FOUR),
62
+ });
@@ -32,9 +32,11 @@ const handleMessage = addMessageListener(HEADING_ONE, ({ editor }) => {
32
32
  });
33
33
  });
34
34
 
35
- const ToolbarButton = () => {
35
+ const ToolbarButton = ({ showToolbarButton }) => {
36
36
  const editor = useSlate();
37
37
 
38
+ if (!showToolbarButton) return null;
39
+
38
40
  return (
39
41
  <Toolbar.Button
40
42
  active={isBlockActive(editor, HEADING_ONE)}
@@ -48,11 +50,13 @@ const ToolbarButton = () => {
48
50
  );
49
51
  };
50
52
 
51
- export default () =>
53
+ export default ({ showToolbarButton = true } = {}) =>
52
54
  createPlugin({
53
55
  name: HEADING_ONE,
54
56
  renderElement,
55
57
  handleMessage,
56
- ToolbarButton,
58
+ ToolbarButton: () => (
59
+ <ToolbarButton showToolbarButton={showToolbarButton} />
60
+ ),
57
61
  isActive: (editor) => isBlockActive(editor, HEADING_ONE),
58
62
  });
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import { Transforms } from 'slate';
3
+ import { useSlate } from 'slate-react';
4
+ import {
5
+ createPlugin,
6
+ isBlockActive,
7
+ makeRenderElement,
8
+ addMessageListener,
9
+ postMessage,
10
+ isList,
11
+ } from '../helpers';
12
+ import Toolbar from '../components/Toolbar';
13
+ import Icon from '../components/Icon';
14
+ import { HEADING_THREE } from '../constants';
15
+
16
+ const renderElement = makeRenderElement(
17
+ HEADING_THREE,
18
+ ({ attributes, children }) => <h3 {...attributes}>{children}</h3>,
19
+ );
20
+
21
+ const handleMessage = addMessageListener(HEADING_THREE, ({ editor }) => {
22
+ const isActive = isBlockActive(editor, HEADING_THREE);
23
+
24
+ // Unwrap all the items of any list
25
+ Transforms.unwrapNodes(editor, {
26
+ match: isList,
27
+ split: true,
28
+ });
29
+
30
+ Transforms.setNodes(editor, {
31
+ type: isActive ? 'paragraph' : HEADING_THREE,
32
+ });
33
+ });
34
+
35
+ const ToolbarButton = ({ showToolbarButton }) => {
36
+ const editor = useSlate();
37
+
38
+ if (!showToolbarButton) return null;
39
+
40
+ return (
41
+ <Toolbar.Button
42
+ active={isBlockActive(editor, HEADING_THREE)}
43
+ onMouseDown={(event) => {
44
+ event.preventDefault();
45
+ postMessage(HEADING_THREE, {}, editor);
46
+ }}
47
+ >
48
+ <Icon>looks_three</Icon>
49
+ </Toolbar.Button>
50
+ );
51
+ };
52
+
53
+ export default ({ showToolbarButton = true } = {}) =>
54
+ createPlugin({
55
+ name: HEADING_THREE,
56
+ renderElement,
57
+ handleMessage,
58
+ ToolbarButton: () => (
59
+ <ToolbarButton showToolbarButton={showToolbarButton} />
60
+ ),
61
+ isActive: (editor) => isBlockActive(editor, HEADING_THREE),
62
+ });