mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-03 19:36:20 +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