@vonaffenfels/slate-editor 1.0.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.
Files changed (77) hide show
  1. package/.babelrc +44 -0
  2. package/README.md +5 -0
  3. package/componentLoader.js +52 -0
  4. package/dist/BlockEditor.css +93 -0
  5. package/dist/BlockEditor.js +2 -0
  6. package/dist/BlockEditor.js.LICENSE.txt +61 -0
  7. package/dist/Renderer.js +2 -0
  8. package/dist/Renderer.js.LICENSE.txt +15 -0
  9. package/dist/fromHTML.js +1 -0
  10. package/dist/index.css +93 -0
  11. package/dist/index.js +2 -0
  12. package/dist/index.js.LICENSE.txt +69 -0
  13. package/dist/toHTML.js +2 -0
  14. package/dist/toHTML.js.LICENSE.txt +23 -0
  15. package/dist/toText.js +2 -0
  16. package/dist/toText.js.LICENSE.txt +23 -0
  17. package/package.json +79 -0
  18. package/postcss.config.js +7 -0
  19. package/scss/demo.scss +142 -0
  20. package/scss/editor.scss +394 -0
  21. package/scss/storybook.scss +66 -0
  22. package/scss/toolbar.scss +160 -0
  23. package/src/BlockEditor.js +252 -0
  24. package/src/Blocks/EmptyBlock.js +12 -0
  25. package/src/Blocks/EmptyWrapper.js +5 -0
  26. package/src/Blocks/ErrorBoundary.js +41 -0
  27. package/src/Blocks/LayoutBlock.js +179 -0
  28. package/src/Blocks/LayoutSlot.js +61 -0
  29. package/src/Context/StorybookContext.js +7 -0
  30. package/src/Nodes/Default.js +151 -0
  31. package/src/Nodes/Element.js +72 -0
  32. package/src/Nodes/Leaf.js +40 -0
  33. package/src/Nodes/Storybook.js +170 -0
  34. package/src/Nodes/StorybookDisplay.js +118 -0
  35. package/src/Nodes/Text.js +67 -0
  36. package/src/Renderer.js +41 -0
  37. package/src/Serializer/Html.js +43 -0
  38. package/src/Serializer/Serializer.js +318 -0
  39. package/src/Serializer/Text.js +18 -0
  40. package/src/Serializer/ads.js +175 -0
  41. package/src/Serializer/index.js +4 -0
  42. package/src/SidebarEditor/SidebarEditorField.js +249 -0
  43. package/src/SidebarEditor.css +90 -0
  44. package/src/SidebarEditor.js +236 -0
  45. package/src/Storybook.js +152 -0
  46. package/src/Toolbar/Align.js +65 -0
  47. package/src/Toolbar/Block.js +121 -0
  48. package/src/Toolbar/Element.js +49 -0
  49. package/src/Toolbar/Formats.js +60 -0
  50. package/src/Toolbar/Insert.js +29 -0
  51. package/src/Toolbar/Layout.js +333 -0
  52. package/src/Toolbar/Link.js +165 -0
  53. package/src/Toolbar/Toolbar.js +164 -0
  54. package/src/Tools/Margin.js +52 -0
  55. package/src/dev/App.js +61 -0
  56. package/src/dev/draftToSlate.json +3148 -0
  57. package/src/dev/index.css +3 -0
  58. package/src/dev/index.html +11 -0
  59. package/src/dev/index.js +5 -0
  60. package/src/dev/sampleValue1.json +4295 -0
  61. package/src/dev/sampleValue2.json +0 -0
  62. package/src/dev/sampleValueValid.json +411 -0
  63. package/src/dev/testComponents/TestStory.js +9 -0
  64. package/src/dev/testComponents/TestStory.stories.js +172 -0
  65. package/src/dev/testSampleValue.json +747 -0
  66. package/src/fromHTML.js +5 -0
  67. package/src/index.js +9 -0
  68. package/src/plugins/ListItem.js +49 -0
  69. package/src/plugins/SoftBreak.js +24 -0
  70. package/src/toHTML.js +7 -0
  71. package/src/toText.js +7 -0
  72. package/src/util.js +20 -0
  73. package/storyLoader.js +46 -0
  74. package/tailwind.config.js +5 -0
  75. package/webpack.config.build.js +53 -0
  76. package/webpack.config.dev.js +61 -0
  77. package/webpack.config.js +117 -0
@@ -0,0 +1,333 @@
1
+ import {useSlate} from "slate-react";
2
+ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
3
+ import React from "react";
4
+ import {Transforms} from "slate";
5
+ import classNames from "classnames";
6
+
7
+
8
+ export const InsertGridButton = () => {
9
+ return <span
10
+ className={
11
+ classNames({"toolbar-btn-expand": true})
12
+ }
13
+ >
14
+ <div className="toolbar-btn-expand-inner">
15
+ <span className="toolbar-btn">Grid hinzufügen</span>
16
+ <InsertLayoutButton insert={{
17
+ "type": "layout",
18
+ "children": [
19
+ {
20
+ "type": "layout-slot",
21
+ "attributes": {"name": "grid-1"},
22
+ "children": [
23
+ {
24
+ "type": "paragraph",
25
+ "children": [
26
+ {"text": ""},
27
+ ],
28
+ },
29
+ ],
30
+ },
31
+ {
32
+ "type": "layout-slot",
33
+ "attributes": {"name": "grid-2"},
34
+ "children": [
35
+ {
36
+ "type": "paragraph",
37
+ "children": [
38
+ {"text": ""},
39
+ ],
40
+ },
41
+ ],
42
+ },
43
+ ],
44
+ }}>
45
+ <span>1 | 2</span>
46
+ </InsertLayoutButton>
47
+ <InsertLayoutButton insert={{
48
+ "type": "layout",
49
+ "children": [
50
+ {
51
+ "type": "layout-slot",
52
+ "attributes": {"name": "grid-2"},
53
+ "children": [
54
+ {
55
+ "type": "paragraph",
56
+ "children": [
57
+ {"text": ""},
58
+ ],
59
+ },
60
+ ],
61
+ },
62
+ {
63
+ "type": "layout-slot",
64
+ "attributes": {"name": "grid-1"},
65
+ "children": [
66
+ {
67
+ "type": "paragraph",
68
+ "children": [
69
+ {"text": ""},
70
+ ],
71
+ },
72
+ ],
73
+ },
74
+ ],
75
+ }}>
76
+ <span>2 | 1</span>
77
+ </InsertLayoutButton>
78
+ <InsertLayoutButton insert={{
79
+ "type": "layout",
80
+ "children": [
81
+ {
82
+ "type": "layout-slot",
83
+ "attributes": {"name": "grid-half"},
84
+ "children": [
85
+ {
86
+ "type": "paragraph",
87
+ "children": [
88
+ {"text": ""},
89
+ ],
90
+ },
91
+ ],
92
+ },
93
+ {
94
+ "type": "layout-slot",
95
+ "attributes": {"name": "grid-half"},
96
+ "children": [
97
+ {
98
+ "type": "paragraph",
99
+ "children": [
100
+ {"text": ""},
101
+ ],
102
+ },
103
+ ],
104
+ },
105
+ ],
106
+ }}>
107
+ <span>1 | 1 </span>
108
+ </InsertLayoutButton>
109
+ <InsertLayoutButton insert={{
110
+ "type": "layout",
111
+ "children": [
112
+ {
113
+ "type": "layout-slot",
114
+ "attributes": {"name": "grid-1"},
115
+ "children": [
116
+ {
117
+ "type": "paragraph",
118
+ "children": [
119
+ {"text": ""},
120
+ ],
121
+ },
122
+ ],
123
+ },
124
+ {
125
+ "type": "layout-slot",
126
+ "attributes": {"name": "grid-1"},
127
+ "children": [
128
+ {
129
+ "type": "paragraph",
130
+ "children": [
131
+ {"text": ""},
132
+ ],
133
+ },
134
+ ],
135
+ },
136
+ {
137
+ "type": "layout-slot",
138
+ "attributes": {"name": "grid-1"},
139
+ "children": [
140
+ {
141
+ "type": "paragraph",
142
+ "children": [
143
+ {"text": ""},
144
+ ],
145
+ },
146
+ ],
147
+ },
148
+ ],
149
+ }}>
150
+ <span>1 | 1 | 1</span>
151
+ </InsertLayoutButton>
152
+ <InsertLayoutButton insert={{
153
+ "type": "layout",
154
+ "children": [
155
+ {
156
+ "type": "layout-slot",
157
+ "attributes": {"name": "grid-1"},
158
+ "children": [
159
+ {
160
+ "type": "paragraph",
161
+ "children": [
162
+ {"text": ""},
163
+ ],
164
+ },
165
+ ],
166
+ },
167
+ {
168
+ "type": "layout-slot",
169
+ "attributes": {"name": "grid-1"},
170
+ "children": [
171
+ {
172
+ "type": "paragraph",
173
+ "children": [
174
+ {"text": ""},
175
+ ],
176
+ },
177
+ ],
178
+ },
179
+ {
180
+ "type": "layout-slot",
181
+ "attributes": {"name": "grid-1"},
182
+ "children": [
183
+ {
184
+ "type": "paragraph",
185
+ "children": [
186
+ {"text": ""},
187
+ ],
188
+ },
189
+ ],
190
+ },
191
+ {
192
+ "type": "layout-slot",
193
+ "attributes": {"name": "grid-1"},
194
+ "children": [
195
+ {
196
+ "type": "paragraph",
197
+ "children": [
198
+ {"text": ""},
199
+ ],
200
+ },
201
+ ],
202
+ },
203
+ ],
204
+ }}>
205
+ <span>1 | 1 | 1 | 1</span>
206
+ </InsertLayoutButton>
207
+ <InsertLayoutButton insert={{
208
+ "type": "layout",
209
+ "children": [
210
+ {
211
+ "type": "layout-slot",
212
+ "attributes": {"name": "grid-auto"},
213
+ "children": [
214
+ {
215
+ "type": "paragraph",
216
+ "children": [
217
+ {"text": ""},
218
+ ],
219
+ },
220
+ ],
221
+ },
222
+ {
223
+ "type": "layout-slot",
224
+ "attributes": {"name": "grid-fill"},
225
+ "children": [
226
+ {
227
+ "type": "paragraph",
228
+ "children": [
229
+ {"text": ""},
230
+ ],
231
+ },
232
+ ],
233
+ },
234
+ ],
235
+ }}>
236
+ <span>auto | fill</span>
237
+ </InsertLayoutButton>
238
+ <InsertLayoutButton insert={{
239
+ "type": "layout",
240
+ "children": [
241
+ {
242
+ "type": "layout-slot",
243
+ "attributes": {"name": "grid-fill"},
244
+ "children": [
245
+ {
246
+ "type": "paragraph",
247
+ "children": [
248
+ {"text": ""},
249
+ ],
250
+ },
251
+ ],
252
+ },
253
+ {
254
+ "type": "layout-slot",
255
+ "attributes": {"name": "grid-auto"},
256
+ "children": [
257
+ {
258
+ "type": "paragraph",
259
+ "children": [
260
+ {"text": ""},
261
+ ],
262
+ },
263
+ ],
264
+ },
265
+ ],
266
+ }}>
267
+ <span>fill | auto</span>
268
+ </InsertLayoutButton>
269
+ <InsertLayoutButton insert={{
270
+ "type": "layout",
271
+ "children": [
272
+ {
273
+ "type": "layout-slot",
274
+ "attributes": {"name": "grid-auto"},
275
+ "children": [
276
+ {
277
+ "type": "paragraph",
278
+ "children": [
279
+ {"text": ""},
280
+ ],
281
+ },
282
+ ],
283
+ },
284
+ {
285
+ "type": "layout-slot",
286
+ "attributes": {"name": "grid-fill"},
287
+ "children": [
288
+ {
289
+ "type": "paragraph",
290
+ "children": [
291
+ {"text": ""},
292
+ ],
293
+ },
294
+ ],
295
+ },
296
+ {
297
+ "type": "layout-slot",
298
+ "attributes": {"name": "grid-auto"},
299
+ "children": [
300
+ {
301
+ "type": "paragraph",
302
+ "children": [
303
+ {"text": ""},
304
+ ],
305
+ },
306
+ ],
307
+ },
308
+ ],
309
+ }}>
310
+ <span>auto | fill | auto</span>
311
+ </InsertLayoutButton>
312
+ </div>
313
+ </span>;
314
+ };
315
+
316
+ export const InsertLayoutButton = ({
317
+ insert,
318
+ icon,
319
+ children,
320
+ }) => {
321
+ const editor = useSlate();
322
+ const onClick = (event) => {
323
+ event.preventDefault();
324
+ Transforms.insertNodes(editor, [insert]);
325
+ };
326
+
327
+ return (
328
+ <span onMouseDown={onClick} className="toolbar-btn">
329
+ {!!icon && <FontAwesomeIcon icon={icon} />}
330
+ {children}
331
+ </span>
332
+ );
333
+ };
@@ -0,0 +1,165 @@
1
+ import {useSlate} from "slate-react";
2
+ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
3
+ import React, {useState} from "react";
4
+ import classNames from "classnames";
5
+ import {
6
+ Editor, Transforms, Element as SlateElement, Range,
7
+ } from "slate";
8
+ import {faLink} from "@fortawesome/free-solid-svg-icons";
9
+
10
+ const initialLinkState = {
11
+ href: "",
12
+ title: "",
13
+ noFollow: false,
14
+ };
15
+
16
+ export const LinkButtonElement = ({
17
+ icon,
18
+ children,
19
+ }) => {
20
+ const [savedSelection, setSavedSelection] = useState(null);
21
+ const [linkState, setLinkState] = useState(initialLinkState);
22
+ const editor = useSlate();
23
+ const onClick = (event) => {
24
+ event.preventDefault();
25
+ editor.selection = savedSelection;
26
+ toggleLink(
27
+ editor, linkState.href, linkState.title, linkState.noFollow,
28
+ );
29
+ };
30
+
31
+ const onInputChange = (name, value) => {
32
+ setLinkState({
33
+ ...linkState,
34
+ [name]: value,
35
+ });
36
+ };
37
+
38
+ const saveSelection = () => {
39
+ if (editor.selection) {
40
+ const activeLink = getActiveLink(editor);
41
+ if (activeLink) {
42
+ setLinkState({
43
+ href: activeLink[0].attributes.href,
44
+ title: activeLink[0].attributes.title,
45
+ noFollow: activeLink[0].attributes.noFollow,
46
+ });
47
+ }
48
+
49
+ setSavedSelection(editor.selection);
50
+ }
51
+ };
52
+
53
+ const removeLink = () => {
54
+ editor.selection = savedSelection;
55
+ toggleLink(editor);
56
+ };
57
+
58
+ const resetLinkState = () => {
59
+ setLinkState(initialLinkState);
60
+ };
61
+
62
+ const linkActive = isLinkActive(editor);
63
+
64
+ return (
65
+ <span
66
+ className={
67
+ classNames({
68
+ "toolbar-btn": true,
69
+ "active": linkActive,
70
+ })
71
+ }
72
+ onMouseEnter={saveSelection}
73
+ onMouseLeave={resetLinkState}
74
+ >
75
+ {!!icon && <FontAwesomeIcon icon={icon} />}
76
+ {children}
77
+ <span className="toolbar-btn-config">
78
+ <span className="toolbar-btn-config-inner">
79
+ <span className="toolbar-btn-config-row">
80
+ <label className="toolbar-form-label">
81
+ URL
82
+ </label>
83
+ <span className="toolbar-form-input">
84
+ <input type="text" value={linkState.href} onChange={(e) => onInputChange("href", e.target.value)}/>
85
+ </span>
86
+ </span>
87
+ <span className="toolbar-btn-config-row">
88
+ <label className="toolbar-form-label">
89
+ Titel
90
+ </label>
91
+ <span className="toolbar-form-input">
92
+ <input type="text" value={linkState.title} onChange={(e) => onInputChange("title", e.target.value)}/>
93
+ </span>
94
+ </span>
95
+ <span className="toolbar-btn-config-row">
96
+ <label className="toolbar-form-label">
97
+ noFollow
98
+ </label>
99
+ <span className="toolbar-form-input">
100
+ <input type="checkbox" value="1" checked={linkState.noFollow} onChange={(e) => onInputChange("noFollow", e.target.checked)}/>
101
+ </span>
102
+ </span>
103
+ <span className="toolbar-btn-config-row">
104
+ <span className="toolbar-btn" onMouseDown={onClick}>Apply</span>
105
+ {linkActive && <span className="toolbar-btn" onMouseDown={removeLink}>Remove</span>}
106
+ </span>
107
+ </span>
108
+ </span>
109
+ </span>
110
+ );
111
+ };
112
+
113
+ export const getActiveLink = (editor) => {
114
+ const [link] = Editor.nodes(editor, {
115
+ match: n => {
116
+ return !Editor.isEditor(n) && SlateElement.isElement(n) && n.type === 'link';
117
+ },
118
+ });
119
+ return link;
120
+ };
121
+
122
+ export const isLinkActive = (editor) => {
123
+ return !!getActiveLink(editor);
124
+ };
125
+
126
+
127
+ const unwrapLink = editor => {
128
+ Transforms.unwrapNodes(editor, {
129
+ match: n =>
130
+ !Editor.isEditor(n) && SlateElement.isElement(n) && n.type === 'link',
131
+ });
132
+ };
133
+
134
+ const toggleLink = (
135
+ editor, href, title = "", nofollow = false,
136
+ ) => {
137
+ if (isLinkActive(editor)) {
138
+ unwrapLink(editor);
139
+ }
140
+
141
+ if (href) {
142
+ const {selection} = editor;
143
+ const isCollapsed = selection && Range.isCollapsed(selection);
144
+ const link = {
145
+ type: 'link',
146
+ attributes: {
147
+ href: href,
148
+ title: title,
149
+ nofollow: !!nofollow,
150
+ },
151
+ children: isCollapsed ? [{text: href}] : [],
152
+ };
153
+
154
+ if (isCollapsed) {
155
+ Transforms.insertNodes(editor, link);
156
+ } else {
157
+ Transforms.wrapNodes(editor, link, {split: true});
158
+ Transforms.collapse(editor, {edge: 'end'});
159
+ }
160
+ }
161
+ };
162
+
163
+ export const LinkButton = () => {
164
+ return <LinkButtonElement icon={faLink}/>;
165
+ };
@@ -0,0 +1,164 @@
1
+ import ReactDOM from "react-dom";
2
+ import React, {
3
+ useEffect, useRef, useState,
4
+ } from "react";
5
+ import {
6
+ ReactEditor, useSlate,
7
+ } from "slate-react";
8
+ import classNames from "classnames";
9
+ // eslint-disable-next-line import/no-unresolved
10
+ import "scss/toolbar.scss";
11
+ import {
12
+ FormatButtonBold, FormatButtonItalic, FormatButtonStrikethrough, FormatButtonUnderline,
13
+ } from "./Formats";
14
+ import {
15
+ AlignButtonLeft, AlignButtonCenter, AlignButtonRight,
16
+ } from "./Align";
17
+ import {
18
+ BlockButtonHeading, BlockButtonUnorderedList, BlockButtonOrderedList, BlockButtonBlockquote, BlockButtonArrowList,
19
+ } from "./Block";
20
+ import {StorybookButton} from "./Element";
21
+ import {LinkButton} from "./Link";
22
+ import {InsertDividerButton} from "./Insert";
23
+ import {InsertGridButton} from "./Layout";
24
+
25
+ export const Portal = ({children}) => {
26
+ return ReactDOM.createPortal(children, window.document.body);
27
+ };
28
+
29
+ export const Toolbar = ({hover}) => {
30
+ const ref = useRef();
31
+ const editor = useSlate();
32
+
33
+ useEffect(() => {
34
+ if (!hover) {
35
+ return;
36
+ }
37
+
38
+ const el = ref.current;
39
+ const {selection} = editor;
40
+
41
+ if (!el) {
42
+ return;
43
+ }
44
+
45
+ if (
46
+ // !selection ||
47
+ !ReactEditor.isFocused(editor) // ||
48
+ // Range.isCollapsed(selection) ||
49
+ // Editor.string(editor, selection) === ''
50
+ ) {
51
+ el.removeAttribute('style');
52
+ el.classList.remove("active");
53
+ return;
54
+ }
55
+
56
+ try {
57
+ const domSelection = window.getSelection();
58
+ const domRange = domSelection.getRangeAt(0);
59
+ const rect = domRange.getBoundingClientRect();
60
+
61
+ el.classList.add("active");
62
+
63
+ let distanceToMouse = 10;
64
+ let top = rect.top + window.pageYOffset - el.offsetHeight - distanceToMouse;
65
+ let left = rect.left + window.pageXOffset - el.offsetWidth / 2 + rect.width / 2 + distanceToMouse;
66
+
67
+ if (top < 0) {
68
+ top = el.offsetHeight;
69
+ }
70
+
71
+ if (left < 0) {
72
+ left = 0;
73
+ }
74
+
75
+ el.style.top = `${top}px`;
76
+ el.style.left = `${left}px`;
77
+ } catch (e) {
78
+ el.removeAttribute('style');
79
+ el.classList.remove("active");
80
+ }
81
+ });
82
+
83
+ function renderMenu() {
84
+ return <Menu
85
+ ref={ref}
86
+ className={classNames({
87
+ active: !hover,
88
+ "toolbar-static": !hover,
89
+ toolbar: true,
90
+ })}
91
+ >
92
+ <div className="toolbar-btns">
93
+ <FormatButtonBold/>
94
+ <FormatButtonItalic/>
95
+ <FormatButtonUnderline/>
96
+ <FormatButtonStrikethrough/>
97
+
98
+ <LinkButton/>
99
+
100
+ <ToobarHoverExpandButton>
101
+ <BlockButtonHeading level={2}/>
102
+ <BlockButtonHeading level={3}/>
103
+ <BlockButtonHeading level={4}/>
104
+ </ToobarHoverExpandButton>
105
+
106
+ <ToobarHoverExpandButton>
107
+ <BlockButtonUnorderedList/>
108
+ <BlockButtonOrderedList/>
109
+ <BlockButtonArrowList/>
110
+ </ToobarHoverExpandButton>
111
+
112
+ <BlockButtonBlockquote/>
113
+
114
+ <ToobarHoverExpandButton>
115
+ <AlignButtonLeft/>
116
+ <AlignButtonCenter/>
117
+ <AlignButtonRight/>
118
+ </ToobarHoverExpandButton>
119
+
120
+ <ToobarHoverExpandButton>
121
+ <StorybookButton/>
122
+ <InsertDividerButton/>
123
+ <InsertGridButton/>
124
+ </ToobarHoverExpandButton>
125
+
126
+ </div>
127
+ </Menu>;
128
+ }
129
+
130
+ if (hover) {
131
+ return (
132
+ <Portal>
133
+ {renderMenu()}
134
+ </Portal>
135
+ );
136
+ } else {
137
+ return renderMenu();
138
+ }
139
+ };
140
+
141
+ export const ToobarHoverExpandButton = ({children}) => {
142
+ return <span
143
+ className={
144
+ classNames({"toolbar-btn-expand": true})
145
+ }
146
+ >
147
+ <div className="toolbar-btn-expand-inner">
148
+ {children}
149
+ </div>
150
+ </span>;
151
+ };
152
+
153
+ export const Menu = React.forwardRef(({
154
+ className, ...props
155
+ },
156
+ ref) => (
157
+ <div
158
+ {...props}
159
+ ref={ref}
160
+ className={className}
161
+ />
162
+ ));
163
+
164
+ Menu.displayName = "Menu";