@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 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,7 @@
1
+ module.exports = {
2
+ plugins: {
3
+ 'postcss-import': {},
4
+ tailwindcss: {},
5
+ autoprefixer: {},
6
+ },
7
+ };
@@ -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
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8"/>
5
+ <title>Slate Block Editor Contentful App</title>
6
+ <script src="https://media-library.cloudinary.com/global/all.js"></script>
7
+ </head>
8
+ <body>
9
+ <div id="root">
10
+ </div>
11
+ </body>
12
+ </html>
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,6 @@
1
+
2
+ module.exports = {
3
+ content: [
4
+ './src/**/*.js'
5
+ ],
6
+ };
@@ -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
+ });