@contentful/field-editor-slug 1.2.0 → 1.3.0

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.
Files changed (39) hide show
  1. package/dist/cjs/SlugEditor.js +139 -0
  2. package/dist/cjs/SlugEditor.test.js +508 -0
  3. package/dist/cjs/SlugEditorField.js +186 -0
  4. package/dist/cjs/TrackingFieldConnector.js +137 -0
  5. package/dist/cjs/index.js +24 -0
  6. package/dist/cjs/services/makeSlug.js +42 -0
  7. package/dist/cjs/services/makeSlug.test.js +14 -0
  8. package/dist/cjs/services/slugify.js +64 -0
  9. package/dist/cjs/services/slugify.test.js +30 -0
  10. package/dist/cjs/styles.js +68 -0
  11. package/dist/esm/SlugEditor.js +90 -0
  12. package/dist/esm/SlugEditor.test.js +460 -0
  13. package/dist/esm/SlugEditorField.js +129 -0
  14. package/dist/esm/TrackingFieldConnector.js +88 -0
  15. package/dist/esm/index.js +3 -0
  16. package/dist/esm/services/makeSlug.js +24 -0
  17. package/dist/esm/services/makeSlug.test.js +10 -0
  18. package/dist/esm/services/slugify.js +49 -0
  19. package/dist/esm/services/slugify.test.js +26 -0
  20. package/dist/esm/styles.js +33 -0
  21. package/dist/{SlugEditor.d.ts → types/SlugEditor.d.ts} +24 -24
  22. package/dist/types/SlugEditor.test.d.ts +1 -0
  23. package/dist/{SlugEditorField.d.ts → types/SlugEditorField.d.ts} +18 -18
  24. package/dist/{TrackingFieldConnector.d.ts → types/TrackingFieldConnector.d.ts} +29 -29
  25. package/dist/{index.d.ts → types/index.d.ts} +3 -3
  26. package/dist/{services → types/services}/makeSlug.d.ts +8 -8
  27. package/dist/types/services/makeSlug.test.d.ts +1 -0
  28. package/dist/{services → types/services}/slugify.d.ts +12 -12
  29. package/dist/types/services/slugify.test.d.ts +1 -0
  30. package/dist/{styles.d.ts → types/styles.d.ts} +6 -6
  31. package/package.json +25 -11
  32. package/CHANGELOG.md +0 -206
  33. package/dist/field-editor-slug.cjs.development.js +0 -463
  34. package/dist/field-editor-slug.cjs.development.js.map +0 -1
  35. package/dist/field-editor-slug.cjs.production.min.js +0 -2
  36. package/dist/field-editor-slug.cjs.production.min.js.map +0 -1
  37. package/dist/field-editor-slug.esm.js +0 -454
  38. package/dist/field-editor-slug.esm.js.map +0 -1
  39. package/dist/index.js +0 -8
@@ -0,0 +1,460 @@
1
+ import * as React from 'react';
2
+ import { createFakeFieldAPI, createFakeLocalesAPI } from '@contentful/field-editor-test-utils';
3
+ import { render, configure, cleanup, wait, fireEvent } from '@testing-library/react';
4
+ import identity from 'lodash/identity';
5
+ import '@testing-library/jest-dom/extend-expect';
6
+ import { SlugEditor } from './SlugEditor';
7
+ configure({
8
+ testIdAttribute: 'data-test-id'
9
+ });
10
+ jest.mock('lodash/throttle', ()=>({
11
+ default: identity
12
+ }), {
13
+ virtual: true
14
+ });
15
+ jest.mock('use-debounce', ()=>({
16
+ useDebounce: (text)=>[
17
+ text
18
+ ]
19
+ }));
20
+ function createMocks(initialValues = {}) {
21
+ const [field] = createFakeFieldAPI((field)=>({
22
+ ...field,
23
+ id: 'slug-id',
24
+ onValueChanged: jest.fn().mockImplementation(field.onValueChanged),
25
+ setValue: jest.fn().mockImplementation(field.setValue)
26
+ }), initialValues.field || '');
27
+ const [titleField] = createFakeFieldAPI((field)=>({
28
+ ...field,
29
+ id: 'title-id',
30
+ setValue: jest.fn().mockImplementation(field.setValue),
31
+ getValue: jest.fn().mockImplementation(field.getValue),
32
+ onValueChanged: jest.fn().mockImplementation(field.onValueChanged)
33
+ }), initialValues.titleField || '');
34
+ const [descriptionField] = createFakeFieldAPI((field)=>({
35
+ ...field,
36
+ id: 'description-id',
37
+ setValue: jest.fn().mockImplementation(field.setValue),
38
+ getValue: jest.fn().mockImplementation(field.getValue),
39
+ onValueChanged: jest.fn().mockImplementation(field.onValueChanged)
40
+ }), initialValues.descriptionField || '');
41
+ const sdk = {
42
+ locales: createFakeLocalesAPI(),
43
+ space: {
44
+ getEntries: jest.fn().mockResolvedValue({
45
+ total: 0
46
+ })
47
+ },
48
+ entry: {
49
+ getSys: jest.fn().mockReturnValue({
50
+ id: 'entry-id',
51
+ publishedVersion: undefined,
52
+ createdAt: '2020-01-24T15:33:47.906Z',
53
+ contentType: {
54
+ sys: {
55
+ id: 'content-type-id'
56
+ }
57
+ }
58
+ }),
59
+ onSysChanged: jest.fn(),
60
+ fields: {
61
+ 'title-id': titleField,
62
+ 'entry-id': field,
63
+ 'description-id': descriptionField
64
+ }
65
+ },
66
+ contentType: {
67
+ displayField: 'title-id'
68
+ }
69
+ };
70
+ return {
71
+ field,
72
+ titleField,
73
+ descriptionField,
74
+ sdk
75
+ };
76
+ }
77
+ describe('SlugEditor', ()=>{
78
+ afterEach(cleanup);
79
+ describe('should not subscribe to title changes', ()=>{
80
+ it('when entry is published', async ()=>{
81
+ const { field , titleField , sdk } = createMocks();
82
+ sdk.entry.getSys.mockReturnValue({
83
+ publishedVersion: 2
84
+ });
85
+ render(React.createElement(SlugEditor, {
86
+ field: field,
87
+ baseSdk: sdk,
88
+ isInitiallyDisabled: false
89
+ }));
90
+ await wait();
91
+ expect(field.setValue).not.toHaveBeenCalled();
92
+ expect(titleField.onValueChanged).toHaveBeenCalledWith('en-US', expect.any(Function));
93
+ expect(sdk.space.getEntries).not.toHaveBeenCalled();
94
+ expect(sdk.entry.fields['title-id'].getValue).toHaveBeenCalledTimes(1);
95
+ expect(sdk.entry.getSys).toHaveBeenCalledTimes(2);
96
+ });
97
+ it('when title and slug are the same field', async ()=>{
98
+ const { field , titleField , sdk } = createMocks();
99
+ sdk.contentType.displayField = 'entry-id';
100
+ render(React.createElement(SlugEditor, {
101
+ field: field,
102
+ baseSdk: sdk,
103
+ isInitiallyDisabled: false
104
+ }));
105
+ await wait();
106
+ expect(titleField.onValueChanged).not.toHaveBeenCalled();
107
+ expect(field.setValue).not.toHaveBeenCalled();
108
+ });
109
+ it('when a saved slug is different from a title at the render', async ()=>{
110
+ const { field , titleField , sdk } = createMocks({
111
+ titleField: 'Hello world!',
112
+ field: 'something-different'
113
+ });
114
+ render(React.createElement(SlugEditor, {
115
+ field: field,
116
+ baseSdk: sdk,
117
+ isInitiallyDisabled: false
118
+ }));
119
+ await wait();
120
+ expect(titleField.onValueChanged).toHaveBeenCalledWith('en-US', expect.any(Function));
121
+ expect(field.setValue).not.toHaveBeenCalled();
122
+ });
123
+ });
124
+ describe('should check for uniqueness', ()=>{
125
+ it('if it is published', async ()=>{
126
+ const { field , titleField , sdk } = createMocks({
127
+ titleField: 'Slug value',
128
+ field: 'slug-value'
129
+ });
130
+ sdk.entry.getSys.mockReturnValue({
131
+ id: 'entry-id',
132
+ publishedVersion: 2,
133
+ contentType: {
134
+ sys: {
135
+ id: 'content-type-id'
136
+ }
137
+ }
138
+ });
139
+ sdk.space.getEntries.mockResolvedValue({
140
+ total: 0
141
+ });
142
+ const { queryByTestId , queryByText } = render(React.createElement(SlugEditor, {
143
+ field: field,
144
+ baseSdk: sdk,
145
+ isInitiallyDisabled: false
146
+ }));
147
+ await wait();
148
+ expect(titleField.onValueChanged).toHaveBeenCalledWith('en-US', expect.any(Function));
149
+ expect(sdk.space.getEntries).toHaveBeenLastCalledWith({
150
+ content_type: 'content-type-id',
151
+ 'fields.slug-id.en-US': 'slug-value',
152
+ limit: 0,
153
+ 'sys.id[ne]': 'entry-id',
154
+ 'sys.publishedAt[exists]': true
155
+ });
156
+ expect(sdk.space.getEntries).toHaveBeenCalledTimes(1);
157
+ expect(queryByTestId('slug-editor-spinner')).not.toBeInTheDocument();
158
+ expect(queryByText('This slug has already been published in another entry')).not.toBeInTheDocument();
159
+ });
160
+ it('if it is not published', async ()=>{
161
+ const { field , titleField , sdk } = createMocks({
162
+ titleField: 'Slug value',
163
+ field: 'slug-value'
164
+ });
165
+ sdk.entry.getSys.mockReturnValue({
166
+ id: 'entry-id',
167
+ publishedVersion: undefined,
168
+ contentType: {
169
+ sys: {
170
+ id: 'content-type-id'
171
+ }
172
+ }
173
+ });
174
+ sdk.space.getEntries.mockResolvedValue({
175
+ total: 2
176
+ });
177
+ const { queryByTestId , queryByText , getByTestId } = render(React.createElement(SlugEditor, {
178
+ field: field,
179
+ baseSdk: sdk,
180
+ isInitiallyDisabled: false
181
+ }));
182
+ await wait();
183
+ expect(titleField.onValueChanged).toHaveBeenCalledWith('en-US', expect.any(Function));
184
+ expect(sdk.space.getEntries).toHaveBeenLastCalledWith({
185
+ content_type: 'content-type-id',
186
+ 'fields.slug-id.en-US': 'slug-value',
187
+ limit: 0,
188
+ 'sys.id[ne]': 'entry-id',
189
+ 'sys.publishedAt[exists]': true
190
+ });
191
+ expect(sdk.space.getEntries).toHaveBeenCalledTimes(1);
192
+ expect(queryByTestId('slug-editor-spinner')).not.toBeInTheDocument();
193
+ expect(queryByText('This slug has already been published in another entry')).toBeInTheDocument();
194
+ expect(getByTestId('cf-ui-text-input')).toHaveValue('slug-value');
195
+ sdk.space.getEntries.mockResolvedValue({
196
+ total: 0
197
+ });
198
+ fireEvent.change(getByTestId('cf-ui-text-input'), {
199
+ target: {
200
+ value: '123'
201
+ }
202
+ });
203
+ await wait();
204
+ expect(field.setValue).toHaveBeenCalledTimes(1);
205
+ expect(field.setValue).toHaveBeenCalledWith('123');
206
+ expect(sdk.space.getEntries).toHaveBeenCalledTimes(2);
207
+ expect(sdk.space.getEntries).toHaveBeenLastCalledWith({
208
+ content_type: 'content-type-id',
209
+ 'fields.slug-id.en-US': '123',
210
+ limit: 0,
211
+ 'sys.id[ne]': 'entry-id',
212
+ 'sys.publishedAt[exists]': true
213
+ });
214
+ expect(queryByText('This slug has already been published in another entry')).not.toBeInTheDocument();
215
+ });
216
+ });
217
+ describe('should react to title changes', ()=>{
218
+ it('when field is disabled', async ()=>{
219
+ const { field , titleField , sdk } = createMocks({
220
+ field: '',
221
+ titleField: ''
222
+ });
223
+ render(React.createElement(SlugEditor, {
224
+ field: field,
225
+ baseSdk: sdk,
226
+ isInitiallyDisabled: true
227
+ }));
228
+ await wait();
229
+ expect(field.setValue).toHaveBeenCalled();
230
+ expect(titleField.onValueChanged).toHaveBeenCalledWith('en-US', expect.any(Function));
231
+ expect(sdk.space.getEntries).toHaveBeenCalled();
232
+ expect(sdk.entry.fields['title-id'].getValue).toHaveBeenCalledTimes(1);
233
+ expect(sdk.entry.getSys).toHaveBeenCalledTimes(2);
234
+ });
235
+ it('should generate unique value with date if title is empty', async ()=>{
236
+ const { field , titleField , sdk } = createMocks({
237
+ field: '',
238
+ titleField: ''
239
+ });
240
+ render(React.createElement(SlugEditor, {
241
+ field: field,
242
+ baseSdk: sdk,
243
+ isInitiallyDisabled: false
244
+ }));
245
+ await wait();
246
+ expect(titleField.onValueChanged).toHaveBeenCalledWith('en-US', expect.any(Function));
247
+ expect(field.setValue).toHaveBeenCalledTimes(1);
248
+ expect(field.setValue).toHaveBeenLastCalledWith('untitled-entry-2020-01-24-at-15-33-47');
249
+ await sdk.entry.fields['title-id'].setValue('Hello world!');
250
+ await wait();
251
+ expect(field.setValue).toHaveBeenCalledTimes(2);
252
+ expect(field.setValue).toHaveBeenLastCalledWith('hello-world');
253
+ expect(sdk.space.getEntries).toHaveBeenCalledTimes(2);
254
+ await sdk.entry.fields['title-id'].setValue('фраза написанная по русски');
255
+ await wait();
256
+ expect(field.setValue).toHaveBeenCalledTimes(3);
257
+ expect(field.setValue).toHaveBeenLastCalledWith('fraza-napisannaya-po-russki');
258
+ expect(sdk.space.getEntries).toHaveBeenCalledTimes(3);
259
+ });
260
+ it('should generate value from title if it is not empty', async ()=>{
261
+ const { field , titleField , sdk } = createMocks({
262
+ field: '',
263
+ titleField: 'This is initial title value'
264
+ });
265
+ render(React.createElement(SlugEditor, {
266
+ field: field,
267
+ baseSdk: sdk,
268
+ isInitiallyDisabled: false
269
+ }));
270
+ await wait();
271
+ expect(titleField.onValueChanged).toHaveBeenCalledWith('en-US', expect.any(Function));
272
+ expect(field.setValue).toHaveBeenCalledTimes(1);
273
+ expect(field.setValue).toHaveBeenLastCalledWith('this-is-initial-title-value');
274
+ await sdk.entry.fields['title-id'].setValue('Hello world!');
275
+ await wait();
276
+ expect(field.setValue).toHaveBeenCalledTimes(2);
277
+ expect(field.setValue).toHaveBeenLastCalledWith('hello-world');
278
+ expect(sdk.space.getEntries).toHaveBeenCalledTimes(2);
279
+ });
280
+ it('should stop tracking value after user intentionally changes slug value', async ()=>{
281
+ const { field , titleField , sdk } = createMocks({
282
+ field: '',
283
+ titleField: ''
284
+ });
285
+ const { getByTestId } = render(React.createElement(SlugEditor, {
286
+ field: field,
287
+ baseSdk: sdk,
288
+ isInitiallyDisabled: false
289
+ }));
290
+ await wait();
291
+ await sdk.entry.fields['title-id'].setValue('Hello world!');
292
+ await wait();
293
+ expect(titleField.onValueChanged).toHaveBeenCalledWith('en-US', expect.any(Function));
294
+ expect(field.setValue).toHaveBeenCalledTimes(2);
295
+ expect(field.setValue).toHaveBeenCalledWith('untitled-entry-2020-01-24-at-15-33-47');
296
+ expect(field.setValue).toHaveBeenLastCalledWith('hello-world');
297
+ expect(sdk.space.getEntries).toHaveBeenCalledTimes(2);
298
+ fireEvent.change(getByTestId('cf-ui-text-input'), {
299
+ target: {
300
+ value: 'new-custom-slug'
301
+ }
302
+ });
303
+ await wait();
304
+ expect(field.setValue).toHaveBeenCalledTimes(3);
305
+ expect(field.setValue).toHaveBeenLastCalledWith('new-custom-slug');
306
+ await sdk.entry.fields['title-id'].setValue('I decided to update my title');
307
+ await wait();
308
+ expect(field.setValue).toHaveBeenCalledTimes(3);
309
+ await sdk.entry.fields['title-id'].setValue('I decided to update my title again');
310
+ await wait();
311
+ expect(field.setValue).toHaveBeenCalledTimes(3);
312
+ });
313
+ it('should start tracking again after potential slug equals real one', async ()=>{
314
+ const { field , sdk } = createMocks({
315
+ field: '',
316
+ titleField: ''
317
+ });
318
+ const { getByTestId } = render(React.createElement(SlugEditor, {
319
+ field: field,
320
+ baseSdk: sdk,
321
+ isInitiallyDisabled: false
322
+ }));
323
+ await wait();
324
+ await sdk.entry.fields['title-id'].setValue('ABC DEF');
325
+ await wait();
326
+ expect(field.setValue).toHaveBeenLastCalledWith('abc-def');
327
+ expect(field.setValue).toHaveBeenCalledTimes(2);
328
+ fireEvent.change(getByTestId('cf-ui-text-input'), {
329
+ target: {
330
+ value: 'abc'
331
+ }
332
+ });
333
+ await sdk.entry.fields['title-id'].setValue('ABC D');
334
+ await wait();
335
+ expect(field.setValue).toHaveBeenLastCalledWith('abc');
336
+ expect(field.setValue).toHaveBeenCalledTimes(3);
337
+ await sdk.entry.fields['title-id'].setValue('ABC');
338
+ await sdk.entry.fields['title-id'].setValue('ABC ABC');
339
+ await wait();
340
+ expect(field.setValue).toHaveBeenLastCalledWith('abc-abc');
341
+ expect(field.setValue).toHaveBeenCalledTimes(4);
342
+ await wait();
343
+ });
344
+ });
345
+ describe('for non default locales', ()=>{
346
+ it('locale is not optional and has no fallback then it should track default locale changes & current locale changes', async ()=>{
347
+ const { sdk , field , titleField } = createMocks();
348
+ field.locale = 'ru-RU';
349
+ field.required = false;
350
+ sdk.locales.available = [
351
+ 'de-DE',
352
+ 'ru-RU'
353
+ ];
354
+ sdk.locales.default = 'de-DE';
355
+ sdk.locales.optional = {
356
+ 'de-DE': false,
357
+ 'ru-RU': false
358
+ };
359
+ sdk.locales.fallbacks = {
360
+ 'de-DE': undefined,
361
+ 'ru-RU': undefined
362
+ };
363
+ render(React.createElement(SlugEditor, {
364
+ field: field,
365
+ baseSdk: sdk,
366
+ isInitiallyDisabled: false
367
+ }));
368
+ await wait();
369
+ expect(field.setValue).toHaveBeenCalledWith('untitled-entry-2020-01-24-at-15-33-47');
370
+ expect(titleField.onValueChanged).toHaveBeenCalledWith('ru-RU', expect.any(Function));
371
+ expect(titleField.onValueChanged).toHaveBeenCalledWith('de-DE', expect.any(Function));
372
+ });
373
+ it('locale is optional and has a fallback then it should track only current locale changes', async ()=>{
374
+ const { sdk , field , titleField } = createMocks();
375
+ field.locale = 'ru-RU';
376
+ field.required = false;
377
+ sdk.locales.available = [
378
+ 'de-DE',
379
+ 'ru-RU'
380
+ ];
381
+ sdk.locales.default = 'de-DE';
382
+ sdk.locales.optional = {
383
+ 'de-DE': false,
384
+ 'ru-RU': true
385
+ };
386
+ sdk.locales.fallbacks = {
387
+ 'de-DE': undefined,
388
+ 'ru-RU': 'de-DE'
389
+ };
390
+ render(React.createElement(SlugEditor, {
391
+ field: field,
392
+ baseSdk: sdk,
393
+ isInitiallyDisabled: false
394
+ }));
395
+ await wait();
396
+ expect(field.setValue).not.toHaveBeenCalled();
397
+ expect(titleField.onValueChanged).toHaveBeenCalledWith('ru-RU', expect.any(Function));
398
+ expect(titleField.onValueChanged).not.toHaveBeenCalledWith('de-DE', expect.any(Function));
399
+ });
400
+ });
401
+ it('slug suggestion is limited to 75 symbols', async ()=>{
402
+ const { field , sdk } = createMocks({
403
+ field: '',
404
+ titleField: ''
405
+ });
406
+ render(React.createElement(SlugEditor, {
407
+ field: field,
408
+ baseSdk: sdk,
409
+ isInitiallyDisabled: false
410
+ }));
411
+ await wait();
412
+ await sdk.entry.fields['title-id'].setValue('a'.repeat(80));
413
+ await wait();
414
+ const expectedSlug = 'a'.repeat(75);
415
+ expect(field.setValue).toHaveBeenLastCalledWith(expectedSlug);
416
+ });
417
+ it('slug suggestion does not contain cut-off words', async ()=>{
418
+ const { field , sdk } = createMocks({
419
+ field: '',
420
+ titleField: ''
421
+ });
422
+ render(React.createElement(SlugEditor, {
423
+ field: field,
424
+ baseSdk: sdk,
425
+ isInitiallyDisabled: false
426
+ }));
427
+ await wait();
428
+ await sdk.entry.fields['title-id'].setValue(`one two three ${'a'.repeat(80)}`);
429
+ await wait();
430
+ const expectedSlug = 'one-two-three';
431
+ expect(field.setValue).toHaveBeenLastCalledWith(expectedSlug);
432
+ });
433
+ it('should subscribe for changes in custom field id', async ()=>{
434
+ const { field , titleField , descriptionField , sdk } = createMocks({
435
+ field: '',
436
+ titleField: 'This is initial title value',
437
+ descriptionField: 'This is initial description value'
438
+ });
439
+ render(React.createElement(SlugEditor, {
440
+ field: field,
441
+ baseSdk: sdk,
442
+ isInitiallyDisabled: false,
443
+ parameters: {
444
+ instance: {
445
+ trackingFieldId: 'description-id'
446
+ }
447
+ }
448
+ }));
449
+ await wait();
450
+ expect(titleField.onValueChanged).not.toHaveBeenCalled();
451
+ expect(descriptionField.onValueChanged).toHaveBeenCalledWith('en-US', expect.any(Function));
452
+ expect(field.setValue).toHaveBeenCalledTimes(1);
453
+ expect(field.setValue).toHaveBeenLastCalledWith('this-is-initial-description-value');
454
+ await sdk.entry.fields['description-id'].setValue('Hello world!');
455
+ await wait();
456
+ expect(field.setValue).toHaveBeenCalledTimes(2);
457
+ expect(field.setValue).toHaveBeenLastCalledWith('hello-world');
458
+ expect(sdk.space.getEntries).toHaveBeenCalledTimes(2);
459
+ });
460
+ });
@@ -0,0 +1,129 @@
1
+ import * as React from 'react';
2
+ import { Spinner, TextInput, ValidationMessage } from '@contentful/f36-components';
3
+ import { LinkIcon } from '@contentful/f36-icons';
4
+ import { useDebounce } from 'use-debounce';
5
+ import { makeSlug } from './services/makeSlug';
6
+ import * as styles from './styles';
7
+ function useSlugUpdater(props, check) {
8
+ const { value , setValue , createdAt , locale , titleValue , isOptionalLocaleWithFallback } = props;
9
+ React.useEffect(()=>{
10
+ if (check === false) {
11
+ return;
12
+ }
13
+ const newSlug = makeSlug(titleValue, {
14
+ isOptionalLocaleWithFallback,
15
+ locale,
16
+ createdAt
17
+ });
18
+ if (newSlug !== value) {
19
+ setValue(newSlug);
20
+ }
21
+ }, [
22
+ value,
23
+ titleValue,
24
+ isOptionalLocaleWithFallback,
25
+ check,
26
+ createdAt,
27
+ locale,
28
+ setValue
29
+ ]);
30
+ }
31
+ function useUniqueChecker(props) {
32
+ const { performUniqueCheck } = props;
33
+ const [status, setStatus] = React.useState(props.value ? 'checking' : 'unique');
34
+ const [debouncedValue] = useDebounce(props.value, 1000);
35
+ React.useEffect(()=>{
36
+ if (!debouncedValue) {
37
+ setStatus('unique');
38
+ return;
39
+ }
40
+ setStatus('checking');
41
+ performUniqueCheck(debouncedValue).then((unique)=>{
42
+ setStatus(unique ? 'unique' : 'duplicate');
43
+ }).catch(()=>{
44
+ setStatus('checking');
45
+ });
46
+ }, [
47
+ debouncedValue,
48
+ performUniqueCheck
49
+ ]);
50
+ return status;
51
+ }
52
+ export function SlugEditorFieldStatic(props) {
53
+ const { hasError , isDisabled , value , setValue , onChange , onBlur } = props;
54
+ const status = useUniqueChecker(props);
55
+ return React.createElement("div", {
56
+ className: styles.inputContainer
57
+ }, React.createElement(LinkIcon, {
58
+ className: styles.icon
59
+ }), React.createElement(TextInput, {
60
+ className: styles.input,
61
+ isInvalid: hasError || status === 'duplicate',
62
+ isDisabled: isDisabled,
63
+ value: value || '',
64
+ onChange: (e)=>{
65
+ setValue(e.target.value);
66
+ if (onChange) {
67
+ onChange();
68
+ }
69
+ },
70
+ onBlur: ()=>{
71
+ if (onBlur) {
72
+ onBlur();
73
+ }
74
+ }
75
+ }), status === 'checking' && React.createElement("div", {
76
+ className: styles.spinnerContainer
77
+ }, React.createElement(Spinner, {
78
+ testId: "slug-editor-spinner"
79
+ })), status === 'duplicate' && React.createElement(ValidationMessage, {
80
+ testId: "slug-editor-duplicate-error",
81
+ className: styles.uniqueValidationError
82
+ }, "This slug has already been published in another entry"));
83
+ }
84
+ export function SlugEditorField(props) {
85
+ const { titleValue , isOptionalLocaleWithFallback , locale , createdAt , value } = props;
86
+ const areEqual = React.useCallback(()=>{
87
+ const potentialSlug = makeSlug(titleValue, {
88
+ isOptionalLocaleWithFallback: isOptionalLocaleWithFallback,
89
+ locale: locale,
90
+ createdAt: createdAt
91
+ });
92
+ return value === potentialSlug;
93
+ }, [
94
+ titleValue,
95
+ isOptionalLocaleWithFallback,
96
+ locale,
97
+ createdAt,
98
+ value
99
+ ]);
100
+ const [check, setCheck] = React.useState(()=>{
101
+ if (props.value) {
102
+ if (!props.titleValue) {
103
+ return false;
104
+ }
105
+ return areEqual();
106
+ }
107
+ return true;
108
+ });
109
+ React.useEffect(()=>{
110
+ if (areEqual()) {
111
+ setCheck(true);
112
+ }
113
+ }, [
114
+ props.titleValue,
115
+ areEqual
116
+ ]);
117
+ useSlugUpdater(props, check);
118
+ return React.createElement(SlugEditorFieldStatic, {
119
+ ...props,
120
+ onChange: ()=>{
121
+ setCheck(false);
122
+ },
123
+ onBlur: ()=>{
124
+ if (areEqual()) {
125
+ setCheck(true);
126
+ }
127
+ }
128
+ });
129
+ }
@@ -0,0 +1,88 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
14
+ import * as React from 'react';
15
+ function getTitleField(sdk, trackingFieldId) {
16
+ const { entry , contentType } = sdk;
17
+ if (trackingFieldId && entry.fields[trackingFieldId]) {
18
+ return entry.fields[trackingFieldId];
19
+ }
20
+ return entry.fields[contentType.displayField];
21
+ }
22
+ var _React_Component;
23
+ export class TrackingFieldConnector extends (_React_Component = React.Component) {
24
+ componentDidMount() {
25
+ this.unsubscribeSysChanges = this.props.sdk.entry.onSysChanged((sys)=>{
26
+ this.setState({
27
+ isPublished: Boolean(sys.publishedVersion)
28
+ });
29
+ });
30
+ const titleField = getTitleField(this.props.sdk, this.props.trackingFieldId);
31
+ if (!titleField) {
32
+ return;
33
+ }
34
+ if (!this.state.isSame) {
35
+ this.unsubscribeLocalizedValue = titleField.onValueChanged(this.props.field.locale, (value)=>{
36
+ this.setState({
37
+ titleValue: value
38
+ });
39
+ });
40
+ }
41
+ if (this.props.field.locale !== this.props.defaultLocale) {
42
+ if (!this.props.isOptionalLocaleWithFallback) {
43
+ this.unsubscribeValue = titleField.onValueChanged(this.props.defaultLocale, (value)=>{
44
+ if (!titleField.getValue(this.props.field.locale)) {
45
+ this.setState({
46
+ titleValue: value
47
+ });
48
+ }
49
+ });
50
+ }
51
+ }
52
+ }
53
+ componentWillUnmount() {
54
+ if (typeof this.unsubscribeValue === 'function') {
55
+ this.unsubscribeValue();
56
+ }
57
+ if (typeof this.unsubscribeLocalizedValue === 'function') {
58
+ this.unsubscribeLocalizedValue();
59
+ }
60
+ if (typeof this.unsubscribeSysChanges === 'function') {
61
+ this.unsubscribeSysChanges();
62
+ }
63
+ }
64
+ render() {
65
+ return this.props.children({
66
+ ...this.state
67
+ });
68
+ }
69
+ constructor(props){
70
+ super(props);
71
+ _define_property(this, "unsubscribeValue", null);
72
+ _define_property(this, "unsubscribeLocalizedValue", null);
73
+ _define_property(this, "unsubscribeSysChanges", null);
74
+ const titleField = getTitleField(props.sdk, props.trackingFieldId);
75
+ const entrySys = props.sdk.entry.getSys();
76
+ const isSame = titleField ? props.field.id === titleField.id : false;
77
+ this.state = {
78
+ titleValue: titleField ? titleField.getValue() : '',
79
+ isPublished: Boolean(entrySys.publishedVersion),
80
+ isSame
81
+ };
82
+ }
83
+ }
84
+ _define_property(TrackingFieldConnector, "defaultProps", {
85
+ children: ()=>{
86
+ return null;
87
+ }
88
+ });
@@ -0,0 +1,3 @@
1
+ export { SlugEditor } from './SlugEditor';
2
+ export { slugify } from './services/slugify';
3
+ export { makeSlug } from './services/makeSlug';