@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,61 @@
1
+ import React, {
2
+ createContext, forwardRef, Fragment,
3
+ } from "react";
4
+ import {Transforms} from "slate";
5
+ import {ReactEditor} from "slate-react";
6
+ import classNames from "classnames";
7
+ import {ToolMargin} from "../Tools/Margin";
8
+
9
+ export const LayoutSlotContext = createContext({isInSlot: false});
10
+
11
+ export const LayoutSlot = ({
12
+ editor,
13
+ children,
14
+ element,
15
+ attributes,
16
+ onElementClick = () => {},
17
+ }) => {
18
+ const onMarginChange = (value) => {
19
+ let node = ReactEditor.toSlateNode(editor, attributes.ref.current);
20
+ let path = ReactEditor.findPath(editor, node);
21
+
22
+ Transforms.setNodes(editor, {
23
+ type: "layout-slot",
24
+ attributes: {
25
+ ...(element.attributes || {}),
26
+ margin: value,
27
+ },
28
+ }, {at: path});
29
+ };
30
+
31
+ return <div
32
+ className={classNames({
33
+ ["layout-slot layout-slot-" + element.attributes.name]: true,
34
+ "editor-mt-4": element?.attributes?.margin?.top,
35
+ "editor-mr-4": element?.attributes?.margin?.right,
36
+ "editor-mb-4": element?.attributes?.margin?.bottom,
37
+ "editor-ml-4": element?.attributes?.margin?.left,
38
+ })}
39
+ onClick={() => onElementClick(element)}
40
+ {...attributes}
41
+ >
42
+ <LayoutSlotContext.Provider value={{isInSlot : true}}>
43
+ <div
44
+ className="layout-slot-options"
45
+ contentEditable={false}
46
+ >
47
+ <span
48
+ className="layout-slot-name"
49
+ contentEditable={false}
50
+ >
51
+ {element.attributes.name}
52
+ </span>
53
+
54
+ <ToolMargin margin={element?.attributes?.margin} onChange={onMarginChange}/>
55
+ </div>
56
+ <div className="layout-slot-content">
57
+ {children}
58
+ </div>
59
+ </LayoutSlotContext.Provider>
60
+ </div>;
61
+ };
@@ -0,0 +1,7 @@
1
+ import {createContext} from "react";
2
+
3
+ export const StorybookContext = createContext({
4
+ storybookUrl: null,
5
+ storybookTarget: null,
6
+ activeStorybook: null,
7
+ });
@@ -0,0 +1,151 @@
1
+ import classNames from "classnames";
2
+ import React from "react";
3
+
4
+ export const Default = ({
5
+ attributes,
6
+ elementPropsMap,
7
+ specialClassMap,
8
+ transformAttributes,
9
+ children,
10
+ element,
11
+ isRenderer,
12
+ isInSlot = false,
13
+ defaultComponentReplacement,
14
+ onElementClick = () => {},
15
+ ...props
16
+ }) => {
17
+ const getPropsForType = (type) => {
18
+ if (!elementPropsMap) {
19
+ return {};
20
+ }
21
+
22
+ if (isInSlot) {
23
+ if (elementPropsMap[type + "-in-slot"]) {
24
+ return elementPropsMap[type + "-in-slot"];
25
+ }
26
+ }
27
+
28
+ return elementPropsMap[type] || {};
29
+ };
30
+ const {
31
+ type,
32
+ align,
33
+ } = element;
34
+ let elementAttributes = {
35
+ ...(element.attributes || {}),
36
+ ...getPropsForType(type),
37
+ };
38
+
39
+ if (typeof transformAttributes === "function") {
40
+ elementAttributes = transformAttributes(type, elementAttributes);
41
+ }
42
+
43
+ let alignedClassname = attributes.className || elementAttributes.className || "";
44
+ let Wrapper = elementAttributes.wrapper || function ({children}) {
45
+ return <>{children}</>;
46
+ };
47
+
48
+ if (align) {
49
+ if (specialClassMap && specialClassMap[align]) {
50
+ alignedClassname += specialClassMap[align];
51
+ } else {
52
+ alignedClassname += "align-" + align;
53
+ }
54
+ }
55
+
56
+ attributes.onClick = () => onElementClick(element);
57
+
58
+ switch (type) {
59
+ case "blockquote":
60
+ return <Wrapper>
61
+ <blockquote {...attributes} className={alignedClassname.trim()}>{children}</blockquote>
62
+ </Wrapper>;
63
+ case "list-item":
64
+ return <Wrapper>
65
+ <li {...attributes} className={alignedClassname.trim()}>{children}</li>
66
+ </Wrapper>;
67
+ case "unordered-list":
68
+ return <Wrapper>
69
+ <ul {...attributes} className={alignedClassname.trim()}>{children}</ul>
70
+ </Wrapper>;
71
+ case "ordered-list":
72
+ return <Wrapper>
73
+ <ol {...attributes} className={alignedClassname.trim()}>{children}</ol>
74
+ </Wrapper>;
75
+ case "arrow-list":
76
+ return <Wrapper>
77
+ <ul
78
+ {...attributes}
79
+ className={classNames({
80
+ ...alignedClassname.trim(),
81
+ "arrow-list": true,
82
+ })}>{children}</ul>
83
+ </Wrapper>;
84
+ case "code":
85
+ if (isRenderer) {
86
+ return <Wrapper code={element?.attributes?.code} />;
87
+ } else {
88
+ return <Wrapper><div {...attributes} className={alignedClassname.trim()}><div dangerouslySetInnerHTML={{__html: element?.attributes?.code}} /></div></Wrapper>;
89
+ }
90
+ case "heading":
91
+ switch (element.attributes.level) {
92
+ case 1:
93
+ return <Wrapper level={element.attributes.level}><h1 {...attributes} id={element?.attributes?.id || undefined} className={alignedClassname.trim() || undefined}>{children}</h1></Wrapper>;
94
+ case 2:
95
+ return <Wrapper level={element.attributes.level}><h2 {...attributes} id={element?.attributes?.id || undefined} className={alignedClassname.trim() || undefined}>{children}</h2></Wrapper>;
96
+ case 3:
97
+ return <Wrapper level={element.attributes.level}><h3 {...attributes} id={element?.attributes?.id || undefined} className={alignedClassname.trim() || undefined}>{children}</h3></Wrapper>;
98
+ case 4:
99
+ return <Wrapper level={element.attributes.level}><h4 {...attributes} id={element?.attributes?.id || undefined} className={alignedClassname.trim() || undefined}>{children}</h4></Wrapper>;
100
+ case 5:
101
+ return <Wrapper level={element.attributes.level}><h5 {...attributes} id={element?.attributes?.id || undefined} className={alignedClassname.trim() || undefined}>{children}</h5></Wrapper>;
102
+ case 6:
103
+ return <Wrapper level={element.attributes.level}><h6 {...attributes} id={element?.attributes?.id || undefined} className={alignedClassname.trim() || undefined}>{children}</h6></Wrapper>;
104
+ default:
105
+ return null;
106
+ }
107
+ case "link": {
108
+ let relProp = `noopener noreferrer`;
109
+ let {
110
+ noFollow,
111
+ nofollow,
112
+ ...linkAttributes
113
+ } = elementAttributes;
114
+
115
+ const nofollowProp = noFollow || nofollow;
116
+
117
+ if (nofollowProp) {
118
+ relProp += " nofollow";
119
+ }
120
+
121
+ let CustomLink = typeof defaultComponentReplacement === "function" ? defaultComponentReplacement(type) : null;
122
+
123
+ if (CustomLink) {
124
+ return <Wrapper><CustomLink {...linkAttributes} {...attributes} rel={relProp} className={alignedClassname.trim()}>{children}</CustomLink></Wrapper>;
125
+ } else {
126
+ return <Wrapper><a {...linkAttributes} {...attributes} rel={relProp} className={alignedClassname.trim()}>{children}</a></Wrapper>;
127
+ }
128
+ }
129
+ case "paragraph":
130
+ return <Wrapper><p {...element.attributes} {...attributes} className={alignedClassname.trim()}>{children}</p></Wrapper>;
131
+ case "divider":
132
+ return <Wrapper>
133
+ <hr {...attributes} />
134
+ {children}
135
+ </Wrapper>;
136
+ case "div":
137
+ return <Wrapper>
138
+ <div {...element.attributes} {...attributes}>{children}</div>
139
+ </Wrapper>;
140
+ case "span":
141
+ return <Wrapper>
142
+ <span {...element.attributes} {...attributes}>{children}</span>
143
+ </Wrapper>;
144
+ default:
145
+ if (isRenderer) {
146
+ return <Wrapper>{children}</Wrapper>;
147
+ } else {
148
+ return <span {...attributes} className={alignedClassname.trim()}>{children}</span>;
149
+ }
150
+ }
151
+ };
@@ -0,0 +1,72 @@
1
+ import React from "react";
2
+ import {StorybookNode} from "./Storybook";
3
+ import {Default} from "./Default";
4
+ import {useContext} from "react";
5
+ import {LayoutBlock} from "../Blocks/LayoutBlock";
6
+ import {
7
+ LayoutSlot, LayoutSlotContext,
8
+ } from "../Blocks/LayoutSlot";
9
+
10
+ export const Element = ({
11
+ children,
12
+ storybookComponentLoader,
13
+ storybookComponentDataLoader,
14
+ editor,
15
+ onStorybookElementClick,
16
+ onElementClick,
17
+ ...props
18
+ }) => {
19
+ const {isInSlot} = useContext(LayoutSlotContext);
20
+
21
+ const getPropsForType = (type) => {
22
+ if (!props.elementPropsMap) {
23
+ return {};
24
+ }
25
+
26
+ if (isInSlot) {
27
+ if (props.elementPropsMap[type + "-in-slot"]) {
28
+ return props.elementPropsMap[type + "-in-slot"];
29
+ }
30
+ }
31
+
32
+ return props.elementPropsMap[type] || {};
33
+ };
34
+
35
+ let typeProps = getPropsForType(props.element.type);
36
+
37
+ switch (props.element.type) {
38
+ case 'storybook':
39
+ return <StorybookNode
40
+ {...props}
41
+ {...typeProps}
42
+ isInSlot={isInSlot}
43
+ storybookComponentLoader={storybookComponentLoader}
44
+ storybookComponentDataLoader={storybookComponentDataLoader}
45
+ editor={editor}
46
+ onStorybookElementClick={onStorybookElementClick}
47
+ >{children}</StorybookNode>;
48
+ case 'layout':
49
+ return <LayoutBlock
50
+ editor={editor}
51
+ isInSlot={isInSlot}
52
+ onElementClick={onElementClick}
53
+ {...props}
54
+ {...typeProps}
55
+ >
56
+ {children}
57
+ </LayoutBlock>;
58
+ case 'layout-slot':
59
+ return <LayoutSlot
60
+ editor={editor}
61
+ isInSlot={isInSlot}
62
+ onElementClick={onElementClick}
63
+ {...props}
64
+ {...typeProps}
65
+ >
66
+ {children}
67
+ </LayoutSlot>;
68
+ case 'layout-slot-element':
69
+ default:
70
+ return <Default {...props} onElementClick={onElementClick} isInSlot={isInSlot}>{children}</Default>;
71
+ }
72
+ };
@@ -0,0 +1,40 @@
1
+ import React from "react";
2
+ import {Bold, Italic, Text, Underlined, Strikethrough, Small} from "./Text";
3
+
4
+ export const Leaf = ({attributes, children, leaf, isRenderer, typeProps, ...props}) => {
5
+
6
+ if (leaf.bold) {
7
+ children = <Bold typeProps={typeProps} attributes={attributes}><Text
8
+ attributes={attributes}
9
+ typeProps={typeProps}
10
+ isRenderer={isRenderer}>{children}</Text></Bold>;
11
+ }
12
+
13
+ if (leaf.italic) {
14
+ children =
15
+ <Italic typeProps={typeProps} attributes={attributes}><Text attributes={attributes} typeProps={typeProps} isRenderer={isRenderer}>{children}</Text></Italic>;
16
+ }
17
+
18
+ if (leaf.underlined) {
19
+ children = <Underlined typeProps={typeProps} attributes={attributes}><Text
20
+ attributes={attributes}
21
+ typeProps={typeProps}
22
+ isRenderer={isRenderer}>{children}</Text></Underlined>;
23
+ }
24
+
25
+ if (leaf.strikethrough) {
26
+ children = <Strikethrough typeProps={typeProps} attributes={attributes}><Text
27
+ attributes={attributes}
28
+ typeProps={typeProps}
29
+ isRenderer={isRenderer}>{children}</Text></Strikethrough>;
30
+ }
31
+
32
+ if (leaf.small) {
33
+ children = <Small typeProps={typeProps} attributes={attributes}><Text
34
+ attributes={attributes}
35
+ typeProps={typeProps}
36
+ isRenderer={isRenderer}>{children}</Text></Small>;
37
+ }
38
+
39
+ return <Text attributes={attributes} typeProps={typeProps} isRenderer={isRenderer}>{children}</Text>;
40
+ };
@@ -0,0 +1,170 @@
1
+ import React, {
2
+ memo, useContext, useState,
3
+ } from "react";
4
+ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
5
+ import {
6
+ faCog, faArrowUp, faArrowDown, faTimes, faCompress,
7
+ } from "@fortawesome/free-solid-svg-icons";
8
+ import {Storybook} from "../Storybook";
9
+ import {Transforms} from "slate";
10
+ import {StorybookDisplay} from "./StorybookDisplay";
11
+ import classNames from "classnames";
12
+ import {ReactEditor} from "slate-react";
13
+ import {ToolMargin} from "../Tools/Margin";
14
+
15
+ const StorybookNodeComponent = ({
16
+ attributes,
17
+ editor,
18
+ element,
19
+ children,
20
+ storybookComponentLoader,
21
+ storybookComponentDataLoader,
22
+ onStorybookElementClick,
23
+ }) => {
24
+
25
+ const onEditClick = () => {
26
+ onStorybookElementClick(element, attributes);
27
+ };
28
+
29
+ const onDeleteClick = () => {
30
+ let node = ReactEditor.toSlateNode(editor, attributes.ref.current);
31
+ let path = ReactEditor.findPath(editor, node);
32
+
33
+ Transforms.setNodes(editor, {isDeleted: true}, {at: path});
34
+ Transforms.removeNodes(editor, {at: path});
35
+ };
36
+
37
+ const moveUp = () => {
38
+ let node = ReactEditor.toSlateNode(editor, attributes.ref.current);
39
+ let path = ReactEditor.findPath(editor, node);
40
+
41
+ if (!path[0]) {
42
+ return;
43
+ }
44
+
45
+ try {
46
+ Transforms.moveNodes(editor, {
47
+ from: [path[0]],
48
+ match: node => {
49
+ return node.type === "storybook";
50
+ },
51
+ to: [path[0] - 1],
52
+ mode: "highest",
53
+ voids: false,
54
+ });
55
+ } catch (e) {
56
+ console.error(e);
57
+ }
58
+ };
59
+
60
+ const moveDown = () => {
61
+ let node = ReactEditor.toSlateNode(editor, attributes.ref.current);
62
+ let path = ReactEditor.findPath(editor, node);
63
+
64
+ try {
65
+ Transforms.moveNodes(editor, {
66
+ from: [path[0]],
67
+ match: node => {
68
+ return node.type === "storybook";
69
+ },
70
+ to: [path[0] + 1],
71
+ mode: "highest",
72
+ voids: false,
73
+ });
74
+ } catch (e) {
75
+ console.error(e);
76
+ }
77
+ };
78
+
79
+ const switchSize = (e) => {
80
+ let node = ReactEditor.toSlateNode(editor, attributes.ref.current);
81
+ let fromPath = ReactEditor.findPath(editor, node);
82
+
83
+ if (!fromPath) {
84
+ return;
85
+ }
86
+
87
+ let widths = ["", "site", "article", "small"];
88
+ let width = element?.attributes?.blockWidth || "";
89
+ let currWidthIndex = widths.indexOf(width) || 0;
90
+ let newWidth = widths[currWidthIndex + 1] || widths[0];
91
+
92
+ Transforms.setNodes(editor, {
93
+ ...element,
94
+ attributes: {
95
+ ...(element.attributes || {}),
96
+ blockWidth: newWidth,
97
+ },
98
+ }, {at: fromPath});
99
+ };
100
+
101
+ const onMarginChange = (value) => {
102
+ let node = ReactEditor.toSlateNode(editor, attributes.ref.current);
103
+ let path = ReactEditor.findPath(editor, node);
104
+
105
+ Transforms.setNodes(editor, {
106
+ ...element,
107
+ attributes: {
108
+ ...(element.attributes || {}),
109
+ margin: value,
110
+ },
111
+ }, {at: path});
112
+ };
113
+
114
+ return (
115
+ <span
116
+ {...attributes}
117
+ className={classNames({
118
+ "storybook-element options-wrapper": true,
119
+ "storybook-element-float-left": element?.attributes?.float === "left",
120
+ "storybook-element-float-right": element?.attributes?.float === "right",
121
+ })}>
122
+ <span className="options-container">
123
+ <span className="options-container-option options-container-option-edit" title="Edit" onClick={onEditClick}><FontAwesomeIcon
124
+ icon={faCog}
125
+ size="lg"/></span>
126
+ {!element.isInline && (<>
127
+ <span className="options-container-option options-container-option-move-up" title="Move up" onClick={moveUp}>
128
+ <FontAwesomeIcon icon={faArrowUp} size="lg"/>
129
+ </span>
130
+ <span className="options-container-option options-container-option-move-down" title="Move down" onClick={moveDown}>
131
+ <FontAwesomeIcon icon={faArrowDown} size="lg"/>
132
+ </span>
133
+ <span className="options-container-option options-container-option-expand-text" onClick={switchSize}>
134
+ {!element?.attributes?.blockWidth && "Volle Breite"}
135
+ {element?.attributes?.blockWidth === "article" && "Artikel Breite"}
136
+ {element?.attributes?.blockWidth === "site" && "Seiten Breite"}
137
+ {element?.attributes?.blockWidth === "small" && "Klein"}
138
+ </span>
139
+ <ToolMargin margin={element?.attributes?.margin} onChange={onMarginChange} />
140
+ </>)}
141
+ </span>
142
+ <span className="options-container options-container-right">
143
+ <span className="options-container-option options-container-option-delete" title="Delete" onClick={onDeleteClick}><FontAwesomeIcon
144
+ icon={faTimes}
145
+ size="lg"/></span>
146
+ </span>
147
+ <span className={classNames({
148
+ "storybook-element-component": true,
149
+ "storybook-inline": element.isInline,
150
+ })}>
151
+ <StorybookDisplay {...element.attributes} element={element} storybookComponentLoader={storybookComponentLoader} isEditor={true} storybookComponentDataLoader={storybookComponentDataLoader}/>
152
+ </span>
153
+ {children}
154
+ </span>
155
+ );
156
+ };
157
+
158
+ /**
159
+ * Only return true if the storybook block has to rerender!
160
+ *
161
+ * @param prevProps
162
+ * @param nextProps
163
+ * @returns {boolean}
164
+ */
165
+ function areEqualProps(prevProps, nextProps) {
166
+ const areEqual = JSON.stringify(prevProps.element) === JSON.stringify(nextProps.element);
167
+ return areEqual;
168
+ }
169
+
170
+ export const StorybookNode = memo(StorybookNodeComponent, areEqualProps);
@@ -0,0 +1,118 @@
1
+ import React, {
2
+ useEffect, useRef, useState,
3
+ } from "react";
4
+ import {EmptyBlock} from "../Blocks/EmptyBlock";
5
+ import ErrorBoundary from "../Blocks/ErrorBoundary";
6
+ import classNames from "classnames";
7
+
8
+ export const StorybookDisplay = (props) => {
9
+ const {
10
+ storybookComponentLoader,
11
+ storybookComponentDataLoader,
12
+ element,
13
+ isEditor,
14
+ } = props;
15
+
16
+ const elRef = useRef();
17
+
18
+ useEffect(() => {
19
+ if (!isEditor || !elRef.current) {
20
+ return;
21
+ }
22
+
23
+ const interval = setInterval(() => {
24
+ // innerText for regular elements, img for inline images and such :)
25
+ if (elRef.current && !elRef.current.innerText && !elRef.current.querySelector("img, svg, iframe, video, audio, picture, frame")) {
26
+ elRef.current.classList.add("storybook-element-display-empty");
27
+ elRef.current.dataset.content = `Leeres Element :: ${element.block}`;
28
+ } else if (elRef.current) {
29
+ elRef.current.classList.remove("storybook-element-display-empty");
30
+ delete elRef.current.dataset.content;
31
+ }
32
+ }, 100);
33
+
34
+ return () => {
35
+ clearInterval(interval);
36
+ };
37
+ }, [elRef.current]);
38
+
39
+ if (element.isEmpty || !element.block) {
40
+ return <EmptyBlock {...element.attributes} />;
41
+ }
42
+
43
+ if (isEditor) {
44
+ return <ErrorBoundary name={element.block} isEditor={isEditor}>
45
+ <span ref={elRef} className="storybook-element-display">
46
+ <BlockComponent
47
+ block={element.block}
48
+ storybookComponentLoader={storybookComponentLoader}
49
+ storybookComponentDataLoader={storybookComponentDataLoader}
50
+ attributes={{...element.attributes}}/>
51
+ </span>
52
+ </ErrorBoundary>;
53
+ } else {
54
+ return <ErrorBoundary name={element.block} isEditor={isEditor}>
55
+ <BlockComponent
56
+ block={element.block}
57
+ storybookComponentLoader={storybookComponentLoader}
58
+ storybookComponentDataLoader={storybookComponentDataLoader}
59
+ attributes={{...element.attributes}}/>
60
+ </ErrorBoundary>;
61
+ }
62
+ };
63
+
64
+ const BlockComponent = ({
65
+ block,
66
+ storybookComponentLoader,
67
+ storybookComponentDataLoader,
68
+ attributes,
69
+ }) => {
70
+ const [loadedAttributes, setLoadedAttributes] = useState(attributes);
71
+ let LoadedComponent = storybookComponentLoader(block);
72
+
73
+ if (!LoadedComponent) {
74
+ LoadedComponent = EmptyBlock;
75
+ } else {
76
+ if (!LoadedComponent.displayName) {
77
+ LoadedComponent.displayName = block;
78
+ }
79
+ }
80
+
81
+ useEffect(() => {
82
+ const loadData = async () => {
83
+ if (!storybookComponentDataLoader) {
84
+ return;
85
+ }
86
+
87
+ return await storybookComponentDataLoader(block, attributes);
88
+ };
89
+
90
+ let mounted = true;
91
+ loadData().then((result) => {
92
+ if (result && mounted) {
93
+ setLoadedAttributes({
94
+ ...attributes,
95
+ ...(result || {}),
96
+ });
97
+ }
98
+ });
99
+
100
+ return () => {
101
+ mounted = false;
102
+ };
103
+ }, [attributes, block, storybookComponentDataLoader]);
104
+
105
+ if (loadedAttributes.blockWidth || loadedAttributes.margin) {
106
+ return <span className={classNames({
107
+ "block-article-width": loadedAttributes.blockWidth === "article",
108
+ "block-site-width": loadedAttributes.blockWidth === "site",
109
+ "block-small-width": loadedAttributes.blockWidth === "small",
110
+ "editor-mt-large": loadedAttributes?.margin?.top,
111
+ "editor-mr-large": loadedAttributes?.margin?.right,
112
+ "editor-mb-large": loadedAttributes?.margin?.bottom,
113
+ "editor-ml-large": loadedAttributes?.margin?.left,
114
+ })}><LoadedComponent {...loadedAttributes} /></span>;
115
+ } else {
116
+ return <LoadedComponent {...loadedAttributes} />;
117
+ }
118
+ };
@@ -0,0 +1,67 @@
1
+ import React, {Fragment} from "react";
2
+
3
+ const EmptyWrapper = ({children}) => <>{children}</>;
4
+
5
+ export const Text = ({isRenderer, attributes, children, typeProps}) => {
6
+ if (isRenderer) {
7
+ if (!children) {
8
+ return null;
9
+ }
10
+
11
+ if (typeof children === "string") {
12
+ const breakParts = children.split("\n");
13
+
14
+ return <Fragment>
15
+ {breakParts.length > 1 ? breakParts.map((v, i) => {
16
+ return <Fragment key={i}>
17
+ {renderTextLine(v, typeProps)}{i !== breakParts.length - 1 ? <br/> : null}
18
+ </Fragment>;
19
+ }) : renderTextLine(children, typeProps)}
20
+ </Fragment>;
21
+ } else {
22
+ return <Fragment>
23
+ {children}
24
+ </Fragment>;
25
+ }
26
+
27
+ }
28
+
29
+ return <span {...attributes}>{children}</span>;
30
+ };
31
+
32
+ function renderTextLine(text, typeProps) {
33
+ if (!typeProps || !text || typeof text !== "string") {
34
+ return text;
35
+ }
36
+
37
+ if (typeProps.line_transform) {
38
+ for (let startsWithStr in typeProps.line_transform) {
39
+ if (text.startsWith(startsWithStr)) {
40
+ const WrapperComponent = typeProps.line_transform[startsWithStr];
41
+ return <WrapperComponent>{text.substr(startsWithStr.length).trim()}</WrapperComponent>;
42
+ }
43
+ }
44
+ }
45
+
46
+ return text;
47
+ }
48
+
49
+ export const Bold = ({attributes, children}) => {
50
+ return <strong {...attributes}>{children}</strong>;
51
+ };
52
+
53
+ export const Italic = ({attributes, children}) => {
54
+ return <i {...attributes}>{children}</i>;
55
+ };
56
+
57
+ export const Underlined = ({attributes, children}) => {
58
+ return <u {...attributes}>{children}</u>;
59
+ };
60
+
61
+ export const Strikethrough = ({attributes, children}) => {
62
+ return <del {...attributes}>{children}</del>;
63
+ };
64
+
65
+ export const Small = ({attributes, children}) => {
66
+ return <small {...attributes}>{children}</small>;
67
+ };