@vonaffenfels/slate-editor 1.0.60 → 1.0.62
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/dist/BlockEditor.css +1 -1
- package/dist/BlockEditor.js +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/scss/editor.scss +14 -6
- package/src/BlockEditor.js +75 -2
- package/src/ElementAutocomplete.js +84 -0
- package/src/SidebarEditor/Resizable.js +18 -13
- package/src/SidebarEditor.js +53 -110
- package/src/Toolbar/Toolbar.js +13 -83
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vonaffenfels/slate-editor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.62",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"cssnano": "^5.0.1",
|
|
72
72
|
"escape-html": "^1.0.3"
|
|
73
73
|
},
|
|
74
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "6ad3de5f5d37e2ca7447e7591f8f9e4d32089f30",
|
|
75
75
|
"publishConfig": {
|
|
76
76
|
"access": "public"
|
|
77
77
|
}
|
package/scss/editor.scss
CHANGED
|
@@ -397,15 +397,9 @@
|
|
|
397
397
|
}
|
|
398
398
|
|
|
399
399
|
.button.button--secondary {
|
|
400
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important;
|
|
401
|
-
width: 100%;
|
|
402
|
-
padding: 8px 0.75rem !important;
|
|
403
400
|
color: #036fe3 !important;
|
|
404
401
|
border: 1px solid #036fe3 !important;
|
|
405
|
-
transition: all 100ms ease-in-out;
|
|
406
402
|
background-color: transparent !important;
|
|
407
|
-
font-size: 0.875rem !important;
|
|
408
|
-
border-radius: 4px !important;
|
|
409
403
|
}
|
|
410
404
|
|
|
411
405
|
.button.button--secondary:hover {
|
|
@@ -418,6 +412,20 @@
|
|
|
418
412
|
color: #FFFFFF !important;
|
|
419
413
|
}
|
|
420
414
|
|
|
415
|
+
.button.button--tertiary {
|
|
416
|
+
color: rgb(90, 101, 124) !important;
|
|
417
|
+
background-color: white !important;
|
|
418
|
+
border: 1px solid #cfd9e0 !important;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.button.button--tertiary:hover {
|
|
422
|
+
background-color: rgb(248, 248, 248) !important;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.button.button--tertiary:active {
|
|
426
|
+
background-color: rgb(237, 237, 237) !important;
|
|
427
|
+
}
|
|
428
|
+
|
|
421
429
|
.resizable {
|
|
422
430
|
.resizable-left {
|
|
423
431
|
position: relative;
|
package/src/BlockEditor.js
CHANGED
|
@@ -93,6 +93,32 @@ export default function BlockEditor({
|
|
|
93
93
|
loadStories();
|
|
94
94
|
}, []);
|
|
95
95
|
|
|
96
|
+
const handleKeyDown = (e) => {
|
|
97
|
+
let keyCode = e.keyCode || e.which;
|
|
98
|
+
|
|
99
|
+
switch (keyCode) {
|
|
100
|
+
case 46: // delete
|
|
101
|
+
case 8: // backspace
|
|
102
|
+
handleSidebarDeleteClick();
|
|
103
|
+
break;
|
|
104
|
+
case 27: // escape
|
|
105
|
+
handleSidebarClose();
|
|
106
|
+
break;
|
|
107
|
+
default:
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (selectedStorybookElement) {
|
|
114
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
115
|
+
} else {
|
|
116
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
120
|
+
}, [selectedStorybookElement]);
|
|
121
|
+
|
|
96
122
|
const resetEditor = () => {
|
|
97
123
|
if (confirm("This action will delete all data in the editor, are you sure?")) {
|
|
98
124
|
onChange(emptyValue);
|
|
@@ -204,7 +230,7 @@ export default function BlockEditor({
|
|
|
204
230
|
setSelectedStorybookElement(mergedValue);
|
|
205
231
|
};
|
|
206
232
|
|
|
207
|
-
const handleSidebarDeleteClick = (
|
|
233
|
+
const handleSidebarDeleteClick = () => {
|
|
208
234
|
contentfulSdk.dialogs
|
|
209
235
|
.openConfirm({
|
|
210
236
|
title: 'Element löschen',
|
|
@@ -271,6 +297,50 @@ export default function BlockEditor({
|
|
|
271
297
|
}
|
|
272
298
|
};
|
|
273
299
|
|
|
300
|
+
const handleSidebarDuplicateClick = () => {
|
|
301
|
+
let path = selectedStorybookElement.path;
|
|
302
|
+
|
|
303
|
+
let selectedStorybookElementCopy = JSON.parse(JSON.stringify({
|
|
304
|
+
...selectedStorybookElement,
|
|
305
|
+
editorAttributes: null,
|
|
306
|
+
path: null,
|
|
307
|
+
node: null,
|
|
308
|
+
}));
|
|
309
|
+
|
|
310
|
+
delete selectedStorybookElementCopy.path;
|
|
311
|
+
delete selectedStorybookElementCopy.node;
|
|
312
|
+
delete selectedStorybookElementCopy.editorAttributes;
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
Transforms.insertNodes(editor, [selectedStorybookElementCopy], {at: path});
|
|
316
|
+
} catch (e) {
|
|
317
|
+
console.error(e);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const handleSidebarInsertClick = direction => {
|
|
322
|
+
let node = ReactEditor.toSlateNode(editor, selectedStorybookElement.editorAttributes.ref.current);
|
|
323
|
+
let path = ReactEditor.findPath(editor, node);
|
|
324
|
+
|
|
325
|
+
let at;
|
|
326
|
+
|
|
327
|
+
if (direction === "above") {
|
|
328
|
+
at = [path[0]];
|
|
329
|
+
} else {
|
|
330
|
+
at = [path[0] + 1];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
Transforms.insertNodes(editor, [
|
|
334
|
+
{
|
|
335
|
+
children: [{text: ''}],
|
|
336
|
+
block: "Blocks/Empty",
|
|
337
|
+
attributes: {blockWidth: "site"},
|
|
338
|
+
isEmpty: true,
|
|
339
|
+
type: "storybook",
|
|
340
|
+
},
|
|
341
|
+
], {at});
|
|
342
|
+
};
|
|
343
|
+
|
|
274
344
|
const handleSidebarClose = () => {
|
|
275
345
|
setSelectedStorybookElement(null);
|
|
276
346
|
};
|
|
@@ -337,7 +407,10 @@ export default function BlockEditor({
|
|
|
337
407
|
onClose={handleSidebarClose}
|
|
338
408
|
onDelete={handleSidebarDeleteClick}
|
|
339
409
|
onMove={handleSidebarMoveClick}
|
|
340
|
-
|
|
410
|
+
onDuplicate={handleSidebarDuplicateClick}
|
|
411
|
+
onInsert={handleSidebarInsertClick}
|
|
412
|
+
editor={editor}
|
|
413
|
+
lastSelection={lastSelection} />
|
|
341
414
|
}
|
|
342
415
|
/>
|
|
343
416
|
</div>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {Autocomplete} from "@contentful/forma-36-react-components";
|
|
2
|
+
import {
|
|
3
|
+
useEffect, useState,
|
|
4
|
+
} from "react";
|
|
5
|
+
import {Transforms} from "slate";
|
|
6
|
+
|
|
7
|
+
const devMode = localStorage.getItem("dev-mode") === "true";
|
|
8
|
+
|
|
9
|
+
export const ElementAutocomplete = ({
|
|
10
|
+
storybookStories,
|
|
11
|
+
isLoading,
|
|
12
|
+
editor,
|
|
13
|
+
storyContext = "",
|
|
14
|
+
portal,
|
|
15
|
+
lastSelection,
|
|
16
|
+
onChange,
|
|
17
|
+
...props
|
|
18
|
+
}) => {
|
|
19
|
+
const items = (storybookStories || []).map(story => {
|
|
20
|
+
let storyTitleSplit = String(story.title || "").split("/");
|
|
21
|
+
|
|
22
|
+
if (!story.id) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let splitStoryContext = String(storyContext || "").split(",");
|
|
27
|
+
let isItemInStoryContext = splitStoryContext.find(context => {
|
|
28
|
+
return Array.isArray(story.storyContext) ? story.storyContext.includes(context) : context === story.storyContext;
|
|
29
|
+
});
|
|
30
|
+
let isItemInPortalContext = !story.portalContext || story.portalContext.includes(portal);
|
|
31
|
+
|
|
32
|
+
if (!devMode && (!isItemInStoryContext || !isItemInPortalContext)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
value: story.id.toLowerCase(),
|
|
38
|
+
label: storyTitleSplit[storyTitleSplit.length - 1],
|
|
39
|
+
stories: story.stories,
|
|
40
|
+
};
|
|
41
|
+
}).filter(Boolean);
|
|
42
|
+
|
|
43
|
+
const [filteredItems, setFilteredItems] = useState(items);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
setFilteredItems(items);
|
|
47
|
+
}, [storybookStories]);
|
|
48
|
+
|
|
49
|
+
const handleQueryChange = (query) => {
|
|
50
|
+
setFilteredItems(query ? items.filter((item) => item.label.toLowerCase().includes(query.toLowerCase())) : items);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleOnChange = (item) => {
|
|
54
|
+
onChange(item);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
let autoCompleteElement = document.getElementsByClassName("element-autocomplete")[0];
|
|
59
|
+
|
|
60
|
+
if (autoCompleteElement) {
|
|
61
|
+
autoCompleteElement.value = "";
|
|
62
|
+
}
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Autocomplete
|
|
67
|
+
items={filteredItems}
|
|
68
|
+
onQueryChange={handleQueryChange}
|
|
69
|
+
isLoading={isLoading}
|
|
70
|
+
placeholder={'Element hinzufügen'}
|
|
71
|
+
emptyListMessage={'Keine Komponenten gefunden'}
|
|
72
|
+
noMatchesMessage={'Keine Ergebnisse gefunden'}
|
|
73
|
+
dropdownProps={{isFullWidth: true}}
|
|
74
|
+
maxHeight={300}
|
|
75
|
+
onChange={handleOnChange}
|
|
76
|
+
width="medium"
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
{(options) =>
|
|
80
|
+
options.map((option) => <span key={option.value}>{option.label}</span>)
|
|
81
|
+
}
|
|
82
|
+
</Autocomplete>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useEffect, useRef,
|
|
3
|
+
} from "react";
|
|
2
4
|
|
|
3
|
-
export const Resizable = ({
|
|
5
|
+
export const Resizable = ({
|
|
6
|
+
left,
|
|
7
|
+
right,
|
|
8
|
+
}) => {
|
|
4
9
|
const containerRef = useRef();
|
|
5
10
|
const leftRef = useRef();
|
|
6
11
|
const handleRef = useRef();
|
|
@@ -14,11 +19,11 @@ export const Resizable = ({left, right}) => {
|
|
|
14
19
|
if (width < 300) {
|
|
15
20
|
return 300;
|
|
16
21
|
} else if (containerRef.current.getBoundingClientRect().width - width < 300) {
|
|
17
|
-
return containerRef.current.getBoundingClientRect().width - 300
|
|
22
|
+
return containerRef.current.getBoundingClientRect().width - 300;
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
return width;
|
|
21
|
-
}
|
|
26
|
+
};
|
|
22
27
|
|
|
23
28
|
useEffect(() => {
|
|
24
29
|
if (rightRef.current) {
|
|
@@ -41,18 +46,18 @@ export const Resizable = ({left, right}) => {
|
|
|
41
46
|
return;
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
let newWidth = rightRef.current.resizingStartWidth - (e.pageX - rightRef.current.resizingStartMouseX)
|
|
49
|
+
let newWidth = rightRef.current.resizingStartWidth - (e.pageX - rightRef.current.resizingStartMouseX);
|
|
45
50
|
newWidth = checkWidth(newWidth);
|
|
46
51
|
|
|
47
52
|
localStorage.setItem("slate-editor-resizable-width", newWidth);
|
|
48
53
|
|
|
49
54
|
rightRef.current.style.width = newWidth + 'px';
|
|
50
|
-
}
|
|
55
|
+
};
|
|
51
56
|
const onMouseUp = (e) => {
|
|
52
57
|
if (rightRef.current) {
|
|
53
58
|
rightRef.current.resizing = false;
|
|
54
59
|
}
|
|
55
|
-
}
|
|
60
|
+
};
|
|
56
61
|
|
|
57
62
|
rightRef.current.addEventListener("mousedown", onMouseDown);
|
|
58
63
|
document.addEventListener("mousemove", onMouseMove);
|
|
@@ -64,15 +69,15 @@ export const Resizable = ({left, right}) => {
|
|
|
64
69
|
}
|
|
65
70
|
document.removeEventListener("mousemove", onMouseMove);
|
|
66
71
|
document.removeEventListener("mouseup", onMouseUp);
|
|
67
|
-
}
|
|
72
|
+
};
|
|
68
73
|
}
|
|
69
74
|
}, [right, left]);
|
|
70
75
|
|
|
71
|
-
return <div className="w-full
|
|
72
|
-
<div ref={leftRef} className="resizable-left
|
|
73
|
-
{right && <div ref={rightRef} className="resizable-right">
|
|
76
|
+
return <div className="resizable flex w-full" ref={containerRef}>
|
|
77
|
+
<div ref={leftRef} className="resizable-left grow">{left}</div>
|
|
78
|
+
{right && <div ref={rightRef} className="resizable-right shrink-0">
|
|
74
79
|
<div ref={handleRef} className="resizable-x-handle"/>
|
|
75
80
|
{right}
|
|
76
81
|
</div>}
|
|
77
|
-
</div
|
|
78
|
-
}
|
|
82
|
+
</div>;
|
|
83
|
+
};
|
package/src/SidebarEditor.js
CHANGED
|
@@ -5,6 +5,7 @@ import {SidebarEditorField} from "./SidebarEditor/SidebarEditorField";
|
|
|
5
5
|
import {ToolMargin} from "./Tools/Margin";
|
|
6
6
|
import "../scss/sidebarEditor.scss";
|
|
7
7
|
import {Spinner} from "@contentful/forma-36-react-components";
|
|
8
|
+
import {ElementAutocomplete} from "./ElementAutocomplete";
|
|
8
9
|
|
|
9
10
|
const devMode = localStorage.getItem("dev-mode") === "true";
|
|
10
11
|
|
|
@@ -16,9 +17,14 @@ const SidebarEditor = ({
|
|
|
16
17
|
onChange,
|
|
17
18
|
onDelete,
|
|
18
19
|
onMove,
|
|
20
|
+
onDuplicate,
|
|
21
|
+
onInsert,
|
|
19
22
|
editor,
|
|
20
23
|
isLoading,
|
|
24
|
+
lastSelection,
|
|
21
25
|
}) => {
|
|
26
|
+
const portal = sdk?.entry?.fields?.portal.getValue();
|
|
27
|
+
|
|
22
28
|
const [versions, setVersions] = useState([]);
|
|
23
29
|
const [lastChangedProperty, setLastChangedProperty] = useState(null);
|
|
24
30
|
const [versionCount, setVersionCount] = useState(0);
|
|
@@ -164,8 +170,33 @@ const SidebarEditor = ({
|
|
|
164
170
|
}
|
|
165
171
|
};
|
|
166
172
|
|
|
173
|
+
const handleAutocompleteChange = (item) => {
|
|
174
|
+
if (!item) {
|
|
175
|
+
return onChange({}); // reset to empty
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// reset to the first or second story
|
|
179
|
+
onChange({
|
|
180
|
+
...storybookElement,
|
|
181
|
+
block: item.value,
|
|
182
|
+
attributes: {...(item?.stories?.[0]?.args || item?.stories?.[1]?.args || {})},
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
|
|
167
186
|
useEffect(() => resetVersions, [storybookElement?.editorAttributes?.ref]);
|
|
168
187
|
|
|
188
|
+
const renderTitle = () => {
|
|
189
|
+
const story = storybookStories?.find(v => storybookElement.block?.toLowerCase() === v.id?.toLowerCase());
|
|
190
|
+
|
|
191
|
+
if (!story?.title) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let storyTitleSplit = String(story.title || "").split("/");
|
|
196
|
+
|
|
197
|
+
return <h2 className="mb-2 text-lg font-bold">{storyTitleSplit[storyTitleSplit.length - 1]}</h2>;
|
|
198
|
+
};
|
|
199
|
+
|
|
169
200
|
return (
|
|
170
201
|
<div id="sidebar-editor-wrapper">
|
|
171
202
|
<div id="sidebar-editor">
|
|
@@ -204,17 +235,31 @@ const SidebarEditor = ({
|
|
|
204
235
|
</div>
|
|
205
236
|
)}
|
|
206
237
|
</div>
|
|
207
|
-
{!!onClose && <IconButton
|
|
238
|
+
{!!onClose && <IconButton onClick={onClose} title="Schließen">⨉</IconButton>}
|
|
208
239
|
</div>
|
|
209
240
|
<hr className="mt-2" style={{borderColor: "#cfd9e0"}}/>
|
|
210
241
|
</div>
|
|
211
242
|
<div className="grow overflow-y-auto pr-2 pt-2">
|
|
243
|
+
{renderTitle()}
|
|
212
244
|
<div className="mb-2">
|
|
213
|
-
<
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
245
|
+
<button className="button button--tertiary" onClick={onDuplicate}>Duplizieren</button>
|
|
246
|
+
</div>
|
|
247
|
+
<div className="mb-2 flex">
|
|
248
|
+
<button className="button button--tertiary mr-1" onClick={() => onInsert("above")}>Davor hinzufügen</button>
|
|
249
|
+
<button className="button button--tertiary ml-1" onClick={() => onInsert("below")}>Danach hinzufügen</button>
|
|
250
|
+
</div>
|
|
251
|
+
<div className="mb-2">
|
|
252
|
+
<ElementAutocomplete
|
|
253
|
+
isLoading={isLoading}
|
|
254
|
+
storybookStories={storybookStories}
|
|
255
|
+
editor={editor}
|
|
256
|
+
storyContext={sdk.parameters.instance.storyContext}
|
|
257
|
+
portal={portal}
|
|
258
|
+
lastSelection={lastSelection}
|
|
259
|
+
onChange={handleAutocompleteChange}
|
|
260
|
+
placeholder="Element wählen"
|
|
261
|
+
width="full"
|
|
262
|
+
/>
|
|
218
263
|
</div>
|
|
219
264
|
{storybookElement?.block && (
|
|
220
265
|
<div>
|
|
@@ -367,108 +412,6 @@ const VariantSelect = ({
|
|
|
367
412
|
);
|
|
368
413
|
};
|
|
369
414
|
|
|
370
|
-
|
|
371
|
-
stories,
|
|
372
|
-
active,
|
|
373
|
-
onChange,
|
|
374
|
-
className,
|
|
375
|
-
storyContext,
|
|
376
|
-
}) => {
|
|
377
|
-
if (!onChange) {
|
|
378
|
-
return null;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const onSelectChange = (story) => {
|
|
382
|
-
if (!story) {
|
|
383
|
-
return onChange({}); // reset to empty
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// reset to the first or second story
|
|
387
|
-
onChange({
|
|
388
|
-
...active,
|
|
389
|
-
block: story.id,
|
|
390
|
-
attributes: {...(story?.stories?.[0]?.args || story?.stories?.[1]?.args || {})},
|
|
391
|
-
});
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
function groupArrayToObject(array) {
|
|
395
|
-
const result = {};
|
|
396
|
-
|
|
397
|
-
array.forEach(item => {
|
|
398
|
-
if (!item?.id) {
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
let splitStoryContext = String(storyContext || "").split(",");
|
|
403
|
-
let isItemInContext = splitStoryContext.find(context => {
|
|
404
|
-
return Array.isArray(item.storyContext) ? item.storyContext.includes(context) : context === item.storyContext;
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
if (!devMode && !isItemInContext) {
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const parts = item.title.split('/');
|
|
412
|
-
let currentLevel = result;
|
|
413
|
-
|
|
414
|
-
parts.forEach((part, index) => {
|
|
415
|
-
if (!currentLevel[part]) {
|
|
416
|
-
currentLevel[part] = {level: index};
|
|
417
|
-
}
|
|
418
|
-
currentLevel = currentLevel[part];
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
currentLevel.title = item.title;
|
|
422
|
-
currentLevel.shortTitle = parts[parts.length - 1];
|
|
423
|
-
currentLevel.id = item.id;
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
return result;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
let storyGroups = groupArrayToObject(stories);
|
|
430
|
-
|
|
431
|
-
const renderOptions = (obj) => Object.keys(obj).map((key, index) => {
|
|
432
|
-
const option = obj[key];
|
|
433
|
-
|
|
434
|
-
if (!option) {
|
|
435
|
-
return null;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (option.title && option.id) {
|
|
439
|
-
return (
|
|
440
|
-
<option key={option.id} value={option.id.toLowerCase()}>
|
|
441
|
-
{option.shortTitle}
|
|
442
|
-
</option>
|
|
443
|
-
);
|
|
444
|
-
} else if (typeof option === "object") {
|
|
445
|
-
return (
|
|
446
|
-
<Fragment key={`${key}-${index}`}>
|
|
447
|
-
<option
|
|
448
|
-
disabled
|
|
449
|
-
style={{
|
|
450
|
-
color: "rgba(0, 0, 0, 0.3)",
|
|
451
|
-
fontWeight: option.level === 0 ? "bold" : "normal",
|
|
452
|
-
}}>{key}</option>
|
|
453
|
-
{renderOptions(option)}
|
|
454
|
-
</Fragment>
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
return (
|
|
460
|
-
<div className={className}>
|
|
461
|
-
<label className="block">Element</label>
|
|
462
|
-
<select
|
|
463
|
-
onChange={e => onSelectChange(stories.find(s => s.id?.toLowerCase() === e.target.value))}
|
|
464
|
-
value={active?.block?.toLowerCase()}
|
|
465
|
-
className="font-bold"
|
|
466
|
-
>
|
|
467
|
-
<option>Element wählen</option>
|
|
468
|
-
{renderOptions(storyGroups)}
|
|
469
|
-
</select>
|
|
470
|
-
</div>
|
|
471
|
-
);
|
|
472
|
-
};
|
|
415
|
+
export default SidebarEditor;
|
|
473
416
|
|
|
474
|
-
|
|
417
|
+
// :)
|
package/src/Toolbar/Toolbar.js
CHANGED
|
@@ -25,8 +25,7 @@ import {
|
|
|
25
25
|
Autocomplete, Spinner,
|
|
26
26
|
} from "@contentful/forma-36-react-components";
|
|
27
27
|
import {Transforms} from "slate";
|
|
28
|
-
|
|
29
|
-
const devMode = localStorage.getItem("dev-mode") === "true";
|
|
28
|
+
import {ElementAutocomplete} from "../ElementAutocomplete";
|
|
30
29
|
|
|
31
30
|
export const Portal = ({children}) => {
|
|
32
31
|
return ReactDOM.createPortal(children, window.document.body);
|
|
@@ -95,6 +94,17 @@ export const Toolbar = ({
|
|
|
95
94
|
}
|
|
96
95
|
}, [hover, ref, editor]);
|
|
97
96
|
|
|
97
|
+
const handleAutocompleteChange = item => {
|
|
98
|
+
let element = {
|
|
99
|
+
children: [{text: ''}],
|
|
100
|
+
type: "storybook",
|
|
101
|
+
block: item.value,
|
|
102
|
+
attributes: {...(item?.stories?.[0]?.args || item?.stories?.[1]?.args || {})},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
Transforms.insertNodes(editor, [element], {at: [lastSelection?.anchor?.path?.[0]]});
|
|
106
|
+
};
|
|
107
|
+
|
|
98
108
|
function renderMenu() {
|
|
99
109
|
return <Menu
|
|
100
110
|
ref={ref}
|
|
@@ -149,6 +159,7 @@ export const Toolbar = ({
|
|
|
149
159
|
storyContext={sdk.parameters.instance.storyContext}
|
|
150
160
|
portal={portal}
|
|
151
161
|
lastSelection={lastSelection}
|
|
162
|
+
onChange={handleAutocompleteChange}
|
|
152
163
|
/>
|
|
153
164
|
</div>
|
|
154
165
|
</div>
|
|
@@ -170,87 +181,6 @@ export const Toolbar = ({
|
|
|
170
181
|
}
|
|
171
182
|
};
|
|
172
183
|
|
|
173
|
-
const ElementAutocomplete = ({
|
|
174
|
-
storybookStories,
|
|
175
|
-
isLoading,
|
|
176
|
-
editor,
|
|
177
|
-
storyContext = "",
|
|
178
|
-
portal,
|
|
179
|
-
lastSelection,
|
|
180
|
-
}) => {
|
|
181
|
-
const items = (storybookStories || []).map(story => {
|
|
182
|
-
let storyTitleSplit = String(story.title || "").split("/");
|
|
183
|
-
|
|
184
|
-
if (!story.id) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
let splitStoryContext = String(storyContext || "").split(",");
|
|
189
|
-
let isItemInStoryContext = splitStoryContext.find(context => {
|
|
190
|
-
return Array.isArray(story.storyContext) ? story.storyContext.includes(context) : context === story.storyContext;
|
|
191
|
-
});
|
|
192
|
-
let isItemInPortalContext = !story.portalContext || story.portalContext.includes(portal);
|
|
193
|
-
|
|
194
|
-
if (!devMode && (!isItemInStoryContext || !isItemInPortalContext)) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
value: story.id.toLowerCase(),
|
|
200
|
-
label: storyTitleSplit[storyTitleSplit.length - 1],
|
|
201
|
-
stories: story.stories,
|
|
202
|
-
};
|
|
203
|
-
}).filter(Boolean);
|
|
204
|
-
|
|
205
|
-
const [filteredItems, setFilteredItems] = useState(items);
|
|
206
|
-
|
|
207
|
-
useEffect(() => {
|
|
208
|
-
setFilteredItems(items);
|
|
209
|
-
}, [storybookStories]);
|
|
210
|
-
|
|
211
|
-
const handleQueryChange = (query) => {
|
|
212
|
-
setFilteredItems(query ? items.filter((item) => item.label.toLowerCase().includes(query.toLowerCase())) : items);
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const handleOnChange = (item) => {
|
|
216
|
-
let element = {
|
|
217
|
-
children: [{text: ''}],
|
|
218
|
-
type: "storybook",
|
|
219
|
-
block: item.value,
|
|
220
|
-
attributes: {...(item?.stories?.[0]?.args || item?.stories?.[1]?.args || {})},
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
Transforms.insertNodes(editor, [element], {at: [lastSelection?.anchor?.path?.[0]]});
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
useEffect(() => {
|
|
227
|
-
let autoCompleteElement = document.getElementsByClassName("element-autocomplete")[0];
|
|
228
|
-
|
|
229
|
-
if (autoCompleteElement) {
|
|
230
|
-
autoCompleteElement.value = "";
|
|
231
|
-
}
|
|
232
|
-
}, []);
|
|
233
|
-
|
|
234
|
-
return (
|
|
235
|
-
<Autocomplete
|
|
236
|
-
items={filteredItems}
|
|
237
|
-
onQueryChange={handleQueryChange}
|
|
238
|
-
isLoading={isLoading}
|
|
239
|
-
placeholder={'Element hinzufügen'}
|
|
240
|
-
emptyListMessage={'Keine Komponenten gefunden'}
|
|
241
|
-
noMatchesMessage={'Keine Ergebnisse gefunden'}
|
|
242
|
-
dropdownProps={{isFullWidth: true}}
|
|
243
|
-
maxHeight={300}
|
|
244
|
-
onChange={handleOnChange}
|
|
245
|
-
width="medium"
|
|
246
|
-
>
|
|
247
|
-
{(options) =>
|
|
248
|
-
options.map((option) => <span key={option.value}>{option.label}</span>)
|
|
249
|
-
}
|
|
250
|
-
</Autocomplete>
|
|
251
|
-
);
|
|
252
|
-
};
|
|
253
|
-
|
|
254
184
|
export const ToobarHoverExpandButton = ({children}) => {
|
|
255
185
|
return <span
|
|
256
186
|
className={
|