@contentful/field-editor-slug 3.0.0 → 3.0.1-canary.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.
- package/dist/cjs/SlugEditor.js +14 -1
- package/dist/cjs/SlugEditor.test.js +33 -8
- package/dist/cjs/SlugEditorField.js +12 -7
- package/dist/cjs/services/makeSlug.js +3 -3
- package/dist/cjs/services/makeSlug.test.js +8 -0
- package/dist/cjs/services/slugify.js +3 -3
- package/dist/cjs/services/slugify.test.js +9 -0
- package/dist/esm/SlugEditor.js +14 -1
- package/dist/esm/SlugEditor.test.js +33 -8
- package/dist/esm/SlugEditorField.js +12 -7
- package/dist/esm/services/makeSlug.js +3 -3
- package/dist/esm/services/makeSlug.test.js +8 -0
- package/dist/esm/services/slugify.js +3 -3
- package/dist/esm/services/slugify.test.js +9 -0
- package/dist/types/SlugEditorField.d.ts +1 -0
- package/dist/types/services/makeSlug.d.ts +1 -0
- package/dist/types/services/slugify.d.ts +5 -2
- package/package.json +2 -2
package/dist/cjs/SlugEditor.js
CHANGED
|
@@ -53,10 +53,20 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
53
53
|
}
|
|
54
54
|
return newObj;
|
|
55
55
|
}
|
|
56
|
+
const DEFAULT_SYMBOL_FIELD_MAX_LENGTH = 256;
|
|
57
|
+
function getSlugFieldMaxLength(validations = []) {
|
|
58
|
+
const maxFromValidations = validations.reduce((currentMax, validation)=>{
|
|
59
|
+
if (!('size' in validation) || typeof validation.size?.max !== 'number') {
|
|
60
|
+
return currentMax;
|
|
61
|
+
}
|
|
62
|
+
return currentMax === null ? validation.size.max : Math.min(currentMax, validation.size.max);
|
|
63
|
+
}, null);
|
|
64
|
+
return maxFromValidations === null ? DEFAULT_SYMBOL_FIELD_MAX_LENGTH : Math.min(DEFAULT_SYMBOL_FIELD_MAX_LENGTH, maxFromValidations);
|
|
65
|
+
}
|
|
56
66
|
function isSupportedFieldTypes(val) {
|
|
57
67
|
return val === 'Symbol';
|
|
58
68
|
}
|
|
59
|
-
function FieldConnectorCallback({ Component, value, disabled, setValue, errors, titleValue, isOptionalLocaleWithFallback, locale, createdAt, performUniqueCheck, isUniqueValidationEnabled, id }) {
|
|
69
|
+
function FieldConnectorCallback({ Component, value, disabled, setValue, errors, titleValue, isOptionalLocaleWithFallback, locale, createdAt, maxLength, performUniqueCheck, isUniqueValidationEnabled, id }) {
|
|
60
70
|
const safeSetValue = _react.useCallback(async (...args)=>{
|
|
61
71
|
try {
|
|
62
72
|
await setValue(...args);
|
|
@@ -69,6 +79,7 @@ function FieldConnectorCallback({ Component, value, disabled, setValue, errors,
|
|
|
69
79
|
}, /*#__PURE__*/ _react.createElement(Component, {
|
|
70
80
|
locale: locale,
|
|
71
81
|
createdAt: createdAt,
|
|
82
|
+
maxLength: maxLength,
|
|
72
83
|
performUniqueCheck: performUniqueCheck,
|
|
73
84
|
isUniqueValidationEnabled: isUniqueValidationEnabled,
|
|
74
85
|
hasError: errors.length > 0,
|
|
@@ -89,6 +100,7 @@ function SlugEditor(props) {
|
|
|
89
100
|
const trackingFieldId = parameters?.instance?.trackingFieldId ?? undefined;
|
|
90
101
|
const entrySys = entry.getSys();
|
|
91
102
|
const isUniqueValidationEnabled = (field.validations || []).some((validation)=>'unique' in validation && validation.unique === true);
|
|
103
|
+
const maxLength = getSlugFieldMaxLength(field.validations);
|
|
92
104
|
const isLocaleOptional = locales.optional[field.locale];
|
|
93
105
|
const localeFallbackCode = locales.fallbacks[field.locale];
|
|
94
106
|
const isOptionalFieldLocale = Boolean(!field.required || isLocaleOptional);
|
|
@@ -136,6 +148,7 @@ function SlugEditor(props) {
|
|
|
136
148
|
isOptionalLocaleWithFallback: isOptionalLocaleWithFallback,
|
|
137
149
|
createdAt: entrySys.createdAt,
|
|
138
150
|
locale: field.locale,
|
|
151
|
+
maxLength: maxLength,
|
|
139
152
|
performUniqueCheck: performUniqueCheck,
|
|
140
153
|
isUniqueValidationEnabled: isUniqueValidationEnabled,
|
|
141
154
|
key: `slug-editor-${externalReset}`,
|
|
@@ -316,6 +316,7 @@ describe('SlugEditor', ()=>{
|
|
|
316
316
|
locale: "en-US",
|
|
317
317
|
titleValue: "Slug value",
|
|
318
318
|
createdAt: "2020-01-24T15:33:47.906Z",
|
|
319
|
+
maxLength: 256,
|
|
319
320
|
setValue: setValue,
|
|
320
321
|
performUniqueCheck: performUniqueCheck
|
|
321
322
|
}));
|
|
@@ -527,22 +528,46 @@ describe('SlugEditor', ()=>{
|
|
|
527
528
|
});
|
|
528
529
|
});
|
|
529
530
|
});
|
|
530
|
-
it('slug suggestion is limited to
|
|
531
|
+
it('slug suggestion is limited to 256 characters by default', async ()=>{
|
|
532
|
+
const longTitle = 'a'.repeat(300);
|
|
531
533
|
const { field, sdk } = createMocks({
|
|
532
534
|
field: '',
|
|
533
|
-
titleField:
|
|
535
|
+
titleField: longTitle
|
|
534
536
|
});
|
|
535
|
-
(0, _react1.render)(/*#__PURE__*/ _react.createElement(_SlugEditor.SlugEditor, {
|
|
537
|
+
const { getByTestId } = (0, _react1.render)(/*#__PURE__*/ _react.createElement(_SlugEditor.SlugEditor, {
|
|
536
538
|
field: field,
|
|
537
539
|
baseSdk: sdk,
|
|
538
540
|
isInitiallyDisabled: false
|
|
539
541
|
}));
|
|
540
|
-
await (0, _react1.waitFor)(async ()=>{
|
|
541
|
-
await sdk.entry.fields['title-id'].setValue('a'.repeat(80));
|
|
542
|
-
});
|
|
543
542
|
await (0, _react1.waitFor)(()=>{
|
|
544
|
-
const expectedSlug = 'a'.repeat(
|
|
543
|
+
const expectedSlug = 'a'.repeat(256);
|
|
545
544
|
expect(field.setValue).toHaveBeenLastCalledWith(expectedSlug);
|
|
545
|
+
expect(getByTestId('cf-ui-text-input')).toHaveAttribute('maxlength', '256');
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
it('slug suggestion respects size max validation', async ()=>{
|
|
549
|
+
const { field, sdk } = createMocks({
|
|
550
|
+
field: '',
|
|
551
|
+
titleField: 'one two three four'
|
|
552
|
+
});
|
|
553
|
+
field.validations = [
|
|
554
|
+
{
|
|
555
|
+
unique: true
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
size: {
|
|
559
|
+
max: 13
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
];
|
|
563
|
+
const { getByTestId } = (0, _react1.render)(/*#__PURE__*/ _react.createElement(_SlugEditor.SlugEditor, {
|
|
564
|
+
field: field,
|
|
565
|
+
baseSdk: sdk,
|
|
566
|
+
isInitiallyDisabled: false
|
|
567
|
+
}));
|
|
568
|
+
await (0, _react1.waitFor)(()=>{
|
|
569
|
+
expect(field.setValue).toHaveBeenLastCalledWith('one-two-three');
|
|
570
|
+
expect(getByTestId('cf-ui-text-input')).toHaveAttribute('maxlength', '13');
|
|
546
571
|
});
|
|
547
572
|
});
|
|
548
573
|
it('slug suggestion does not contain cut-off words', async ()=>{
|
|
@@ -556,7 +581,7 @@ describe('SlugEditor', ()=>{
|
|
|
556
581
|
isInitiallyDisabled: false
|
|
557
582
|
}));
|
|
558
583
|
await (0, _react1.waitFor)(async ()=>{
|
|
559
|
-
await sdk.entry.fields['title-id'].setValue(`one two three ${'a'.repeat(
|
|
584
|
+
await sdk.entry.fields['title-id'].setValue(`one two three ${'a'.repeat(300)}`);
|
|
560
585
|
});
|
|
561
586
|
await (0, _react1.waitFor)(()=>{
|
|
562
587
|
const expectedSlug = 'one-two-three';
|
|
@@ -66,7 +66,7 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
66
66
|
return newObj;
|
|
67
67
|
}
|
|
68
68
|
function useSlugUpdater(props, check) {
|
|
69
|
-
const { value, setValue, createdAt, locale, titleValue, isOptionalLocaleWithFallback } = props;
|
|
69
|
+
const { value, setValue, createdAt, locale, titleValue, isOptionalLocaleWithFallback, maxLength } = props;
|
|
70
70
|
_react.useEffect(()=>{
|
|
71
71
|
if (check === false) {
|
|
72
72
|
return;
|
|
@@ -74,7 +74,8 @@ function useSlugUpdater(props, check) {
|
|
|
74
74
|
const newSlug = (0, _makeSlug.makeSlug)(titleValue, {
|
|
75
75
|
isOptionalLocaleWithFallback,
|
|
76
76
|
locale,
|
|
77
|
-
createdAt
|
|
77
|
+
createdAt,
|
|
78
|
+
maxLength
|
|
78
79
|
});
|
|
79
80
|
if (newSlug !== value) {
|
|
80
81
|
setValue(newSlug);
|
|
@@ -86,6 +87,7 @@ function useSlugUpdater(props, check) {
|
|
|
86
87
|
check,
|
|
87
88
|
createdAt,
|
|
88
89
|
locale,
|
|
90
|
+
maxLength,
|
|
89
91
|
setValue
|
|
90
92
|
]);
|
|
91
93
|
}
|
|
@@ -111,7 +113,7 @@ function useUniqueChecker(props) {
|
|
|
111
113
|
return status;
|
|
112
114
|
}
|
|
113
115
|
function SlugEditorFieldStatic(props) {
|
|
114
|
-
const { hasError, isDisabled, value, setValue, onChange, onBlur, isUniqueValidationEnabled, id } = props;
|
|
116
|
+
const { hasError, isDisabled, value, setValue, onChange, onBlur, isUniqueValidationEnabled, id, maxLength } = props;
|
|
115
117
|
const status = useUniqueChecker(props);
|
|
116
118
|
const hasDuplicate = status === 'duplicate';
|
|
117
119
|
const shouldShowDuplicateAsError = hasDuplicate && isUniqueValidationEnabled && !hasError;
|
|
@@ -126,6 +128,7 @@ function SlugEditorFieldStatic(props) {
|
|
|
126
128
|
isDisabled: isDisabled,
|
|
127
129
|
value: value || '',
|
|
128
130
|
id: id,
|
|
131
|
+
maxLength: maxLength,
|
|
129
132
|
onChange: (e)=>{
|
|
130
133
|
setValue(e.target.value);
|
|
131
134
|
if (onChange) {
|
|
@@ -157,12 +160,13 @@ function SlugEditorFieldStatic(props) {
|
|
|
157
160
|
})));
|
|
158
161
|
}
|
|
159
162
|
function SlugEditorField(props) {
|
|
160
|
-
const { titleValue, isOptionalLocaleWithFallback, locale, createdAt, value } = props;
|
|
163
|
+
const { titleValue, isOptionalLocaleWithFallback, locale, createdAt, value, maxLength } = props;
|
|
161
164
|
const areEqual = _react.useCallback(()=>{
|
|
162
165
|
const potentialSlug = (0, _makeSlug.makeSlug)(titleValue, {
|
|
163
|
-
isOptionalLocaleWithFallback
|
|
164
|
-
locale
|
|
165
|
-
createdAt
|
|
166
|
+
isOptionalLocaleWithFallback,
|
|
167
|
+
locale,
|
|
168
|
+
createdAt,
|
|
169
|
+
maxLength
|
|
166
170
|
});
|
|
167
171
|
return value === potentialSlug;
|
|
168
172
|
}, [
|
|
@@ -170,6 +174,7 @@ function SlugEditorField(props) {
|
|
|
170
174
|
isOptionalLocaleWithFallback,
|
|
171
175
|
locale,
|
|
172
176
|
createdAt,
|
|
177
|
+
maxLength,
|
|
173
178
|
value
|
|
174
179
|
]);
|
|
175
180
|
const [check, setCheck] = _react.useState(()=>{
|
|
@@ -30,13 +30,13 @@ function formatUtcDate(date) {
|
|
|
30
30
|
const seconds = formatTwoDigit(date.getUTCSeconds());
|
|
31
31
|
return `${year} ${month} ${day} at ${hour} ${minutes} ${seconds}`;
|
|
32
32
|
}
|
|
33
|
-
function untitledSlug({ isOptionalLocaleWithFallback, createdAt }) {
|
|
33
|
+
function untitledSlug({ isOptionalLocaleWithFallback, createdAt, maxLength }) {
|
|
34
34
|
if (isOptionalLocaleWithFallback) {
|
|
35
35
|
return '';
|
|
36
36
|
}
|
|
37
37
|
const createdAtFormatted = formatUtcDate(new Date(createdAt));
|
|
38
|
-
return (0, _slugify.slugify)('Untitled entry ' + createdAtFormatted, 'en-US');
|
|
38
|
+
return (0, _slugify.slugify)('Untitled entry ' + createdAtFormatted, 'en-US', maxLength);
|
|
39
39
|
}
|
|
40
40
|
function makeSlug(title, options) {
|
|
41
|
-
return title ? (0, _slugify.slugify)(title, options.locale) : untitledSlug(options);
|
|
41
|
+
return title ? (0, _slugify.slugify)(title, options.locale, options.maxLength) : untitledSlug(options);
|
|
42
42
|
}
|
|
@@ -11,4 +11,12 @@ describe('makeSlug', ()=>{
|
|
|
11
11
|
createdAt: '2020-01-14T14:45:39.709Z'
|
|
12
12
|
})).toBe('untitled-entry-2020-01-14-at-14-45-39');
|
|
13
13
|
});
|
|
14
|
+
it('should respect maxLength when title is present', ()=>{
|
|
15
|
+
expect((0, _makeSlug.makeSlug)('a'.repeat(300), {
|
|
16
|
+
locale: 'en',
|
|
17
|
+
isOptionalLocaleWithFallback: false,
|
|
18
|
+
createdAt: '2020-01-14T14:45:39.709Z',
|
|
19
|
+
maxLength: 256
|
|
20
|
+
})).toBe('a'.repeat(256));
|
|
21
|
+
});
|
|
14
22
|
});
|
|
@@ -14,7 +14,7 @@ function _interop_require_default(obj) {
|
|
|
14
14
|
default: obj
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
|
-
const
|
|
17
|
+
const DEFAULT_SLUG_MAX_LENGTH = 75;
|
|
18
18
|
const languages = [
|
|
19
19
|
'ar',
|
|
20
20
|
'az',
|
|
@@ -49,11 +49,11 @@ function supportedLanguage(locale) {
|
|
|
49
49
|
const prefix = locale.slice(0, 2).toLowerCase();
|
|
50
50
|
return languages[languages.indexOf(prefix)];
|
|
51
51
|
}
|
|
52
|
-
function slugify(text, locale = 'en') {
|
|
52
|
+
function slugify(text, locale = 'en', maxLength = DEFAULT_SLUG_MAX_LENGTH) {
|
|
53
53
|
return (0, _speakingurl.default)(text, {
|
|
54
54
|
separator: '-',
|
|
55
55
|
lang: supportedLanguage(locale) || 'en',
|
|
56
|
-
truncate:
|
|
56
|
+
truncate: maxLength + 1,
|
|
57
57
|
custom: {
|
|
58
58
|
"'": '',
|
|
59
59
|
'`': '',
|
|
@@ -27,4 +27,13 @@ describe('slugify', ()=>{
|
|
|
27
27
|
expect((0, _slugify.slugify)(input[0])).toBe(input[1]);
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
|
+
it('defaults to a 75 character limit', ()=>{
|
|
31
|
+
expect((0, _slugify.slugify)('a'.repeat(80))).toBe('a'.repeat(75));
|
|
32
|
+
});
|
|
33
|
+
it('accepts a custom max length', ()=>{
|
|
34
|
+
expect((0, _slugify.slugify)('a'.repeat(80), 'en', 80)).toBe('a'.repeat(80));
|
|
35
|
+
});
|
|
36
|
+
it('does not cut off words when a custom max length is provided', ()=>{
|
|
37
|
+
expect((0, _slugify.slugify)('one two three four', 'en', 13)).toBe('one-two-three');
|
|
38
|
+
});
|
|
30
39
|
});
|
package/dist/esm/SlugEditor.js
CHANGED
|
@@ -2,10 +2,20 @@ import * as React from 'react';
|
|
|
2
2
|
import { FieldConnector } from '@contentful/field-editor-shared';
|
|
3
3
|
import { SlugEditorField, SlugEditorFieldStatic } from './SlugEditorField';
|
|
4
4
|
import { TrackingFieldConnector } from './TrackingFieldConnector';
|
|
5
|
+
const DEFAULT_SYMBOL_FIELD_MAX_LENGTH = 256;
|
|
6
|
+
function getSlugFieldMaxLength(validations = []) {
|
|
7
|
+
const maxFromValidations = validations.reduce((currentMax, validation)=>{
|
|
8
|
+
if (!('size' in validation) || typeof validation.size?.max !== 'number') {
|
|
9
|
+
return currentMax;
|
|
10
|
+
}
|
|
11
|
+
return currentMax === null ? validation.size.max : Math.min(currentMax, validation.size.max);
|
|
12
|
+
}, null);
|
|
13
|
+
return maxFromValidations === null ? DEFAULT_SYMBOL_FIELD_MAX_LENGTH : Math.min(DEFAULT_SYMBOL_FIELD_MAX_LENGTH, maxFromValidations);
|
|
14
|
+
}
|
|
5
15
|
function isSupportedFieldTypes(val) {
|
|
6
16
|
return val === 'Symbol';
|
|
7
17
|
}
|
|
8
|
-
function FieldConnectorCallback({ Component, value, disabled, setValue, errors, titleValue, isOptionalLocaleWithFallback, locale, createdAt, performUniqueCheck, isUniqueValidationEnabled, id }) {
|
|
18
|
+
function FieldConnectorCallback({ Component, value, disabled, setValue, errors, titleValue, isOptionalLocaleWithFallback, locale, createdAt, maxLength, performUniqueCheck, isUniqueValidationEnabled, id }) {
|
|
9
19
|
const safeSetValue = React.useCallback(async (...args)=>{
|
|
10
20
|
try {
|
|
11
21
|
await setValue(...args);
|
|
@@ -18,6 +28,7 @@ function FieldConnectorCallback({ Component, value, disabled, setValue, errors,
|
|
|
18
28
|
}, /*#__PURE__*/ React.createElement(Component, {
|
|
19
29
|
locale: locale,
|
|
20
30
|
createdAt: createdAt,
|
|
31
|
+
maxLength: maxLength,
|
|
21
32
|
performUniqueCheck: performUniqueCheck,
|
|
22
33
|
isUniqueValidationEnabled: isUniqueValidationEnabled,
|
|
23
34
|
hasError: errors.length > 0,
|
|
@@ -38,6 +49,7 @@ export function SlugEditor(props) {
|
|
|
38
49
|
const trackingFieldId = parameters?.instance?.trackingFieldId ?? undefined;
|
|
39
50
|
const entrySys = entry.getSys();
|
|
40
51
|
const isUniqueValidationEnabled = (field.validations || []).some((validation)=>'unique' in validation && validation.unique === true);
|
|
52
|
+
const maxLength = getSlugFieldMaxLength(field.validations);
|
|
41
53
|
const isLocaleOptional = locales.optional[field.locale];
|
|
42
54
|
const localeFallbackCode = locales.fallbacks[field.locale];
|
|
43
55
|
const isOptionalFieldLocale = Boolean(!field.required || isLocaleOptional);
|
|
@@ -85,6 +97,7 @@ export function SlugEditor(props) {
|
|
|
85
97
|
isOptionalLocaleWithFallback: isOptionalLocaleWithFallback,
|
|
86
98
|
createdAt: entrySys.createdAt,
|
|
87
99
|
locale: field.locale,
|
|
100
|
+
maxLength: maxLength,
|
|
88
101
|
performUniqueCheck: performUniqueCheck,
|
|
89
102
|
isUniqueValidationEnabled: isUniqueValidationEnabled,
|
|
90
103
|
key: `slug-editor-${externalReset}`,
|
|
@@ -271,6 +271,7 @@ describe('SlugEditor', ()=>{
|
|
|
271
271
|
locale: "en-US",
|
|
272
272
|
titleValue: "Slug value",
|
|
273
273
|
createdAt: "2020-01-24T15:33:47.906Z",
|
|
274
|
+
maxLength: 256,
|
|
274
275
|
setValue: setValue,
|
|
275
276
|
performUniqueCheck: performUniqueCheck
|
|
276
277
|
}));
|
|
@@ -482,22 +483,46 @@ describe('SlugEditor', ()=>{
|
|
|
482
483
|
});
|
|
483
484
|
});
|
|
484
485
|
});
|
|
485
|
-
it('slug suggestion is limited to
|
|
486
|
+
it('slug suggestion is limited to 256 characters by default', async ()=>{
|
|
487
|
+
const longTitle = 'a'.repeat(300);
|
|
486
488
|
const { field, sdk } = createMocks({
|
|
487
489
|
field: '',
|
|
488
|
-
titleField:
|
|
490
|
+
titleField: longTitle
|
|
489
491
|
});
|
|
490
|
-
render(/*#__PURE__*/ React.createElement(SlugEditor, {
|
|
492
|
+
const { getByTestId } = render(/*#__PURE__*/ React.createElement(SlugEditor, {
|
|
491
493
|
field: field,
|
|
492
494
|
baseSdk: sdk,
|
|
493
495
|
isInitiallyDisabled: false
|
|
494
496
|
}));
|
|
495
|
-
await waitFor(async ()=>{
|
|
496
|
-
await sdk.entry.fields['title-id'].setValue('a'.repeat(80));
|
|
497
|
-
});
|
|
498
497
|
await waitFor(()=>{
|
|
499
|
-
const expectedSlug = 'a'.repeat(
|
|
498
|
+
const expectedSlug = 'a'.repeat(256);
|
|
500
499
|
expect(field.setValue).toHaveBeenLastCalledWith(expectedSlug);
|
|
500
|
+
expect(getByTestId('cf-ui-text-input')).toHaveAttribute('maxlength', '256');
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
it('slug suggestion respects size max validation', async ()=>{
|
|
504
|
+
const { field, sdk } = createMocks({
|
|
505
|
+
field: '',
|
|
506
|
+
titleField: 'one two three four'
|
|
507
|
+
});
|
|
508
|
+
field.validations = [
|
|
509
|
+
{
|
|
510
|
+
unique: true
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
size: {
|
|
514
|
+
max: 13
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
];
|
|
518
|
+
const { getByTestId } = render(/*#__PURE__*/ React.createElement(SlugEditor, {
|
|
519
|
+
field: field,
|
|
520
|
+
baseSdk: sdk,
|
|
521
|
+
isInitiallyDisabled: false
|
|
522
|
+
}));
|
|
523
|
+
await waitFor(()=>{
|
|
524
|
+
expect(field.setValue).toHaveBeenLastCalledWith('one-two-three');
|
|
525
|
+
expect(getByTestId('cf-ui-text-input')).toHaveAttribute('maxlength', '13');
|
|
501
526
|
});
|
|
502
527
|
});
|
|
503
528
|
it('slug suggestion does not contain cut-off words', async ()=>{
|
|
@@ -511,7 +536,7 @@ describe('SlugEditor', ()=>{
|
|
|
511
536
|
isInitiallyDisabled: false
|
|
512
537
|
}));
|
|
513
538
|
await waitFor(async ()=>{
|
|
514
|
-
await sdk.entry.fields['title-id'].setValue(`one two three ${'a'.repeat(
|
|
539
|
+
await sdk.entry.fields['title-id'].setValue(`one two three ${'a'.repeat(300)}`);
|
|
515
540
|
});
|
|
516
541
|
await waitFor(()=>{
|
|
517
542
|
const expectedSlug = 'one-two-three';
|
|
@@ -7,7 +7,7 @@ import { useDebounce } from 'use-debounce';
|
|
|
7
7
|
import { makeSlug } from './services/makeSlug';
|
|
8
8
|
import * as styles from './styles';
|
|
9
9
|
function useSlugUpdater(props, check) {
|
|
10
|
-
const { value, setValue, createdAt, locale, titleValue, isOptionalLocaleWithFallback } = props;
|
|
10
|
+
const { value, setValue, createdAt, locale, titleValue, isOptionalLocaleWithFallback, maxLength } = props;
|
|
11
11
|
React.useEffect(()=>{
|
|
12
12
|
if (check === false) {
|
|
13
13
|
return;
|
|
@@ -15,7 +15,8 @@ function useSlugUpdater(props, check) {
|
|
|
15
15
|
const newSlug = makeSlug(titleValue, {
|
|
16
16
|
isOptionalLocaleWithFallback,
|
|
17
17
|
locale,
|
|
18
|
-
createdAt
|
|
18
|
+
createdAt,
|
|
19
|
+
maxLength
|
|
19
20
|
});
|
|
20
21
|
if (newSlug !== value) {
|
|
21
22
|
setValue(newSlug);
|
|
@@ -27,6 +28,7 @@ function useSlugUpdater(props, check) {
|
|
|
27
28
|
check,
|
|
28
29
|
createdAt,
|
|
29
30
|
locale,
|
|
31
|
+
maxLength,
|
|
30
32
|
setValue
|
|
31
33
|
]);
|
|
32
34
|
}
|
|
@@ -52,7 +54,7 @@ function useUniqueChecker(props) {
|
|
|
52
54
|
return status;
|
|
53
55
|
}
|
|
54
56
|
export function SlugEditorFieldStatic(props) {
|
|
55
|
-
const { hasError, isDisabled, value, setValue, onChange, onBlur, isUniqueValidationEnabled, id } = props;
|
|
57
|
+
const { hasError, isDisabled, value, setValue, onChange, onBlur, isUniqueValidationEnabled, id, maxLength } = props;
|
|
56
58
|
const status = useUniqueChecker(props);
|
|
57
59
|
const hasDuplicate = status === 'duplicate';
|
|
58
60
|
const shouldShowDuplicateAsError = hasDuplicate && isUniqueValidationEnabled && !hasError;
|
|
@@ -67,6 +69,7 @@ export function SlugEditorFieldStatic(props) {
|
|
|
67
69
|
isDisabled: isDisabled,
|
|
68
70
|
value: value || '',
|
|
69
71
|
id: id,
|
|
72
|
+
maxLength: maxLength,
|
|
70
73
|
onChange: (e)=>{
|
|
71
74
|
setValue(e.target.value);
|
|
72
75
|
if (onChange) {
|
|
@@ -98,12 +101,13 @@ export function SlugEditorFieldStatic(props) {
|
|
|
98
101
|
})));
|
|
99
102
|
}
|
|
100
103
|
export function SlugEditorField(props) {
|
|
101
|
-
const { titleValue, isOptionalLocaleWithFallback, locale, createdAt, value } = props;
|
|
104
|
+
const { titleValue, isOptionalLocaleWithFallback, locale, createdAt, value, maxLength } = props;
|
|
102
105
|
const areEqual = React.useCallback(()=>{
|
|
103
106
|
const potentialSlug = makeSlug(titleValue, {
|
|
104
|
-
isOptionalLocaleWithFallback
|
|
105
|
-
locale
|
|
106
|
-
createdAt
|
|
107
|
+
isOptionalLocaleWithFallback,
|
|
108
|
+
locale,
|
|
109
|
+
createdAt,
|
|
110
|
+
maxLength
|
|
107
111
|
});
|
|
108
112
|
return value === potentialSlug;
|
|
109
113
|
}, [
|
|
@@ -111,6 +115,7 @@ export function SlugEditorField(props) {
|
|
|
111
115
|
isOptionalLocaleWithFallback,
|
|
112
116
|
locale,
|
|
113
117
|
createdAt,
|
|
118
|
+
maxLength,
|
|
114
119
|
value
|
|
115
120
|
]);
|
|
116
121
|
const [check, setCheck] = React.useState(()=>{
|
|
@@ -12,13 +12,13 @@ export function formatUtcDate(date) {
|
|
|
12
12
|
const seconds = formatTwoDigit(date.getUTCSeconds());
|
|
13
13
|
return `${year} ${month} ${day} at ${hour} ${minutes} ${seconds}`;
|
|
14
14
|
}
|
|
15
|
-
function untitledSlug({ isOptionalLocaleWithFallback, createdAt }) {
|
|
15
|
+
function untitledSlug({ isOptionalLocaleWithFallback, createdAt, maxLength }) {
|
|
16
16
|
if (isOptionalLocaleWithFallback) {
|
|
17
17
|
return '';
|
|
18
18
|
}
|
|
19
19
|
const createdAtFormatted = formatUtcDate(new Date(createdAt));
|
|
20
|
-
return slugify('Untitled entry ' + createdAtFormatted, 'en-US');
|
|
20
|
+
return slugify('Untitled entry ' + createdAtFormatted, 'en-US', maxLength);
|
|
21
21
|
}
|
|
22
22
|
export function makeSlug(title, options) {
|
|
23
|
-
return title ? slugify(title, options.locale) : untitledSlug(options);
|
|
23
|
+
return title ? slugify(title, options.locale, options.maxLength) : untitledSlug(options);
|
|
24
24
|
}
|
|
@@ -7,4 +7,12 @@ describe('makeSlug', ()=>{
|
|
|
7
7
|
createdAt: '2020-01-14T14:45:39.709Z'
|
|
8
8
|
})).toBe('untitled-entry-2020-01-14-at-14-45-39');
|
|
9
9
|
});
|
|
10
|
+
it('should respect maxLength when title is present', ()=>{
|
|
11
|
+
expect(makeSlug('a'.repeat(300), {
|
|
12
|
+
locale: 'en',
|
|
13
|
+
isOptionalLocaleWithFallback: false,
|
|
14
|
+
createdAt: '2020-01-14T14:45:39.709Z',
|
|
15
|
+
maxLength: 256
|
|
16
|
+
})).toBe('a'.repeat(256));
|
|
17
|
+
});
|
|
10
18
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import getSlug from 'speakingurl';
|
|
2
|
-
const
|
|
2
|
+
const DEFAULT_SLUG_MAX_LENGTH = 75;
|
|
3
3
|
const languages = [
|
|
4
4
|
'ar',
|
|
5
5
|
'az',
|
|
@@ -34,11 +34,11 @@ function supportedLanguage(locale) {
|
|
|
34
34
|
const prefix = locale.slice(0, 2).toLowerCase();
|
|
35
35
|
return languages[languages.indexOf(prefix)];
|
|
36
36
|
}
|
|
37
|
-
export function slugify(text, locale = 'en') {
|
|
37
|
+
export function slugify(text, locale = 'en', maxLength = DEFAULT_SLUG_MAX_LENGTH) {
|
|
38
38
|
return getSlug(text, {
|
|
39
39
|
separator: '-',
|
|
40
40
|
lang: supportedLanguage(locale) || 'en',
|
|
41
|
-
truncate:
|
|
41
|
+
truncate: maxLength + 1,
|
|
42
42
|
custom: {
|
|
43
43
|
"'": '',
|
|
44
44
|
'`': '',
|
|
@@ -23,4 +23,13 @@ describe('slugify', ()=>{
|
|
|
23
23
|
expect(slugify(input[0])).toBe(input[1]);
|
|
24
24
|
});
|
|
25
25
|
});
|
|
26
|
+
it('defaults to a 75 character limit', ()=>{
|
|
27
|
+
expect(slugify('a'.repeat(80))).toBe('a'.repeat(75));
|
|
28
|
+
});
|
|
29
|
+
it('accepts a custom max length', ()=>{
|
|
30
|
+
expect(slugify('a'.repeat(80), 'en', 80)).toBe('a'.repeat(80));
|
|
31
|
+
});
|
|
32
|
+
it('does not cut off words when a custom max length is provided', ()=>{
|
|
33
|
+
expect(slugify('one two three four', 'en', 13)).toBe('one-two-three');
|
|
34
|
+
});
|
|
26
35
|
});
|
|
@@ -8,6 +8,7 @@ interface SlugEditorFieldProps {
|
|
|
8
8
|
locale: string;
|
|
9
9
|
titleValue: string | null | undefined;
|
|
10
10
|
createdAt: string;
|
|
11
|
+
maxLength: number;
|
|
11
12
|
setValue: (value: string | null | undefined) => void;
|
|
12
13
|
performUniqueCheck: (value: string) => Promise<boolean>;
|
|
13
14
|
id?: string;
|
|
@@ -2,6 +2,7 @@ type MakeSlugOptions = {
|
|
|
2
2
|
locale: string;
|
|
3
3
|
isOptionalLocaleWithFallback: boolean;
|
|
4
4
|
createdAt: string;
|
|
5
|
+
maxLength?: number;
|
|
5
6
|
};
|
|
6
7
|
export declare function formatUtcDate(date: Date): string;
|
|
7
8
|
export declare function makeSlug(title: string | null | undefined, options: MakeSlugOptions): string;
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
* If the locale belongs to a language supported by SpeakingURL, it
|
|
4
4
|
* is used as the symbol language. Otherwise, the symbol language
|
|
5
5
|
* is english.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* By default slug suggestions are limited to 75 characters.
|
|
8
|
+
* Consumers can override that limit via maxLength.
|
|
7
9
|
*
|
|
8
10
|
* @param {string} text To be turned into a slug.
|
|
9
11
|
* @param {string?} locale
|
|
12
|
+
* @param {number?} maxLength
|
|
10
13
|
* @returns {string} Slug for provided text.
|
|
11
14
|
*/
|
|
12
|
-
export declare function slugify(text: string, locale?: string): string;
|
|
15
|
+
export declare function slugify(text: string, locale?: string, maxLength?: number): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/field-editor-slug",
|
|
3
|
-
"version": "3.0.0",
|
|
3
|
+
"version": "3.0.1-canary.0+bb95f1db",
|
|
4
4
|
"main": "dist/cjs/index.js",
|
|
5
5
|
"module": "dist/esm/index.js",
|
|
6
6
|
"types": "dist/types/index.d.ts",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"publishConfig": {
|
|
60
60
|
"registry": "https://npm.pkg.github.com/"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "bb95f1dbd95f3bcfcd9bfb9cbd058e5810796f44"
|
|
63
63
|
}
|