@vonaffenfels/contentful-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.
- package/.babelrc +38 -0
- package/.nvmrc +1 -0
- package/package.json +59 -0
- package/postcss.config.js +7 -0
- package/src/components/ConfigScreen.js +71 -0
- package/src/components/Dialog.js +57 -0
- package/src/components/DialogCloudinary.js +50 -0
- package/src/components/EditorField.js +107 -0
- package/src/components/EntryEditor.js +46 -0
- package/src/components/Field.js +30 -0
- package/src/hooks/useOnScreen.js +24 -0
- package/src/index.html +12 -0
- package/src/index.js +51 -0
- package/src/scss/dialog-cloudinary.scss +18 -0
- package/src/scss/index.scss +19 -0
- package/tailwind.config.js +6 -0
- package/webpack.config.dev.js +23 -0
- package/webpack.config.js +134 -0
package/.babelrc
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"presets": [
|
|
3
|
+
"next/babel",
|
|
4
|
+
[
|
|
5
|
+
"@babel/preset-env",
|
|
6
|
+
{
|
|
7
|
+
"modules": false,
|
|
8
|
+
"targets": {
|
|
9
|
+
"browsers": [
|
|
10
|
+
"last 2 Chrome versions",
|
|
11
|
+
"last 2 Firefox versions",
|
|
12
|
+
"last 2 Safari versions",
|
|
13
|
+
"last 2 iOS versions",
|
|
14
|
+
"last 1 Android version",
|
|
15
|
+
"last 1 ChromeAndroid version",
|
|
16
|
+
"ie 11"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"@babel/preset-react"
|
|
22
|
+
],
|
|
23
|
+
"plugins": [
|
|
24
|
+
[
|
|
25
|
+
"@babel/plugin-proposal-private-property-in-object",
|
|
26
|
+
{
|
|
27
|
+
"loose": true
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
[
|
|
31
|
+
"@babel/plugin-proposal-class-properties",
|
|
32
|
+
{
|
|
33
|
+
"loose": true
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"@babel/plugin-syntax-dynamic-import"
|
|
37
|
+
]
|
|
38
|
+
}
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
v14.16.1
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vonaffenfels/contentful-slate-editor",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"devDependencies": {
|
|
5
|
+
"@babel/core": "^7.20.12",
|
|
6
|
+
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
|
7
|
+
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
|
8
|
+
"@babel/preset-env": "^7.13.15",
|
|
9
|
+
"@babel/preset-react": "^7.13.13",
|
|
10
|
+
"@contentful/app-sdk": "^3.33.0",
|
|
11
|
+
"@contentful/field-editor-single-line": "^0.14.1",
|
|
12
|
+
"@contentful/field-editor-test-utils": "^0.11.1",
|
|
13
|
+
"@contentful/forma-36-fcss": "^0.3.1",
|
|
14
|
+
"@contentful/forma-36-react-components": "^3.88.3",
|
|
15
|
+
"@contentful/forma-36-tokens": "^0.10.1",
|
|
16
|
+
"@svgr/webpack": "^5.5.0",
|
|
17
|
+
"babel-loader": "^8.2.2",
|
|
18
|
+
"clean-webpack-plugin": "^3.0.0",
|
|
19
|
+
"cloudinary": "^1.25.1",
|
|
20
|
+
"contentful-management": "^7.17.0",
|
|
21
|
+
"copy-webpack-plugin": "^8.1.1",
|
|
22
|
+
"cross-env": "^7.0.3",
|
|
23
|
+
"css-loader": "^5.2.4",
|
|
24
|
+
"cssnano": "^5.0.1",
|
|
25
|
+
"dotenv-webpack": "^7.0.2",
|
|
26
|
+
"file-loader": "^6.2.0",
|
|
27
|
+
"html-webpack-plugin": "^5.3.1",
|
|
28
|
+
"mini-css-extract-plugin": "^1.5.0",
|
|
29
|
+
"postcss": "8.4.14",
|
|
30
|
+
"postcss-loader": "^5.2.0",
|
|
31
|
+
"react": "18.2.0",
|
|
32
|
+
"react-dom": "18.2.0",
|
|
33
|
+
"react-uid": "^2.3.1",
|
|
34
|
+
"sass": "^1.32.11",
|
|
35
|
+
"sass-loader": "^11.0.1",
|
|
36
|
+
"speakingurl": "^14.0.1",
|
|
37
|
+
"style-loader": "^2.0.0",
|
|
38
|
+
"tailwindcss": "3.3.2",
|
|
39
|
+
"url-loader": "^4.1.1",
|
|
40
|
+
"webpack-cli": "^4.6.0",
|
|
41
|
+
"webpack-dev-server": "^4.0.0-beta.2"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"prepublish": "yarn run build",
|
|
45
|
+
"dev": "yarn run start",
|
|
46
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
47
|
+
"build": "echo \"Don't build this module!\"",
|
|
48
|
+
"start": "webpack serve --config webpack.config.dev.js",
|
|
49
|
+
"vercelDev": "cp ./.vercel.project.json ../../.vercel/project.json && cd ../../ && vercel dev"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@vonaffenfels/slate-editor": "^1.0.1",
|
|
53
|
+
"webpack": "5.73.0"
|
|
54
|
+
},
|
|
55
|
+
"gitHead": "06971175660ea52650bfb65a3d68b34a7926475e",
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React, {Component} from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Heading, Form, TextField, Workbench, Paragraph,
|
|
4
|
+
} from '@contentful/forma-36-react-components';
|
|
5
|
+
import {css} from 'emotion';
|
|
6
|
+
|
|
7
|
+
export default class Config extends Component {
|
|
8
|
+
|
|
9
|
+
constructor(props) {
|
|
10
|
+
super(props);
|
|
11
|
+
this.state = {parameters: {}};
|
|
12
|
+
|
|
13
|
+
// `onConfigure` allows to configure a callback to be
|
|
14
|
+
// invoked when a user attempts to install the app or update
|
|
15
|
+
// its configuration.
|
|
16
|
+
props.sdk.app.onConfigure(() => this.onConfigure());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async componentDidMount() {
|
|
20
|
+
// Get current parameters of the app.
|
|
21
|
+
// If the app is not installed yet, `parameters` will be `null`.
|
|
22
|
+
const parameters = await this.props.sdk.app.getParameters();
|
|
23
|
+
|
|
24
|
+
this.setState(parameters ? {parameters} : this.state, () => {
|
|
25
|
+
// Once preparation has finished, call `setReady` to hide
|
|
26
|
+
// the loading screen and present the app to a user.
|
|
27
|
+
this.props.sdk.app.setReady();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async onConfigure() {
|
|
32
|
+
// This method will be called when a user clicks on "Install"
|
|
33
|
+
// or "Save" in the configuration screen.
|
|
34
|
+
// for more details see https://www.contentful.com/developers/docs/extensibility/ui-extensions/sdk-reference/#register-an-app-configuration-hook
|
|
35
|
+
|
|
36
|
+
// Get current the state of EditorInterface and other entities
|
|
37
|
+
// related to this app installation
|
|
38
|
+
const currentState = await this.props.sdk.app.getCurrentState();
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
// Parameters to be persisted as the app configuration.
|
|
42
|
+
parameters: this.state.parameters,
|
|
43
|
+
// In case you don't want to submit any update to app
|
|
44
|
+
// locations, you can just pass the currentState as is
|
|
45
|
+
targetState: currentState,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
onFieldChanged(value, field) {
|
|
50
|
+
this.setState({
|
|
51
|
+
parameters: {
|
|
52
|
+
...this.state.parameters,
|
|
53
|
+
[field]: value,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
render() {
|
|
59
|
+
return (
|
|
60
|
+
<Workbench className={css({
|
|
61
|
+
padding: '80px',
|
|
62
|
+
backgroundColor: "#FFF",
|
|
63
|
+
})}>
|
|
64
|
+
<Form>
|
|
65
|
+
<Heading>No Configuration necessary</Heading>
|
|
66
|
+
</Form>
|
|
67
|
+
</Workbench>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React, {useEffect, useState} from 'react';
|
|
2
|
+
import DialogCloudinary from "./DialogCloudinary";
|
|
3
|
+
import componentLoader from "@vonaffenfels/slate-editor/componentLoader";
|
|
4
|
+
// eslint-disable-next-line import/no-unresolved
|
|
5
|
+
import BlockEditor from "@vonaffenfels/slate-editor/dist/BlockEditor";
|
|
6
|
+
// eslint-disable-next-line import/no-unresolved
|
|
7
|
+
import storybookStories from "storybookStories";
|
|
8
|
+
|
|
9
|
+
const Dialog = ({sdk}) => {
|
|
10
|
+
const defaultValue = [
|
|
11
|
+
{
|
|
12
|
+
type: "paragraph",
|
|
13
|
+
children: [
|
|
14
|
+
{text: ""},
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
const [value, setValue] = useState(sdk.parameters.invocation.value || defaultValue);
|
|
19
|
+
const [loadedStorybookStories, setLoadedStorybookStories] = useState([]);
|
|
20
|
+
|
|
21
|
+
const loadStories = async () => {
|
|
22
|
+
setLoadedStorybookStories(await storybookStories());
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const saveChanges = () => {
|
|
26
|
+
sdk.close(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
loadStories();
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
switch (sdk.parameters.invocation.type) {
|
|
34
|
+
case "cloudinary":
|
|
35
|
+
return <DialogCloudinary sdk={sdk} config={sdk.parameters.invocation}/>;
|
|
36
|
+
default:
|
|
37
|
+
return <div>
|
|
38
|
+
<BlockEditor
|
|
39
|
+
storybookStories={loadedStorybookStories}
|
|
40
|
+
onChange={setValue}
|
|
41
|
+
elementPropsMap={{}}
|
|
42
|
+
value={value}
|
|
43
|
+
storybookComponentLoader={(block) => {
|
|
44
|
+
return componentLoader(block);
|
|
45
|
+
}}
|
|
46
|
+
storybookComponentDataLoader={async (block, attributes) => {
|
|
47
|
+
return {};
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
<div>
|
|
51
|
+
<button onClick={saveChanges}>Änderungen speichern</button>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default Dialog;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useEffect, useRef,
|
|
3
|
+
} from 'react';
|
|
4
|
+
import "../scss/dialog-cloudinary.scss";
|
|
5
|
+
|
|
6
|
+
/* global cloudinary */
|
|
7
|
+
|
|
8
|
+
const DialogCloudinary = ({
|
|
9
|
+
sdk,
|
|
10
|
+
config,
|
|
11
|
+
}) => {
|
|
12
|
+
const cloudinaryApiKey = config?.cloudinaryApiKey;
|
|
13
|
+
const cloudinaryCloudName = config?.cloudinaryCloudName;
|
|
14
|
+
|
|
15
|
+
const cloudinaryRef = useRef(null);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
// sdk.window.startAutoResizer();
|
|
19
|
+
}, []);
|
|
20
|
+
|
|
21
|
+
const eventHandlers = {
|
|
22
|
+
insertHandler: (data) => {
|
|
23
|
+
sdk.close(data);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const libraryOptions = {
|
|
28
|
+
cloud_name: cloudinaryCloudName,
|
|
29
|
+
api_key: cloudinaryApiKey,
|
|
30
|
+
remove_header: true,
|
|
31
|
+
max_files: config.max_files || undefined,
|
|
32
|
+
multiple: config.multiple || false,
|
|
33
|
+
insert_caption: 'Insert',
|
|
34
|
+
inline_container: '#cloudinary-root',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (cloudinaryRef.current) {
|
|
39
|
+
const library = cloudinary.createMediaLibrary(libraryOptions, eventHandlers);
|
|
40
|
+
|
|
41
|
+
library.show(libraryOptions);
|
|
42
|
+
|
|
43
|
+
sdk.window.updateHeight(window.outerHeight);
|
|
44
|
+
}
|
|
45
|
+
}, [cloudinaryRef]);
|
|
46
|
+
|
|
47
|
+
return <div ref={cloudinaryRef} className="cloudinary-dialog" id="cloudinary-root"/>;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default DialogCloudinary;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useState, useEffect, useRef,
|
|
3
|
+
} from 'react';
|
|
4
|
+
// eslint-disable-next-line import/no-unresolved
|
|
5
|
+
import BlockEditor from "@vonaffenfels/slate-editor/dist/BlockEditor";
|
|
6
|
+
// eslint-disable-next-line import/no-unresolved
|
|
7
|
+
import '@vonaffenfels/slate-editor/dist/index.css';
|
|
8
|
+
import useOnScreen from "../hooks/useOnScreen";
|
|
9
|
+
// eslint-disable-next-line import/no-unresolved
|
|
10
|
+
import storybookStories from "storybookStories";
|
|
11
|
+
// eslint-disable-next-line import/no-unresolved
|
|
12
|
+
import componentLoader from "@vonaffenfels/slate-editor/componentLoader";
|
|
13
|
+
|
|
14
|
+
const EditorField = ({
|
|
15
|
+
fieldSdk,
|
|
16
|
+
windowSdk,
|
|
17
|
+
entrySdk,
|
|
18
|
+
sdk,
|
|
19
|
+
portal,
|
|
20
|
+
locale,
|
|
21
|
+
onStorybookElementClick,
|
|
22
|
+
}) => {
|
|
23
|
+
const wrapperRef = useRef();
|
|
24
|
+
const loadedValue = fieldSdk.getValue(locale);
|
|
25
|
+
const emptyValue = [
|
|
26
|
+
{
|
|
27
|
+
type: "paragraph",
|
|
28
|
+
children: [
|
|
29
|
+
{text: ""},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
const [value, setValue] = useState(validateValue(loadedValue));
|
|
34
|
+
const [externalValue, setExternalValue] = useState(value);
|
|
35
|
+
const isVisible = useOnScreen(wrapperRef);
|
|
36
|
+
|
|
37
|
+
function validateValue(value) {
|
|
38
|
+
if (!value) {
|
|
39
|
+
return emptyValue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
value = value.filter(v => v.type !== "paragraph" || v.children);
|
|
43
|
+
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function onChange(newValue) {
|
|
48
|
+
newValue = validateValue(newValue);
|
|
49
|
+
|
|
50
|
+
setValue(newValue);
|
|
51
|
+
|
|
52
|
+
if (newValue) {
|
|
53
|
+
fieldSdk.setValue(newValue, locale);
|
|
54
|
+
} else {
|
|
55
|
+
fieldSdk.removeValue(locale);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (windowSdk) {
|
|
61
|
+
windowSdk.startAutoResizer();
|
|
62
|
+
}
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (isVisible) {
|
|
67
|
+
setValue(externalValue[locale]);
|
|
68
|
+
}
|
|
69
|
+
}, [isVisible]);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
setValue(validateValue(fieldSdk.getValue(locale)));
|
|
73
|
+
|
|
74
|
+
// Handler for external field value changes (e.g. when multiple authors are working on the same entry).
|
|
75
|
+
const detatchValueChangeHandler = fieldSdk.onValueChanged(locale, (externalChangeValue) => {
|
|
76
|
+
setExternalValue({
|
|
77
|
+
...externalValue,
|
|
78
|
+
[locale]: externalChangeValue,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return detatchValueChangeHandler;
|
|
83
|
+
}, [portal, locale]);
|
|
84
|
+
|
|
85
|
+
const fakeActiveContent = {};
|
|
86
|
+
for (let field in entrySdk.fields) {
|
|
87
|
+
fakeActiveContent.sys = entrySdk.getSys();
|
|
88
|
+
fakeActiveContent[field] = entrySdk.fields[field].getValue();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return <div ref={wrapperRef} className="editor-field">
|
|
92
|
+
<BlockEditor
|
|
93
|
+
onChange={onChange}
|
|
94
|
+
value={validateValue(value)}
|
|
95
|
+
contentfulSdk={sdk}
|
|
96
|
+
config={JSON.parse("{}")}
|
|
97
|
+
storybookComponentLoader={componentLoader}
|
|
98
|
+
storybookStories={storybookStories}
|
|
99
|
+
storybookComponentDataLoader={async (block, attributes) => {
|
|
100
|
+
return [];
|
|
101
|
+
}}
|
|
102
|
+
onStorybookElementClick={onStorybookElementClick}
|
|
103
|
+
/>
|
|
104
|
+
</div>;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export default EditorField;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useEffect, useState,
|
|
3
|
+
} from 'react';
|
|
4
|
+
import EditorField from "./EditorField";
|
|
5
|
+
|
|
6
|
+
const Entry = ({
|
|
7
|
+
sdk,
|
|
8
|
+
onStorybookElementClick,
|
|
9
|
+
}) => {
|
|
10
|
+
const field = sdk.parameters.instance.contentField;
|
|
11
|
+
const fieldSdk = sdk.entry.fields[field];
|
|
12
|
+
|
|
13
|
+
const [locale, setLocale] = useState(sdk.locales.default);
|
|
14
|
+
const [portal, setPortal] = useState();
|
|
15
|
+
const portalField = sdk?.entry?.fields?.portal;
|
|
16
|
+
|
|
17
|
+
portalField?.onValueChanged(() => {
|
|
18
|
+
if (portalField.getValue() !== portal) {
|
|
19
|
+
setPortal(portalField.getValue());
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const detachHandler = sdk.editor.onLocaleSettingsChanged((loc) => {
|
|
25
|
+
setLocale(loc?.focused || sdk.locales.default);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return () => {
|
|
29
|
+
detachHandler();
|
|
30
|
+
};
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
return <EditorField
|
|
34
|
+
preview={false}
|
|
35
|
+
fieldSdk={fieldSdk}
|
|
36
|
+
editorSdk={sdk.editor}
|
|
37
|
+
locale={locale}
|
|
38
|
+
entrySdk={sdk.entry}
|
|
39
|
+
appSdk={sdk.app}
|
|
40
|
+
sdk={sdk}
|
|
41
|
+
portal={portal}
|
|
42
|
+
onStorybookElementClick={onStorybookElementClick}
|
|
43
|
+
/>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default Entry;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
const Field = ({sdk}) => {
|
|
3
|
+
const openDialog = () => {
|
|
4
|
+
console.log("KIENZ_DEBUG: :14 / openDialog", sdk.field.getValue());
|
|
5
|
+
|
|
6
|
+
sdk.dialogs.openCurrentApp({
|
|
7
|
+
title: 'Inhalt bearbeiten',
|
|
8
|
+
width: 'fullWidth',
|
|
9
|
+
position: "center",
|
|
10
|
+
allowHeightOverflow: true,
|
|
11
|
+
shouldCloseOnOverlayClick: false,
|
|
12
|
+
shouldCloseOnEscapePress: false,
|
|
13
|
+
minHeight: 500,
|
|
14
|
+
parameters: {
|
|
15
|
+
value: sdk.field.getValue(),
|
|
16
|
+
},
|
|
17
|
+
}).then((data) => {
|
|
18
|
+
console.log("KIENZ_DEBUG: :26 / ANON", data);
|
|
19
|
+
if (data) {
|
|
20
|
+
sdk.field.setValue(data);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return <div>
|
|
26
|
+
<button onClick={openDialog}>Inhalt bearbeiten</button>
|
|
27
|
+
</div>
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default Field;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useState, useEffect, useRef,
|
|
3
|
+
} from "react";
|
|
4
|
+
|
|
5
|
+
export default function useOnScreen(ref) {
|
|
6
|
+
const [isIntersecting, setIntersecting] = useState(false);
|
|
7
|
+
const observerRef = useRef();
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!observerRef.current && ref.current) {
|
|
11
|
+
observerRef.current = new IntersectionObserver(([entry]) => setIntersecting(entry.isIntersecting));
|
|
12
|
+
|
|
13
|
+
observerRef.current.observe(ref.current);
|
|
14
|
+
|
|
15
|
+
return () => {
|
|
16
|
+
if (observerRef.current) {
|
|
17
|
+
observerRef.current.disconnect();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
return isIntersecting;
|
|
24
|
+
}
|
package/src/index.html
ADDED
package/src/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {render} from 'react-dom';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
init,
|
|
6
|
+
locations,
|
|
7
|
+
} from '@contentful/app-sdk';
|
|
8
|
+
import '@contentful/forma-36-react-components/dist/styles.css';
|
|
9
|
+
import '@contentful/forma-36-fcss/dist/styles.css';
|
|
10
|
+
import '@contentful/forma-36-tokens/dist/css/index.css';
|
|
11
|
+
import './scss/index.scss';
|
|
12
|
+
|
|
13
|
+
import EntryEditor from './components/EntryEditor';
|
|
14
|
+
import Config from './components/ConfigScreen';
|
|
15
|
+
import Field from './components/Field';
|
|
16
|
+
import Dialog from "./components/Dialog";
|
|
17
|
+
|
|
18
|
+
init((sdk) => {
|
|
19
|
+
const root = document.getElementById('root');
|
|
20
|
+
|
|
21
|
+
const ComponentLocationSettings = [
|
|
22
|
+
{
|
|
23
|
+
location: locations.LOCATION_APP_CONFIG,
|
|
24
|
+
component: <Config sdk={sdk}/>,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
location: locations.LOCATION_ENTRY_FIELD,
|
|
28
|
+
component: <Field sdk={sdk}/>,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
location: locations.LOCATION_ENTRY_EDITOR,
|
|
32
|
+
component: <EntryEditor
|
|
33
|
+
sdk={sdk}
|
|
34
|
+
onStorybookElementClick={element => {
|
|
35
|
+
const storybookElementChangeEvent = new CustomEvent("storybookElementChange", {detail: element});
|
|
36
|
+
|
|
37
|
+
window.parent.document.dispatchEvent(storybookElementChangeEvent);
|
|
38
|
+
}}/>,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
location: locations.LOCATION_DIALOG,
|
|
42
|
+
component: <Dialog sdk={sdk}/>,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
ComponentLocationSettings.forEach((componentLocationSetting) => {
|
|
47
|
+
if (sdk.location.is(componentLocationSetting.location)) {
|
|
48
|
+
render(componentLocationSetting.component, root);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
html, body, #root {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: 100%;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.cloudinary-dialog {
|
|
7
|
+
width: 100%;
|
|
8
|
+
height: 100%;
|
|
9
|
+
min-width: 100%;
|
|
10
|
+
min-height: 100%;
|
|
11
|
+
|
|
12
|
+
iframe {
|
|
13
|
+
width: 100%;
|
|
14
|
+
height: 100%;
|
|
15
|
+
min-width: 100%;
|
|
16
|
+
min-height: 100%;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
html, body, div {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
border: 0;
|
|
5
|
+
font-size: 100%;
|
|
6
|
+
font: inherit;
|
|
7
|
+
vertical-align: baseline;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.editor-field {
|
|
11
|
+
height: 100%;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@media (prefers-color-scheme: dark) {
|
|
15
|
+
.editor-field {
|
|
16
|
+
color: white;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
let config = require("./webpack.config")({
|
|
3
|
+
componentImportRoot: "@vonaffenfels/slate-editor/src/dev/testComponents",
|
|
4
|
+
relativePathToComponents: path.resolve(__dirname + "../../../@vonaffenfels/slate-editor/src/dev/testComponents"),
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
config.mode = "development";
|
|
8
|
+
config.watch = true;
|
|
9
|
+
config.watchOptions = {ignored: ['dist/**', 'node_modules/**']};
|
|
10
|
+
config.cache = {type: "memory"};
|
|
11
|
+
|
|
12
|
+
config.output.publicPath = "/dist/";
|
|
13
|
+
config.devtool = 'inline-source-map';
|
|
14
|
+
|
|
15
|
+
config.devServer = {
|
|
16
|
+
static: path.join(__dirname, 'dist'),
|
|
17
|
+
compress: true,
|
|
18
|
+
historyApiFallback: true,
|
|
19
|
+
port: process.env.PORT,
|
|
20
|
+
devMiddleware: {writeToDisk: true},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
module.exports = config;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|
3
|
+
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
4
|
+
const webpack = require("webpack");
|
|
5
|
+
const {readFileSync} = require("fs");
|
|
6
|
+
const babelConfig = JSON.parse(readFileSync("../../.babelrc").toString());
|
|
7
|
+
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
|
|
8
|
+
|
|
9
|
+
module.exports = ({
|
|
10
|
+
componentImportRoot = "@vonaffenfels/slate-editor/src/dev/testComponents",
|
|
11
|
+
relativePathToComponents = path.resolve(__dirname + "../../../@vonaffenfels/slate-editor/src/dev/testComponents"),
|
|
12
|
+
}) => ({
|
|
13
|
+
mode: 'production',
|
|
14
|
+
entry: {index: './src/index.js'},
|
|
15
|
+
module: {
|
|
16
|
+
rules: [
|
|
17
|
+
{
|
|
18
|
+
test: /storyLoader.js/i,
|
|
19
|
+
use: [
|
|
20
|
+
{
|
|
21
|
+
loader: "@vonaffenfels/slate-editor/storyLoader.js",
|
|
22
|
+
options: {
|
|
23
|
+
storiesRoot: relativePathToComponents,
|
|
24
|
+
storiesImportRoot: componentImportRoot,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
test: /componentLoader.js/i,
|
|
31
|
+
use: [
|
|
32
|
+
{
|
|
33
|
+
loader: "@vonaffenfels/slate-editor/componentLoader.js",
|
|
34
|
+
options: {
|
|
35
|
+
componentRoot: relativePathToComponents,
|
|
36
|
+
componentImportRoot: componentImportRoot,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
test: /\.js$/,
|
|
43
|
+
include: [
|
|
44
|
+
path.resolve(__dirname, 'src'),
|
|
45
|
+
relativePathToComponents,
|
|
46
|
+
],
|
|
47
|
+
use: [
|
|
48
|
+
{
|
|
49
|
+
loader: 'babel-loader',
|
|
50
|
+
options: babelConfig,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
test: /\.s[ac]ss$/i,
|
|
56
|
+
include: path.resolve(__dirname, 'src', 'scss'),
|
|
57
|
+
use: [
|
|
58
|
+
"style-loader",
|
|
59
|
+
"css-loader",
|
|
60
|
+
"sass-loader",
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
test: /\.css$/i,
|
|
65
|
+
use: [
|
|
66
|
+
'style-loader',
|
|
67
|
+
{
|
|
68
|
+
loader: 'css-loader',
|
|
69
|
+
options: {importLoaders: 1},
|
|
70
|
+
},
|
|
71
|
+
{loader: 'postcss-loader'},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
test: /\.svg$/,
|
|
76
|
+
resourceQuery: {not: [/url/]}, // exclude react component if *.svg?url
|
|
77
|
+
use: [
|
|
78
|
+
{
|
|
79
|
+
loader: '@svgr/webpack',
|
|
80
|
+
options: {
|
|
81
|
+
dimensions: false,
|
|
82
|
+
svgo: false,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
'url-loader',
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
test: /\.(woff(2)?|ttf|eot|png)(\?v=\d+\.\d+\.\d+)?$/,
|
|
90
|
+
use: [
|
|
91
|
+
{
|
|
92
|
+
loader: 'file-loader',
|
|
93
|
+
options: {
|
|
94
|
+
name: '[name].[ext]',
|
|
95
|
+
outputPath: 'fonts/',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
test: /\.(graphql|gql)$/,
|
|
102
|
+
include: [
|
|
103
|
+
path.resolve(__dirname, 'src'),
|
|
104
|
+
path.resolve(__dirname, '../../@frontend/components'),
|
|
105
|
+
],
|
|
106
|
+
loader: 'graphql-tag/loader',
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
plugins: [
|
|
111
|
+
new CleanWebpackPlugin(),
|
|
112
|
+
new HtmlWebpackPlugin({template: path.join(__dirname, 'src', 'index.html')}),
|
|
113
|
+
new MiniCssExtractPlugin({}),
|
|
114
|
+
new webpack.DefinePlugin({'process.env': JSON.stringify(process.env)}),
|
|
115
|
+
],
|
|
116
|
+
resolve: {
|
|
117
|
+
extensions: ['.json', '.js', '.jsx'],
|
|
118
|
+
fallback: {
|
|
119
|
+
stream: false,
|
|
120
|
+
path: false,
|
|
121
|
+
timers: false,
|
|
122
|
+
},
|
|
123
|
+
alias: {
|
|
124
|
+
storybookStories: '@vonaffenfels/slate-editor/storyLoader.js',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
output: {
|
|
128
|
+
filename: '[name].js',
|
|
129
|
+
libraryTarget: "umd",
|
|
130
|
+
publicPath: "/",
|
|
131
|
+
globalObject: "this",
|
|
132
|
+
path: path.resolve(__dirname, 'dist'),
|
|
133
|
+
},
|
|
134
|
+
});
|