mirror of
https://github.com/strapi/strapi.git
synced 2025-11-02 02:44:55 +00:00
Merge branch 'master' into fix/graphql-queries-context
This commit is contained in:
commit
5e4c2bd394
@ -33,9 +33,120 @@ By default, the administration panel is exposed via [http://localhost:1337/admin
|
||||
|
||||
The panel will be available through [http://localhost:1337/dashboard](http://localhost:1337/dashboard) with the configurations above.
|
||||
|
||||
---
|
||||
|
||||
### Development mode
|
||||
|
||||
**_Currently not available_**
|
||||
To enable the front-end development mode you need to start your application using the `--watch-admin` flag.
|
||||
|
||||
```bash
|
||||
cd my-app
|
||||
strapi develop --watch-admin
|
||||
```
|
||||
|
||||
With this option you can do the following:
|
||||
|
||||
#### Customize the `strapi-admin` package
|
||||
|
||||
All files added in `my-app/admin/src/` will either be replaced or added
|
||||
|
||||
**Example: Changing the available locales of your application**
|
||||
|
||||
```bash
|
||||
# Create both the admin and admin/src/translations folders
|
||||
cd my-app && mkdir -p admin/src/translations
|
||||
# Change the available locales of the administration panel
|
||||
touch admin/src/i18n.js
|
||||
# Change the import and exports of the translations files
|
||||
touch admin/src/translations/index.js
|
||||
```
|
||||
|
||||
**Path --** `my-app/admin/src/translations/index.js`
|
||||
|
||||
```js
|
||||
import en from './en.json';
|
||||
import fr from './fr.json';
|
||||
|
||||
const trads = {
|
||||
en,
|
||||
fr,
|
||||
};
|
||||
|
||||
export default trads;
|
||||
```
|
||||
|
||||
**Path --** `my-app/admin/src/i18n.js`
|
||||
|
||||
```js
|
||||
import { addLocaleData } from 'react-intl';
|
||||
import { reduce } from 'lodash';
|
||||
import en from 'react-intl/locale-data/en';
|
||||
import fr from 'react-intl/locale-data/fr';
|
||||
import trads from './translations';
|
||||
|
||||
// We dismiss pt-BR and zh-Hans locales since they are not supported by react-intl
|
||||
const locales = {
|
||||
en,
|
||||
fr,
|
||||
};
|
||||
const languages = Object.keys(trads);
|
||||
|
||||
/**
|
||||
* Dynamically generate `translationsMessages object`.
|
||||
*/
|
||||
const translationMessages = reduce(
|
||||
languages,
|
||||
(result, language) => {
|
||||
const obj = result;
|
||||
obj[language] = trads[language];
|
||||
|
||||
if (locales[language]) {
|
||||
addLocaleData(locales[language]);
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
export { languages, translationMessages };
|
||||
```
|
||||
|
||||
::: note
|
||||
With this modification only English and French will be available in your admin
|
||||
:::
|
||||
|
||||
#### Customize a plugin
|
||||
|
||||
Similarly to the back-end override system any file added in `my-app/extensions/<plugin-name>/admin/` will be copied and used instead of the original one (use with care).
|
||||
|
||||
**Example: Changing the current WYSIWYG**
|
||||
|
||||
```bash
|
||||
cd my-app/extensions
|
||||
# Create the content manager folder
|
||||
mkdir content-manager && cd content-manager
|
||||
# Create the admin folder
|
||||
mkdir -p admin/src
|
||||
# Create the components folder and the WysiwygWithErrors one
|
||||
cd admin/src && mkdir -p components/WysiwygWithErrors
|
||||
# Create the index.js so the original file is overridden
|
||||
touch components/WysiwygWithErrors/index.js
|
||||
```
|
||||
|
||||
**Path --** `my-app/extensions/content-manager/admin/src/components/WysiwygWithErrors/index.js`
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import MyNewWYSIWYG from 'my-awesome-lib';
|
||||
|
||||
// This is a dummy example
|
||||
const WysiwygWithErrors = props => <MyNewWYSIWYG {...props} />;
|
||||
|
||||
export default WysiwygWithErrors;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Styles
|
||||
|
||||
@ -45,6 +156,14 @@ For example, to change the top-left displayed admin panel's color, `./node_modul
|
||||
|
||||
Thus, you are replacing the files that would normally be in `node_modules/strapi-admin/admin/src` and directing them to `admin/src/some/file/path`.
|
||||
|
||||
To apply your changes you need to rebuild your admin panel
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Logo
|
||||
|
||||
To change the top-left displayed admin panel's logo, add your custom image at `./admin/src/assets/images/logo-strapi.png`.
|
||||
|
||||
@ -37,6 +37,18 @@ Start a Strapi application with autoReload enabled.
|
||||
|
||||
Strapi modifies/creates files at runtime and needs to restart when new files are created. To achieve this, `strapi develop` adds a file watcher and restarts the application when necessary.
|
||||
|
||||
```
|
||||
strapi develop
|
||||
options: [--no-build |--watch-admin ]
|
||||
```
|
||||
|
||||
- **strapi develop**<br/>
|
||||
Starts your application with the autoReload enabled
|
||||
- **strapi develop --no-build**<br/>
|
||||
Starts your application with the autoReload enabled and skip the administration panel build process
|
||||
- **strapi develop --watch-admin**<br/>
|
||||
Starts your application with the autoReload enabled and the front-end development server. It allows you to customize the administration panel.
|
||||
|
||||
::: note
|
||||
You should never use this command to run a Strapi application in production.
|
||||
:::
|
||||
|
||||
@ -110,7 +110,9 @@ Strapi comes with the following providers:
|
||||
Set your providers credentials in the admin interface (Plugin Users & Permissions > Providers).
|
||||
Then update and enable the provider you want use.
|
||||
|
||||
To authenticate the user, use the GET method to request the url, `/connect/:provider`. eg: `GET /connect/facebook`
|
||||
To authenticate the user, use the GET method to request the url, `/connect/:provider`. eg: `GET /connect/facebook`.
|
||||
|
||||
You can also pass a custom callback url instead of using the default registered provider callback, by passing `callback` in the query. eg: `GET /connect/facebook?callback=https://my-frontend.com/en/auth/facebook`.
|
||||
|
||||
After authentication, create and customize your own redirect callback at `/auth/:provider/callback`. The `jwt` and `user` data will be available in a .json response.
|
||||
|
||||
|
||||
@ -27,51 +27,67 @@ describe('<LocaleToggle />', () => {
|
||||
it('should return the en flag', () => {
|
||||
const renderedComponent = shallow(<LocaleToggle {...props} />);
|
||||
const { getFlagUrl } = renderedComponent.instance();
|
||||
|
||||
expect(getFlagUrl('en')).toEqual('https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/us.svg');
|
||||
|
||||
expect(getFlagUrl('en')).toEqual(
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/us.svg'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the pt-BR flag', () => {
|
||||
const renderedComponent = shallow(<LocaleToggle {...props} />);
|
||||
const { getFlagUrl } = renderedComponent.instance();
|
||||
|
||||
expect(getFlagUrl('pt-BR')).toEqual('https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/br.svg');
|
||||
|
||||
expect(getFlagUrl('pt-BR')).toEqual(
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/br.svg'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the zh flag', () => {
|
||||
const renderedComponent = shallow(<LocaleToggle {...props} />);
|
||||
const { getFlagUrl } = renderedComponent.instance();
|
||||
|
||||
expect(getFlagUrl('zh')).toEqual('https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/tw.svg');
|
||||
expect(getFlagUrl('zh-Hans')).toEqual('https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/cn.svg');
|
||||
|
||||
expect(getFlagUrl('zh')).toEqual(
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/tw.svg'
|
||||
);
|
||||
expect(getFlagUrl('zh-Hans')).toEqual(
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/cn.svg'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the ar flag', () => {
|
||||
const renderedComponent = shallow(<LocaleToggle {...props} />);
|
||||
const { getFlagUrl } = renderedComponent.instance();
|
||||
|
||||
expect(getFlagUrl('ar')).toEqual('https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/sa.svg');
|
||||
|
||||
expect(getFlagUrl('ar')).toEqual(
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/sa.svg'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the ko flag', () => {
|
||||
const renderedComponent = shallow(<LocaleToggle {...props} />);
|
||||
const { getFlagUrl } = renderedComponent.instance();
|
||||
|
||||
expect(getFlagUrl('ko')).toEqual('https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/kr.svg');
|
||||
|
||||
expect(getFlagUrl('ko')).toEqual(
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/kr.svg'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the ja flag', () => {
|
||||
const renderedComponent = shallow(<LocaleToggle {...props} />);
|
||||
const { getFlagUrl } = renderedComponent.instance();
|
||||
|
||||
expect(getFlagUrl('ja')).toEqual('https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/jp.svg');
|
||||
|
||||
expect(getFlagUrl('ja')).toEqual(
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/jp.svg'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the locale flag', () => {
|
||||
const renderedComponent = shallow(<LocaleToggle {...props} />);
|
||||
const { getFlagUrl } = renderedComponent.instance();
|
||||
const locale = 'fr';
|
||||
expect(getFlagUrl(locale)).toEqual(`https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/${locale}.svg`);
|
||||
expect(getFlagUrl(locale)).toEqual(
|
||||
`https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/${locale}.svg`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -100,18 +116,17 @@ describe('<LocaleToggle />', () => {
|
||||
it('should be injected', () => {
|
||||
const dispatch = jest.fn();
|
||||
const result = mapDispatchToProps(dispatch);
|
||||
|
||||
|
||||
expect(result.changeLocale).toBeDefined();
|
||||
});
|
||||
|
||||
|
||||
it('should dispatch the changeLocale action when called', () => {
|
||||
const dispatch = jest.fn();
|
||||
const result = mapDispatchToProps(dispatch);
|
||||
result.changeLocale();
|
||||
|
||||
|
||||
expect(dispatch).toHaveBeenCalledWith(changeLocale());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const webpack = require('webpack');
|
||||
const getWebpackConfig = require('./webpack.config.js');
|
||||
const WebpackDevServer = require('webpack-dev-server');
|
||||
const chalk = require('chalk');
|
||||
const chokidar = require('chokidar');
|
||||
|
||||
const getPkgPath = name =>
|
||||
path.dirname(require.resolve(`${name}/package.json`));
|
||||
@ -83,7 +87,7 @@ async function copyCustomAdmin(src, dest) {
|
||||
await fs.copy(src, path.resolve(dest, 'admin'));
|
||||
}
|
||||
|
||||
async function build({ dir, env, options }) {
|
||||
async function createCacheDir(dir) {
|
||||
const cacheDir = path.resolve(dir, '.cache');
|
||||
|
||||
const pkgJSON = require(path.join(dir, 'package.json'));
|
||||
@ -113,9 +117,34 @@ async function build({ dir, env, options }) {
|
||||
await copyCustomAdmin(path.join(dir, 'admin'), cacheDir);
|
||||
}
|
||||
|
||||
// override plugins' admin code with user customizations
|
||||
const pluginsToOverride = pluginsToCopy.reduce((acc, current) => {
|
||||
const pluginName = current.replace(/^strapi-plugin-/i, '');
|
||||
|
||||
if (fs.pathExistsSync(path.join(dir, 'extensions', pluginName, 'admin'))) {
|
||||
acc.push(pluginName);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
await Promise.all(
|
||||
pluginsToOverride.map(plugin =>
|
||||
copyCustomAdmin(
|
||||
path.join(dir, 'extensions', plugin, 'admin'),
|
||||
path.join(cacheDir, 'plugins', `strapi-plugin-${plugin}`)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function build({ dir, env, options }) {
|
||||
// Create the cache dir containing the front-end files.
|
||||
await createCacheDir(dir);
|
||||
|
||||
const cacheDir = path.resolve(dir, '.cache');
|
||||
const entry = path.resolve(cacheDir, 'admin', 'src', 'app.js');
|
||||
const dest = path.resolve(dir, 'build');
|
||||
|
||||
const config = getWebpackConfig({ entry, dest, env, options });
|
||||
|
||||
const compiler = webpack(config);
|
||||
@ -152,7 +181,150 @@ async function build({ dir, env, options }) {
|
||||
});
|
||||
}
|
||||
|
||||
async function watchAdmin({ dir, port, options }) {
|
||||
// Create the cache dir containing the front-end files.
|
||||
await createCacheDir(dir);
|
||||
|
||||
const entry = path.join(dir, '.cache', 'admin', 'src', 'app.js');
|
||||
const dest = path.join(dir, 'build');
|
||||
const env = 'development';
|
||||
|
||||
const args = {
|
||||
entry,
|
||||
dest,
|
||||
env,
|
||||
port,
|
||||
options,
|
||||
};
|
||||
|
||||
const opts = {
|
||||
clientLogLevel: 'silent',
|
||||
hot: true,
|
||||
quiet: true,
|
||||
open: true,
|
||||
publicPath: options.publicPath,
|
||||
historyApiFallback: {
|
||||
index: options.publicPath,
|
||||
},
|
||||
};
|
||||
|
||||
const server = new WebpackDevServer(webpack(getWebpackConfig(args)), opts);
|
||||
|
||||
server.listen(port, 'localhost', function(err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
console.log(chalk.green('Starting the development server...'));
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.green(
|
||||
`Admin development at http://localhost:${port}${opts.publicPath}`
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
watchFiles(dir);
|
||||
}
|
||||
|
||||
async function watchFiles(dir) {
|
||||
const cacheDir = path.join(dir, '.cache');
|
||||
const pkgJSON = require(path.join(dir, 'package.json'));
|
||||
const admin = path.join(dir, 'admin');
|
||||
|
||||
const appPlugins = Object.keys(pkgJSON.dependencies).filter(
|
||||
dep =>
|
||||
dep.startsWith('strapi-plugin') &&
|
||||
fs.existsSync(path.resolve(getPkgPath(dep), 'admin', 'src', 'index.js'))
|
||||
);
|
||||
const pluginsToWatch = appPlugins.map(plugin =>
|
||||
path.join(
|
||||
dir,
|
||||
'extensions',
|
||||
plugin.replace(/^strapi-plugin-/i, ''),
|
||||
'admin'
|
||||
)
|
||||
);
|
||||
const filesToWatch = [admin, ...pluginsToWatch];
|
||||
|
||||
const watcher = chokidar.watch(filesToWatch, {
|
||||
ignoreInitial: true,
|
||||
ignorePermissionErrors: true,
|
||||
});
|
||||
|
||||
watcher.on('all', async (event, filePath) => {
|
||||
const re = /\/extensions\/([^\/]*)\/.*$/gm;
|
||||
const matched = re.exec(filePath);
|
||||
const isExtension = matched !== null;
|
||||
const pluginName = isExtension ? matched[1] : '';
|
||||
|
||||
const packageName = isExtension
|
||||
? `strapi-plugin-${pluginName}`
|
||||
: 'strapi-admin';
|
||||
const targetPath = isExtension
|
||||
? filePath.split('/extensions/')[1].replace(pluginName, '')
|
||||
: filePath.split('/admin')[1];
|
||||
|
||||
const destFolder = isExtension
|
||||
? path.join(cacheDir, 'plugins', packageName)
|
||||
: path.join(cacheDir, 'admin');
|
||||
|
||||
if (event === 'unlink' || event === 'unlinkDir') {
|
||||
const originalFilePathInNodeModules = path.join(
|
||||
getPkgPath(packageName),
|
||||
isExtension ? '' : 'admin',
|
||||
targetPath
|
||||
);
|
||||
|
||||
// Remove the file or folder
|
||||
// We need to copy the original files when deleting an override one
|
||||
try {
|
||||
fs.removeSync(path.join(destFolder, targetPath));
|
||||
} catch (err) {
|
||||
console.log('An error occured while deleting the file', err);
|
||||
}
|
||||
|
||||
// Check if the file or folder exists in node_modules
|
||||
// If so copy the old one
|
||||
if (fs.pathExistsSync(path.resolve(originalFilePathInNodeModules))) {
|
||||
try {
|
||||
await fs.copy(
|
||||
path.resolve(originalFilePathInNodeModules),
|
||||
path.join(destFolder, targetPath)
|
||||
);
|
||||
|
||||
// The plugins.js file needs to be recreated
|
||||
// when we delete either the admin folder
|
||||
// the admin/src folder
|
||||
// or the plugins.js file
|
||||
// since the path are different when developing inside the monorepository or inside an app
|
||||
const shouldCopyPluginsJSFile =
|
||||
filePath.split('/admin/src').filter(p => !!p).length === 1;
|
||||
|
||||
if (
|
||||
(event === 'unlinkDir' &&
|
||||
!isExtension &&
|
||||
shouldCopyPluginsJSFile) ||
|
||||
(!isExtension && filePath.includes('plugins.js'))
|
||||
) {
|
||||
await createPluginsJs(appPlugins, path.join(cacheDir));
|
||||
}
|
||||
} catch (err) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// In any other case just copy the file into the .cache folder
|
||||
try {
|
||||
await fs.copy(filePath, path.join(destFolder, targetPath));
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
build,
|
||||
createPluginsJs,
|
||||
watchAdmin,
|
||||
};
|
||||
|
||||
@ -102,6 +102,7 @@
|
||||
"license": "MIT",
|
||||
"gitHead": "c85658a19b8fef0f3164c19693a45db305dc07a9",
|
||||
"devDependencies": {
|
||||
"chokidar": "^3.1.1",
|
||||
"webpack": "^4.40.1",
|
||||
"webpack-cli": "^3.3.2",
|
||||
"webpack-dev-server": "^3.4.1"
|
||||
|
||||
@ -37,12 +37,15 @@ module.exports = ({
|
||||
filename: '[name].[chunkhash].css',
|
||||
chunkFilename: '[name].[chunkhash].chunkhash.css',
|
||||
}),
|
||||
new WebpackBar(),
|
||||
]
|
||||
: [
|
||||
new DuplicatePckgChecker({
|
||||
verbose: true,
|
||||
}),
|
||||
new FriendlyErrorsWebpackPlugin(),
|
||||
new FriendlyErrorsWebpackPlugin({
|
||||
clearConsole: false,
|
||||
}),
|
||||
];
|
||||
|
||||
const scssLoader = isProduction
|
||||
@ -237,7 +240,6 @@ module.exports = ({
|
||||
mainFields: ['browser', 'jsnext:main', 'main'],
|
||||
},
|
||||
plugins: [
|
||||
new WebpackBar(),
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
template: path.resolve(__dirname, 'index.html'),
|
||||
|
||||
@ -11,11 +11,12 @@ import PropTypes from 'prop-types';
|
||||
import styles from './styles.scss';
|
||||
|
||||
class GlobalPagination extends React.Component {
|
||||
getLastPageNumber = () => Math.ceil(this.props.count / this.props.params._limit) || 1;
|
||||
getLastPageNumber = () =>
|
||||
Math.ceil(this.props.count / this.props.params._limit) || 1;
|
||||
|
||||
handleDotsClick = (e) => e.preventDefault();
|
||||
handleDotsClick = e => e.preventDefault();
|
||||
|
||||
handlePreviousPageClick = (e) => {
|
||||
handlePreviousPageClick = e => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.isFirstPage()) {
|
||||
@ -25,9 +26,9 @@ class GlobalPagination extends React.Component {
|
||||
};
|
||||
this.props.onChangeParams({ target });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleNextPageClick = (e) => {
|
||||
handleNextPageClick = e => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.isLastPage()) {
|
||||
@ -37,31 +38,32 @@ class GlobalPagination extends React.Component {
|
||||
};
|
||||
this.props.onChangeParams({ target });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleFirstPageClick = (e) => {
|
||||
handleFirstPageClick = e => {
|
||||
e.preventDefault();
|
||||
const target = {
|
||||
name: 'params._page',
|
||||
value: 1,
|
||||
};
|
||||
this.props.onChangeParams({ target });
|
||||
}
|
||||
};
|
||||
|
||||
handleLastPageClick = (e) => {
|
||||
handleLastPageClick = e => {
|
||||
e.preventDefault();
|
||||
const target = {
|
||||
name: 'params._page',
|
||||
value: this.getLastPageNumber(),
|
||||
};
|
||||
this.props.onChangeParams({ target });
|
||||
}
|
||||
};
|
||||
|
||||
isFirstPage = () => this.props.params._page === 1;
|
||||
|
||||
isLastPage = () => this.props.params._page === this.getLastPageNumber();
|
||||
|
||||
needAfterLinksDots = () => this.props.params._page < this.getLastPageNumber() - 1;
|
||||
needAfterLinksDots = () =>
|
||||
this.props.params._page < this.getLastPageNumber() - 1;
|
||||
|
||||
needPreviousLinksDots = () => this.props.params._page > 3;
|
||||
|
||||
@ -111,19 +113,18 @@ class GlobalPagination extends React.Component {
|
||||
}
|
||||
|
||||
// Generate links
|
||||
return (
|
||||
map(linksOptions, (linksOption, key) => (
|
||||
<li
|
||||
className={`${linksOption.isActive && styles.navLiActive}`}
|
||||
key={key}
|
||||
return map(linksOptions, (linksOption, key) => (
|
||||
<li className={`${linksOption.isActive && styles.navLiActive}`} key={key}>
|
||||
<a
|
||||
href=""
|
||||
disabled={linksOption.isActive}
|
||||
onClick={linksOption.handleClick}
|
||||
>
|
||||
<a href="" disabled={linksOption.isActive} onClick={linksOption.handleClick}>
|
||||
{linksOption.value}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
);
|
||||
}
|
||||
{linksOption.value}
|
||||
</a>
|
||||
</li>
|
||||
));
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
@ -141,9 +142,7 @@ class GlobalPagination extends React.Component {
|
||||
<i className="fa fa-angle-left" aria-hidden="true"></i>
|
||||
</a>
|
||||
<nav className={styles.nav}>
|
||||
<ul className={styles.navUl}>
|
||||
{this.renderLinks()}
|
||||
</ul>
|
||||
<ul className={styles.navUl}>{this.renderLinks()}</ul>
|
||||
</nav>
|
||||
<a
|
||||
href=""
|
||||
@ -172,16 +171,10 @@ GlobalPagination.defaultProps = {
|
||||
};
|
||||
|
||||
GlobalPagination.propTypes = {
|
||||
count: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.bool,
|
||||
]),
|
||||
count: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
|
||||
onChangeParams: PropTypes.func,
|
||||
params: PropTypes.shape({
|
||||
_page: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
_page: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
_limit: PropTypes.number,
|
||||
}),
|
||||
};
|
||||
|
||||
@ -33,13 +33,16 @@ module.exports = function(strapi) {
|
||||
* Initialize the hook
|
||||
*/
|
||||
|
||||
initialize () {
|
||||
initialize() {
|
||||
// Force cache mode in production
|
||||
if (strapi.config.environment === 'production') {
|
||||
strapi.config.hook.settings.ejs.cache = true;
|
||||
}
|
||||
|
||||
render(strapi.app, Object.assign(this.defaults, strapi.config.hook.settings.ejs));
|
||||
render(
|
||||
strapi.app,
|
||||
Object.assign(this.defaults, strapi.config.hook.settings.ejs)
|
||||
);
|
||||
|
||||
strapi.app.context.render = co.wrap(strapi.app.context.render);
|
||||
},
|
||||
|
||||
@ -159,7 +159,9 @@ module.exports = {
|
||||
return _.set(
|
||||
acc,
|
||||
current,
|
||||
property ? property.map(val => val[assocModel.primaryKey] || val) : property
|
||||
property
|
||||
? property.map(val => val[assocModel.primaryKey] || val)
|
||||
: property
|
||||
);
|
||||
}
|
||||
|
||||
@ -187,12 +189,14 @@ module.exports = {
|
||||
return assocModel.updateMany(
|
||||
{
|
||||
[assocModel.primaryKey]: {
|
||||
$in: property ? property.map(
|
||||
val =>
|
||||
new mongoose.Types.ObjectId(
|
||||
val[assocModel.primaryKey] || val
|
||||
$in: property
|
||||
? property.map(
|
||||
val =>
|
||||
new mongoose.Types.ObjectId(
|
||||
val[assocModel.primaryKey] || val
|
||||
)
|
||||
)
|
||||
) : property,
|
||||
: property,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -172,9 +172,9 @@ const buildAssocResolvers = (model, name, { plugin }) => {
|
||||
queryOpts,
|
||||
['query', ref.primaryKey],
|
||||
obj[association.alias]
|
||||
? obj[association.alias].map(
|
||||
val => val[ref.primaryKey] || val
|
||||
).sort()
|
||||
? obj[association.alias]
|
||||
.map(val => val[ref.primaryKey] || val)
|
||||
.sort()
|
||||
: []
|
||||
);
|
||||
} else {
|
||||
|
||||
@ -46,8 +46,8 @@
|
||||
"configurable": false,
|
||||
"required": true
|
||||
},
|
||||
"public_id": {
|
||||
"type": "string",
|
||||
"provider_metadata": {
|
||||
"type": "json",
|
||||
"configurable": false
|
||||
},
|
||||
"related": {
|
||||
|
||||
@ -263,7 +263,8 @@ module.exports = {
|
||||
if (!_.get(config, 'enabled')) {
|
||||
return ctx.badRequest(null, 'This provider is disabled.');
|
||||
}
|
||||
|
||||
// Ability to pass OAuth callback dynamically
|
||||
grantConfig[provider].callback = ctx.query && ctx.query.callback ? ctx.query.callback : grantConfig[provider].callback;
|
||||
return grant(grantConfig)(ctx, next);
|
||||
},
|
||||
|
||||
|
||||
@ -42,18 +42,23 @@ module.exports = {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
file.public_id = image.public_id;
|
||||
file.url = image.secure_url;
|
||||
file.provider_metadata = {
|
||||
public_id: image.public_id,
|
||||
resource_type: image.resource_type,
|
||||
};
|
||||
resolve();
|
||||
},
|
||||
}
|
||||
);
|
||||
intoStream(file.buffer).pipe(upload_stream);
|
||||
});
|
||||
},
|
||||
async delete(file) {
|
||||
try {
|
||||
const response = await cloudinary.uploader.destroy(file.public_id, {
|
||||
const { resource_type, public_id } = file.provider_metadata;
|
||||
const response = await cloudinary.uploader.destroy(public_id, {
|
||||
invalidate: true,
|
||||
resource_type: resource_type || 'image',
|
||||
});
|
||||
if (response.result !== 'ok') {
|
||||
throw {
|
||||
|
||||
@ -112,6 +112,7 @@ program
|
||||
.command('develop')
|
||||
.alias('dev')
|
||||
.option('--no-build', 'Disable build', false)
|
||||
.option('--watch-admin', 'Enable watch', true)
|
||||
.description('Start your Strapi application in development mode')
|
||||
.action(getLocalScript('develop'));
|
||||
|
||||
@ -190,6 +191,12 @@ program
|
||||
.option('-d, --delete-files', 'Delete files', false)
|
||||
.action(getLocalScript('uninstall'));
|
||||
|
||||
// `$ strapi watch-admin`
|
||||
program
|
||||
.command('watch-admin')
|
||||
.description('Starts the admin dev server')
|
||||
.action(getLocalScript('watchAdmin'));
|
||||
|
||||
/**
|
||||
* Normalize help argument
|
||||
*/
|
||||
|
||||
@ -56,7 +56,7 @@ const createQueryManager = () => {
|
||||
*/
|
||||
|
||||
class Strapi extends EventEmitter {
|
||||
constructor({ dir, autoReload = false } = {}) {
|
||||
constructor({ dir, autoReload = false, serveAdminPanel = true } = {}) {
|
||||
super();
|
||||
|
||||
this.setMaxListeners(100);
|
||||
@ -92,6 +92,7 @@ class Strapi extends EventEmitter {
|
||||
|
||||
// Default configurations.
|
||||
this.config = {
|
||||
serveAdminPanel,
|
||||
launchedAt: Date.now(),
|
||||
appPath: this.dir,
|
||||
autoReload,
|
||||
@ -170,7 +171,10 @@ class Strapi extends EventEmitter {
|
||||
);
|
||||
this.log.info('To shut down your server, press <CTRL> + C at any time');
|
||||
console.log();
|
||||
this.log.info(`☄️ Admin panel: ${this.config.admin.url}`);
|
||||
|
||||
if (this.config.serveAdminPanel === true) {
|
||||
this.log.info(`☄️ Admin panel: ${this.config.admin.url}`);
|
||||
}
|
||||
this.log.info(`⚡️ Server: ${this.config.url}`);
|
||||
console.log();
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ const _ = require('lodash');
|
||||
const { green, yellow } = require('chalk');
|
||||
const strapiAdmin = require('strapi-admin');
|
||||
const loadConfigFile = require('../load/load-config-files');
|
||||
|
||||
const addSlash = require('../utils/addSlash');
|
||||
/**
|
||||
* `$ strapi build`
|
||||
*/
|
||||
@ -52,12 +52,3 @@ module.exports = async () => {
|
||||
process.exit(1);
|
||||
});
|
||||
};
|
||||
|
||||
function addSlash(path) {
|
||||
if (typeof path !== 'string') throw new Error('admin.path must be a string');
|
||||
if (path === '' || path === '/') return '/';
|
||||
|
||||
if (path[0] != '/') path = '/' + path;
|
||||
if (path[path.length - 1] != '/') path = path + '/';
|
||||
return path;
|
||||
}
|
||||
|
||||
@ -7,16 +7,18 @@ const chokidar = require('chokidar');
|
||||
const execa = require('execa');
|
||||
|
||||
const { logger } = require('strapi-utils');
|
||||
|
||||
const strapi = require('../index');
|
||||
|
||||
/**
|
||||
* `$ strapi develop`
|
||||
*
|
||||
*/
|
||||
module.exports = async function({ build }) {
|
||||
module.exports = async function({ build, watchAdmin }) {
|
||||
const dir = process.cwd();
|
||||
|
||||
if (build && !fs.existsSync(path.join(dir, 'build'))) {
|
||||
// Don't run the build process if the admin is in watch mode
|
||||
if (build && !watchAdmin && !fs.existsSync(path.join(dir, 'build'))) {
|
||||
try {
|
||||
execa.shellSync('npm run -s build', {
|
||||
stdio: 'inherit',
|
||||
@ -27,9 +29,24 @@ module.exports = async function({ build }) {
|
||||
}
|
||||
|
||||
try {
|
||||
const strapiInstance = strapi({ dir, autoReload: true });
|
||||
const strapiInstance = strapi({
|
||||
dir,
|
||||
autoReload: true,
|
||||
serveAdminPanel: watchAdmin ? false : true,
|
||||
});
|
||||
|
||||
if (cluster.isMaster) {
|
||||
// Start the front-end dev server
|
||||
if (watchAdmin) {
|
||||
try {
|
||||
execa('npm', ['run', '-s', 'strapi', 'watch-admin'], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
cluster.on('message', (worker, message) => {
|
||||
switch (message) {
|
||||
case 'reload':
|
||||
|
||||
32
packages/strapi/lib/commands/watchAdmin.js
Normal file
32
packages/strapi/lib/commands/watchAdmin.js
Normal file
@ -0,0 +1,32 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
const path = require('path');
|
||||
const strapiAdmin = require('strapi-admin');
|
||||
const _ = require('lodash');
|
||||
|
||||
const loadConfigFile = require('../load/load-config-files');
|
||||
const addSlash = require('../utils/addSlash');
|
||||
|
||||
module.exports = async function() {
|
||||
const dir = process.cwd();
|
||||
const envConfigDir = path.join(dir, 'config', 'environments', 'development');
|
||||
const serverConfig = await loadConfigFile(envConfigDir, 'server.+(js|json)');
|
||||
|
||||
const port = _.get(serverConfig, 'port', 1337);
|
||||
const host = _.get(serverConfig, 'host', 'localhost');
|
||||
const adminPort = _.get(serverConfig, 'admin.port', 8000);
|
||||
const adminBackend = _.get(
|
||||
serverConfig,
|
||||
'admin.build.backend',
|
||||
`http://${host}:${port}`
|
||||
);
|
||||
const adminPath = _.get(serverConfig, 'admin.path', '/admin');
|
||||
|
||||
strapiAdmin.watchAdmin({
|
||||
dir,
|
||||
port: adminPort,
|
||||
options: {
|
||||
backend: adminBackend,
|
||||
publicPath: addSlash(adminPath),
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -58,6 +58,8 @@ module.exports = strapi => {
|
||||
})
|
||||
);
|
||||
|
||||
if (!strapi.config.serveAdminPanel) return;
|
||||
|
||||
const basename = _.get(
|
||||
strapi.config.currentEnvironment.server,
|
||||
'admin.path'
|
||||
|
||||
8
packages/strapi/lib/utils/addSlash.js
Normal file
8
packages/strapi/lib/utils/addSlash.js
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = path => {
|
||||
if (typeof path !== 'string') throw new Error('admin.path must be a string');
|
||||
if (path === '' || path === '/') return '/';
|
||||
|
||||
if (path[0] != '/') path = '/' + path;
|
||||
if (path[path.length - 1] != '/') path = path + '/';
|
||||
return path;
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user