Merge branch 'master' into patch-1
19
README.md
@ -1,4 +1,15 @@
|
||||
<p align="center"><img src="https://blog.strapi.io/content/images/2017/10/logo.png" width="318px" /></p>
|
||||
<p align="center">
|
||||
<strong>We're hiring!</strong> Located in Paris 🇫🇷 and dreaming of being full-time on Strapi?
|
||||
<a href="https://strapi.io/company#looking-for-talents">Join us</a>!
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<a href="https://strapi.io">
|
||||
<img src="https://blog.strapi.io/content/images/2017/10/logo.png" width="318px" alt="Strapi logo" />
|
||||
</a>
|
||||
</p>
|
||||
<h3 align="center">API creation made simple, secure and fast.</h3>
|
||||
<p align="center">The most advanced open-source Content Management Framework to build powerful API with no effort.</p>
|
||||
<br />
|
||||
@ -19,7 +30,11 @@
|
||||
|
||||
<br>
|
||||
|
||||
<p align="center"><img src="https://blog.strapi.io/content/images/2017/10/Github-Preview.png" /></p>
|
||||
<p align="center">
|
||||
<a href="https://strapi.io">
|
||||
<img src="https://blog.strapi.io/content/images/2017/10/Github-Preview.png" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br>
|
||||
|
||||
|
@ -62,6 +62,8 @@ The panel will be available through [http://localhost:1337/dashboard](http://loc
|
||||
|
||||
### Development mode
|
||||
|
||||
Note that to modify the administration panel, your project needs to be created with using the `dev` flag, an example of such would be: `strapi new strapi --dev`.
|
||||
|
||||
**#1 — Install its own dependencies**
|
||||
|
||||
Run `npm install` from the `./admin` folder.
|
||||
@ -99,14 +101,16 @@ Note: make sure the size of your image is the same as the existing one (434px x
|
||||
|
||||
## Build
|
||||
|
||||
To build the administration, run the following command from the `./admin` folder:
|
||||
To build the administration, run the following command from the root directory of your project.
|
||||
|
||||
```
|
||||
npm run build
|
||||
npm run setup
|
||||
```
|
||||
|
||||
This will replace the folder's content located at `./admin/admin/build`. Visit http://localhost:1337/admin/ to make sure your updates have been taken in account.
|
||||
|
||||
After you have built the admininistration you can now create a new project to develop your API with the changes implemented. **Note:** You should now create a project without `--dev`
|
||||
|
||||
***
|
||||
|
||||
## Deployment
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Hooks
|
||||
|
||||
The hooks are modules that add functionality to the core. They are loaded during the server boot. For example, if your project needs to work with a SQL database, your will have to add the hook `strapi-bookshelf` to be able to connect your app with your database.
|
||||
The hooks are modules that add functionality to the core. They are loaded during the server boot. For example, if your project needs to work with a SQL database, your will have to add the hook `strapi-hook-bookshelf` to be able to connect your app with your database.
|
||||
|
||||
**Path —** `./hooks/documentation/lib/index.js`.
|
||||
```js
|
||||
@ -76,13 +76,13 @@ The `index.js` is the entry point to your hook. It should look like the example
|
||||
|
||||
## Dependencies
|
||||
|
||||
It happens that a hook has a dependency to another one. For example, the `strapi-bookshelf` has a dependency to `strapi-knex`. Without it, the `strapi-bookshelf` can't work correctly. It also means that the `strapi-knex` hook has to be loaded before.
|
||||
It happens that a hook has a dependency to another one. For example, the `strapi-hook-bookshelf` has a dependency to `strapi-hook-knex`. Without it, the `strapi-hook-bookshelf` can't work correctly. It also means that the `strapi-hook-knex` hook has to be loaded before.
|
||||
|
||||
To handle this case, you need to update the `package.json` at the root of your hook.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "strapi-bookshelf",
|
||||
"name": "strapi-hook-bookshelf",
|
||||
"version": "x.x.x",
|
||||
"description": "Bookshelf hook for the Strapi framework",
|
||||
"dependencies": {
|
||||
@ -90,10 +90,10 @@ To handle this case, you need to update the `package.json` at the root of your h
|
||||
},
|
||||
"strapi": {
|
||||
"dependencies": [
|
||||
"strapi-knex"
|
||||
"strapi-hook-knex"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Custom hooks
|
||||
@ -107,7 +107,9 @@ The framework allows to load hooks from the project directly without having to i
|
||||
└─── config
|
||||
└─── hooks
|
||||
│ └─── strapi-documentation
|
||||
│ - index.js
|
||||
│ └─── strapi-server-side-rendering
|
||||
│ - index.js
|
||||
└─── plugins
|
||||
└─── public
|
||||
- favicon.ico
|
||||
|
@ -65,8 +65,12 @@ The core of Strapi embraces a small list of middlewares for performances, securi
|
||||
A middleware needs to follow the structure below:
|
||||
|
||||
```
|
||||
/lib
|
||||
- index.js
|
||||
/middleware
|
||||
└─── lib
|
||||
- index.js
|
||||
- LICENSE.md
|
||||
- package.json
|
||||
- README.md
|
||||
```
|
||||
|
||||
The `index.js` is the entry point to your middleware. It should look like the example above.
|
||||
@ -82,7 +86,9 @@ The framework allows the application to override the default middlewares and add
|
||||
└─── config
|
||||
└─── middlewares
|
||||
│ └─── responseTime // It will override the core default responseTime middleware
|
||||
│ - index.js
|
||||
│ └─── views // It will be added into the stack of middleware
|
||||
│ - index.js
|
||||
└─── plugins
|
||||
└─── public
|
||||
- favicon.ico
|
||||
|
@ -13,7 +13,7 @@ Here is the list of the collected data and why we need them.
|
||||
*Understand how the developers are using the different configurations? How many projects are started in production mode?*
|
||||
- **Node modules names**
|
||||
*Are developers integrating Strapi with Stripe? It means that we should develop a plugin to simplify the development process with Stripe.
|
||||
Are developers using Strapi with strapi-bookshelf or strapi-mongoose? It helps us prioritize the issues.*
|
||||
Are developers using Strapi with strapi-hook-bookshelf or strapi-hook-mongoose? It helps us prioritize the issues.*
|
||||
- **OS**
|
||||
*Is the community using Windows, Linux or Mac? It helps us prioritize the issues.*
|
||||
- **Build configurations**
|
||||
|
@ -11,7 +11,7 @@ Create a new project
|
||||
```bash
|
||||
strapi new <name>
|
||||
|
||||
options: [--dev|--dbclient=<dbclient> --dbhost=<dbhost> --dbport=<dbport> --dbname=<dbname> --dbusername=<dbusername> --dbpassword=<dbpassword>]
|
||||
options: [--dev|--dbclient=<dbclient> --dbhost=<dbhost> --dbport=<dbport> --dbname=<dbname> --dbusername=<dbusername> --dbpassword=<dbpassword> --dbssl=<dbssl> --dbauth=<dbauth>]
|
||||
```
|
||||
|
||||
- **strapi new <name>**<br/>
|
||||
@ -20,8 +20,8 @@ options: [--dev|--dbclient=<dbclient> --dbhost=<dbhost> --dbport=<dbport> --dbna
|
||||
- **strapi new <name> --dev**<br/>
|
||||
Generates a new project called **<name>** and creates symlinks for the `./admin` folder and each plugin inside the `./plugin` folder. It means that the Strapi's development workflow has been set up on the machine earlier.
|
||||
|
||||
- **strapi new <name> --dbclient=<dbclient> --dbhost=<dbhost> --dbport=<dbport> --dbname=<dbname> --dbusername=<dbusername> --dbpassword=<dbpassword>**<br/>
|
||||
Generates a new project called **<name>** and skip the interactive database configuration and initilize with these options. **<dbclient>** can be `mongo`, `postgres`, `mysql`, `sqlite3` or `redis`. **<dbusername>** and **<dbpassword>** are optional.
|
||||
- **strapi new <name> --dbclient=<dbclient> --dbhost=<dbhost> --dbport=<dbport> --dbname=<dbname> --dbusername=<dbusername> --dbpassword=<dbpassword> --dbssl=<dbssl> --dbauth=<dbauth>**<br/>
|
||||
Generates a new project called **<name>** and skip the interactive database configuration and initilize with these options. **<dbclient>** can be `mongo`, `postgres`, `mysql`, `sqlite3` or `redis`. **<dbssl>** and **<dbauth>** are optional.
|
||||
|
||||
|
||||
See the [CONTRIBUTING guide](https://github.com/strapi/strapi/blob/master/CONTRIBUTING.md) for more details.
|
||||
|
@ -164,7 +164,7 @@ Most of the application's configurations are defined by environment. It means th
|
||||
- `defaultConnection` (string): Connection by default for models which are not related to a specific `connection`. Default value: `default`.
|
||||
- `connections` List of all available connections.
|
||||
- `default`
|
||||
- `connector` (string): Connector used by the current connection. Default value: `strapi-mongoose`.
|
||||
- `connector` (string): Connector used by the current connection. Default value: `strapi-hook-mongoose`.
|
||||
- `client` (string): Client used to store session. Default value: `cookie`.
|
||||
- `key` (string): Cookie key name. Default value: `strapi.sid`
|
||||
- `maxAge` (integer): Time in milliseconds before the session expire. Default value: `86400000`.
|
||||
@ -192,7 +192,7 @@ Most of the application's configurations are defined by environment. It means th
|
||||
"defaultConnection": "default",
|
||||
"connections": {
|
||||
"default": {
|
||||
"connector": "strapi-mongoose",
|
||||
"connector": "strapi-hook-mongoose",
|
||||
"settings": {
|
||||
"client": "mongo",
|
||||
"host": "localhost",
|
||||
@ -208,7 +208,7 @@ Most of the application's configurations are defined by environment. It means th
|
||||
}
|
||||
},
|
||||
"postgres": {
|
||||
"connector": "strapi-bookshelf",
|
||||
"connector": "strapi-hook-bookshelf",
|
||||
"settings": {
|
||||
"client": "postgres",
|
||||
"host": "localhost",
|
||||
@ -223,7 +223,7 @@ Most of the application's configurations are defined by environment. It means th
|
||||
}
|
||||
},
|
||||
"mysql": {
|
||||
"connector": "strapi-bookshelf",
|
||||
"connector": "strapi-hook-bookshelf",
|
||||
"settings": {
|
||||
"client": "mysql",
|
||||
"host": "localhost",
|
||||
@ -367,7 +367,7 @@ In any JSON configurations files in your project, you can inject dynamic values
|
||||
"defaultConnection": "default",
|
||||
"connections": {
|
||||
"default": {
|
||||
"connector": "strapi-mongoose",
|
||||
"connector": "strapi-hook-mongoose",
|
||||
"settings": {
|
||||
"client": "mongo",
|
||||
"uri": "${process.env.DATABASE_URI || ''}",
|
||||
|
@ -27,8 +27,8 @@ To install a new provider run:
|
||||
$ npm install strapi-email-sendgrid@alpha --save
|
||||
```
|
||||
|
||||
We have two providers available `strapi-email-sendgrid` and `strapi-upload-mailgun`, use the alpha tag to install one of them. Then, visit `/admin/plugins/email/configurations/development` and configure the provider.
|
||||
We have two providers available `strapi-email-sendgrid` and `strapi-upload-mailgun`, use the alpha tag to install one of them. Then, visit `/admin/plugins/email/configurations/development` on your web browser and configure the provider.
|
||||
|
||||
If you want to create your own, make sure the name starts with `strapi-email-` (duplicating an existing one will be easier to create), modify the `auth` config object and customize the `send` functions.
|
||||
|
||||
Check all community providers available on npmjs.org - [Providers list](https://www.npmjs.com/search?q=strapi-email-)
|
||||
Check all community providers available on npmjs.org - [Providers list](https://www.npmjs.com/search?q=strapi-email-)
|
||||
|
@ -133,7 +133,7 @@ To install a new provider run:
|
||||
$ npm install strapi-upload-aws-s3@alpha --save
|
||||
```
|
||||
|
||||
We have two providers available `strapi-upload-aws-s3` and `strapi-upload-cloudinary`, use the alpha tag to install one of them. Then, visit `/admin/plugins/upload/configurations/development` and configure the provider.
|
||||
We have two providers available `strapi-upload-aws-s3` and `strapi-upload-cloudinary`, use the alpha tag to install one of them. Then, visit `/admin/plugins/upload/configurations/development` on your web browser and configure the provider.
|
||||
|
||||
If you want to create your own, make sure the name starts with `strapi-upload-` (duplicating an existing one will be easier to create), modify the `auth` config object and customize the `upload` and `delete` functions.
|
||||
|
||||
|
@ -69,13 +69,17 @@ function LeftMenuLinkContainer({ layout, plugins }) {
|
||||
// Check if the plugins list is empty or not and display plugins by name
|
||||
const pluginsLinks = !isEmpty(pluginsObject) ? (
|
||||
map(sortBy(pluginsObject, 'name'), plugin => {
|
||||
if (plugin.id !== 'email' && plugin.id !== 'content-manager' && plugin.id !== 'settings-manager') {
|
||||
if (plugin.id !== 'email' && plugin.id !== 'settings-manager') {
|
||||
const basePath = `/plugins/${get(plugin, 'id')}`;
|
||||
// NOTE: this should be dynamic
|
||||
const destination = plugin.id === 'content-manager' ? `${basePath}/ctm-configurations` : basePath;
|
||||
|
||||
return (
|
||||
<LeftMenuLink
|
||||
key={get(plugin, 'id')}
|
||||
icon={get(plugin, 'icon') || 'plug'}
|
||||
label={get(plugin, 'name')}
|
||||
destination={`/plugins/${get(plugin, 'id')}`}
|
||||
destination={destination}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import Row from 'components/Row';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
class ListPlugins extends React.Component {
|
||||
class ListPlugins extends React.PureComponent {
|
||||
render() {
|
||||
const listSize = size(this.props.plugins);
|
||||
let titleType = listSize === 1 ? 'singular' : 'plural';
|
||||
|
@ -8,7 +8,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { includes, isEmpty } from 'lodash';
|
||||
|
||||
// Design
|
||||
import IcoContainer from 'components/IcoContainer';
|
||||
@ -17,6 +17,8 @@ import PopUpWarning from 'components/PopUpWarning';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
const PLUGINS_WITH_CONFIG = ['content-manager', 'email', 'upload'];
|
||||
|
||||
class Row extends React.Component {
|
||||
state = { showModal: false };
|
||||
|
||||
@ -33,8 +35,10 @@ class Row extends React.Component {
|
||||
|
||||
render() {
|
||||
// const uploadPath = `/plugins/upload/configurations/${this.context.currentEnvironment}`;
|
||||
const settingsPath = `/plugins/${this.props.name}/configurations/${this.context.currentEnvironment}`;
|
||||
const icons = this.props.name === 'upload' || this.props.name === 'email' ? [
|
||||
// Make sure to match the ctm config URI instead of content-type view URI
|
||||
const settingsPath = this.props.name === 'content-manager' ? '/plugins/content-manager/ctm-configurations' : `/plugins/${this.props.name}/configurations/${this.context.currentEnvironment}`;
|
||||
// const icons = this.props.name === 'upload' || this.props.name === 'email' ? [
|
||||
const icons = includes(PLUGINS_WITH_CONFIG, this.props.name) ? [
|
||||
{
|
||||
icoType: 'cog',
|
||||
onClick: (e) => {
|
||||
|
@ -11,15 +11,15 @@ import { GET_GA_STATUS, GET_LAYOUT } from './constants';
|
||||
|
||||
function* getGaStatus() {
|
||||
try {
|
||||
const [allowGa, strapiVersion, currentEnvironment] = yield [
|
||||
const [{ allowGa }, { strapiVersion }, { currentEnvironment }] = yield [
|
||||
call(request, '/admin/gaConfig', { method: 'GET' }),
|
||||
call(request, '/admin/strapiVersion', { method: 'GET' }),
|
||||
call(request, '/admin/currentEnvironment', { method: 'GET' }),
|
||||
];
|
||||
|
||||
yield put(getCurrEnvSucceeded(currentEnvironment.currentEnvironment));
|
||||
yield put(getCurrEnvSucceeded(currentEnvironment));
|
||||
yield put(getGaStatusSucceeded(allowGa));
|
||||
yield put(getStrapiVersionSucceeded(strapiVersion.strapiVersion));
|
||||
yield put(getStrapiVersionSucceeded(strapiVersion));
|
||||
} catch(err) {
|
||||
strapi.notification.error('notification.error');
|
||||
}
|
||||
|
@ -14,16 +14,14 @@ import LeftMenuFooter from 'components/LeftMenuFooter';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
export class LeftMenu extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.leftMenu}>
|
||||
<LeftMenuHeader />
|
||||
<LeftMenuLinkContainer {...this.props} />
|
||||
<LeftMenuFooter plugins={this.props.plugins} version={this.props.version} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function LeftMenu(props) {
|
||||
return (
|
||||
<div className={styles.leftMenu}>
|
||||
<LeftMenuHeader />
|
||||
<LeftMenuLinkContainer {...props} />
|
||||
<LeftMenuFooter plugins={props.plugins} version={props.version} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
LeftMenu.defaultProps = {
|
||||
|
5
packages/strapi-generate-api/jest.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
name: 'generate-api',
|
||||
displayName: 'Generated API',
|
||||
testMatch: ['**/test/?(*.)+(spec|test).js']
|
||||
};
|
@ -10,7 +10,7 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
// Strapi utilities.
|
||||
const utils = require('strapi-bookshelf/lib/utils/');
|
||||
const utils = require('strapi-hook-bookshelf/lib/utils/');
|
||||
|
||||
module.exports = {
|
||||
|
||||
|
@ -83,7 +83,7 @@ module.exports = {
|
||||
const entry = await <%= globalID %>.create(data);
|
||||
|
||||
// Create relational data and return the entry.
|
||||
return <%= globalID %>.updateRelations({ id: entry.id, values: relations });
|
||||
return <%= globalID %>.updateRelations({ _id: entry.id, values: relations });
|
||||
},
|
||||
|
||||
/**
|
||||
|
843
packages/strapi-generate-api/test/endpoint.test.js
Normal file
@ -0,0 +1,843 @@
|
||||
// Helpers.
|
||||
const {login} = require('../../../test/helpers/auth');
|
||||
const form = require('../../../test/helpers/generators');
|
||||
const restart = require('../../../test/helpers/restart');
|
||||
const rq = require('../../../test/helpers/request');
|
||||
|
||||
const cleanDate = (entry) => {
|
||||
delete entry.updatedAt;
|
||||
delete entry.createdAt;
|
||||
delete entry.created_at;
|
||||
delete entry.updated_at;
|
||||
};
|
||||
|
||||
let data;
|
||||
|
||||
describe('App setup auth', () => {
|
||||
test(
|
||||
'Login admin user',
|
||||
async () => {
|
||||
await restart(rq);
|
||||
|
||||
const body = await login();
|
||||
|
||||
rq.defaults({
|
||||
headers: {
|
||||
'Authorization': `Bearer ${body.jwt}`
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('Generate test APIs', () => {
|
||||
beforeEach(async () => {
|
||||
await restart(rq);
|
||||
}, 60000);
|
||||
|
||||
test(
|
||||
'Create new article API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models`,
|
||||
method: 'POST',
|
||||
body: form.article,
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create new tag API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models`,
|
||||
method: 'POST',
|
||||
body: form.tag,
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create new category API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models`,
|
||||
method: 'POST',
|
||||
body: form.category,
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create new reference API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models`,
|
||||
method: 'POST',
|
||||
body: form.reference,
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('Test manyToMany relation (article - tag) with Content Manager', () => {
|
||||
beforeAll(() => {
|
||||
data = {
|
||||
articles: [],
|
||||
tags: []
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await restart(rq);
|
||||
}, 60000);
|
||||
|
||||
test(
|
||||
'Create tag1',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/tag`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
name: 'tag1'
|
||||
},
|
||||
json: true
|
||||
});
|
||||
|
||||
data.tags.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(Array.isArray(body.articles)).toBeTruthy();
|
||||
expect(body.name).toBe('tag1');
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create tag2',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/tag`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
name: 'tag2'
|
||||
},
|
||||
json: true
|
||||
});
|
||||
|
||||
data.tags.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(Array.isArray(body.articles)).toBeTruthy();
|
||||
expect(body.name).toBe('tag2');
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create tag3',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/tag`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
name: 'tag3'
|
||||
},
|
||||
json: true
|
||||
});
|
||||
|
||||
data.tags.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(Array.isArray(body.articles)).toBeTruthy();
|
||||
expect(body.name).toBe('tag3');
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create article1 without relation',
|
||||
async () => {
|
||||
const entry = {
|
||||
title: 'Article 1',
|
||||
content: 'My super content 1'
|
||||
};
|
||||
|
||||
let body = await rq({
|
||||
url: `/article`,
|
||||
method: 'POST',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.articles.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
expect(body.tags.length).toBe(0);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create article2 with tag1',
|
||||
async () => {
|
||||
const entry = {
|
||||
title: 'Article 2',
|
||||
content: 'Content 2',
|
||||
tags: [data.tags[0]]
|
||||
};
|
||||
|
||||
let body = await rq({
|
||||
url: `/article`,
|
||||
method: 'POST',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.articles.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
expect(body.tags.length).toBe(1);
|
||||
expect(body.tags[0].id).toBe(data.tags[0].id);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update article1 add tag2',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.articles[0], {
|
||||
tags: [data.tags[1]]
|
||||
});
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/article/${entry.id}`,
|
||||
method: 'PUT',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.articles[0] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
expect(body.tags.length).toBe(1);
|
||||
expect(body.tags[0].id).toBe(data.tags[1].id);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update article1 add tag1 and tag3',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.articles[0]);
|
||||
entry.tags.push(data.tags[0]);
|
||||
entry.tags.push(data.tags[2]);
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/article/${entry.id}`,
|
||||
method: 'PUT',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.articles[0] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
expect(body.tags.length).toBe(3);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update article1 remove one tag',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.articles[0]);
|
||||
entry.tags = entry.tags.slice(1);
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/article/${entry.id}`,
|
||||
method: 'PUT',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.articles[0] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
expect(body.tags.length).toBe(2);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update article1 remove all tag',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.articles[0], {
|
||||
tags: []
|
||||
});
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/article/${entry.id}`,
|
||||
method: 'PUT',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.articles[0] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
expect(body.tags.length).toBe(0);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('Test oneToMany - manyToOne relation (article - category) with Content Manager', () => {
|
||||
beforeAll(() => {
|
||||
data = {
|
||||
articles: [],
|
||||
categories: []
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await restart(rq);
|
||||
}, 60000);
|
||||
|
||||
test(
|
||||
'Create cat1',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/category`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
name: 'cat1'
|
||||
},
|
||||
json: true
|
||||
});
|
||||
|
||||
data.categories.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(Array.isArray(body.articles)).toBeTruthy();
|
||||
expect(body.name).toBe('cat1');
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create cat2',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/category`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
name: 'cat2'
|
||||
},
|
||||
json: true
|
||||
});
|
||||
|
||||
data.categories.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(Array.isArray(body.articles)).toBeTruthy();
|
||||
expect(body.name).toBe('cat2');
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create article1 with cat1',
|
||||
async () => {
|
||||
const entry = {
|
||||
title: 'Article 1',
|
||||
content: 'Content 1',
|
||||
category: data.categories[0]
|
||||
};
|
||||
|
||||
let body = await rq({
|
||||
url: `/article`,
|
||||
method: 'POST',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.articles.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(body.category.name).toBe(entry.category.name);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update article1 with cat2',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.articles[0], {
|
||||
category: data.categories[1]
|
||||
});
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/article/${entry.id}`,
|
||||
method: 'PUT',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.articles[0] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(body.category.name).toBe(entry.category.name);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create article2',
|
||||
async () => {
|
||||
const entry = {
|
||||
title: 'Article 2',
|
||||
content: 'Content 2'
|
||||
};
|
||||
|
||||
let body = await rq({
|
||||
url: `/article`,
|
||||
method: 'POST',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.articles.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update article2 with cat2',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.articles[1], {
|
||||
category: data.categories[1]
|
||||
});
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/article/${entry.id}`,
|
||||
method: 'PUT',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.articles[1] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(body.category.name).toBe(entry.category.name);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update cat1 with article1',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.categories[0]);
|
||||
entry.articles.push(data.articles[0]);
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/category/${entry.id}`,
|
||||
method: 'PUT',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.categories[0] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(Array.isArray(body.articles)).toBeTruthy();
|
||||
expect(body.articles.length).toBe(1);
|
||||
expect(body.name).toBe(entry.name);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create cat3 with article1',
|
||||
async () => {
|
||||
const entry = {
|
||||
name: 'cat3',
|
||||
articles: [data.articles[0]]
|
||||
};
|
||||
|
||||
let body = await rq({
|
||||
url: `/category`,
|
||||
method: 'POST',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.categories.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(Array.isArray(body.articles)).toBeTruthy();
|
||||
expect(body.articles.length).toBe(1);
|
||||
expect(body.name).toBe(entry.name);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Get article1 with cat3',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/article/${data.articles[0].id}`,
|
||||
method: 'GET',
|
||||
json: true
|
||||
});
|
||||
|
||||
expect(body.id);
|
||||
expect(body.category.id).toBe(data.categories[2].id)
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Get article2 with cat2',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/article/${data.articles[1].id}`,
|
||||
method: 'GET',
|
||||
json: true
|
||||
});
|
||||
|
||||
expect(body.id);
|
||||
expect(body.category.id).toBe(data.categories[1].id)
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Get cat1 without relations',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/category/${data.categories[0].id}`,
|
||||
method: 'GET',
|
||||
json: true
|
||||
});
|
||||
|
||||
expect(body.id);
|
||||
expect(body.articles.length).toBe(0);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Get cat2 with article2',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/category/${data.categories[1].id}`,
|
||||
method: 'GET',
|
||||
json: true
|
||||
});
|
||||
|
||||
expect(body.id);
|
||||
expect(body.articles.length).toBe(1);
|
||||
expect(body.articles[0].id).toBe(data.articles[1].id);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Get cat3 with article1',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/category/${data.categories[2].id}`,
|
||||
method: 'GET',
|
||||
json: true
|
||||
});
|
||||
|
||||
expect(body.id);
|
||||
expect(body.articles.length).toBe(1);
|
||||
expect(body.articles[0].id).toBe(data.articles[0].id);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('Test oneToOne relation (article - reference) with Content Manager', () => {
|
||||
beforeAll(() => {
|
||||
data = {
|
||||
articles: [],
|
||||
references: []
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await restart(rq);
|
||||
}, 60000);
|
||||
|
||||
test(
|
||||
'Create ref1',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/reference`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
name: 'ref1'
|
||||
},
|
||||
json: true
|
||||
});
|
||||
|
||||
data.references.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.name).toBe('ref1');
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create article1',
|
||||
async () => {
|
||||
const entry = {
|
||||
title: 'Article 1',
|
||||
content: 'Content 1'
|
||||
};
|
||||
|
||||
let body = await rq({
|
||||
url: `/article`,
|
||||
method: 'POST',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.articles.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update article1 with ref1',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.articles[0], {
|
||||
reference: data.references[0].id
|
||||
});
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/article/${entry.id}`,
|
||||
method: 'PUT',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.articles[0] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(body.reference.id).toBe(entry.reference);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create article2 with ref1',
|
||||
async () => {
|
||||
const entry = {
|
||||
title: 'Article 2',
|
||||
content: 'Content 2',
|
||||
reference: data.references[0].id
|
||||
};
|
||||
|
||||
let body = await rq({
|
||||
url: `/article`,
|
||||
method: 'POST',
|
||||
body: entry,
|
||||
json: true
|
||||
});
|
||||
|
||||
data.articles.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(body.reference.id).toBe(entry.reference);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Get article1 without relations',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/article/${data.articles[0].id}`,
|
||||
method: 'GET',
|
||||
json: true
|
||||
});
|
||||
|
||||
expect(body.id);
|
||||
expect(body.reference).toBe(null);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('Test oneWay relation (reference - tag) with Content Manager', () => {
|
||||
beforeEach(async () => {
|
||||
await restart(rq);
|
||||
}, 60000);
|
||||
|
||||
test(
|
||||
'Attach Tag to a Reference',
|
||||
async () => {
|
||||
const tagToCreate = await rq({
|
||||
url: `/tag`,
|
||||
method: 'POST',
|
||||
json: true,
|
||||
body: {
|
||||
name: 'tag111'
|
||||
}
|
||||
});
|
||||
|
||||
const referenceToCreate = await rq({
|
||||
url: `/reference`,
|
||||
method: 'POST',
|
||||
json: true,
|
||||
body: {
|
||||
name: 'cat111',
|
||||
tag: tagToCreate
|
||||
}
|
||||
});
|
||||
|
||||
expect(referenceToCreate.tag.id).toBe(tagToCreate.id);
|
||||
}
|
||||
);
|
||||
|
||||
test(
|
||||
'Detach Tag to a Reference',
|
||||
async () => {
|
||||
const tagToCreate = await rq({
|
||||
url: `/tag`,
|
||||
method: 'POST',
|
||||
json: true,
|
||||
body: {
|
||||
name: 'tag111'
|
||||
}
|
||||
});
|
||||
|
||||
const referenceToCreate = await rq({
|
||||
url: `/reference`,
|
||||
method: 'POST',
|
||||
json: true,
|
||||
body: {
|
||||
name: 'cat111',
|
||||
tag: tagToCreate
|
||||
}
|
||||
});
|
||||
|
||||
expect(referenceToCreate.tag.id).toBe(tagToCreate.id);
|
||||
|
||||
const referenceToUpdate = await rq({
|
||||
url: `/reference/${referenceToCreate.id}`,
|
||||
method: 'PUT',
|
||||
json: true,
|
||||
body: {
|
||||
tag: null
|
||||
}
|
||||
});
|
||||
|
||||
expect(referenceToUpdate.tag).toBe(null);
|
||||
}
|
||||
);
|
||||
|
||||
test(
|
||||
'Delete Tag so the relation in the Reference side should be removed',
|
||||
async () => {
|
||||
const tagToCreate = await rq({
|
||||
url: `/tag`,
|
||||
method: 'POST',
|
||||
json: true,
|
||||
body: {
|
||||
name: 'tag111'
|
||||
}
|
||||
});
|
||||
|
||||
const referenceToCreate = await rq({
|
||||
url: `/reference`,
|
||||
method: 'POST',
|
||||
json: true,
|
||||
body: {
|
||||
name: 'cat111',
|
||||
tag: tagToCreate
|
||||
}
|
||||
});
|
||||
|
||||
const tagToDelete = await rq({
|
||||
url: `/tag/${tagToCreate.id}`,
|
||||
method: 'DELETE',
|
||||
json: true
|
||||
});
|
||||
|
||||
const referenceToGet = await rq({
|
||||
url: `/reference/${referenceToCreate.id}`,
|
||||
method: 'GET',
|
||||
json: true
|
||||
});
|
||||
|
||||
try {
|
||||
if (Object.keys(referenceToGet.tag).length == 0) {
|
||||
referenceToGet.tag = null;
|
||||
}
|
||||
} catch(err) {
|
||||
// Silent
|
||||
}
|
||||
|
||||
expect(referenceToGet.tag).toBe(null);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('Delete test APIs', () => {
|
||||
beforeEach(async () => {
|
||||
await restart(rq);
|
||||
}, 60000);
|
||||
|
||||
test(
|
||||
'Delete article API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models/article`,
|
||||
method: 'DELETE',
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Delete tag API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models/tag`,
|
||||
method: 'DELETE',
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Delete category API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models/category`,
|
||||
method: 'DELETE',
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Delete reference API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models/reference`,
|
||||
method: 'DELETE',
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
@ -2,7 +2,7 @@
|
||||
"defaultConnection": "default",
|
||||
"connections": {
|
||||
"default": {
|
||||
"connector": "strapi-mongoose",
|
||||
"connector": "strapi-hook-mongoose",
|
||||
"settings": {
|
||||
"client": "mongo",
|
||||
"uri": "${process.env.DATABASE_URI || ''}",
|
||||
|
@ -2,7 +2,7 @@
|
||||
"defaultConnection": "default",
|
||||
"connections": {
|
||||
"default": {
|
||||
"connector": "strapi-mongoose",
|
||||
"connector": "strapi-hook-mongoose",
|
||||
"settings": {
|
||||
"client": "mongo",
|
||||
"uri": "${process.env.DATABASE_URI || ''}",
|
||||
|
@ -69,14 +69,14 @@ module.exports = (scope, cb) => {
|
||||
name: 'MongoDB (recommended)',
|
||||
value: {
|
||||
database: 'mongo',
|
||||
connector: 'strapi-mongoose'
|
||||
connector: 'strapi-hook-mongoose'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Postgres',
|
||||
value: {
|
||||
database: 'postgres',
|
||||
connector: 'strapi-bookshelf',
|
||||
connector: 'strapi-hook-bookshelf',
|
||||
module: 'pg'
|
||||
}
|
||||
},
|
||||
@ -84,7 +84,7 @@ module.exports = (scope, cb) => {
|
||||
name: 'MySQL',
|
||||
value: {
|
||||
database: 'mysql',
|
||||
connector: 'strapi-bookshelf',
|
||||
connector: 'strapi-hook-bookshelf',
|
||||
module: 'mysql'
|
||||
}
|
||||
}
|
||||
@ -192,7 +192,7 @@ module.exports = (scope, cb) => {
|
||||
])
|
||||
.then(answers => {
|
||||
if (hasDatabaseConfig) {
|
||||
answers = _.omit(scope.database.settings, ['client']);
|
||||
answers = _.merge((_.omit(scope.database.settings, ['client'])), scope.database.options);
|
||||
}
|
||||
|
||||
scope.database.settings.host = answers.host;
|
||||
@ -214,7 +214,7 @@ module.exports = (scope, cb) => {
|
||||
let packageCmd = packageManager.commands('install --prefix', scope.tmpPath);
|
||||
// Manually create the temp directory for yarn
|
||||
if (!isStrapiInstalledWithNPM) {
|
||||
shell.exec('mkdir tmp');
|
||||
shell.exec(`mkdir ${scope.tmpPath}`);
|
||||
}
|
||||
|
||||
let cmd = `${packageCmd} ${scope.client.connector}@alpha`;
|
||||
@ -223,18 +223,18 @@ module.exports = (scope, cb) => {
|
||||
cmd += ` ${scope.client.module}`;
|
||||
}
|
||||
|
||||
if (scope.client.connector === 'strapi-bookshelf') {
|
||||
cmd += ` strapi-knex@alpha`;
|
||||
if (scope.client.connector === 'strapi-hook-bookshelf') {
|
||||
cmd += ` strapi-hook-knex@alpha`;
|
||||
|
||||
scope.additionalsDependencies = ['strapi-knex', 'knex'];
|
||||
scope.additionalsDependencies = ['strapi-hook-knex', 'knex'];
|
||||
}
|
||||
|
||||
exec(cmd, () => {
|
||||
if (scope.client.module) {
|
||||
const lock = require(path.join(`${scope.tmpPath}`,`/node_modules/`,`${scope.client.module}/package.json`));
|
||||
const lock = require(path.join(`${scope.tmpPath}`, '/node_modules/', `${scope.client.module}/package.json`));
|
||||
scope.client.version = lock.version;
|
||||
|
||||
if (scope.developerMode === true && scope.client.connector === 'strapi-bookshelf') {
|
||||
if (scope.developerMode === true && scope.client.connector === 'strapi-hook-bookshelf') {
|
||||
const knexVersion = require(path.join(`${scope.tmpPath}`,`/node_modules/`,`knex/package.json`));
|
||||
scope.additionalsDependencies[1] = `knex@${knexVersion.version || 'latest'}`;
|
||||
}
|
||||
@ -248,7 +248,7 @@ module.exports = (scope, cb) => {
|
||||
Promise.all(asyncFn)
|
||||
.then(() => {
|
||||
try {
|
||||
require(path.join(`${scope.tmpPath}`,`/node_modules/`,`${scope.client.connector}/lib/utils/connectivity.js`))(scope, cb.success, connectionValidation);
|
||||
require(path.join(`${scope.tmpPath}`, '/node_modules/', `${scope.client.connector}/lib/utils/connectivity.js`))(scope, cb.success, connectionValidation);
|
||||
} catch(err) {
|
||||
shell.rm('-r', scope.tmpPath);
|
||||
cb.success();
|
||||
|
@ -1,13 +1,25 @@
|
||||
.gradientOff {
|
||||
background-image: linear-gradient( to bottom right, #F65A1D, #F68E0E );
|
||||
color: white !important;
|
||||
box-shadow: inset -1px 1px 3px rgba(0,0,0,0.1);
|
||||
z-index: 0!important;
|
||||
|
||||
&:active, :hover {
|
||||
box-shadow: inset -1px 1px 3px rgba(0,0,0,0.1);
|
||||
background-image: linear-gradient( to bottom right, #F65A1D, #F68E0E );
|
||||
color: white !important;
|
||||
z-index: 0!important;
|
||||
}
|
||||
}
|
||||
|
||||
.gradientOn {
|
||||
background-image: linear-gradient( to bottom right, #005EEA, #0097F6);
|
||||
color: white !important;
|
||||
box-shadow: inset 1px 1px 3px rgba(0,0,0,0.1);
|
||||
&:active, :hover {
|
||||
background-image: linear-gradient( to bottom right, #005EEA, #0097F6);
|
||||
color: white !important;
|
||||
z-index: 0!important;
|
||||
}
|
||||
}
|
||||
|
||||
.inputToggleContainer {
|
||||
|
@ -21,8 +21,6 @@ import InputPasswordWithErrors from 'components/InputPasswordWithErrors';
|
||||
import InputTextAreaWithErrors from 'components/InputTextAreaWithErrors';
|
||||
import InputTextWithErrors from 'components/InputTextWithErrors';
|
||||
import InputToggleWithErrors from 'components/InputToggleWithErrors';
|
||||
import WysiwygWithErrors from 'components/WysiwygWithErrors';
|
||||
import InputJSONWithErrors from 'components/InputJSONWithErrors';
|
||||
|
||||
const DefaultInputError = ({ type }) => <div>Your input type: <b>{type}</b> does not exist</div>;
|
||||
|
||||
@ -32,7 +30,6 @@ const inputs = {
|
||||
date: InputDateWithErrors,
|
||||
email: InputEmailWithErrors,
|
||||
file: InputFileWithErrors,
|
||||
json: InputJSONWithErrors,
|
||||
number: InputNumberWithErrors,
|
||||
password: InputPasswordWithErrors,
|
||||
search: InputSearchWithErrors,
|
||||
@ -41,7 +38,6 @@ const inputs = {
|
||||
text: InputTextWithErrors,
|
||||
textarea: InputTextAreaWithErrors,
|
||||
toggle: InputToggleWithErrors,
|
||||
wysiwyg: WysiwygWithErrors,
|
||||
};
|
||||
|
||||
function InputsIndex(props) {
|
||||
|
@ -48,6 +48,7 @@
|
||||
}
|
||||
|
||||
.modalPosition {
|
||||
max-width: 37.5rem !important;
|
||||
> div {
|
||||
width: 37.5rem;
|
||||
padding: 0 !important;
|
||||
|
@ -48,11 +48,9 @@
|
||||
"bootstrap": "^4.0.0-alpha.6",
|
||||
"chalk": "^2.1.0",
|
||||
"classnames": "^2.2.5",
|
||||
"codemirror": "^5.38.0",
|
||||
"copy-webpack-plugin": "^4.3.1",
|
||||
"cross-env": "^5.0.5",
|
||||
"css-loader": "^0.28.5",
|
||||
"draft-js": "^0.10.5",
|
||||
"eslint": "4.4.1",
|
||||
"eslint-config-airbnb": "15.1.0",
|
||||
"eslint-config-airbnb-base": "11.3.1",
|
||||
@ -108,7 +106,6 @@
|
||||
"rimraf": "^2.6.1",
|
||||
"sass-loader": "^6.0.6",
|
||||
"shelljs": "^0.7.8",
|
||||
"showdown": "^1.8.6",
|
||||
"style-loader": "^0.18.2",
|
||||
"styled-components": "^3.2.6",
|
||||
"url-loader": "^0.5.9",
|
||||
|
@ -1,12 +1,12 @@
|
||||
# strapi-bookshelf
|
||||
# strapi-hook-bookshelf
|
||||
|
||||
[](https://www.npmjs.org/package/strapi-bookshelf)
|
||||
[](https://www.npmjs.org/package/strapi-bookshelf)
|
||||
[](https://david-dm.org/strapi/strapi-bookshelf)
|
||||
[](https://travis-ci.org/strapi/strapi-bookshelf)
|
||||
[](https://www.npmjs.org/package/strapi-hook-bookshelf)
|
||||
[](https://www.npmjs.org/package/strapi-hook-bookshelf)
|
||||
[](https://david-dm.org/strapi/strapi-hook-bookshelf)
|
||||
[](https://travis-ci.org/strapi/strapi-hook-bookshelf)
|
||||
[](http://slack.strapi.io)
|
||||
|
||||
This built-in hook allows you to use the [Bookshelf ORM](http://bookshelfjs.org/) using the `strapi-knex` hook.
|
||||
This built-in hook allows you to use the [Bookshelf ORM](http://bookshelfjs.org/) using the `strapi-hook-knex` hook.
|
||||
|
||||
[Bookshelf ORM](http://bookshelfjs.org/) is a JavaScript ORM for Node.js, built on the [Knex node module](http://knexjs.org/) SQL query builder. Featuring both promise based and traditional callback interfaces, providing transaction support, eager/nested-eager relation loading, polymorphic associations, and support for one-to-one, one-to-many, and many-to-many relations.
|
||||
|
@ -45,7 +45,7 @@ module.exports = function(strapi) {
|
||||
*/
|
||||
|
||||
initialize: async cb => {
|
||||
const connections = _.pickBy(strapi.config.connections, { connector: 'strapi-bookshelf' });
|
||||
const connections = _.pickBy(strapi.config.connections, { connector: 'strapi-hook-bookshelf' });
|
||||
|
||||
const databaseUpdate = [];
|
||||
|
@ -10,15 +10,17 @@ const _ = require('lodash');
|
||||
// Utils
|
||||
const { models: { getValuePrimaryKey } } = require('strapi-utils');
|
||||
|
||||
const transformToArrayID = (array) => {
|
||||
const transformToArrayID = (array, association) => {
|
||||
if(_.isArray(array)) {
|
||||
return array.map(value => {
|
||||
array = array.map(value => {
|
||||
if (_.isPlainObject(value)) {
|
||||
return value._id || value.id;
|
||||
return value._id || value.id || false;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
|
||||
return array.filter(n => n);
|
||||
}
|
||||
|
||||
if (association.type === 'model' || (association.type === 'collection' && _.isObject(array))) {
|
||||
@ -140,8 +142,8 @@ module.exports = {
|
||||
case 'manyToMany':
|
||||
if (response[current] && _.isArray(response[current]) && current !== 'id') {
|
||||
// Compare array of ID to find deleted files.
|
||||
const currentValue = transformToArrayID(response[current]).map(id => id.toString());
|
||||
const storedValue = transformToArrayID(params.values[current]).map(id => id.toString());
|
||||
const currentValue = transformToArrayID(response[current], association).map(id => id.toString());
|
||||
const storedValue = transformToArrayID(params.values[current], association).map(id => id.toString());
|
||||
|
||||
const toAdd = _.difference(storedValue, currentValue);
|
||||
const toRemove = _.difference(currentValue, storedValue);
|
||||
@ -229,8 +231,8 @@ module.exports = {
|
||||
case 'oneToManyMorph':
|
||||
case 'manyToManyMorph': {
|
||||
// Compare array of ID to find deleted files.
|
||||
const currentValue = transformToArrayID(response[current]).map(id => id.toString());
|
||||
const storedValue = transformToArrayID(params.values[current]).map(id => id.toString());
|
||||
const currentValue = transformToArrayID(response[current], association).map(id => id.toString());
|
||||
const storedValue = transformToArrayID(params.values[current], association).map(id => id.toString());
|
||||
|
||||
const toAdd = _.difference(storedValue, currentValue);
|
||||
const toRemove = _.difference(currentValue, storedValue);
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "strapi-bookshelf",
|
||||
"name": "strapi-hook-bookshelf",
|
||||
"version": "3.0.0-alpha.12.7.1",
|
||||
"description": "Bookshelf hook for the Strapi framework",
|
||||
"homepage": "http://strapi.io",
|
||||
@ -20,13 +20,12 @@
|
||||
"inquirer": "^5.2.0",
|
||||
"lodash": "^4.17.4",
|
||||
"pluralize": "^6.0.0",
|
||||
"strapi-knex": "3.0.0-alpha.12.7.1",
|
||||
"strapi-hook-knex": "3.0.0-alpha.12.7.1",
|
||||
"strapi-utils": "3.0.0-alpha.12.7.1"
|
||||
},
|
||||
"strapi": {
|
||||
"isHook": true,
|
||||
"dependencies": [
|
||||
"strapi-knex"
|
||||
"knex"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "strapi-ejs",
|
||||
"name": "strapi-hook-ejs",
|
||||
"version": "3.0.0-alpha.12.7.1",
|
||||
"description": "EJS hook for the Strapi framework",
|
||||
"homepage": "http://strapi.io",
|
||||
@ -16,9 +16,6 @@
|
||||
"co": "^4.6.0",
|
||||
"koa-ejs": "^4.1.0"
|
||||
},
|
||||
"strapi": {
|
||||
"isHook": true
|
||||
},
|
||||
"scripts": {
|
||||
"prepublishOnly": "npm prune"
|
||||
},
|
@ -1,9 +1,9 @@
|
||||
# strapi-knex
|
||||
# strapi-hook-knex
|
||||
|
||||
[](https://www.npmjs.org/package/strapi-knex)
|
||||
[](https://www.npmjs.org/package/strapi-knex)
|
||||
[](https://david-dm.org/strapi/strapi-knex)
|
||||
[](https://travis-ci.org/strapi/strapi-knex)
|
||||
[](https://www.npmjs.org/package/strapi-hook-knex)
|
||||
[](https://www.npmjs.org/package/strapi-hook-knex)
|
||||
[](https://david-dm.org/strapi/strapi-hook-knex)
|
||||
[](https://travis-ci.org/strapi/strapi-hook-knex)
|
||||
[](http://slack.strapi.io)
|
||||
|
||||
This built-in hook allows you to directly make SQL queries from your Strapi connections to your databases thanks to the [Knex node module](http://knexjs.org/).
|
@ -46,7 +46,7 @@ module.exports = strapi => {
|
||||
|
||||
initialize: cb => {
|
||||
// For each connection in the config register a new Knex connection.
|
||||
_.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-bookshelf'}), (connection, name) => {
|
||||
_.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-hook-bookshelf'}), (connection, name) => {
|
||||
|
||||
// Make sure we use the client even if the typo is not the exact one.
|
||||
switch (connection.settings.client) {
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "strapi-knex",
|
||||
"name": "strapi-hook-knex",
|
||||
"version": "3.0.0-alpha.12.7.1",
|
||||
"description": "Knex hook for the Strapi framework",
|
||||
"homepage": "http://strapi.io",
|
||||
@ -19,9 +19,6 @@
|
||||
"knex": "^0.13.0",
|
||||
"lodash": "^4.17.4"
|
||||
},
|
||||
"strapi": {
|
||||
"isHook": true
|
||||
},
|
||||
"author": {
|
||||
"email": "hi@strapi.io",
|
||||
"name": "Strapi team",
|
@ -1,9 +1,9 @@
|
||||
# strapi-mongoose
|
||||
# strapi-hook-mongoose
|
||||
|
||||
[](https://www.npmjs.org/package/strapi-mongoose)
|
||||
[](https://www.npmjs.org/package/strapi-mongoose)
|
||||
[](https://david-dm.org/strapi/strapi-mongoose)
|
||||
[](https://travis-ci.org/strapi/strapi-bookshelf)
|
||||
[](https://www.npmjs.org/package/strapi-hook-mongoose)
|
||||
[](https://www.npmjs.org/package/strapi-hook-mongoose)
|
||||
[](https://david-dm.org/strapi/strapi-hook-mongoose)
|
||||
[](https://travis-ci.org/strapi/strapi-hook-mongoose)
|
||||
[](http://slack.strapi.io)
|
||||
|
||||
This built-in hook allows you to use the [Mongoose ORM](http://mongoosejs.com/).
|
@ -49,7 +49,7 @@ module.exports = function (strapi) {
|
||||
*/
|
||||
|
||||
initialize: cb => {
|
||||
_.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-mongoose'}), (connection, connectionName) => {
|
||||
_.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-hook-mongoose'}), (connection, connectionName) => {
|
||||
const instance = new Mongoose();
|
||||
const { uri, host, port, username, password, database } = _.defaults(connection.settings, strapi.config.hook.settings.mongoose);
|
||||
const uriOptions = uri ? url.parse(uri, true).query : {};
|
@ -174,7 +174,7 @@ module.exports = {
|
||||
strapi.models[_.toLower(obj.ref)].globalId;
|
||||
|
||||
// Define the object stored in database.
|
||||
// The shape is this object is defined by the strapi-mongoose connector.
|
||||
// The shape is this object is defined by the strapi-hook-mongoose connector.
|
||||
return {
|
||||
ref: obj.refId,
|
||||
kind: globalId,
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "strapi-mongoose",
|
||||
"name": "strapi-hook-mongoose",
|
||||
"version": "3.0.0-alpha.12.7.1",
|
||||
"description": "Mongoose hook for the Strapi framework",
|
||||
"homepage": "http://strapi.io",
|
||||
@ -21,9 +21,6 @@
|
||||
"pluralize": "^6.0.0",
|
||||
"strapi-utils": "3.0.0-alpha.12.7.1"
|
||||
},
|
||||
"strapi": {
|
||||
"isHook": true
|
||||
},
|
||||
"author": {
|
||||
"email": "hi@strapi.io",
|
||||
"name": "Strapi team",
|
@ -38,13 +38,13 @@ module.exports = function(strapi) {
|
||||
|
||||
initialize: cb => {
|
||||
if (_.isEmpty(strapi.models) || !_.pickBy(strapi.config.connections, {
|
||||
connector: 'strapi-redis'
|
||||
connector: 'strapi-hook-redis'
|
||||
})) {
|
||||
return cb();
|
||||
}
|
||||
|
||||
const connections = _.pickBy(strapi.config.connections, {
|
||||
connector: 'strapi-redis'
|
||||
connector: 'strapi-hook-redis'
|
||||
});
|
||||
|
||||
if(_.size(connections) === 0) {
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "strapi-redis",
|
||||
"name": "strapi-hook-redis",
|
||||
"version": "3.0.0-alpha.12.7.1",
|
||||
"description": "Redis hook for the Strapi framework",
|
||||
"homepage": "http://strapi.io",
|
||||
@ -20,9 +20,6 @@
|
||||
"stack-trace": "0.0.10",
|
||||
"strapi-utils": "3.0.0-alpha.12.7.1"
|
||||
},
|
||||
"strapi": {
|
||||
"isHook": true
|
||||
},
|
||||
"author": {
|
||||
"email": "hi@strapi.io",
|
||||
"name": "Strapi team",
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "strapi-middleware-views",
|
||||
"version": "3.0.0-alpha.12.7.1",
|
||||
"description": "Views hook to enable server-side rendering for the Strapi framework",
|
||||
"description": "Views middleware to enable server-side rendering for the Strapi framework",
|
||||
"homepage": "http://strapi.io",
|
||||
"keywords": [
|
||||
"redis",
|
||||
@ -19,9 +19,6 @@
|
||||
"koa-views": "^6.1.1",
|
||||
"lodash": "^4.17.4"
|
||||
},
|
||||
"strapi": {
|
||||
"isHook": true
|
||||
},
|
||||
"author": {
|
||||
"email": "hi@strapi.io",
|
||||
"name": "Strapi team",
|
||||
|
@ -0,0 +1 @@
|
||||
<svg width="19" height="10" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><text font-family="Lato-Semibold, Lato" font-size="11" font-weight="500" fill="#41464E" transform="translate(0 -2)"><tspan x="1" y="11">abc</tspan></text><path d="M.5 6.5h18" stroke="#2C3039" stroke-linecap="square"/></g></svg>
|
After Width: | Height: | Size: 325 B |
@ -0,0 +1 @@
|
||||
<svg width="9" height="10" xmlns="http://www.w3.org/2000/svg"><text transform="translate(-12 -10)" fill="#333740" fill-rule="evenodd" font-size="13" font-family="Baskerville-SemiBold, Baskerville" font-weight="500"><tspan x="12" y="20">B</tspan></text></svg>
|
After Width: | Height: | Size: 258 B |
@ -0,0 +1 @@
|
||||
<svg width="13" height="7" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#333740" d="M5 0h8v1H5zM5 2h8v1H5zM5 4h8v1H5zM5 6h8v1H5z"/><rect stroke="#333740" x=".5" y=".5" width="2" height="2" rx="1"/><rect stroke="#333740" x=".5" y="4.5" width="2" height="2" rx="1"/></g></svg>
|
After Width: | Height: | Size: 311 B |
@ -0,0 +1 @@
|
||||
<svg width="12" height="8" xmlns="http://www.w3.org/2000/svg"><g fill="#333740" fill-rule="evenodd"><path d="M3.653 7.385a.632.632 0 0 1-.452-.191L.214 4.154a.66.66 0 0 1 0-.922L3.201.19a.632.632 0 0 1 .905 0 .66.66 0 0 1 0 .921l-2.534 2.58 2.534 2.58a.66.66 0 0 1 0 .922.632.632 0 0 1-.453.19zM8.347 7.385a.632.632 0 0 0 .452-.191l2.987-3.04a.66.66 0 0 0 0-.922L8.799.19a.632.632 0 0 0-.905 0 .66.66 0 0 0 0 .921l2.534 2.58-2.534 2.58a.66.66 0 0 0 0 .922c.125.127.289.19.453.19z"/></g></svg>
|
After Width: | Height: | Size: 492 B |
@ -0,0 +1 @@
|
||||
<svg width="6" height="9" xmlns="http://www.w3.org/2000/svg"><text transform="translate(-13 -11)" fill="#333740" fill-rule="evenodd" font-weight="500" font-size="13" font-family="Baskerville-SemiBoldItalic, Baskerville" font-style="italic"><tspan x="13" y="20">I</tspan></text></svg>
|
After Width: | Height: | Size: 283 B |
@ -0,0 +1 @@
|
||||
<svg width="12" height="6" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M6.063 1.5H6h.063z" fill="#000"/><path d="M9.516 0H8s.813.531.988 1.5h.528c.55 0 .984.434.984.984v1c0 .55-.434 1.016-.984 1.016h-3.5A1.03 1.03 0 0 1 5 3.484V2.5H3.5v.984A2.518 2.518 0 0 0 6.016 6h3.5C10.896 6 12 4.866 12 3.484v-1A2.473 2.473 0 0 0 9.516 0z" fill="#333740"/><path d="M8.3 1.5A2.473 2.473 0 0 0 6.016 0h-3.5C1.134 0 0 1.103 0 2.484v1A2.526 2.526 0 0 0 2.516 6H4s-.806-.531-1.003-1.5h-.481A1.03 1.03 0 0 1 1.5 3.484v-1c0-.55.466-.984 1.016-.984h3.5c.55 0 .984.434.984.984V3.5h1.5V2.484c0-.35-.072-.684-.2-.984z" fill="#333740"/></g></svg>
|
After Width: | Height: | Size: 658 B |
@ -0,0 +1 @@
|
||||
<svg width="12" height="11" xmlns="http://www.w3.org/2000/svg"><g fill="#333740" fill-rule="evenodd"><path d="M9 4.286a1.286 1.286 0 1 0 0-2.572 1.286 1.286 0 0 0 0 2.572z"/><path d="M11.25 0H.75C.332 0 0 .34 0 .758v8.77c0 .418.332.758.75.758h10.5c.418 0 .75-.34.75-.758V.758A.752.752 0 0 0 11.25 0zM8.488 5.296a.46.46 0 0 0-.342-.167c-.137 0-.234.065-.343.153l-.501.423c-.105.075-.188.126-.308.126a.443.443 0 0 1-.295-.11 3.5 3.5 0 0 1-.115-.11L5.143 4.054a.59.59 0 0 0-.897.008L.857 8.148V1.171a.353.353 0 0 1 .351-.314h9.581a.34.34 0 0 1 .346.322l.008 6.975-2.655-2.858z"/></g></svg>
|
After Width: | Height: | Size: 586 B |
@ -0,0 +1 @@
|
||||
<svg width="12" height="8" xmlns="http://www.w3.org/2000/svg"><g fill="#333740" fill-rule="evenodd"><path d="M2.4 3H.594v-.214h.137c.123 0 .212-.01.266-.032.053-.022.086-.052.1-.092a.67.67 0 0 0 .018-.188V.74a.46.46 0 0 0-.03-.194C1.064.504 1.021.476.955.46A1.437 1.437 0 0 0 .643.435H.539V.23c.332-.035.565-.067.7-.096.135-.03.258-.075.37-.134h.275v2.507c0 .104.023.177.07.218.047.04.14.061.278.061H2.4V3zM2.736 6.695l-.132.528h-.246a.261.261 0 0 0 .015-.074c0-.058-.049-.087-.146-.087H.293v-.198c.258-.173.511-.367.76-.581.25-.215.457-.437.623-.667.166-.23.249-.447.249-.653a.49.49 0 0 0-.321-.478.794.794 0 0 0-.582-.006.482.482 0 0 0-.196.138.284.284 0 0 0-.07.182c0 .074.04.17.12.289.006.008.009.015.009.02 0 .012-.041.03-.123.053l-.19.057a.693.693 0 0 1-.115.03c-.031 0-.067-.038-.108-.114a.516.516 0 0 1 .071-.586.899.899 0 0 1 .405-.238c.18-.058.4-.087.657-.087.317 0 .566.044.749.132.183.087.306.187.37.3a.64.64 0 0 1 .094.312c0 .197-.089.389-.266.575a5.296 5.296 0 0 1-.916.74 62.947 62.947 0 0 1-.62.413h1.843zM4 0h8v1H4zM4 2h8v1H4zM4 4h8v1H4zM4 6h8v1H4z"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1 @@
|
||||
<svg width="9" height="9" xmlns="http://www.w3.org/2000/svg"><g fill="#333740" fill-rule="evenodd"><path d="M3 0C2.047 0 1.301.263.782.782.263 1.302 0 2.047 0 3v6h3.75V3H1.5c0-.54.115-.93.343-1.157C2.07 1.615 2.46 1.5 3 1.5M8.25 0c-.953 0-1.699.263-2.218.782-.519.52-.782 1.265-.782 2.218v6H9V3H6.75c0-.54.115-.93.343-1.157.227-.228.617-.343 1.157-.343"/></g></svg>
|
After Width: | Height: | Size: 365 B |
@ -0,0 +1 @@
|
||||
<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg"><text transform="translate(-10 -11)" fill="#101622" fill-rule="evenodd" font-size="13" font-family="Baskerville-SemiBold, Baskerville" font-weight="500"><tspan x="10" y="20">U</tspan></text></svg>
|
After Width: | Height: | Size: 259 B |
@ -1,18 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="8px" height="8px" viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Shape</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Pages" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Content-Manager---List-view" transform="translate(-568.000000, -172.000000)" fill="#007EFF">
|
||||
<g id="Container" transform="translate(234.000000, 0.000000)">
|
||||
<g id="Filter" transform="translate(29.000000, 160.000000)">
|
||||
<g id="Filter-#1" transform="translate(91.000000, 0.000000)">
|
||||
<path d="M221.779774,18.7197709 L219.060003,16 L221.779774,13.2802291 C222.073409,12.9865938 222.073409,12.5138618 221.779774,12.2202265 C221.486138,11.9265912 221.013406,11.9265912 220.719771,12.2202265 L218,14.9399974 L215.280229,12.2202265 C214.986594,11.9265912 214.513862,11.9265912 214.220226,12.2202265 C213.926591,12.5138618 213.926591,12.9865938 214.220226,13.2802291 L216.939997,16 L214.220226,18.7197709 C213.926591,19.0134062 213.926591,19.4861382 214.220226,19.7797735 C214.513862,20.0734088 214.986594,20.0734088 215.280229,19.7797735 L218,17.0600026 L220.719771,19.7797735 C221.013406,20.0734088 221.486138,20.0734088 221.779774,19.7797735 C222.071326,19.4861382 222.071326,19.0113237 221.779774,18.7197709 L221.779774,18.7197709 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg width="8" height="8" xmlns="http://www.w3.org/2000/svg"><path d="M7.78 6.72L5.06 4l2.72-2.72a.748.748 0 0 0 0-1.06.748.748 0 0 0-1.06 0L4 2.94 1.28.22a.748.748 0 0 0-1.06 0 .748.748 0 0 0 0 1.06L2.94 4 .22 6.72a.748.748 0 0 0 0 1.06.748.748 0 0 0 1.06 0L4 5.06l2.72 2.72a.748.748 0 0 0 1.06 0 .752.752 0 0 0 0-1.06z" fill="#007EFF" fill-rule="evenodd"/></svg>
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 364 B |
@ -1,14 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="8px" height="8px" viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Shape</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Search" transform="translate(-300.000000, -29.000000)" fill="#B3B5B9">
|
||||
<g id="Shape">
|
||||
<path d="M307.779774,35.7197709 L305.060003,33 L307.779774,30.2802291 C308.073409,29.9865938 308.073409,29.5138618 307.779774,29.2202265 C307.486138,28.9265912 307.013406,28.9265912 306.719771,29.2202265 L304,31.9399974 L301.280229,29.2202265 C300.986594,28.9265912 300.513862,28.9265912 300.220226,29.2202265 C299.926591,29.5138618 299.926591,29.9865938 300.220226,30.2802291 L302.939997,33 L300.220226,35.7197709 C299.926591,36.0134062 299.926591,36.4861382 300.220226,36.7797735 C300.513862,37.0734088 300.986594,37.0734088 301.280229,36.7797735 L304,34.0600026 L306.719771,36.7797735 C307.013406,37.0734088 307.486138,37.0734088 307.779774,36.7797735 C308.071326,36.4861382 308.071326,36.0113237 307.779774,35.7197709 L307.779774,35.7197709 Z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg width="8" height="8" xmlns="http://www.w3.org/2000/svg"><path d="M7.78 6.72L5.06 4l2.72-2.72a.748.748 0 0 0 0-1.06.748.748 0 0 0-1.06 0L4 2.94 1.28.22a.748.748 0 0 0-1.06 0 .748.748 0 0 0 0 1.06L2.94 4 .22 6.72a.748.748 0 0 0 0 1.06.748.748 0 0 0 1.06 0L4 5.06l2.72 2.72a.748.748 0 0 0 1.06 0 .752.752 0 0 0 0-1.06z" fill="#B3B5B9" fill-rule="evenodd"/></svg>
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 364 B |
@ -0,0 +1 @@
|
||||
<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg"><path d="M2.396 9.155l.6-.6-1.55-1.552-.601.601v.706h.845v.845h.706zM5.848 3.03c0-.097-.048-.146-.145-.146a.153.153 0 0 0-.112.047L2.013 6.508a.153.153 0 0 0-.046.112c0 .097.048.146.145.146a.153.153 0 0 0 .112-.047l3.578-3.577a.153.153 0 0 0 .046-.112zm-.356-1.268l2.746 2.746L2.746 10H0V7.254l5.492-5.492zM10 2.396a.809.809 0 0 1-.244.594L8.66 4.086 5.914 1.34 7.01.25A.784.784 0 0 1 7.604 0a.82.82 0 0 1 .6.25l1.552 1.545a.845.845 0 0 1 .244.601z" fill="#007EFF" fill-rule="nonzero"/></svg>
|
After Width: | Height: | Size: 555 B |
@ -1,13 +1,24 @@
|
||||
import { generateMenu } from 'containers/App/sagas';
|
||||
import { map, omit } from 'lodash';
|
||||
import request from 'utils/request';
|
||||
|
||||
// This method is executed before the load of the plugin
|
||||
const bootstrap = (plugin) => new Promise((resolve, reject) => {
|
||||
generateMenu()
|
||||
.then(menu => {
|
||||
request('/content-manager/models', { method: 'GET' })
|
||||
.then(models => {
|
||||
const menu = [{
|
||||
name: 'Content Types',
|
||||
links: map(omit(models.models.models, 'plugins'), (model, key) => ({
|
||||
label: model.labelPlural || model.label || key,
|
||||
destination: key,
|
||||
})),
|
||||
}];
|
||||
plugin.leftMenuSections = menu;
|
||||
resolve(plugin);
|
||||
})
|
||||
.catch(e => reject(e));
|
||||
.catch(e => {
|
||||
strapi.notification.error('content-manager.error.model.fetch');
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
|
||||
export default bootstrap;
|
||||
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
*
|
||||
* Block
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
const Block = ({ children, description, title }) => (
|
||||
<div className="col-md-12">
|
||||
<div className={styles.ctmBlock}>
|
||||
<div className={styles.ctmBlockTitle}>
|
||||
<FormattedMessage id={title} />
|
||||
<FormattedMessage id={description}>
|
||||
{msg => <p>{msg}</p>}
|
||||
</FormattedMessage>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
Block.defaultProps = {
|
||||
children: null,
|
||||
description: 'app.utils.defaultMessage',
|
||||
title: 'app.utils.defaultMessage',
|
||||
};
|
||||
|
||||
Block.propTypes = {
|
||||
children: PropTypes.any,
|
||||
description: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Block;
|
@ -0,0 +1,22 @@
|
||||
.ctmBlock{
|
||||
margin-bottom: 35px;
|
||||
background: #ffffff;
|
||||
padding: 22px 28px 18px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 4px #E3E9F3;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.ctmBlockTitle {
|
||||
padding-top: 0px;
|
||||
line-height: 18px;
|
||||
> span {
|
||||
font-weight: 600;
|
||||
color: #333740;
|
||||
font-size: 18px;
|
||||
}
|
||||
> p {
|
||||
color: #787E8F;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
/**
|
||||
*
|
||||
* DraggableAttr
|
||||
*/
|
||||
|
||||
/* eslint-disable react/no-find-dom-node */
|
||||
import React from 'react';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import {
|
||||
DragSource,
|
||||
DropTarget,
|
||||
} from 'react-dnd';
|
||||
import { flow, upperFirst } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import cn from 'classnames';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
const draggableAttrSource = {
|
||||
beginDrag: (props) => {
|
||||
props.updateSiblingHoverState();
|
||||
|
||||
return {
|
||||
id: props.id,
|
||||
index: props.index,
|
||||
};
|
||||
},
|
||||
endDrag: (props) => {
|
||||
props.updateSiblingHoverState();
|
||||
|
||||
return {};
|
||||
},
|
||||
};
|
||||
|
||||
const draggableAttrTarget = {
|
||||
hover: (props, monitor, component) => {
|
||||
const dragIndex = monitor.getItem().index;
|
||||
const hoverIndex = props.index;
|
||||
|
||||
// Don't replace items with themselves
|
||||
if (dragIndex === hoverIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine rectangle on screen
|
||||
const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
|
||||
|
||||
// Get vertical middle
|
||||
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
|
||||
|
||||
// Determine mouse position
|
||||
const clientOffset = monitor.getClientOffset();
|
||||
|
||||
// Get pixels to the top
|
||||
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
|
||||
|
||||
// Only perform the move when the mouse has crossed half of the items height
|
||||
// When dragging downwards, only move when the cursor is below 50%
|
||||
// When dragging upwards, only move when the cursor is above 50%
|
||||
|
||||
// Dragging downwards
|
||||
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dragging upwards
|
||||
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Time to actually perform the action
|
||||
props.moveAttr(dragIndex, hoverIndex, props.keys);
|
||||
|
||||
// Note: we're mutating the monitor item here!
|
||||
// Generally it's better to avoid mutations,
|
||||
// but it's good here for the sake of performance
|
||||
// to avoid expensive index searches.
|
||||
monitor.getItem().index = hoverIndex;
|
||||
},
|
||||
};
|
||||
|
||||
class DraggableAttr extends React.Component {
|
||||
state = { isOver: false, dragStart: false };
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { isDraggingSibling } = this.props;
|
||||
|
||||
if (isDraggingSibling !== prevProps.isDraggingSibling && isDraggingSibling) {
|
||||
this.handleMouseLeave();
|
||||
}
|
||||
}
|
||||
|
||||
handleClickEdit = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onClickEditListItem(this.props.index);
|
||||
}
|
||||
|
||||
handleMouseEnter = () => {
|
||||
if (!this.props.isDraggingSibling) {
|
||||
this.setState({ isOver: true });
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseLeave = () => this.setState({ isOver: false });
|
||||
|
||||
handleRemove = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onRemove(this.props.index, this.props.keys);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { label, name, isDragging, isEditing, connectDragSource, connectDropTarget } = this.props;
|
||||
const { isOver, dragStart } = this.state;
|
||||
const opacity = isDragging ? 0.2 : 1;
|
||||
const overClass = isOver ? styles.draggableAttrOvered : '';
|
||||
const className = dragStart ? styles.dragged : styles.draggableAttr;
|
||||
|
||||
return (
|
||||
connectDragSource(
|
||||
connectDropTarget(
|
||||
<div
|
||||
className={cn(className, isEditing && styles.editingAttr, overClass)}
|
||||
onDragStart={() => this.setState({ dragStart: true })}
|
||||
onDragEnd={() => this.setState({ dragStart: false })}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onClick={this.handleClickEdit}
|
||||
style={{ opacity }}
|
||||
>
|
||||
<i className="fa fa-th" aria-hidden="true" />
|
||||
<span>{name}</span>
|
||||
{ isOver && !isDragging && (
|
||||
<div className={styles.info}>
|
||||
<FormattedMessage id="content-manager.components.DraggableAttr.edit" />
|
||||
</div>
|
||||
)}
|
||||
{ !isOver && upperFirst(name) !== label && (
|
||||
<div className={styles.info}>
|
||||
{label}
|
||||
</div>
|
||||
)}
|
||||
{isEditing && !isOver? (
|
||||
<span className={styles.editIcon} onClick={this.handleClickEdit} />
|
||||
) : (
|
||||
<span className={cn( dragStart ? styles.removeIconDragged : styles.removeIcon)} onClick={this.handleRemove} />
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DraggableAttr.defaultProps = {
|
||||
isEditing: false,
|
||||
onRemove: () => {},
|
||||
};
|
||||
|
||||
DraggableAttr.propTypes = {
|
||||
connectDragSource: PropTypes.func.isRequired,
|
||||
connectDropTarget: PropTypes.func.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
isDragging: PropTypes.bool.isRequired,
|
||||
isDraggingSibling: PropTypes.bool.isRequired,
|
||||
isEditing: PropTypes.bool,
|
||||
keys: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
onClickEditListItem: PropTypes.func.isRequired,
|
||||
onRemove: PropTypes.func,
|
||||
};
|
||||
|
||||
const withDropTarget = DropTarget('draggableAttr', draggableAttrTarget, connect => ({
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
}));
|
||||
|
||||
const withDragSource = DragSource('draggableAttr', draggableAttrSource, (connect, monitor) => ({
|
||||
connectDragSource: connect.dragSource(),
|
||||
isDragging: monitor.isDragging(),
|
||||
}));
|
||||
|
||||
export default flow([withDropTarget, withDragSource])(DraggableAttr);
|
@ -0,0 +1,116 @@
|
||||
.draggableAttr {
|
||||
position: relative;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
margin-bottom: 6px;
|
||||
padding-left: 10px;
|
||||
justify-content: space-between;
|
||||
background: #FAFAFB;
|
||||
line-height: 30px;
|
||||
color: #333740;
|
||||
border: 1px solid #E3E9F3;
|
||||
border-radius: 2px;
|
||||
> i {
|
||||
margin-right: 10px;
|
||||
font-size: 11px;
|
||||
color: #B3B5B9;
|
||||
}
|
||||
}
|
||||
|
||||
.draggableAttrOvered {
|
||||
border: 1px solid #AED4FB!important;
|
||||
}
|
||||
|
||||
.editingAttr {
|
||||
background: #E6F0FB!important;
|
||||
border: 1px solid #AED4FB!important;
|
||||
}
|
||||
|
||||
.info {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 40px;
|
||||
color: #B4B6BA;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.removeIcon {
|
||||
width: 30px;
|
||||
background: #F3F4F4;
|
||||
height: 28px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
float: right;
|
||||
|
||||
&:after {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin: auto;
|
||||
margin-top: -3px;
|
||||
background-image: url('../../assets/images/icon-cross.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.removeIconDragged {
|
||||
width: 30px;
|
||||
background: #F3F4F4;
|
||||
height: 28px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
float: right;
|
||||
|
||||
&:after {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin: auto;
|
||||
margin-top: -3px;
|
||||
background-image: url('../../assets/images/icon-cross-blue.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.editIcon {
|
||||
width: 30px;
|
||||
background: #E6F0FB;
|
||||
height: 28px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
float: right;
|
||||
|
||||
&:after {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin: auto;
|
||||
margin-top: -3px;
|
||||
background-image: url('../../assets/images/icon-edit-blue.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.dragged {
|
||||
position: relative;
|
||||
height: 30px !important;
|
||||
width: 100%;
|
||||
margin-bottom: 6px;
|
||||
box-shadow: 0!important;
|
||||
padding-left: 10px;
|
||||
justify-content: space-between;
|
||||
background: #E6F0FB !important;
|
||||
line-height: 30px;
|
||||
color: #333740;
|
||||
border: 1px solid darken(#AED4FB, 20%)!important;
|
||||
border-radius: 2px;
|
||||
> i {
|
||||
margin-right: 10px;
|
||||
font-size: 10px;
|
||||
color: #B3B5B9;
|
||||
}
|
||||
|
||||
> span:last-child {
|
||||
background:#AED4FB;
|
||||
}
|
||||
}
|
@ -22,6 +22,9 @@ import {
|
||||
// or strapi/packages/strapi-helper-plugin/lib/src
|
||||
import Input from 'components/InputsIndex';
|
||||
|
||||
import InputJSONWithErrors from 'components/InputJSONWithErrors';
|
||||
import WysiwygWithErrors from 'components/WysiwygWithErrors';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
const getInputType = (type = '') => {
|
||||
@ -146,6 +149,7 @@ class Edit extends React.PureComponent {
|
||||
<Input
|
||||
autoFocus={key === 0}
|
||||
customBootstrapClass={get(layout, 'className')}
|
||||
customInputs={{ json: InputJSONWithErrors, wysiwyg: WysiwygWithErrors }}
|
||||
didCheckErrors={this.props.didCheckErrors}
|
||||
errors={this.getInputErrors(attr)}
|
||||
key={attr}
|
||||
|
@ -141,6 +141,7 @@ class SelectMany extends React.Component {
|
||||
isLoading={this.state.isLoading}
|
||||
onMenuScrollToBottom={this.handleBottomScroll}
|
||||
onInputChange={this.handleInputChange}
|
||||
onSelectResetsInput={false}
|
||||
multi
|
||||
value={
|
||||
isNull(value) || isUndefined(value) || value.size === 0
|
||||
|
@ -133,6 +133,7 @@ class SelectOne extends React.Component { // eslint-disable-line react/prefer-st
|
||||
isLoading={this.state.isLoading}
|
||||
onMenuScrollToBottom={this.handleBottomScroll}
|
||||
onInputChange={this.handleInputChange}
|
||||
onSelectResetsInput={false}
|
||||
simpleValue
|
||||
value={isNull(value) || isUndefined(value) ? null : {
|
||||
value: isFunction(value.toJS) ? value.toJS() : value,
|
||||
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
*
|
||||
* SettingsRow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { upperFirst } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import IcoContainer from 'components/IcoContainer';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
|
||||
function SettingsRow({ destination, name, onClick }) {
|
||||
return (
|
||||
<div className={styles.settingsRow} onClick={() => onClick(destination)}>
|
||||
<div>
|
||||
<div className={styles.frame}>
|
||||
<div className={styles.icon}>
|
||||
<i className="fa fa-cube"></i>
|
||||
</div>
|
||||
{upperFirst(name)}
|
||||
</div>
|
||||
<IcoContainer icons={[{ icoType: 'cog', onClick: () => onClick(destination) }]} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SettingsRow.propTypes = {
|
||||
destination: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default SettingsRow;
|
@ -0,0 +1,32 @@
|
||||
.settingsRow {
|
||||
height: 54px;
|
||||
|
||||
&:hover {
|
||||
background-color: #F7F8F8;
|
||||
}
|
||||
> div {
|
||||
display: flex;
|
||||
line-height: 53px;
|
||||
margin: 0 28px 0 36px;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid rgba(14,22,34,0.04);
|
||||
font-size: 13px;
|
||||
color: #333740;
|
||||
}
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 53px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.frame {
|
||||
position: relative;
|
||||
padding-left: 55px;
|
||||
font-weight: 500;
|
||||
}
|
@ -22,13 +22,14 @@ class Table extends React.Component {
|
||||
(
|
||||
<TableEmpty
|
||||
filters={this.props.filters}
|
||||
colspan={this.props.headers.length + 1}
|
||||
colspan={this.props.enableBulkActions ? this.props.headers.length + 1 : this.props.headers.length}
|
||||
contentType={this.props.routeParams.slug}
|
||||
search={this.props.search}
|
||||
/>
|
||||
) :
|
||||
this.props.records.map((record, key) => (
|
||||
<TableRow
|
||||
enableBulkActions={this.props.enableBulkActions}
|
||||
onChange={this.props.onClickSelect}
|
||||
key={key}
|
||||
destination={`${this.props.route.path.replace(':slug', this.props.routeParams.slug)}/${record[this.props.primaryKey]}`}
|
||||
@ -42,10 +43,11 @@ class Table extends React.Component {
|
||||
/>
|
||||
));
|
||||
const entriesToDeleteNumber = this.props.entriesToDelete.length;
|
||||
|
||||
|
||||
return (
|
||||
<table className={`table ${styles.table}`}>
|
||||
<TableHeader
|
||||
enableBulkActions={this.props.enableBulkActions}
|
||||
onClickSelectAll={this.props.onClickSelectAll}
|
||||
value={this.props.deleteAllValue}
|
||||
headers={this.props.headers}
|
||||
@ -74,6 +76,7 @@ Table.contextTypes = {
|
||||
};
|
||||
|
||||
Table.defaultProps = {
|
||||
enableBulkActions: true,
|
||||
entriesToDelete: [],
|
||||
handleDelete: () => {},
|
||||
search: '',
|
||||
@ -82,6 +85,7 @@ Table.defaultProps = {
|
||||
|
||||
Table.propTypes = {
|
||||
deleteAllValue: PropTypes.bool.isRequired,
|
||||
enableBulkActions: PropTypes.bool,
|
||||
entriesToDelete: PropTypes.array,
|
||||
filters: PropTypes.array.isRequired,
|
||||
handleDelete: PropTypes.func,
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
|
||||
import CustomInputCheckbox from 'components/CustomInputCheckbox';
|
||||
|
||||
@ -22,17 +23,23 @@ class TableHeader extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
renderBulk = () => (
|
||||
<th key="bulk_action">
|
||||
<CustomInputCheckbox
|
||||
entriesToDelete={this.props.entriesToDelete}
|
||||
isAll
|
||||
name="all"
|
||||
onChange={this.props.onClickSelectAll}
|
||||
value={this.props.value}
|
||||
/>
|
||||
</th>
|
||||
);
|
||||
renderBulk = () => {
|
||||
if (this.props.enableBulkActions) {
|
||||
return (
|
||||
<th key="bulk_action">
|
||||
<CustomInputCheckbox
|
||||
entriesToDelete={this.props.entriesToDelete}
|
||||
isAll
|
||||
name="all"
|
||||
onChange={this.props.onClickSelectAll}
|
||||
value={this.props.value}
|
||||
/>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
// Generate headers list
|
||||
@ -40,7 +47,7 @@ class TableHeader extends React.Component {
|
||||
// Define sort icon
|
||||
let icon;
|
||||
|
||||
if (this.props.sort === header.name) {
|
||||
if (this.props.sort === header.name || this.props.sort === 'id' && header.name === '_id') {
|
||||
icon = <i className={`fa fa-sort-asc ${styles.iconAsc}`} />;
|
||||
} else if (this.props.sort === `-${header.name}`) {
|
||||
icon = <i className={`fa fa-sort-asc ${styles.iconDesc}`} />;
|
||||
@ -49,7 +56,11 @@ class TableHeader extends React.Component {
|
||||
return (
|
||||
<th // eslint-disable-line jsx-a11y/no-static-element-interactions
|
||||
key={i}
|
||||
onClick={() => this.handleChangeSort(header.name)}
|
||||
onClick={() => {
|
||||
if (header.sortable) {
|
||||
this.handleChangeSort(header.name);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
{header.label}
|
||||
@ -64,7 +75,7 @@ class TableHeader extends React.Component {
|
||||
headers.push(<th key="th_action"></th>);
|
||||
|
||||
return (
|
||||
<thead className={styles.tableHeader}>
|
||||
<thead className={cn(styles.tableHeader, this.props.enableBulkActions && styles.withBulk)}>
|
||||
<tr >
|
||||
{[this.renderBulk()].concat(headers)}
|
||||
</tr>
|
||||
@ -74,10 +85,12 @@ class TableHeader extends React.Component {
|
||||
}
|
||||
|
||||
TableHeader.defaultProps = {
|
||||
enableBulkActions: true,
|
||||
value: false,
|
||||
};
|
||||
|
||||
TableHeader.propTypes = {
|
||||
enableBulkActions: PropTypes.bool,
|
||||
entriesToDelete: PropTypes.array.isRequired,
|
||||
headers: PropTypes.array.isRequired,
|
||||
onChangeSort: PropTypes.func.isRequired,
|
||||
|
@ -14,7 +14,9 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.withBulk {
|
||||
> tr {
|
||||
th:first-child {
|
||||
width: 50px;
|
||||
|
@ -8,6 +8,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import { isEmpty, isObject, toString } from 'lodash';
|
||||
import cn from 'classnames';
|
||||
|
||||
import CustomInputCheckbox from 'components/CustomInputCheckbox';
|
||||
import IcoContainer from 'components/IcoContainer';
|
||||
@ -100,19 +101,25 @@ class TableRow extends React.Component {
|
||||
.concat([this.renderAction()]);
|
||||
}
|
||||
|
||||
renderDelete = () => (
|
||||
<td onClick={(e) => e.stopPropagation()} key="i">
|
||||
<CustomInputCheckbox
|
||||
name={this.props.record.id}
|
||||
onChange={this.props.onChange}
|
||||
value={this.props.value}
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
renderDelete = () => {
|
||||
if (this.props.enableBulkActions) {
|
||||
return (
|
||||
<td onClick={(e) => e.stopPropagation()} key="i">
|
||||
<CustomInputCheckbox
|
||||
name={this.props.record.id}
|
||||
onChange={this.props.onChange}
|
||||
value={this.props.value}
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<tr className={styles.tableRow} onClick={() => this.handleClick(this.props.destination)}>
|
||||
<tr className={cn(styles.tableRow, this.props.enableBulkActions && styles.tableRowWithBulk)} onClick={() => this.handleClick(this.props.destination)}>
|
||||
{this.renderCells()}
|
||||
</tr>
|
||||
);
|
||||
@ -124,11 +131,13 @@ TableRow.contextTypes = {
|
||||
};
|
||||
|
||||
TableRow.defaultProps = {
|
||||
enableBulkActions: true,
|
||||
value: false,
|
||||
};
|
||||
|
||||
TableRow.propTypes = {
|
||||
destination: PropTypes.string.isRequired,
|
||||
enableBulkActions: PropTypes.bool,
|
||||
headers: PropTypes.array.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func,
|
||||
|