Skip to content

Commit

Permalink
Merge pull request #305 from VirtusLab-Open-Source/develop
Browse files Browse the repository at this point in the history
Preparation for v2.2.4
  • Loading branch information
cyp3rius authored Feb 2, 2023
2 parents d89f9c4 + 0d220f6 commit a9f2cad
Show file tree
Hide file tree
Showing 23 changed files with 226 additions and 109 deletions.
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ Config for this plugin is stored as a part of the `config/plugins.js` or `config
> *Note v2.0.3 and newer only*
> Changing this file will not automatically change plugin configuration. To synchronize plugin's config with plugins.js file, it is necessary to restore configuration through the settings page
> *Note for newer than v2.2.0*
> `slugify` as been removed. **THIS A BREAKING CHANGE**
```js
module.exports = ({ env }) => ({
// ...
Expand All @@ -162,11 +165,6 @@ Config for this plugin is stored as a part of the `config/plugins.js` or `config
},
allowedLevels: 2,
gql: {...},
slugify: {
customReplacements: [
["🤔", "thinking"],
],
}
}
}
});
Expand Down Expand Up @@ -632,6 +630,33 @@ module.exports = {

If you already got it, make sure that `navigation` plugin is inserted before `graphql`. That should do the job.

### Slug generation

#### Customisation

Slug generation is available as a controller and service. If you have custom requirements outside of what this plugin provides you can add your own logic with [plugins extensions](https://docs.strapi.io/developer-docs/latest/development/plugins-extension.html).

For example:

```ts
// path: ./src/index.js

module.exports = {
// ...
bootstrap({ strapi }) {
const navigationCommonService = strapi.plugin("navigation").service("common");
const originalGetSlug = navigationCommonService.getSlug;
const preprocess = (q) => {
return q + "suffix";
};

navigationCommonService.getSlug = (query) => {
return originalGetSlug(preprocess(query));
};
},
};
```

## 🤝 Contributing

<div>
Expand Down
2 changes: 2 additions & 0 deletions __mocks__/strapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const masterModelMock = {
};

const itemModelMock = {
count: async () => 0,
findOne: async () => ({
id: 1,
title: "home",
Expand Down Expand Up @@ -189,6 +190,7 @@ const strapiFactory = (plugins: (strapi: IStrapi) => StringMap<StrapiPlugin>, co
return {
findOne: () => ({}),
findMany: () => [],
count: () => 0,
}
}
},
Expand Down
7 changes: 5 additions & 2 deletions admin/src/components/Item/ItemCardHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ interface IProps {
}

const wrapperStyle = { zIndex: 2 };
const pathWrapperStyle = { maxWidth: "425px" };

const ItemCardHeader: React.FC<IProps> = ({ title, path, icon, removed, onItemRemove, onItemEdit, onItemRestore, dragRef }) => (
<Wrapper>
Expand All @@ -35,10 +36,12 @@ const ItemCardHeader: React.FC<IProps> = ({ title, path, icon, removed, onItemRe
<Typography variant="omega" fontWeight="bold">
{title}
</Typography>
<Typography variant="omega" fontWeight="bold" textColor='neutral500'>
<Typography variant="omega" fontWeight="bold" textColor='neutral500' ellipsis style={pathWrapperStyle}>
{path}
</Typography>
<Icon as={icon} />
<Flex>
<Icon as={icon} />
</Flex>
</Flex>
<Flex alignItems="center" style={wrapperStyle}>
{removed &&
Expand Down
7 changes: 7 additions & 0 deletions admin/src/pages/DataManagerProvider/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,12 @@ const DataManagerProvider = ({ children }) => {
signal,
});

const slugify = (query) =>
request(
`/${pluginId}/slug?q=${query}`,
{ method: "GET", signal }
);

const hardReset = () => getDataRef.current();

return (
Expand Down Expand Up @@ -379,6 +385,7 @@ const DataManagerProvider = ({ children }) => {
readNavigationItemFromLocale,
handleNavigationsDeletion,
hardReset,
slugify,
}}
>
{isLoading ? <LoadingIndicatorPage /> : children}
Expand Down
2 changes: 1 addition & 1 deletion admin/src/pages/SettingsPage/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type RawPayload = {
}
export type StrapiContentTypeSchema = StrapiContentTypeFullSchema & { available: boolean, isSingle: boolean, plugin: string, label: string }

export type PreparePayload = (payload: { form: RawPayload, pruneObsoleteI18nNavigations: boolean }) => Omit<NavigationPluginConfig, "slugify">;
export type PreparePayload = (payload: { form: RawPayload, pruneObsoleteI18nNavigations: boolean }) => NavigationPluginConfig;
export type OnSave = Effect<RawPayload>;
export type OnPopupClose = Effect<boolean>;
export type HandleSetContentTypeExpanded = Effect<string | undefined>;
Expand Down
54 changes: 39 additions & 15 deletions admin/src/pages/View/components/NavigationItemForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useState, useCallback, BaseSyntheticEvent } from 'react';
import { debounce, find, get, first, isEmpty, isEqual, isNil, isString, isObject, sortBy } from 'lodash';
import slugify from '@sindresorhus/slugify';
import { prop } from 'lodash/fp';
import { useFormik, FormikProps } from 'formik';

//@ts-ignore
Expand All @@ -20,14 +20,12 @@ import { extractRelatedItemLabel } from '../../utils/parsers';
import * as formDefinition from './utils/form';
import { checkFormValidity } from '../../utils/form';
import { getTrad, getTradId } from '../../../../translations';
import { assertString, Audience, NavigationItemAdditionalField, NavigationItemType, ToBeFixed } from '../../../../../../types';
import { ContentTypeSearchQuery, NavigationItemFormData, NavigationItemFormProps, RawFormPayload, SanitizedFormPayload } from './types';
import { assertString, Audience, Effect, NavigationItemAdditionalField, NavigationItemType, ToBeFixed } from '../../../../../../types';
import { ContentTypeSearchQuery, NavigationItemFormData, NavigationItemFormProps, RawFormPayload, SanitizedFormPayload, Slugify } from './types';
import AdditionalFieldInput from '../../../../components/AdditionalFieldInput';
import { getMessage, ResourceState } from '../../../../utils';
import { Id } from 'strapi-typed';

const appendLabelPublicationStatusFallback = () => '';

const NavigationItemForm: React.FC<NavigationItemFormProps> = ({
config,
availableLocale,
Expand All @@ -47,6 +45,7 @@ const NavigationItemForm: React.FC<NavigationItemFormProps> = ({
appendLabelPublicationStatus = appendLabelPublicationStatusFallback,
locale,
readNavigationItemFromLocale,
slugify,
}) => {
const [isLoading, setIsLoading] = useState(isPreloading);
const [hasBeenInitialized, setInitializedState] = useState(false);
Expand All @@ -55,8 +54,19 @@ const NavigationItemForm: React.FC<NavigationItemFormProps> = ({
const [contentTypeSearchInputValue, setContentTypeSearchInputValue] = useState(undefined);
const formik: FormikProps<RawFormPayload> = useFormik<RawFormPayload>({
initialValues: formDefinition.defaultValues,
onSubmit: (payload) => onSubmit(sanitizePayload(payload, data)),
validate: (values) => checkFormValidity(sanitizePayload(values, {}), formDefinition.schemaFactory(isSingleSelected, additionalFields)),
onSubmit: loadingAware(
async (payload) => onSubmit(
await sanitizePayload(slugify, payload, data)
),
setIsLoading
),
validate: loadingAware(
async (values) => checkFormValidity(
await sanitizePayload(slugify, values, {}),
formDefinition.schemaFactory(isSingleSelected, additionalFields)
),
setIsLoading
),
validateOnChange: false,
});
const initialRelatedTypeSelected = get(data, 'relatedType.value');
Expand Down Expand Up @@ -130,14 +140,15 @@ const NavigationItemForm: React.FC<NavigationItemFormProps> = ({

}, [contentTypeEntities, contentTypesNameFields, contentTypes]);

const sanitizePayload = (payload: RawFormPayload, data: Partial<NavigationItemFormData>): SanitizedFormPayload => {
const sanitizePayload = async (slugify: Slugify, payload: RawFormPayload, data: Partial<NavigationItemFormData>): Promise<SanitizedFormPayload> => {
const { related, relatedType, menuAttached, type, ...purePayload } = payload;
const relatedId = related;
const singleRelatedItem = isSingleSelected ? first(contentTypeEntities) : undefined;
const relatedCollectionType = relatedType;
const title = !!payload.title?.trim()
? payload.title
: getDefaultTitle(related, relatedType, isSingleSelected)
const uiRouterKey = await generateUiRouterKey(slugify, title, relatedId, relatedCollectionType);

return {
...data,
Expand All @@ -151,7 +162,7 @@ const NavigationItemForm: React.FC<NavigationItemFormProps> = ({
relatedType: type === navigationItemType.INTERNAL ? relatedCollectionType : undefined,
isSingle: isSingleSelected,
singleRelatedItem,
uiRouterKey: generateUiRouterKey(title, relatedId, relatedCollectionType),
uiRouterKey,
};
};

Expand Down Expand Up @@ -200,17 +211,15 @@ const NavigationItemForm: React.FC<NavigationItemFormProps> = ({
});
}

const generateUiRouterKey = (title: string, related?: string, relatedType?: string): string | undefined => {
const { slugify: customSlugifyConfig } = config;

const generateUiRouterKey = async (slugify: Slugify, title: string, related?: string, relatedType?: string): Promise<string | undefined> => {
if (title) {
return isString(title) && !isEmpty(title) ? slugify(title, customSlugifyConfig).toLowerCase() : undefined;
return isString(title) && !isEmpty(title) ? await slugify(title).then(prop("slug")) : undefined;
} else if (related) {
const relationTitle = extractRelatedItemLabel({
...contentTypeEntities.find(_ => _.id === related),
__collectionUid: relatedType
}, contentTypesNameFields, { contentTypes });
return isString(relationTitle) && !isEmpty(relationTitle) ? slugify(relationTitle, customSlugifyConfig).toLowerCase() : undefined;
return isString(relationTitle) && !isEmpty(relationTitle) ? await slugify(relationTitle).then(prop("slug")) : undefined;
}
return undefined;
};
Expand Down Expand Up @@ -266,7 +275,7 @@ const NavigationItemForm: React.FC<NavigationItemFormProps> = ({
const pathSourceName = isExternal ? 'externalPath' : 'path';

const submitDisabled =
(formik.values.type === navigationItemType.INTERNAL && !isSingleSelected && isNil(formik.values.related));
(formik.values.type === navigationItemType.INTERNAL && !isSingleSelected && isNil(formik.values.related)) || isLoading;

const debouncedSearch = useCallback(
debounce(nextValue => setContentTypeSearchQuery(nextValue), 500),
Expand Down Expand Up @@ -613,4 +622,19 @@ const NavigationItemForm: React.FC<NavigationItemFormProps> = ({
);
};

const appendLabelPublicationStatusFallback = () => "";

const loadingAware =
<T, U>(action: (i: T) => U, isLoading: Effect<boolean>) =>
async (input: T) => {
try {
isLoading(true);

return await action(input);
} catch (_) {
} finally {
isLoading(false);
}
};

export default NavigationItemForm;
5 changes: 5 additions & 0 deletions admin/src/pages/View/components/NavigationItemForm/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export type NavigationItemFormProps = {
availableLocale: string[];
readNavigationItemFromLocale: ToBeFixed;
inputsPrefix: string;
slugify: (q: string) => Promise<{slug: string}>
}

export type ContentTypeSearchQuery = ToBeFixed;
Expand Down Expand Up @@ -103,3 +104,7 @@ export type SanitizedFormPayload = {
singleRelatedItem: ContentTypeEntity | undefined;
uiRouterKey: string | undefined;
}

export type Slugify = (q: string) => Promise<{
slug: string;
}>;
4 changes: 3 additions & 1 deletion admin/src/pages/View/components/NavigationItemPopup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const NavigationItemPopUp = ({
usedContentTypesData,
locale,
readNavigationItemFromLocale,
slugify,
}) => {
const handleOnSubmit = (payload) => {
onSubmit(payload);
Expand All @@ -43,7 +44,6 @@ const NavigationItemPopUp = ({
contentTypesNameFields = {},
} = config;


const appendLabelPublicationStatus = (label = '', item = {}, isCollection = false) => {
const appendix = isRelationPublished({
relatedRef: item,
Expand Down Expand Up @@ -99,6 +99,7 @@ const NavigationItemPopUp = ({
appendLabelPublicationStatus={appendLabelPublicationStatus}
locale={locale}
readNavigationItemFromLocale={readNavigationItemFromLocale}
slugify={slugify}
/>
</ModalLayout>

Expand All @@ -115,6 +116,7 @@ NavigationItemPopUp.propTypes = {
getContentTypeItems: PropTypes.func.isRequired,
locale: PropTypes.string,
readNavigationItemFromLocale: PropTypes.func.isRequired,
slugify: PropTypes.func.isRequired,
};

export default NavigationItemPopUp;
2 changes: 2 additions & 0 deletions admin/src/pages/View/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const View = () => {
error,
availableLocale: allAvailableLocale,
readNavigationItemFromLocale,
slugify,
} = useDataManager();
const availableLocale = useMemo(
() => allAvailableLocale.filter(locale => locale !== changedActiveNavigation?.localeCode),
Expand Down Expand Up @@ -360,6 +361,7 @@ const View = () => {
onClose={onPopUpClose}
locale={activeNavigation.localeCode}
readNavigationItemFromLocale={readNavigationItemFromLocale}
slugify={slugify}
/>}
{i18nCopyItemsModal}
</Main>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"jest-styled-components": "^7.0.2",
"koa": "^2.8.0",
"nodemon": "^2.0.15",
"strapi-typed": "1.0.10",
"strapi-typed": "1.0.13",
"ts-jest": "^27.1.3",
"ts-node": "^10.7.0",
"typescript": "^4.5.5"
Expand Down
1 change: 0 additions & 1 deletion server/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const config: StrapiConfig<NavigationPluginConfig> = {
i18nEnabled: false,
pathDefaultFields: {},
pruneObsoleteI18nNavigations: false,
slugify: {},
cascadeMenuAttached: true,
},
};
Expand Down
13 changes: 0 additions & 13 deletions server/config/setupStrategy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { pick } from "lodash";
import {
assertNotEmpty,
IConfigSetupStrategy,
Expand Down Expand Up @@ -40,10 +39,6 @@ export const configSetupStrategy: IConfigSetupStrategy = async ({ strapi }) => {
allowedLevels: getWithFallback<number>("allowedLevels"),
gql: getWithFallback<PluginConfigGraphQL>("gql"),
i18nEnabled: hasI18nPlugin && getWithFallback<boolean>("i18nEnabled"),
slugify: pick(
getWithFallback<NavigationPluginConfig["slugify"]>("slugify"),
validSlugifyFields
),
pruneObsoleteI18nNavigations: false,
pathDefaultFields: getWithFallback<PluginConfigPathDefaultFields>("pathDefaultFields"),
cascadeMenuAttached: getWithFallback<boolean>("cascadeMenuAttached"),
Expand Down Expand Up @@ -71,11 +66,3 @@ const getWithFallbackFactory =

return value as T;
};

const validSlugifyFields: Array<string> = [
"separator",
"lowercase",
"decamelize",
"customReplacements",
"preserveLeadingUnderscore",
];
3 changes: 2 additions & 1 deletion server/content-types/navigation-item/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ export default {
type: "relation",
relation: "manyToOne",
target: "plugin::navigation.navigation",
configurable: false
configurable: false,
inversedBy: "items",
},
audience: {
type: "relation",
Expand Down
3 changes: 2 additions & 1 deletion server/content-types/navigation/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export default {
type: "relation",
relation: "oneToMany",
target: "plugin::navigation.navigation-item",
configurable: false
configurable: false,
mappedBy: "master"
},
localizations: {
type: "relation",
Expand Down
Loading

0 comments on commit a9f2cad

Please sign in to comment.