@vonaffenfels/portal-slug-field 1.1.2

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/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ v14.16.1
package/package.json ADDED
@@ -0,0 +1,100 @@
1
+ {
2
+ "name": "@vonaffenfels/portal-slug-field",
3
+ "version": "1.1.2",
4
+ "scripts": {
5
+ "prepublish": "yarn run build",
6
+ "dev": "yarn run start",
7
+ "test": "echo \"Error: no test specified\" && exit 1",
8
+ "build": "rm -rf dist && webpack --config webpack.config.js",
9
+ "start": "webpack serve --config webpack.config.dev.js"
10
+ },
11
+ "peerDependencies": {
12
+ "@babel/core": "^7.20.12",
13
+ "@babel/plugin-proposal-class-properties": "^7.13.0",
14
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3",
15
+ "@babel/preset-env": "^7.13.15",
16
+ "@babel/preset-react": "^7.13.13",
17
+ "@contentful/app-sdk": "^3.33.0",
18
+ "@contentful/field-editor-single-line": "^0.14.1",
19
+ "@contentful/field-editor-test-utils": "^0.11.1",
20
+ "@contentful/forma-36-fcss": "^0.3.1",
21
+ "@contentful/forma-36-react-components": "^3.88.3",
22
+ "@contentful/forma-36-tokens": "^0.10.1",
23
+ "@svgr/webpack": "^5.5.0",
24
+ "babel-loader": "^8.2.2",
25
+ "clean-webpack-plugin": "^3.0.0",
26
+ "cloudinary": "^1.25.1",
27
+ "contentful-management": "^7.17.0",
28
+ "copy-webpack-plugin": "^8.1.1",
29
+ "cross-env": "^7.0.3",
30
+ "css-loader": "^5.2.4",
31
+ "cssnano": "^5.0.1",
32
+ "dotenv-webpack": "^7.0.2",
33
+ "file-loader": "^6.2.0",
34
+ "html-webpack-plugin": "^5.3.1",
35
+ "mini-css-extract-plugin": "^1.5.0",
36
+ "postcss": "8.4.14",
37
+ "postcss-loader": "^5.2.0",
38
+ "raw-loader": "^4.0.2",
39
+ "react": "18.2.0",
40
+ "react-dom": "18.2.0",
41
+ "react-uid": "^2.3.1",
42
+ "sass": "^1.32.11",
43
+ "sass-loader": "^11.0.1",
44
+ "speakingurl": "^14.0.1",
45
+ "style-loader": "^2.0.0",
46
+ "tailwindcss": "3.3.2",
47
+ "url-loader": "^4.1.1",
48
+ "webpack-cli": "^4.6.0",
49
+ "webpack-dev-server": "^4.0.0-beta.2",
50
+ "webpack-watch-files-plugin": "^1.2.1"
51
+ },
52
+ "devDependencies": {
53
+ "@babel/core": "^7.20.12",
54
+ "@babel/plugin-proposal-class-properties": "^7.13.0",
55
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3",
56
+ "@babel/preset-env": "^7.13.15",
57
+ "@babel/preset-react": "^7.13.13",
58
+ "@contentful/app-sdk": "^3.33.0",
59
+ "@contentful/field-editor-single-line": "^0.14.1",
60
+ "@contentful/field-editor-test-utils": "^0.11.1",
61
+ "@contentful/forma-36-fcss": "^0.3.1",
62
+ "@contentful/forma-36-react-components": "^3.88.3",
63
+ "@contentful/forma-36-tokens": "^0.10.1",
64
+ "@svgr/webpack": "^5.5.0",
65
+ "babel-loader": "^8.2.2",
66
+ "clean-webpack-plugin": "^3.0.0",
67
+ "cloudinary": "^1.25.1",
68
+ "contentful-management": "^7.17.0",
69
+ "copy-webpack-plugin": "^8.1.1",
70
+ "cross-env": "^7.0.3",
71
+ "css-loader": "^5.2.4",
72
+ "cssnano": "^5.0.1",
73
+ "dotenv-webpack": "^7.0.2",
74
+ "file-loader": "^6.2.0",
75
+ "html-webpack-plugin": "^5.3.1",
76
+ "mini-css-extract-plugin": "^1.5.0",
77
+ "postcss": "8.4.14",
78
+ "postcss-loader": "^5.2.0",
79
+ "raw-loader": "^4.0.2",
80
+ "react": "18.2.0",
81
+ "react-dom": "18.2.0",
82
+ "react-uid": "^2.3.1",
83
+ "sass": "^1.32.11",
84
+ "sass-loader": "^11.0.1",
85
+ "speakingurl": "^14.0.1",
86
+ "style-loader": "^2.0.0",
87
+ "tailwindcss": "3.3.2",
88
+ "url-loader": "^4.1.1",
89
+ "webpack-cli": "^4.6.0",
90
+ "webpack-dev-server": "^4.0.0-beta.2",
91
+ "webpack-watch-files-plugin": "^1.2.1"
92
+ },
93
+ "dependencies": {
94
+ "webpack": "5.88.2"
95
+ },
96
+ "publishConfig": {
97
+ "access": "public"
98
+ },
99
+ "gitHead": "08d7c49cef5ccdc3429453a99dca8c28f904e7ff"
100
+ }
@@ -0,0 +1,88 @@
1
+ import {
2
+ TextInput, Note, InlineEntryCard, MenuItem,
3
+ } from '@contentful/f36-components';
4
+ import React, {
5
+ useState, useEffect,
6
+ } from 'react';
7
+ import getSlug from "speakingurl";
8
+ import {getContentfulClient} from "../lib/contentfulClient";
9
+
10
+ const Field = ({sdk}) => {
11
+ const [value, setValue] = useState(sdk.field.getValue());
12
+ const [error, setError] = useState(false);
13
+
14
+ useEffect(() => {
15
+ // initial check
16
+ checkDuplicate(value);
17
+ }, []);
18
+
19
+ const onChange = (value, sluggify = false) => {
20
+ let localValue = String(value || "").toLowerCase();
21
+ if (sluggify) {
22
+ if (localValue.startsWith("/") || localValue.endsWith("/")) {
23
+ localValue = localValue.replace(/^[/]*(.*?)[/]*$/, "$1");
24
+ }
25
+
26
+ localValue = getSlug(localValue, {
27
+ lang: 'de',
28
+ custom: ["/", "-"],
29
+ });
30
+ }
31
+
32
+ sdk.field.setValue(localValue);
33
+ setValue(localValue);
34
+
35
+ checkDuplicate(localValue);
36
+ };
37
+
38
+ const checkDuplicate = async (value) => {
39
+ const client = getContentfulClient();
40
+ const entries = await client.getEntries({
41
+ content_type: sdk.entry.getSys().contentType.sys.id,
42
+ limit: 1,
43
+ [`fields.${sdk.field.id}`]: value,
44
+ [`sys.id[ne]`]: sdk.entry.getSys().id,
45
+ });
46
+
47
+ const hasDuplicate = !!entries.items.at(0);
48
+
49
+ if (hasDuplicate) {
50
+ sdk.field.setValue("");
51
+ }
52
+
53
+ sdk.field.setInvalid(!!entries.items.at(0));
54
+ setError(entries.items.at(0) || false);
55
+ };
56
+
57
+ const openError = () => {
58
+ sdk.navigator.openEntry(error.sys.id, {slideIn: true});
59
+ };
60
+
61
+ return (
62
+ <>
63
+ <TextInput
64
+ value={value}
65
+ onBlur={(e) => onChange(e.target.value, true)}
66
+ type="text"
67
+ name="slug"
68
+ placeholder=""
69
+ onChange={(e) => onChange(e.target.value)}
70
+ />
71
+ {!!error && <>
72
+ <Note variant="negative">
73
+ Slug wird bereits von <InlineEntryCard
74
+ actions={[
75
+ <MenuItem key="open" onClick={openError}>Öffnen</MenuItem>,
76
+ ]}
77
+ status={error.sys.publishedVersion ? "published" : "draft"}
78
+ title={error.fields.title.de}
79
+ entry={error}
80
+ /> verwendet.
81
+ </Note>
82
+
83
+ </>}
84
+ </>
85
+ );
86
+ };
87
+
88
+ export default Field;
package/src/index.html ADDED
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8"/>
5
+ <title>Remote Select Contentful App</title>
6
+ </head>
7
+ <body>
8
+ <div id="root">
9
+ </div>
10
+ </body>
11
+ </html>
package/src/index.js ADDED
@@ -0,0 +1,33 @@
1
+ import './scss/index.scss';
2
+ import React from 'react';
3
+ import {createRoot} from 'react-dom/client';
4
+
5
+ import {
6
+ init,
7
+ locations,
8
+ } from '@contentful/app-sdk';
9
+ import '@contentful/forma-36-react-components/dist/styles.css';
10
+ import '@contentful/forma-36-fcss/dist/styles.css';
11
+ import '@contentful/forma-36-tokens/dist/css/index.css';
12
+
13
+ import Field from './components/Field';
14
+ import {initContentfulClient} from "./lib/contentfulClient";
15
+
16
+ init((sdk) => {
17
+ initContentfulClient(sdk);
18
+
19
+ const rootContainer = document.getElementById('root');
20
+ const ComponentLocationSettings = [
21
+ {
22
+ location: locations.LOCATION_ENTRY_FIELD,
23
+ component: <Field sdk={sdk}/>,
24
+ },
25
+ ];
26
+
27
+ ComponentLocationSettings.forEach((componentLocationSetting) => {
28
+ if (sdk.location.is(componentLocationSetting.location)) {
29
+ const root = createRoot(rootContainer); // createRoot(container!) if you use TypeScript
30
+ root.render(componentLocationSetting.component);
31
+ }
32
+ });
33
+ });
@@ -0,0 +1,13 @@
1
+ let __contentfulClient;
2
+
3
+ export const getContentfulClient = () => {
4
+ if (!__contentfulClient) {
5
+ throw new Error(`Contentful client not initialized run initContentfulClient(sdk) first`);
6
+ }
7
+
8
+ return __contentfulClient;
9
+ };
10
+ export const initContentfulClient = (sdk) => {
11
+ __contentfulClient = sdk.space;
12
+ };
13
+
@@ -0,0 +1,12 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ html, body, div {
6
+ margin: 0;
7
+ padding: 0;
8
+ border: 0;
9
+ font-size: 100%;
10
+ font: inherit;
11
+ vertical-align: baseline;
12
+ }
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ content: [
3
+ './src/**/*.js',
4
+ ],
5
+ };
@@ -0,0 +1,20 @@
1
+ const path = require('path');
2
+ let config = require("./webpack.config");
3
+
4
+ config.mode = "development";
5
+ config.watch = true;
6
+ config.watchOptions = {ignored: ['dist/**', 'node_modules/**']};
7
+ config.cache = {type: "memory"};
8
+
9
+ config.output.publicPath = "/dist/";
10
+ config.devtool = 'inline-source-map';
11
+
12
+ config.devServer = {
13
+ static: path.join(__dirname, 'dist'),
14
+ compress: true,
15
+ historyApiFallback: true,
16
+ port: process.env.PORT,
17
+ devMiddleware: {writeToDisk: true},
18
+ };
19
+
20
+ module.exports = config;
@@ -0,0 +1,187 @@
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 {CleanWebpackPlugin} = require('clean-webpack-plugin');
6
+
7
+ module.exports = {
8
+ mode: 'production',
9
+ entry: {index: './src/index.js'},
10
+ module: {
11
+ rules: [
12
+ {
13
+ test: /\.js$/,
14
+ include: path.resolve(__dirname, 'src'),
15
+ use: [
16
+ {
17
+ loader: 'babel-loader',
18
+ options: {
19
+ "presets": [
20
+ "next/babel",
21
+ [
22
+ "@babel/preset-env",
23
+ {
24
+ "modules": false,
25
+ "targets": {
26
+ "browsers": [
27
+ "last 2 Chrome versions",
28
+ "last 2 Firefox versions",
29
+ "last 2 Safari versions",
30
+ "last 2 iOS versions",
31
+ "last 1 Android version",
32
+ "last 1 ChromeAndroid version",
33
+ "ie 11",
34
+ ],
35
+ },
36
+ },
37
+ ],
38
+ "@babel/preset-react",
39
+ ],
40
+ "plugins": [
41
+ [
42
+ "@babel/plugin-proposal-private-property-in-object",
43
+ {
44
+ "loose": true,
45
+ },
46
+ ],
47
+ [
48
+ "@babel/plugin-proposal-class-properties",
49
+ {
50
+ "loose": true,
51
+ },
52
+ ],
53
+ "@babel/plugin-syntax-dynamic-import",
54
+ ],
55
+ },
56
+ },
57
+ ],
58
+ },
59
+ {
60
+ test: /\.s[ac]ss$/i,
61
+ include: path.resolve(__dirname, 'src'),
62
+ use: [
63
+ "style-loader",
64
+ {
65
+ loader: 'css-loader',
66
+ options: {importLoaders: 1},
67
+ },
68
+ {
69
+ loader: 'postcss-loader',
70
+ options: {
71
+ postcssOptions: {
72
+ plugins: {
73
+ 'postcss-import': {},
74
+ tailwindcss: {},
75
+ autoprefixer: {},
76
+ },
77
+ },
78
+ },
79
+ },
80
+ "sass-loader",
81
+ ],
82
+ },
83
+ {
84
+ test: /module\.css$/i,
85
+ use: [
86
+ 'style-loader',
87
+ {
88
+ loader: 'css-loader',
89
+ options: {
90
+ importLoaders: 1,
91
+ modules: {exportLocalsConvention: "camelCase"},
92
+ },
93
+ },
94
+ {
95
+ loader: 'postcss-loader',
96
+ options: {postcssOptions: {
97
+ postcssOptions: {
98
+ plugins: {
99
+ 'postcss-import': {},
100
+ tailwindcss: {},
101
+ autoprefixer: {},
102
+ },
103
+ },
104
+ }},
105
+ },
106
+ ],
107
+ },
108
+ {
109
+ test: /\.css$/i,
110
+ exclude: [/module\.css$/i],
111
+ use: [
112
+ 'style-loader',
113
+ {
114
+ loader: 'css-loader',
115
+ options: {importLoaders: 1},
116
+ },
117
+ {
118
+ loader: 'postcss-loader',
119
+ options: {postcssOptions: {
120
+ postcssOptions: {
121
+ plugins: {
122
+ 'postcss-import': {},
123
+ tailwindcss: {},
124
+ autoprefixer: {},
125
+ },
126
+ },
127
+ }},
128
+ },
129
+ ],
130
+ },
131
+ {
132
+ test: /\.svg$/,
133
+ resourceQuery: {not: [/url/]}, // exclude react component if *.svg?url
134
+ use: [
135
+ {
136
+ loader: '@svgr/webpack',
137
+ options: {
138
+ dimensions: false,
139
+ svgo: false,
140
+ },
141
+ },
142
+ 'url-loader',
143
+ ],
144
+ },
145
+ {
146
+ test: /\.(woff(2)?|ttf|eot|png)(\?v=\d+\.\d+\.\d+)?$/,
147
+ use: [
148
+ {
149
+ loader: 'file-loader',
150
+ options: {
151
+ name: '[name].[ext]',
152
+ outputPath: 'fonts/',
153
+ },
154
+ },
155
+ ],
156
+ },
157
+ {
158
+ test: /\.(graphql|gql)$/,
159
+ loader: 'raw-loader',
160
+ },
161
+ ],
162
+ },
163
+ plugins: [
164
+ new CleanWebpackPlugin(),
165
+ new HtmlWebpackPlugin({template: path.join(__dirname, 'src', 'index.html')}),
166
+ new MiniCssExtractPlugin({}),
167
+ ],
168
+ resolve: {
169
+ extensions: ['.json', '.js', '.jsx'],
170
+ symlinks: false,
171
+ fallback: {
172
+ stream: false,
173
+ path: false,
174
+ timers: false,
175
+ },
176
+ },
177
+ output: {
178
+ filename: '[name].js',
179
+ libraryTarget: "umd",
180
+ publicPath: "/",
181
+ globalObject: "this",
182
+ path: path.resolve("./dist"),
183
+ },
184
+ snapshot: {
185
+ managedPaths: [],
186
+ },
187
+ };