@vonaffenfels/portal-slug-field 1.2.1 → 1.2.19

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 CHANGED
@@ -1 +1 @@
1
- v14.16.1
1
+ v14.16.1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vonaffenfels/portal-slug-field",
3
- "version": "1.2.1",
3
+ "version": "1.2.19",
4
4
  "scripts": {
5
5
  "prepublish": "yarn run build",
6
6
  "dev": "yarn run start",
@@ -101,5 +101,5 @@
101
101
  "publishConfig": {
102
102
  "access": "public"
103
103
  },
104
- "gitHead": "b06b79ed032fecf1dfeef2e7e312eb0d42f2e84e"
104
+ "gitHead": "aa2e077d950ae577347ebe01cd13860321b5141b"
105
105
  }
@@ -1,109 +1,109 @@
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
- useEffect(() => {
20
- if (!value) {
21
- // generate from title if empty
22
- const title = sdk.entry.fields.title.getValue();
23
- if (title) {
24
- onChange(getSlug(title));
25
- }
26
- }
27
- }, []);
28
-
29
- const onChange = (value, sluggify = false) => {
30
- const title = sdk.entry.fields.title.getValue();
31
- let localValue = String(value || title).toLowerCase();
32
-
33
- if (sluggify && localValue !== "/") {
34
- if (localValue.startsWith("/") || localValue.endsWith("/")) {
35
- localValue = localValue.replace(/^[/]*(.*?)[/]*$/, "$1");
36
- }
37
-
38
- localValue = getSlug(localValue, {
39
- lang: 'de',
40
- custom: {
41
- "/": "/",
42
- "-": "-",
43
- },
44
- });
45
- }
46
-
47
- sdk.field.setValue(localValue);
48
- setValue(localValue);
49
-
50
- checkDuplicate(localValue);
51
- };
52
-
53
- const checkDuplicate = async (value) => {
54
- if (!value) {
55
- sdk.field.setInvalid(true);
56
- return;
57
- }
58
-
59
- const client = getContentfulClient();
60
- const entries = await client.getEntries({
61
- content_type: sdk.entry.getSys().contentType.sys.id,
62
- limit: 1,
63
- [`fields.${sdk.field.id}`]: value,
64
- [`fields.portal`]: sdk.entry.fields.portal.getValue(),
65
- [`sys.id[ne]`]: sdk.entry.getSys().id,
66
- });
67
-
68
- const hasDuplicate = !!entries.items.at(0);
69
-
70
- if (hasDuplicate) {
71
- sdk.field.setValue("");
72
- }
73
-
74
- sdk.field.setInvalid(!!entries.items.at(0));
75
- setError(entries.items.at(0) || false);
76
- };
77
-
78
- const openError = () => {
79
- sdk.navigator.openEntry(error.sys.id, {slideIn: true});
80
- };
81
-
82
- return (
83
- <>
84
- <TextInput
85
- value={value}
86
- onBlur={(e) => onChange(e.target.value, true)}
87
- type="text"
88
- name="slug"
89
- placeholder=""
90
- onChange={(e) => onChange(e.target.value)}
91
- />
92
- {!!error && <>
93
- <Note variant="negative">
94
- Slug wird bereits von <InlineEntryCard
95
- actions={[
96
- <MenuItem key="open" onClick={openError}>Öffnen</MenuItem>,
97
- ]}
98
- status={error.sys.publishedVersion ? "published" : "draft"}
99
- title={error.fields.title.de}
100
- entry={error}
101
- /> verwendet.
102
- </Note>
103
-
104
- </>}
105
- </>
106
- );
107
- };
108
-
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
+ useEffect(() => {
20
+ if (!value) {
21
+ // generate from title if empty
22
+ const title = sdk.entry.fields.title.getValue();
23
+ if (title) {
24
+ onChange(getSlug(title));
25
+ }
26
+ }
27
+ }, []);
28
+
29
+ const onChange = (value, sluggify = false) => {
30
+ const title = sdk.entry.fields.title.getValue();
31
+ let localValue = String(value || title).toLowerCase();
32
+
33
+ if (sluggify && localValue !== "/") {
34
+ if (localValue.startsWith("/") || localValue.endsWith("/")) {
35
+ localValue = localValue.replace(/^[/]*(.*?)[/]*$/, "$1");
36
+ }
37
+
38
+ localValue = getSlug(localValue, {
39
+ lang: 'de',
40
+ custom: {
41
+ "/": "/",
42
+ "-": "-",
43
+ },
44
+ });
45
+ }
46
+
47
+ sdk.field.setValue(localValue);
48
+ setValue(localValue);
49
+
50
+ checkDuplicate(localValue);
51
+ };
52
+
53
+ const checkDuplicate = async (value) => {
54
+ if (!value) {
55
+ sdk.field.setInvalid(true);
56
+ return;
57
+ }
58
+
59
+ const client = getContentfulClient();
60
+ const entries = await client.getEntries({
61
+ content_type: sdk.entry.getSys().contentType.sys.id,
62
+ limit: 1,
63
+ [`fields.${sdk.field.id}`]: value,
64
+ [`fields.portal`]: sdk.entry.fields.portal.getValue(),
65
+ [`sys.id[ne]`]: sdk.entry.getSys().id,
66
+ });
67
+
68
+ const hasDuplicate = !!entries.items.at(0);
69
+
70
+ if (hasDuplicate) {
71
+ sdk.field.setValue("");
72
+ }
73
+
74
+ sdk.field.setInvalid(!!entries.items.at(0));
75
+ setError(entries.items.at(0) || false);
76
+ };
77
+
78
+ const openError = () => {
79
+ sdk.navigator.openEntry(error.sys.id, {slideIn: true});
80
+ };
81
+
82
+ return (
83
+ <>
84
+ <TextInput
85
+ value={value}
86
+ onBlur={(e) => onChange(e.target.value, true)}
87
+ type="text"
88
+ name="slug"
89
+ placeholder=""
90
+ onChange={(e) => onChange(e.target.value)}
91
+ />
92
+ {!!error && <>
93
+ <Note variant="negative">
94
+ Slug wird bereits von <InlineEntryCard
95
+ actions={[
96
+ <MenuItem key="open" onClick={openError}>Öffnen</MenuItem>,
97
+ ]}
98
+ status={error.sys.publishedVersion ? "published" : "draft"}
99
+ title={error.fields.title.de}
100
+ entry={error}
101
+ /> verwendet.
102
+ </Note>
103
+
104
+ </>}
105
+ </>
106
+ );
107
+ };
108
+
109
109
  export default Field;
package/src/index.html CHANGED
@@ -1,11 +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>
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
11
  </html>
package/src/index.js CHANGED
@@ -1,35 +1,35 @@
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
- sdk.window.startAutoResizer();
20
-
21
- const rootContainer = document.getElementById('root');
22
- const ComponentLocationSettings = [
23
- {
24
- location: locations.LOCATION_ENTRY_FIELD,
25
- component: <Field sdk={sdk}/>,
26
- },
27
- ];
28
-
29
- ComponentLocationSettings.forEach((componentLocationSetting) => {
30
- if (sdk.location.is(componentLocationSetting.location)) {
31
- const root = createRoot(rootContainer); // createRoot(container!) if you use TypeScript
32
- root.render(componentLocationSetting.component);
33
- }
34
- });
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
+ sdk.window.startAutoResizer();
20
+
21
+ const rootContainer = document.getElementById('root');
22
+ const ComponentLocationSettings = [
23
+ {
24
+ location: locations.LOCATION_ENTRY_FIELD,
25
+ component: <Field sdk={sdk}/>,
26
+ },
27
+ ];
28
+
29
+ ComponentLocationSettings.forEach((componentLocationSetting) => {
30
+ if (sdk.location.is(componentLocationSetting.location)) {
31
+ const root = createRoot(rootContainer); // createRoot(container!) if you use TypeScript
32
+ root.render(componentLocationSetting.component);
33
+ }
34
+ });
35
35
  });
@@ -1,13 +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
-
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
+
@@ -1,12 +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;
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
12
  }
@@ -1,5 +1,5 @@
1
- module.exports = {
2
- content: [
3
- './src/**/*.js',
4
- ],
1
+ module.exports = {
2
+ content: [
3
+ './src/**/*.js',
4
+ ],
5
5
  };
@@ -1,20 +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
-
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
20
  module.exports = config;
package/webpack.config.js CHANGED
@@ -1,200 +1,200 @@
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
- {"loose": true},
44
- ],
45
- [
46
- "@babel/plugin-proposal-class-properties",
47
- {"loose": true},
48
- ],
49
- "@babel/plugin-syntax-dynamic-import",
50
- ],
51
- },
52
- },
53
- ],
54
- },
55
- {
56
- test: /\.s[ac]ss$/i,
57
- include: path.resolve(__dirname, 'src'),
58
- use: [
59
- "style-loader",
60
- {
61
- loader: 'css-loader',
62
- options: {importLoaders: 1},
63
- },
64
- {
65
- loader: 'postcss-loader',
66
- options: {
67
- postcssOptions: {
68
- plugins: {
69
- 'postcss-import': {},
70
- tailwindcss: {},
71
- autoprefixer: {},
72
- },
73
- },
74
- },
75
- },
76
- "sass-loader",
77
- ],
78
- },
79
- {
80
- test: /module\.css$/i,
81
- use: [
82
- 'style-loader',
83
- {
84
- loader: 'css-loader',
85
- options: {
86
- importLoaders: 1,
87
- modules: {exportLocalsConvention: "camelCase"},
88
- },
89
- },
90
- {
91
- loader: 'postcss-loader',
92
- options: {
93
- postcssOptions: {
94
- postcssOptions: {
95
- plugins: {
96
- 'postcss-import': {},
97
- tailwindcss: {},
98
- autoprefixer: {},
99
- },
100
- },
101
- },
102
- },
103
- },
104
- ],
105
- },
106
- {
107
- test: /\.css$/i,
108
- exclude: [/module\.css$/i],
109
- use: [
110
- 'style-loader',
111
- {
112
- loader: 'css-loader',
113
- options: {importLoaders: 1},
114
- },
115
- {
116
- loader: 'postcss-loader',
117
- options: {
118
- postcssOptions: {
119
- postcssOptions: {
120
- plugins: {
121
- 'postcss-import': {},
122
- tailwindcss: {},
123
- autoprefixer: {},
124
- },
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: /\.svg$/,
147
- resourceQuery: /url/, // include if *.svg?url
148
- use: [
149
- {
150
- loader: 'file-loader',
151
- options: {
152
- name: '[name].[ext]',
153
- outputPath: 'svgs/',
154
- },
155
- },
156
- ],
157
- },
158
- {
159
- test: /\.(woff(2)?|ttf|eot|png)(\?v=\d+\.\d+\.\d+)?$/,
160
- use: [
161
- {
162
- loader: 'file-loader',
163
- options: {
164
- name: '[name].[ext]',
165
- outputPath: 'fonts/',
166
- },
167
- },
168
- ],
169
- },
170
- {
171
- test: /\.(graphql|gql)$/,
172
- loader: 'raw-loader',
173
- },
174
- ],
175
- },
176
- plugins: [
177
- new CleanWebpackPlugin(),
178
- new HtmlWebpackPlugin({template: path.join(__dirname, 'src', 'index.html')}),
179
- new MiniCssExtractPlugin({}),
180
- ],
181
- resolve: {
182
- extensions: ['.json', '.js', '.jsx'],
183
- symlinks: false,
184
- fallback: {
185
- stream: false,
186
- path: false,
187
- timers: false,
188
- fs: false,
189
- zlib: false,
190
- },
191
- },
192
- output: {
193
- filename: '[name].js',
194
- libraryTarget: "umd",
195
- publicPath: "/",
196
- globalObject: "this",
197
- path: path.resolve("./dist"),
198
- },
199
- snapshot: {managedPaths: []},
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
+ {"loose": true},
44
+ ],
45
+ [
46
+ "@babel/plugin-proposal-class-properties",
47
+ {"loose": true},
48
+ ],
49
+ "@babel/plugin-syntax-dynamic-import",
50
+ ],
51
+ },
52
+ },
53
+ ],
54
+ },
55
+ {
56
+ test: /\.s[ac]ss$/i,
57
+ include: path.resolve(__dirname, 'src'),
58
+ use: [
59
+ "style-loader",
60
+ {
61
+ loader: 'css-loader',
62
+ options: {importLoaders: 1},
63
+ },
64
+ {
65
+ loader: 'postcss-loader',
66
+ options: {
67
+ postcssOptions: {
68
+ plugins: {
69
+ 'postcss-import': {},
70
+ tailwindcss: {},
71
+ autoprefixer: {},
72
+ },
73
+ },
74
+ },
75
+ },
76
+ "sass-loader",
77
+ ],
78
+ },
79
+ {
80
+ test: /module\.css$/i,
81
+ use: [
82
+ 'style-loader',
83
+ {
84
+ loader: 'css-loader',
85
+ options: {
86
+ importLoaders: 1,
87
+ modules: {exportLocalsConvention: "camelCase"},
88
+ },
89
+ },
90
+ {
91
+ loader: 'postcss-loader',
92
+ options: {
93
+ postcssOptions: {
94
+ postcssOptions: {
95
+ plugins: {
96
+ 'postcss-import': {},
97
+ tailwindcss: {},
98
+ autoprefixer: {},
99
+ },
100
+ },
101
+ },
102
+ },
103
+ },
104
+ ],
105
+ },
106
+ {
107
+ test: /\.css$/i,
108
+ exclude: [/module\.css$/i],
109
+ use: [
110
+ 'style-loader',
111
+ {
112
+ loader: 'css-loader',
113
+ options: {importLoaders: 1},
114
+ },
115
+ {
116
+ loader: 'postcss-loader',
117
+ options: {
118
+ postcssOptions: {
119
+ postcssOptions: {
120
+ plugins: {
121
+ 'postcss-import': {},
122
+ tailwindcss: {},
123
+ autoprefixer: {},
124
+ },
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: /\.svg$/,
147
+ resourceQuery: /url/, // include if *.svg?url
148
+ use: [
149
+ {
150
+ loader: 'file-loader',
151
+ options: {
152
+ name: '[name].[ext]',
153
+ outputPath: 'svgs/',
154
+ },
155
+ },
156
+ ],
157
+ },
158
+ {
159
+ test: /\.(woff(2)?|ttf|eot|png)(\?v=\d+\.\d+\.\d+)?$/,
160
+ use: [
161
+ {
162
+ loader: 'file-loader',
163
+ options: {
164
+ name: '[name].[ext]',
165
+ outputPath: 'fonts/',
166
+ },
167
+ },
168
+ ],
169
+ },
170
+ {
171
+ test: /\.(graphql|gql)$/,
172
+ loader: 'raw-loader',
173
+ },
174
+ ],
175
+ },
176
+ plugins: [
177
+ new CleanWebpackPlugin(),
178
+ new HtmlWebpackPlugin({template: path.join(__dirname, 'src', 'index.html')}),
179
+ new MiniCssExtractPlugin({}),
180
+ ],
181
+ resolve: {
182
+ extensions: ['.json', '.js', '.jsx'],
183
+ symlinks: false,
184
+ fallback: {
185
+ stream: false,
186
+ path: false,
187
+ timers: false,
188
+ fs: false,
189
+ zlib: false,
190
+ },
191
+ },
192
+ output: {
193
+ filename: '[name].js',
194
+ libraryTarget: "umd",
195
+ publicPath: "/",
196
+ globalObject: "this",
197
+ path: path.resolve("./dist"),
198
+ },
199
+ snapshot: {managedPaths: []},
200
200
  };