Skip to content

Commit

Permalink
Merge pull request #19 from VirtusLab/feat/configurable-title-fields
Browse files Browse the repository at this point in the history
feat: configurable title fields for content types
  • Loading branch information
cyp3rius authored Nov 17, 2020
2 parents 531d5ea + f4f7aa4 commit 393a989
Show file tree
Hide file tree
Showing 15 changed files with 221 additions and 37 deletions.
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,24 @@ To setup the plugin properly we recommend to put following snippet as part of `c
...
plugins: {
navigation: {
additionalFields: ['audience'], // Additional fields: 'audience', more in the future
excludedContentTypes: ["plugins::", "strapi"], // excluded content types patterns (by default built-in and plugin specific content types)
allowedLevels: 2, // Maximum level for which your're able to mark item as "Menu attached"
additionalFields: ['audience'],
excludedContentTypes: ["plugins::", "strapi"],
allowedLevels: 2,
contentTypesNameFields: {
'blog_posts': ['altTitle'],
'pages': ['title'],
},
},
},
...
```

### Properties
- `additionalFields` - Additional fields: 'audience', more in the future
- `excludedContentTypes` - Excluded content types patterns (by default built-in and plugin specific content types)
- `allowedLevels` - Maximum level for which your're able to mark item as "Menu attached"
- `contentTypesNameFields` - Definition of content type title fields like `'content_type_name': ['field_name_1', 'field_name_2']`, if not set titles are pulled from fields like `['title', 'subject', 'name']`

## Public API Navigation Item model

### Flat
Expand Down
29 changes: 29 additions & 0 deletions __mocks__/helpers/blog-post.settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"kind": "collectionType",
"collectionName": "blog_posts",
"info": {
"name": "Blog posts"
},
"options": {
"increments": true,
"timestamps": true,
"searchable": true,
"previewable": true
},
"attributes": {
"title": {
"type": "string",
"required": true
},
"altTitle": {
"type": "string"
},
"navigation": {
"model": "navigationitem",
"plugin": "navigation",
"via": "related",
"configurable": false,
"hidden": true
}
}
}
26 changes: 26 additions & 0 deletions __mocks__/helpers/page.settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"kind": "collectionType",
"collectionName": "pages",
"info": {
"name": "page"
},
"options": {
"increments": true,
"timestamps": true,
"searchable": true,
"previewable": true
},
"attributes": {
"title": {
"type": "string",
"required": true
},
"navigation": {
"model": "navigationitem",
"plugin": "navigation",
"via": "related",
"configurable": false,
"hidden": true
}
}
}
40 changes: 40 additions & 0 deletions __mocks__/helpers/strapi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
function setupStrapi() {
Object.defineProperty(global, 'strapi', {
value: {
config: {
custom: {
plugins: {
navigation: {
contentTypesNameFields: {
'blog_posts': ['altTitle'],
},
},
},
},
},
contentTypes: {
'page': {
...require('./page.settings.json'),
apiName: 'pages',
},
'blog-post': {
...require('./blog-post.settings.json'),
apiName: 'blog-posts',
},
},
plugins: {
navigation: {
services: {
navigation: jest.fn().mockImplementation(),
},
models: {
'page': require('./page.settings.json'),
'blog-post': require('./blog-post.settings.json'),
}
}
},
},
writable: true,
})
}
module.exports = { setupStrapi };
4 changes: 4 additions & 0 deletions admin/src/components/Item/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const Item = (props) => {
level = 0,
levelPath = '',
allowedLevels,
contentTypesNameFields,
relatedRef,
isFirst = false,
isLast = false,
Expand All @@ -46,6 +47,7 @@ const Item = (props) => {
removed,
menuAttached,
relatedRef,
contentTypesNameFields,
attachButtons: !(isFirst && isLast),
};

Expand Down Expand Up @@ -111,6 +113,7 @@ const Item = (props) => {
level={level + 1}
levelPath={absolutePath}
allowedLevels={allowedLevels}
contentTypesNameFields={contentTypesNameFields}
/>
)}
</CardWrapper>
Expand All @@ -129,6 +132,7 @@ Item.propTypes = {
menuAttached: PropTypes.bool,
}).isRequired,
relatedRef: PropTypes.object,
contentTypesNameFields: PropTypes.object.isRequired,
level: PropTypes.number,
levelPath: PropTypes.string,
isFirst: PropTypes.bool,
Expand Down
21 changes: 6 additions & 15 deletions admin/src/components/ItemFooter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,16 @@ import { faLink, faGlobe, faSitemap } from "@fortawesome/free-solid-svg-icons";
import CardItemRelation from "./CardItemRelation";
import CardItemType from "./CardItemType";
import Wrapper from "./Wrapper";
import { isNil, upperFirst } from "lodash";
import { isNil, get, upperFirst } from "lodash";
import { navigationItemType } from "../../containers/View/utils/enums";
import { extractRelatedItemLabel } from "../../containers/View/utils/parsers";

const ENTITY_NAME_PARAMS = [
"title",
"Title",
"subject",
"Subject",
"name",
"Name",
];
const resolveEntityName = (entity) =>
ENTITY_NAME_PARAMS.map((_) => entity[_]).filter((_) => _)[0] || "";

const ItemFooter = ({ type, removed, relatedRef, attachButtons }) => {
const ItemFooter = ({ type, removed, relatedRef, attachButtons, contentTypesNameFields }) => {
const formatRelationType = () =>
!isNil(relatedRef) ? relatedRef.__contentType : "";
!isNil(relatedRef) ? get(relatedRef, 'labelSingular', get(relatedRef, '__contentType')) : "";

const formatRelationName = () =>
!isNil(relatedRef) ? resolveEntityName(relatedRef) : "";
!isNil(relatedRef) ? extractRelatedItemLabel(relatedRef, contentTypesNameFields) : "";

return (
<Wrapper removed={removed} attachButtons={attachButtons}>
Expand All @@ -46,6 +36,7 @@ const ItemFooter = ({ type, removed, relatedRef, attachButtons }) => {

ItemFooter.propTypes = {
type: PropTypes.string.isRequired,
contentTypesNameFields: PropTypes.object.isRequired,
menuAttached: PropTypes.bool,
removed: PropTypes.bool,
relatedRef: PropTypes.object,
Expand Down
3 changes: 3 additions & 0 deletions admin/src/components/List/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const List = ({
level = 0,
levelPath = '',
allowedLevels,
contentTypesNameFields,
}) => {
const Component = as || Container;
return (
Expand All @@ -34,6 +35,7 @@ const List = ({
isFirst={n === 0}
isLast={n === items.length - 1}
allowedLevels={allowedLevels}
contentTypesNameFields={contentTypesNameFields}
onItemClick={onItemClick}
onItemReOrder={onItemReOrder}
onItemRestoreClick={onItemRestoreClick}
Expand Down Expand Up @@ -61,6 +63,7 @@ List.propTypes = {
items: PropTypes.array,
level: PropTypes.number,
allowedLevels: PropTypes.number,
contentTypesNameFields: PropTypes.object.isRequired,
onItemClick: PropTypes.func.isRequired,
onItemReOrder: PropTypes.func.isRequired,
onItemRestoreClick: PropTypes.func.isRequired,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import { Button, Enumeration, Flex, Label, Text, Toggle } from "@buffetjs/core";
import { useIntl } from "react-intl";
import { find, get, isEmpty, isNil, isString } from "lodash";
import { find, get, isEmpty, isNil, isNumber, isString } from "lodash";
import PropTypes from "prop-types";
import { ButtonModal, ModalBody, ModalForm } from "strapi-helper-plugin";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
Expand All @@ -25,6 +25,7 @@ const NavigationItemForm = ({
usedContentTypeEntities = [],
availableAudience = [],
additionalFields = [],
contentTypesNameFields = {},
onSubmit,
getContentTypeEntities,
}) => {
Expand Down Expand Up @@ -115,7 +116,10 @@ const NavigationItemForm = ({
))
.map((item) => ({
value: item.id,
label: extractRelatedItemLabel(item),
label: extractRelatedItemLabel({
...item,
__collectionName: get(relatedTypeSelectValue, 'value', relatedTypeSelectValue),
}, contentTypesNameFields),
}));

const isExternal = form.type === navigationItemType.EXTERNAL;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@ const NavigationItemPopUp = ({
};

const { related, relatedType } = data;
const { availableAudience = [], additionalFields, contentTypes, contentTypeItems } = config;
const { availableAudience = [], additionalFields, contentTypes, contentTypeItems, contentTypesNameFields = {} } = config;

const prepareFormData = data => ({
...data,
related: related ? {
value: related,
label: extractRelatedItemLabel(find(contentTypeItems, item => item.id === related, {}))
label: extractRelatedItemLabel({
...find(contentTypeItems, item => item.id === related, {}),
__collectionName: relatedType,
}, contentTypesNameFields),
} : undefined,
relatedType: relatedType ? {
value: relatedType,
Expand All @@ -56,6 +59,7 @@ const NavigationItemPopUp = ({
data={prepareFormData(data)}
isLoading={isLoading}
additionalFields={additionalFields}
contentTypesNameFields={contentTypesNameFields}
availableAudience={availableAudience}
contentTypes={contentTypes}
contentTypeEntities={contentTypeItems}
Expand Down
1 change: 1 addition & 0 deletions admin/src/containers/View/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ const View = () => {
onItemLevelAddClick={addNewNavigationItem}
root
allowedLevels={config.allowedLevels}
contentTypesNameFields={config.contentTypesNameFields}
/>
)}
</>
Expand Down
18 changes: 13 additions & 5 deletions admin/src/containers/View/utils/parsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,22 +101,26 @@ const linkRelations = (item, config) => {
const shouldBuildRelated = !relatedRef || (relatedRef && (relatedRef.id !== relatedId));
if (shouldBuildRelated && !shouldFindRelated) {
const { __contentType } = relatedItem;
const __collectionName = get(find(contentTypes, ct => ct.name.toLowerCase() === __contentType.toLowerCase()), 'collectionName');
const relatedContentType = find(contentTypes, ct => ct.contentTypeName.toLowerCase() === __contentType.toLowerCase(), {});
const {collectionName, labelSingular } = relatedContentType;
relation = {
related: relatedItem.id,
relatedRef: {
__collectionName,
__collectionName: collectionName,
labelSingular,
...relatedItem
},
relatedType: __collectionName
relatedType: collectionName
};
} else if (shouldFindRelated) {
const relatedRef = find(contentTypeItems, cti => cti.id === relatedId);
const relatedContentType = find(contentTypes, ct => ct.collectionName.toLowerCase() === relatedType.toLowerCase());
const { contentTypeName, labelSingular } = relatedContentType;
relation = {
relatedRef: {
__collectionName: relatedType,
__contentType: upperFirst(get(relatedContentType, 'name')),
__contentType: contentTypeName,
labelSingular,
...relatedRef,
},
};
Expand Down Expand Up @@ -223,4 +227,8 @@ export const prepareItemToViewPayload = (items = [], viewParentId = null, config
};
});

export const extractRelatedItemLabel = (item = {}) => item.name || item.title || item.label || item.id;
export const extractRelatedItemLabel = (item = {}, fields = {}) => {
const { __collectionName } = item;
const { default: defaultFields = [] } = fields;
return get(fields, `${__collectionName}`, defaultFields).map((_) => item[_]).filter((_) => _)[0] || "";
};
7 changes: 7 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
module.exports = {
name: 'Unit test',
testMatch: ['**/__tests__/?(*.)+(spec|test).js'],
testPathIgnorePatterns: [
"/node_modules/",
".tmp",
".cache",
"/__mocks__/helpers/"
],
testEnvironment: "node",
transform: {},
coverageDirectory: "./coverage/",
collectCoverage: true,
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "strapi-plugin-navigation",
"version": "1.0.0-beta.11",
"version": "1.0.0-beta.12",
"description": "Strapi - Navigation plugin",
"strapi": {
"name": "Navigation",
Expand Down Expand Up @@ -30,7 +30,8 @@
"strapi-helper-plugin": "3.1.3",
"strapi-utils": "3.1.3",
"uuid": "^8.3.0",
"slugify": "^1.4.5"
"slugify": "^1.4.5",
"pluralize": "^8.0.0"
},
"devDependencies": {
"koa": "^2.8.0",
Expand Down
34 changes: 32 additions & 2 deletions services/__tests__/navigation.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,34 @@

describe('Dummy test', () => {
test('Dummy', () => { });
const { setupStrapi } = require("../../__mocks__/helpers/strapi");

beforeAll(setupStrapi);

describe('Navigation service', () => {
it('Strapi is defined', () => {
expect(strapi).toBeDefined();
expect(strapi.contentTypes).toBeDefined();
expect(Object.keys(strapi.contentTypes).length).toBe(2);
});
it('Config Content Types', () => {
const { configContentTypes } = require("../navigation");
const result = [{
collectionName: "pages",
contentTypeName: "Page",
endpoint: "pages",
label: "Pages",
labelSingular: "Page",
name: "page",
visible: true,
}, {
collectionName: "blog_posts",
contentTypeName: "BlogPost",
endpoint: "blog-posts",
label: "Blog posts",
labelSingular: "Blog post",
name: "blog-post",
visible: true,
}];
expect(configContentTypes()[0]).toMatchObject(result[0]);
expect(configContentTypes()[1]).toMatchObject(result[1]);
});
});
Loading

0 comments on commit 393a989

Please sign in to comment.