Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 25 additions & 11 deletions packages/slug/src/SlugEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import identity from 'lodash/identity';
import { render, configure, cleanup, wait, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { SlugEditor } from './SlugEditor';
import { CF_GENERATED_SLUG_MAX_LENGTH } from './services/slugify';
import { createFakeFieldAPI, createFakeLocalesAPI } from '@contentful/field-editor-test-utils';

configure({
Expand Down Expand Up @@ -453,21 +454,34 @@ describe('SlugEditor', () => {
});
});

it('slug suggestion is limited to 75 symbols', async () => {
const { field, sdk } = createMocks({
field: '',
titleField: '',
});
[undefined, 50, 100].forEach((maxSlugLength) => {
it(`slug suggestion is limited to ${
maxSlugLength || `${CF_GENERATED_SLUG_MAX_LENGTH} (default)`
} symbols`, async () => {
const { field, sdk } = createMocks({
field: '',
titleField: '',
});

render(<SlugEditor field={field} baseSdk={sdk as any} isInitiallyDisabled={false} />);
render(
<SlugEditor
field={field}
baseSdk={sdk as any}
isInitiallyDisabled={false}
parameters={{ instance: { maxSlugLength } }}
/>
);

await wait();
await wait();

await sdk.entry.fields['title-id'].setValue('a'.repeat(80));
await wait();
await sdk.entry.fields['title-id'].setValue(
'a'.repeat((maxSlugLength ?? CF_GENERATED_SLUG_MAX_LENGTH) + 10)
);
await wait();

const expectedSlug = 'a'.repeat(75);
expect(field.setValue).toHaveBeenLastCalledWith(expectedSlug);
const expectedSlug = 'a'.repeat(maxSlugLength ?? CF_GENERATED_SLUG_MAX_LENGTH);
expect(field.setValue).toHaveBeenLastCalledWith(expectedSlug);
});
});

it('slug suggestion does not contain cut-off words', async () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/slug/src/SlugEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface SlugEditorProps {
parameters?: {
instance: {
trackingFieldId?: string;
maxSlugLength?: number;
};
};
}
Expand All @@ -39,6 +40,7 @@ function FieldConnectorCallback({
locale,
createdAt,
performUniqueCheck,
maxSlugLength,
}: {
Component: typeof SlugEditorFieldStatic | typeof SlugEditorField;
value: string | null | undefined;
Expand All @@ -50,6 +52,7 @@ function FieldConnectorCallback({
locale: FieldAPI['locale'];
createdAt: string;
performUniqueCheck: (value: string) => Promise<boolean>;
maxSlugLength?: number;
}) {
// it is needed to silent permission errors
// this happens when setValue is called on a field which is disabled for permission reasons
Expand All @@ -76,6 +79,7 @@ function FieldConnectorCallback({
isDisabled={disabled}
titleValue={titleValue}
setValue={safeSetValue}
maxSlugLength={maxSlugLength}
/>
</div>
);
Expand All @@ -90,6 +94,7 @@ export function SlugEditor(props: SlugEditorProps) {
}

const trackingFieldId = parameters?.instance?.trackingFieldId ?? undefined;
const maxSlugLength = parameters?.instance?.maxSlugLength ?? undefined;
const entrySys = entry.getSys();

const isLocaleOptional = locales.optional[field.locale];
Expand Down Expand Up @@ -149,6 +154,7 @@ export function SlugEditor(props: SlugEditorProps) {
createdAt={entrySys.createdAt}
locale={field.locale}
performUniqueCheck={performUniqueCheck}
maxSlugLength={maxSlugLength}
key={`slug-editor-${externalReset}`}
/>
);
Expand Down
11 changes: 7 additions & 4 deletions packages/slug/src/SlugEditorField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ interface SlugEditorFieldProps {
createdAt: string;
setValue: (value: string | null | undefined) => void;
performUniqueCheck: (value: string) => Promise<boolean>;
maxSlugLength?: number;
}

type CheckerState = 'checking' | 'unique' | 'duplicate';

function useSlugUpdater(props: SlugEditorFieldProps, check: boolean) {
const { value, setValue, createdAt, locale, titleValue, isOptionalLocaleWithFallback } = props;
const { value, setValue, createdAt, locale, titleValue, isOptionalLocaleWithFallback, maxSlugLength } = props;

React.useEffect(() => {
if (check === false) {
Expand All @@ -32,11 +33,12 @@ function useSlugUpdater(props: SlugEditorFieldProps, check: boolean) {
isOptionalLocaleWithFallback,
locale,
createdAt,
maxSlugLength
});
if (newSlug !== value) {
setValue(newSlug);
}
}, [value, titleValue, isOptionalLocaleWithFallback, check, createdAt, locale, setValue]);
}, [value, titleValue, isOptionalLocaleWithFallback, check, createdAt, locale, setValue, maxSlugLength]);
}

function useUniqueChecker(props: SlugEditorFieldProps) {
Expand Down Expand Up @@ -111,16 +113,17 @@ export function SlugEditorFieldStatic(
}

export function SlugEditorField(props: SlugEditorFieldProps) {
const { titleValue, isOptionalLocaleWithFallback, locale, createdAt, value } = props;
const { titleValue, isOptionalLocaleWithFallback, locale, createdAt, value, maxSlugLength } = props;

const areEqual = React.useCallback(() => {
const potentialSlug = makeSlug(titleValue, {
isOptionalLocaleWithFallback: isOptionalLocaleWithFallback,
locale: locale,
createdAt: createdAt,
maxSlugLength
});
return value === potentialSlug;
}, [titleValue, isOptionalLocaleWithFallback, locale, createdAt, value]);
}, [titleValue, isOptionalLocaleWithFallback, locale, createdAt, value, maxSlugLength]);

const [check, setCheck] = React.useState<boolean>(() => {
if (props.value) {
Expand Down
3 changes: 2 additions & 1 deletion packages/slug/src/services/makeSlug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type MakeSlugOptions = {
locale: string;
isOptionalLocaleWithFallback: boolean;
createdAt: string;
maxSlugLength?: number;
};

function formatTwoDigit(num: number) {
Expand Down Expand Up @@ -32,5 +33,5 @@ function untitledSlug({ isOptionalLocaleWithFallback, createdAt }: MakeSlugOptio
}

export function makeSlug(title: string | null | undefined, options: MakeSlugOptions) {
return title ? slugify(title, options.locale) : untitledSlug(options);
return title ? slugify(title, options.locale, options.maxSlugLength) : untitledSlug(options);
}
7 changes: 4 additions & 3 deletions packages/slug/src/services/slugify.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import getSlug from 'speakingurl';

const CF_GENERATED_SLUG_MAX_LENGTH = 75;
export const CF_GENERATED_SLUG_MAX_LENGTH = 75;

const languages = [
'ar',
Expand Down Expand Up @@ -51,13 +51,14 @@ function supportedLanguage(locale: string) {
*
* @param {string} text To be turned into a slug.
* @param {string?} locale
* @param {number?} maxLength
* @returns {string} Slug for provided text.
*/
export function slugify(text: string, locale = 'en') {
export function slugify(text: string, locale = 'en', maxLength = CF_GENERATED_SLUG_MAX_LENGTH) {
return getSlug(text, {
separator: '-',
lang: supportedLanguage(locale) || 'en',
truncate: CF_GENERATED_SLUG_MAX_LENGTH + 1,
truncate: maxLength + 1,
custom: {
"'": '',
'`': '',
Expand Down