mirror of
https://github.com/strapi/strapi.git
synced 2025-12-24 05:34:33 +00:00
Merge branch 'master' of github.com:strapi/strapi
This commit is contained in:
commit
d924db42fc
@ -34,6 +34,9 @@ To facilitate the contribution, we drastically reduce the amount of commands nec
|
||||
|
||||
> Note: If the installation failed, please remove the global packages related to Strapi. The command `npm ls strapi` will help you to find where your packages are installed globally.
|
||||
|
||||
> Note: You can run `npm run setup:build` to build the plugins' admin (the setup time will be longer).
|
||||
|
||||
|
||||
The development environment has been installed. Now, you have to create a development project to live-test your updates.
|
||||
|
||||
1. Go to a folder on your computer `cd /path/to/my/folder`.
|
||||
@ -43,8 +46,9 @@ The development environment has been installed. Now, you have to create a develo
|
||||
Awesome! You are now able to make bug fixes or enhancements in the framework layer of Strapi. **To make updates in the administration panel, you need to go a little bit further.**
|
||||
|
||||
4. Open a new tab or new terminal window.
|
||||
5. Go to the `/admin` folder of your currently running app.
|
||||
6. Run `npm start` and go to the following url [http://localhost:4000/admin](http://localhost:4000/admin)
|
||||
5. Link the `strapi-helper-plugin` into the `analytics` plugin : `cd my-app/plugins/analytics && npm link strapi-helper-plugin`.
|
||||
6. Go to the `my-app/admin` folder of your currently running app.
|
||||
7. Run `npm start` and go to the following url [http://localhost:4000/admin](http://localhost:4000/admin)
|
||||
|
||||
### Plugin Development Setup
|
||||
|
||||
|
||||
@ -59,3 +59,4 @@
|
||||
* [Migrating from 3.0.0-alpha.8 to 3.0.0-alpha.9](migration/migration-guide-alpha-8-to-alpha-9.md)
|
||||
* [Migrating from 3.0.0-alpha.9 to 3.0.0-alpha.10](migration/migration-guide-alpha-9-to-alpha-10.md)
|
||||
* [Migrating from 3.0.0-alpha.10 to 3.0.0-alpha.11](migration/migration-guide-alpha-10-to-alpha-11.md)
|
||||
* [Migrating from 3.0.0-alpha.11 to 3.0.0-alpha.12](migration/migration-guide-alpha-11-to-alpha-12.md)
|
||||
|
||||
@ -12,7 +12,7 @@ The entire logic of the admin panel is located in a single folder named `./admin
|
||||
└─── admin
|
||||
| └─── build // Webpack generated build of the front-end
|
||||
| └─── src // Front-end directory
|
||||
| └─── app.js // Entry point of the Reacr application
|
||||
| └─── app.js // Entry point of the React application
|
||||
| └─── assets // Assets directory containing images,...
|
||||
| └─── components // Admin's React components directory
|
||||
| └─── containers // Admin's high level components directory
|
||||
|
||||
@ -24,8 +24,9 @@ Create a development project
|
||||
To generate a new plugin **run the following commands:**
|
||||
1. In your project folder `cd myDevelopmentProject && strapi generate:plugin my-plugin`.
|
||||
2. Link the `strapi-helper-plugin` dependency in your project folder `cd pathToMyProject/myDevelopmentProject/plugins/my-plugin && npm link strapi-helper-plugin`.
|
||||
3. Start the server in the admin folder `cd pathToMyProject/myDevelopmentProject/admin && npm start` and go to the following url [http://localhost:4000/admin](http://localhost:4000/admin).
|
||||
4. In a new terminal window open at the root of your project launch your Strapi server `strapi start`.
|
||||
3. Link the `strapi-helper-plugin` dependency in the `analytics` plugin folder `cd pathToMyProject/myDevelopmentProject/plugins/analytics && npm link strapi-helper-plugin`.
|
||||
4. Start the server in the admin folder `cd pathToMyProject/myDevelopmentProject/admin && npm start` and go to the following url [http://localhost:4000/admin](http://localhost:4000/admin).
|
||||
5. In a new terminal window open at the root of your project launch your Strapi server `strapi start`.
|
||||
|
||||
|
||||
Your are now ready to develop your own plugin and live-test your updates!
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
.leftMenuFooter { /* stylelint-disable */
|
||||
position: absolute;
|
||||
width: calc(100% - 2 * 15px);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
bottom: 0;
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
{
|
||||
"languages": ["en", "fr", "de", "pl", "tr", "zh"]
|
||||
"languages": ["en", "fr", "de", "pl", "tr", "zh", "zh-Hans"]
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -4,6 +4,7 @@
|
||||
*
|
||||
*/
|
||||
import {
|
||||
GET_CURR_ENV_SUCCEEDED,
|
||||
GET_GA_STATUS,
|
||||
GET_GA_STATUS_SUCCEEDED,
|
||||
GET_LAYOUT,
|
||||
@ -11,6 +12,13 @@ import {
|
||||
GET_STRAPI_VERSION_SUCCEEDED,
|
||||
} from './constants';
|
||||
|
||||
export function getCurrEnvSucceeded(currentEnvironment) {
|
||||
return {
|
||||
type: GET_CURR_ENV_SUCCEEDED,
|
||||
currentEnvironment,
|
||||
};
|
||||
}
|
||||
|
||||
export function getGaStatus() {
|
||||
return {
|
||||
type: GET_GA_STATUS,
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
export const GET_CURR_ENV_SUCCEEDED = 'app/Admin/GET_CURR_ENV_SUCCEEDED';
|
||||
export const GET_GA_STATUS = 'app/Admin/GET_GA_STATUS';
|
||||
export const GET_GA_STATUS_SUCCEEDED = 'app/Admin/GET_GA_STATUS_SUCCEEDED';
|
||||
export const GET_LAYOUT = 'app/Admin/GET_LAYOUT';
|
||||
|
||||
@ -59,6 +59,8 @@ import selectAdminPage from './selectors';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
const PLUGINS_TO_BLOCK_PRODUCTION = ['content-type-builder', 'settings-manager'];
|
||||
|
||||
export class AdminPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
state = { hasAlreadyRegistereOtherPlugins: false };
|
||||
|
||||
@ -136,6 +138,20 @@ export class AdminPage extends React.Component { // eslint-disable-line react/pr
|
||||
|
||||
showLeftMenu = () => !includes(this.props.location.pathname, 'users-permissions/auth/');
|
||||
|
||||
retrievePlugins = () => {
|
||||
const { adminPage: { currentEnvironment }, plugins } = this.props;
|
||||
|
||||
if (currentEnvironment === 'production') {
|
||||
let pluginsToDisplay = plugins;
|
||||
PLUGINS_TO_BLOCK_PRODUCTION.map(plugin =>
|
||||
pluginsToDisplay = pluginsToDisplay.delete(plugin));
|
||||
|
||||
return pluginsToDisplay;
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { adminPage } = this.props;
|
||||
const header = this.showLeftMenu() ? <Header /> : '';
|
||||
@ -145,7 +161,7 @@ export class AdminPage extends React.Component { // eslint-disable-line react/pr
|
||||
<div className={styles.adminPage}>
|
||||
{this.showLeftMenu() && (
|
||||
<LeftMenu
|
||||
plugins={this.props.plugins}
|
||||
plugins={this.retrievePlugins()}
|
||||
layout={adminPage.layout}
|
||||
version={adminPage.strapiVersion}
|
||||
/>
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
import { fromJS, Map } from 'immutable';
|
||||
|
||||
import {
|
||||
GET_CURR_ENV_SUCCEEDED,
|
||||
GET_GA_STATUS_SUCCEEDED,
|
||||
GET_LAYOUT_SUCCEEDED,
|
||||
GET_STRAPI_VERSION_SUCCEEDED,
|
||||
@ -14,12 +15,15 @@ import {
|
||||
|
||||
const initialState = fromJS({
|
||||
allowGa: true,
|
||||
currentEnvironment: 'development',
|
||||
layout: Map({}),
|
||||
strapiVersion: '3',
|
||||
});
|
||||
|
||||
function adminPageReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case GET_CURR_ENV_SUCCEEDED:
|
||||
return state.update('currentEnvironment', () => action.currentEnvironment);
|
||||
case GET_GA_STATUS_SUCCEEDED:
|
||||
return state.update('allowGa', () => action.allowGa);
|
||||
case GET_LAYOUT_SUCCEEDED:
|
||||
|
||||
@ -2,6 +2,7 @@ import { fork, call, put, takeLatest } from 'redux-saga/effects';
|
||||
import request from 'utils/request';
|
||||
|
||||
import {
|
||||
getCurrEnvSucceeded,
|
||||
getGaStatusSucceeded,
|
||||
getLayoutSucceeded,
|
||||
getStrapiVersionSucceeded,
|
||||
@ -10,11 +11,13 @@ import { GET_GA_STATUS, GET_LAYOUT } from './constants';
|
||||
|
||||
function* getGaStatus() {
|
||||
try {
|
||||
const [allowGa, strapiVersion] = 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(getGaStatusSucceeded(allowGa));
|
||||
yield put(getStrapiVersionSucceeded(strapiVersion.strapiVersion));
|
||||
} catch(err) {
|
||||
|
||||
162
packages/strapi-admin/admin/src/translations/zh-Hans.json
Normal file
162
packages/strapi-admin/admin/src/translations/zh-Hans.json
Normal file
@ -0,0 +1,162 @@
|
||||
{
|
||||
"app.components.Button.save": "保存",
|
||||
"app.components.Button.cancel": "取消",
|
||||
|
||||
"app.components.ComingSoonPage.comingSoon": "即将推出",
|
||||
"app.components.ComingSoonPage.featuresNotAvailable": "这个功能只在活跃开发中",
|
||||
|
||||
"app.components.DownloadInfo.download": "正在下载...",
|
||||
"app.components.DownloadInfo.text": "这可能需要几分钟,谢谢你的耐心。",
|
||||
|
||||
"app.components.HomePage.welcome": "欢迎回来",
|
||||
"app.components.HomePage.welcome.again": "欢迎 ",
|
||||
"app.components.HomePage.cta": "请确认",
|
||||
"app.components.HomePage.community": "在网络上找到社区",
|
||||
"app.components.HomePage.newsLetter": "订阅Strapi简讯",
|
||||
"app.components.HomePage.community.content": "与团队成员、贡献者和开发人员在不同的渠道进行讨论。",
|
||||
"app.components.HomePage.create": "创建第一个Content Type",
|
||||
"app.components.HomePage.welcomeBlock.content": "我们很高兴有你成为社区成员之一。我们一直在寻找反馈,所以可以随时给我们发送消息。 on\u0020",
|
||||
"app.components.HomePage.welcomeBlock.content.again": "我们希望你在项目上取得进展。请随意阅读关于Strapi的最新消息。我们将尽最大努力根据您的反馈改进产品。",
|
||||
"app.components.HomePage.welcomeBlock.content.issues": "issues.",
|
||||
"app.components.HomePage.welcomeBlock.content.raise": "\u0020or raise\u0020",
|
||||
"app.components.HomePage.createBlock.content.first": "The\u0020",
|
||||
"app.components.HomePage.createBlock.content.second": "\u0020插件将帮助您定义模型的数据结构。如果你是刚接触Strapi,我们强烈推荐你跟随我们。\u0020",
|
||||
"app.components.HomePage.createBlock.content.tutorial": "\u0020tutorial.",
|
||||
"app.components.HomePage.button.quickStart": "快速入门教程",
|
||||
"app.components.HomePage.button.blog": "在博客上看到更多",
|
||||
"app.components.HomePage.support": "支持我们",
|
||||
"app.components.HomePage.support.content": "通过购买T恤来支持我们,它将允许我们继续我们的项目工作,给你最好的体验!",
|
||||
"app.components.HomePage.support.link": "现在购买T恤",
|
||||
|
||||
"app.components.BlockLink.documentation": "阅读文档",
|
||||
"app.components.BlockLink.documentation.content": "发现基本概念,参考指南和教程。",
|
||||
"app.components.BlockLink.code": "代码示例",
|
||||
"app.components.BlockLink.code.content": "通过测试社区的真实项目来学习。",
|
||||
|
||||
|
||||
"app.components.InputFile.newFile": "增加新文件",
|
||||
"app.components.InputFileDetails.open": "在新选项卡中打开",
|
||||
"app.components.InputFileDetails.remove": "删除这个文件",
|
||||
"app.components.InputFileDetails.originalName": "原名称:",
|
||||
"app.components.InputFileDetails.size": "大小:",
|
||||
|
||||
"app.components.ImgPreview.hint": "将文件拖放到该区域或{browse}以供文件上载",
|
||||
"app.components.ImgPreview.hint.browse": "浏览",
|
||||
|
||||
"app.components.InstallPluginPage.helmet": "市场-插件",
|
||||
"app.components.InstallPluginPage.title": "市场-插件",
|
||||
"app.components.InstallPluginPage.description": "轻松地扩展你的应用程序。",
|
||||
"app.components.InstallPluginPage.plugin.support-us.description": "通过购买Strapi T恤支持我们。这将使我们能够继续致力于这个项目,并尝试给你最好的体验!",
|
||||
"app.components.InstallPluginPage.InputSearch.label": " ",
|
||||
"app.components.InstallPluginPage.InputSearch.placeholder": "搜索插件… (ex: authentication)",
|
||||
"app.components.InstallPluginPopup.downloads": "下载",
|
||||
"app.components.InstallPluginPopup.navLink.description": "描述",
|
||||
"app.components.InstallPluginPopup.navLink.screenshots": "截屏",
|
||||
"app.components.InstallPluginPopup.navLink.avis": "avis",
|
||||
"app.components.InstallPluginPopup.navLink.faq": "faq",
|
||||
"app.components.InstallPluginPopup.navLink.changelog": "更新日志",
|
||||
"app.components.InstallPluginPopup.noDescription": "没有描述",
|
||||
|
||||
"app.components.LeftMenuFooter.poweredBy": "技术支持",
|
||||
"app.components.LeftMenuLinkContainer.configuration": "配置",
|
||||
"app.components.LeftMenuLinkContainer.general": "一般",
|
||||
"app.components.LeftMenuLinkContainer.installNewPlugin": "市场",
|
||||
"app.components.LeftMenuLinkContainer.listPlugins": "插件",
|
||||
"app.components.LeftMenuLinkContainer.noPluginsInstalled": "还没有安装插件",
|
||||
"app.components.LeftMenuLinkContainer.plugins": "插件",
|
||||
|
||||
"app.components.ListPluginsPage.helmet.title": "插件列表",
|
||||
"app.components.ListPluginsPage.title": "插件",
|
||||
"app.components.ListPluginsPage.description": "项目中已安装的插件列表",
|
||||
"app.components.listPluginsPage.deletePlugin.error": "卸载插件时出错",
|
||||
"app.components.listPlugins.title.singular": "{number} 个插件已安装",
|
||||
"app.components.listPlugins.title.plural": "{number} 个插件已安装",
|
||||
"app.components.listPlugins.title.none": "还没有安装插件",
|
||||
"app.components.listPlugins.button": "增加新插件",
|
||||
|
||||
"app.components.NotFoundPage.description": "没有找到",
|
||||
"app.components.NotFoundPage.back": "返回主页",
|
||||
|
||||
"app.components.Official": "官方",
|
||||
|
||||
"app.components.PluginCard.compatible": "与你的应用程序兼容",
|
||||
"app.components.PluginCard.compatibleCommunity": "与社区兼容",
|
||||
"app.components.PluginCard.Button.label.download": "下载",
|
||||
"app.components.PluginCard.Button.label.install": "已下载",
|
||||
"app.components.PluginCard.Button.label.support": "支持我们",
|
||||
"app.components.PluginCard.price.free": "免费",
|
||||
"app.components.PluginCard.more-details": "更多细节",
|
||||
|
||||
"app.utils.placeholder.defaultMessage": "\u0020",
|
||||
"app.utils.SelectOption.defaultMessage": "\u0020",
|
||||
"app.utils.defaultMessage": "\u0020",
|
||||
|
||||
"components.AutoReloadBlocker.header": "这个插件需要重新加载特性。",
|
||||
"components.AutoReloadBlocker.description": "打开下面的文件并启用该特性。",
|
||||
|
||||
"components.ErrorBoundary.title": "哪里出问题了…",
|
||||
|
||||
"components.OverlayBlocker.title": "等待重新启动...",
|
||||
"components.OverlayBlocker.description": "这个功能成需要服务器重新启动。请等到服务器启动。",
|
||||
|
||||
"components.PageFooter.select": "每页条目",
|
||||
|
||||
"components.ProductionBlocker.header": "这个插件只能在开发中使用。",
|
||||
"components.ProductionBlocker.description": "为了安全起见,我们必须在其他环境中禁用这个插件。",
|
||||
|
||||
"components.popUpWarning.button.cancel": "取消",
|
||||
"components.popUpWarning.button.confirm": "确认",
|
||||
"components.popUpWarning.title": "请确认",
|
||||
"components.popUpWarning.message": "确实要删除这个吗?",
|
||||
|
||||
"components.Input.error.validation.email": "这不是电子邮件",
|
||||
"components.Input.error.validation.required": "这个值是必须的",
|
||||
"components.Input.error.validation.regex": "格式不正确",
|
||||
"components.Input.error.validation.max": "超过最大值",
|
||||
"components.Input.error.validation.min": "低于最小值",
|
||||
"components.Input.error.validation.maxLength": "超过最大长度",
|
||||
"components.Input.error.validation.minLength": "低于最小长度",
|
||||
"components.Input.error.contentTypeName.taken": "此名称已经存在",
|
||||
"components.Input.error.attribute.taken": "此字段名称已经存在",
|
||||
"components.Input.error.attribute.key.taken": "此值已经存在",
|
||||
"components.Input.error.attribute.sameKeyAndName": "不能相等",
|
||||
"components.Input.error.validation.minSupMax": "最小值超过最大值了",
|
||||
"components.Input.error.custom-error": "{errorMessage} ",
|
||||
|
||||
"components.ListRow.empty": "没有要显示的数据",
|
||||
|
||||
"components.Wysiwyg.collapse": "折叠",
|
||||
"components.Wysiwyg.selectOptions.title": "增加一个标题",
|
||||
"components.Wysiwyg.selectOptions.H1": "Title H1",
|
||||
"components.Wysiwyg.selectOptions.H2": "Title H2",
|
||||
"components.Wysiwyg.selectOptions.H3": "Title H3",
|
||||
"components.Wysiwyg.selectOptions.H4": "Title H4",
|
||||
"components.Wysiwyg.selectOptions.H5": "Title H5",
|
||||
"components.Wysiwyg.selectOptions.H6": "Title H6",
|
||||
"components.Wysiwyg.ToggleMode.markdown": "编辑",
|
||||
"components.Wysiwyg.ToggleMode.preview": "预览",
|
||||
"components.WysiwygBottomControls.charactersIndicators": "characters",
|
||||
"components.WysiwygBottomControls.uploadFiles": "拖放文件,从剪贴板粘贴或 {browse}.",
|
||||
"components.WysiwygBottomControls.uploadFiles.browse": "从文件夹选取",
|
||||
"components.WysiwygBottomControls.fullscreen": "最大化",
|
||||
|
||||
"HomePage.notification.newsLetter.success": "成功订阅简讯",
|
||||
|
||||
"notification.error": "发生了一个错误",
|
||||
"notification.error.layout": "无法获取布局",
|
||||
|
||||
"Users & Permissions": "用户 & 权限",
|
||||
"Content Manager": "内容管理",
|
||||
"Content Type Builder": "内容类型生成器",
|
||||
"Settings Manager": "管理设置",
|
||||
"Email": "邮件",
|
||||
"Password": "密码",
|
||||
"Username": "用户名",
|
||||
"Provider": "供应商",
|
||||
"ResetPasswordToken": "密码重置",
|
||||
"Role": "角色",
|
||||
"New entry": "新入口",
|
||||
"request.error.model.unknow": "这个模型已不存在",
|
||||
"Users": "用户",
|
||||
"Analytics": "分析"
|
||||
}
|
||||
@ -10,17 +10,20 @@
|
||||
"analyze:clean": "node ./node_modules/strapi-helper-plugin/node_modules/.bin/rimraf stats.json",
|
||||
"preanalyze": "npm run analyze:clean",
|
||||
"analyze": "node ./node_modules/strapi-helper-plugin/lib/internals/scripts/analyze.js",
|
||||
"prebuild": "APP_PATH=$APP_PATH node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=production IS_ADMIN=true node ./node_modules/strapi-helper-plugin/lib/internals/scripts/loadAdminConfigurations.js",
|
||||
"build:dev": "npm run build:dll && node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=development IS_ADMIN=true node ./node_modules/strapi-helper-plugin/node_modules/.bin/webpack --config ./node_modules/strapi-helper-plugin/lib/internals/webpack/webpack.prod.babel.js --color -p --progress",
|
||||
"build": "APP_PATH=$APP_PATH npm run build:dll && node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=production IS_ADMIN=true node ./node_modules/strapi-helper-plugin/node_modules/.bin/webpack --config ./node_modules/strapi-helper-plugin/lib/internals/webpack/webpack.prod.babel.js --color -p --progress",
|
||||
"build:dll": "APP_PATH=$APP_PATH node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=production IS_ADMIN=true node ./node_modules/strapi-helper-plugin/node_modules/.bin/webpack --config ./node_modules/strapi-helper-plugin/lib/internals/webpack/webpack.dll.babel.js --color -p --progress",
|
||||
"build:clean": "node ./node_modules/strapi-helper-plugin/node_modules/.bin/rimraf admin/build",
|
||||
"prestart": "node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=development PORT=4000 IS_ADMIN=true node ./node_modules/strapi-helper-plugin/lib/internals/scripts/loadAdminConfigurations.js",
|
||||
"start": "node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=development PORT=4000 IS_ADMIN=true node ./node_modules/strapi-helper-plugin/lib/server",
|
||||
"generate": "node ./node_modules/strapi-helper-plugin/node_modules/.bin/plop --plopfile ./node_modules/strapi-helper-plugin/lib/internals/generators/index.js",
|
||||
"lint": "node ./node_modules/strapi-helper-plugin/node_modules/.bin/eslint --ignore-path ./admin/.gitignore --ignore-pattern build --config ./node_modules/strapi-helper-plugin/lib/internals/eslint/.eslintrc.json admin",
|
||||
"prettier": "node ./node_modules/strapi-helper-plugin/node_modules/.bin/prettier --single-quote --trailing-comma es5 --write \"{admin,__{tests,mocks}__}/**/*.js\"",
|
||||
"test": "npm run lint",
|
||||
"prepublishOnly": "npm run build",
|
||||
"setup": "node ./scripts/setup.js"
|
||||
"setup": "node ./scripts/setup.js",
|
||||
"presetup": "node ./scripts/preSetup.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-ga": "^2.4.1",
|
||||
|
||||
43
packages/strapi-admin/scripts/preSetup.js
Normal file
43
packages/strapi-admin/scripts/preSetup.js
Normal file
@ -0,0 +1,43 @@
|
||||
const shell = require('shelljs');
|
||||
const path = require('path');
|
||||
const _ = require('lodash');
|
||||
|
||||
shell.echo('');
|
||||
shell.echo('🕓 The setup process can take few minutes.');
|
||||
shell.echo('');
|
||||
shell.echo('🔸 Administration Panel');
|
||||
shell.echo('📦 Installing packages...');
|
||||
|
||||
const pwd = shell.pwd();
|
||||
|
||||
const silent = process.env.npm_config_debug !== 'true';
|
||||
const isDevelopmentMode = path.resolve(pwd.stdout).indexOf('strapi-admin') !== -1;
|
||||
const appPath = isDevelopmentMode ? path.resolve(process.env.PWD, '..') : path.resolve(pwd.stdout, '..');
|
||||
|
||||
// We just install the admin's dependencies here
|
||||
|
||||
// Remove package-lock.json.
|
||||
shell.rm('-rf', path.resolve(appPath, 'package-lock.json'));
|
||||
shell.rm('-rf', path.resolve(appPath, 'admin', 'package-lock.json'));
|
||||
|
||||
// Install the project dependencies.
|
||||
shell.exec(`cd "${appPath}" && npm install --ignore-scripts`, {
|
||||
silent
|
||||
});
|
||||
|
||||
// Install the administration dependencies.
|
||||
shell.exec(`cd "${path.resolve(appPath, 'admin')}" && npm install`, {
|
||||
silent
|
||||
});
|
||||
|
||||
if (isDevelopmentMode) {
|
||||
shell.exec(`cd "${path.resolve(appPath, 'admin')}" && npm link strapi-helper-plugin && npm link strapi-utils`, {
|
||||
silent
|
||||
});
|
||||
} else {
|
||||
shell.exec(`cd "${path.resolve(appPath, 'admin', 'node_modules', 'strapi-helper-plugin')}" && npm install`, {
|
||||
silent
|
||||
});
|
||||
}
|
||||
|
||||
shell.echo('Packaged installed successfully');
|
||||
@ -1,44 +1,15 @@
|
||||
const fs = require('fs');
|
||||
const shell = require('shelljs');
|
||||
const path = require('path');
|
||||
const _ = require('lodash');
|
||||
|
||||
shell.echo('');
|
||||
shell.echo('🕓 The setup process can take few minutes.');
|
||||
shell.echo('');
|
||||
shell.echo('🔸 Administration Panel');
|
||||
shell.echo('📦 Installing packages...');
|
||||
|
||||
const pwd = shell.pwd();
|
||||
|
||||
const silent = process.env.npm_config_debug !== 'true';
|
||||
const isDevelopmentMode = path.resolve(pwd.stdout).indexOf('strapi-admin') !== -1;
|
||||
const appPath = isDevelopmentMode ? path.resolve(process.env.PWD, '..') : path.resolve(pwd.stdout, '..');
|
||||
|
||||
// Remove package-lock.json.
|
||||
shell.rm('-rf', path.resolve(appPath, 'package-lock.json'));
|
||||
shell.rm('-rf', path.resolve(appPath, 'admin', 'package-lock.json'));
|
||||
|
||||
// Install the project dependencies.
|
||||
shell.exec(`cd "${appPath}" && npm install --ignore-scripts`, {
|
||||
silent
|
||||
});
|
||||
|
||||
// Install the administration dependencies.
|
||||
shell.exec(`cd "${path.resolve(appPath, 'admin')}" && npm install`, {
|
||||
silent
|
||||
});
|
||||
|
||||
if (isDevelopmentMode) {
|
||||
shell.exec(`cd "${path.resolve(appPath, 'admin')}" && npm link strapi-helper-plugin && npm link strapi-utils`, {
|
||||
silent
|
||||
});
|
||||
} else {
|
||||
shell.exec(`cd "${path.resolve(appPath, 'admin', 'node_modules', 'strapi-helper-plugin')}" && npm install`, {
|
||||
silent
|
||||
});
|
||||
}
|
||||
|
||||
shell.echo('🏗 Building...');
|
||||
shell.echo('🏗 Building the admin...');
|
||||
|
||||
const build = shell.exec(`cd "${path.resolve(appPath, 'admin')}" && APP_PATH="${appPath}" npm run build`, {
|
||||
silent
|
||||
@ -55,35 +26,49 @@ shell.echo('');
|
||||
if (process.env.npm_config_plugins === 'true') {
|
||||
const plugins = path.resolve(appPath, 'plugins');
|
||||
|
||||
shell.ls('* -d', plugins).forEach(function (plugin) {
|
||||
shell.echo(`🔸 Plugin - ${_.upperFirst(plugin)}`);
|
||||
shell.echo('📦 Installing packages...');
|
||||
shell.exec(`cd "${path.resolve(plugins, plugin)}" && npm install`, {
|
||||
silent
|
||||
});
|
||||
// TODO: build plugins in async
|
||||
shell.ls('* -d', plugins)
|
||||
.filter(x => {
|
||||
let hasAdminFolder;
|
||||
|
||||
if (isDevelopmentMode) {
|
||||
shell.exec(`cd "${path.resolve(plugins, plugin)}" && npm link strapi-helper-plugin`, {
|
||||
try {
|
||||
fs.accessSync(path.resolve(appPath, 'plugins', x, 'admin', 'src', 'containers', 'App'));
|
||||
hasAdminFolder = true;
|
||||
} catch(err) {
|
||||
hasAdminFolder = false;
|
||||
}
|
||||
|
||||
return hasAdminFolder;
|
||||
})
|
||||
.forEach(function (plugin) {
|
||||
shell.echo(`🔸 Plugin - ${_.upperFirst(plugin)}`);
|
||||
shell.echo('📦 Installing packages...');
|
||||
shell.exec(`cd "${path.resolve(plugins, plugin)}" && npm install`, {
|
||||
silent
|
||||
});
|
||||
} else {
|
||||
shell.exec(`cd "${path.resolve(plugins, plugin, 'node_modules', 'strapi-helper-plugin')}" && npm install`, {
|
||||
|
||||
if (isDevelopmentMode) {
|
||||
shell.exec(`cd "${path.resolve(plugins, plugin)}" && npm link strapi-helper-plugin`, {
|
||||
silent
|
||||
});
|
||||
} else {
|
||||
shell.exec(`cd "${path.resolve(plugins, plugin, 'node_modules', 'strapi-helper-plugin')}" && npm install`, {
|
||||
silent
|
||||
});
|
||||
}
|
||||
|
||||
shell.echo('🏗 Building...');
|
||||
|
||||
const build = shell.exec(`cd "${path.resolve(plugins, plugin)}" && APP_PATH="${appPath}" npm run build`, {
|
||||
silent
|
||||
});
|
||||
}
|
||||
|
||||
shell.echo('🏗 Building...');
|
||||
if (build.stderr && build.code !== 0) {
|
||||
console.error(build.stderr);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const build = shell.exec(`cd "${path.resolve(plugins, plugin)}" && APP_PATH="${appPath}" npm run build`, {
|
||||
silent
|
||||
shell.echo('✅ Success');
|
||||
shell.echo('');
|
||||
});
|
||||
|
||||
if (build.stderr && build.code !== 0) {
|
||||
console.error(build.stderr);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
shell.echo('✅ Success');
|
||||
shell.echo('');
|
||||
});
|
||||
}
|
||||
|
||||
@ -40,9 +40,11 @@ module.exports = function(strapi) {
|
||||
* Initialize the hook
|
||||
*/
|
||||
|
||||
initialize: cb => {
|
||||
initialize: async cb => {
|
||||
const connections = _.pickBy(strapi.config.connections, { connector: 'strapi-bookshelf' });
|
||||
|
||||
const databaseUpdate = [];
|
||||
|
||||
_.forEach(connections, (connection, connectionName) => {
|
||||
// Apply defaults
|
||||
_.defaults(connection.settings, strapi.config.hook.settings.bookshelf);
|
||||
@ -307,6 +309,233 @@ module.exports = function(strapi) {
|
||||
// Push attributes to be aware of model schema.
|
||||
target[model]._attributes = definition.attributes;
|
||||
|
||||
databaseUpdate.push(new Promise(async (resolve) => {
|
||||
// Equilize database tables
|
||||
const handler = async (table, attributes) => {
|
||||
const tableExist = await ORM.knex.schema.hasTable(table);
|
||||
|
||||
const getType = (attribute, name) => {
|
||||
let type;
|
||||
|
||||
if (!attribute.type) {
|
||||
// Add integer value if there is a relation
|
||||
const relation = definition.associations.find((association) => {
|
||||
return association.alias === name;
|
||||
});
|
||||
|
||||
switch (relation.nature) {
|
||||
case 'oneToOne':
|
||||
case 'manyToOne':
|
||||
type = definition.client === 'pg' ? 'integer' : 'int';
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
switch (attribute.type) {
|
||||
case 'text':
|
||||
case 'json':
|
||||
type = 'text';
|
||||
break;
|
||||
case 'string':
|
||||
case 'password':
|
||||
case 'email':
|
||||
type = 'varchar(255)';
|
||||
break;
|
||||
case 'integer':
|
||||
case 'biginteger':
|
||||
type = definition.client === 'pg' ? 'integer' : 'int';
|
||||
break;
|
||||
case 'float':
|
||||
case 'decimal':
|
||||
type = attribute.type;
|
||||
break;
|
||||
case 'date':
|
||||
case 'time':
|
||||
case 'datetime':
|
||||
case 'timestamp':
|
||||
type = definition.client === 'pg' ? 'timestamp with time zone' : 'timestamp DEFAULT CURRENT_TIMESTAMP';
|
||||
break;
|
||||
case 'timestampUpdate':
|
||||
type = definition.client === 'pg' ? 'timestamp with time zone' : 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP';
|
||||
break;
|
||||
case 'boolean':
|
||||
type = 'boolean';
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
};
|
||||
|
||||
// Apply field type of attributes definition
|
||||
const generateColumns = (attrs, start) => {
|
||||
return Object.keys(attrs).reduce((acc, attr) => {
|
||||
const attribute = attributes[attr];
|
||||
|
||||
const type = getType(attribute, attr);
|
||||
|
||||
if (type) {
|
||||
acc.push(`${quote}${attr}${quote} ${type}`);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, start);
|
||||
};
|
||||
|
||||
if (!tableExist) {
|
||||
const columns = generateColumns(attributes, [`id ${definition.client === 'pg' ? 'SERIAL' : 'INT AUTO_INCREMENT'} NOT NULL PRIMARY KEY`]).join(',\n\r');
|
||||
|
||||
// Create table
|
||||
await ORM.knex.raw(`
|
||||
CREATE TABLE ${quote}${table}${quote} (
|
||||
${columns}
|
||||
)
|
||||
`);
|
||||
} else {
|
||||
const columns = Object.keys(attributes);
|
||||
|
||||
// Fetch existing column
|
||||
const columnsExist = await Promise.all(columns.map(attribute =>
|
||||
ORM.knex.schema.hasColumn(table, attribute)
|
||||
));
|
||||
|
||||
const columnsToAdd = {};
|
||||
|
||||
// Get columns to add
|
||||
columnsExist.forEach((columnExist, index) => {
|
||||
const attribute = attributes[columns[index]];
|
||||
|
||||
if (!columnExist) {
|
||||
columnsToAdd[columns[index]] = attribute;
|
||||
}
|
||||
});
|
||||
|
||||
// Generate and execute query to add missing column
|
||||
if (Object.keys(columnsToAdd).length > 0) {
|
||||
const columns = generateColumns(columnsToAdd, []);
|
||||
const queries = columns.reduce((acc, attribute) => {
|
||||
acc.push(`ALTER TABLE ${quote}${table}${quote} ADD ${attribute};`)
|
||||
return acc;
|
||||
}, []).join('\n\r');
|
||||
|
||||
await ORM.knex.raw(queries);
|
||||
}
|
||||
|
||||
// Execute query to update column type
|
||||
await Promise.all(columns.map(attribute =>
|
||||
new Promise(async (resolve) => {
|
||||
const type = getType(attributes[attribute], attribute);
|
||||
|
||||
if (type) {
|
||||
const changeType = definition.client === 'pg'
|
||||
? `ALTER COLUMN ${quote}${attribute}${quote} TYPE ${type} USING ${quote}${attribute}${quote}::${type}`
|
||||
: `CHANGE ${quote}${attribute}${quote} ${quote}${attribute}${quote} ${type} `;
|
||||
|
||||
const changeRequired = definition.client === 'pg'
|
||||
? `ALTER COLUMN ${quote}${attribute}${quote} ${attributes[attribute].required ? 'SET' : 'DROP'} NOT NULL`
|
||||
: `CHANGE ${quote}${attribute}${quote} ${quote}${attribute}${quote} ${type} ${attributes[attribute].required ? 'NOT' : ''} NULL`;
|
||||
|
||||
await ORM.knex.raw(`ALTER TABLE ${quote}${table}${quote} ${changeType}`);
|
||||
await ORM.knex.raw(`ALTER TABLE ${quote}${table}${quote} ${changeRequired}`);
|
||||
}
|
||||
|
||||
resolve();
|
||||
})
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
const quote = definition.client === 'pg' ? '"' : '`';
|
||||
|
||||
// Add created_at and updated_at field if timestamp option is true
|
||||
if (loadedModel.hasTimestamps) {
|
||||
definition.attributes['created_at'] = {
|
||||
type: 'timestamp'
|
||||
};
|
||||
definition.attributes['updated_at'] = {
|
||||
type: 'timestampUpdate'
|
||||
};
|
||||
}
|
||||
|
||||
// Equilize tables
|
||||
await handler(loadedModel.tableName, definition.attributes);
|
||||
|
||||
// Equilize polymorphic releations
|
||||
const morphRelations = definition.associations.find((association) => {
|
||||
return association.nature.toLowerCase().includes('morphto');
|
||||
});
|
||||
|
||||
if (morphRelations) {
|
||||
const attributes = {
|
||||
[`${loadedModel.tableName}_id`]: {
|
||||
type: 'integer'
|
||||
},
|
||||
[`${morphRelations.alias}_id`]: {
|
||||
type: 'integer'
|
||||
},
|
||||
[`${morphRelations.alias}_type`]: {
|
||||
type: 'text'
|
||||
},
|
||||
[definition.attributes[morphRelations.alias].filter]: {
|
||||
type: 'text'
|
||||
}
|
||||
};
|
||||
|
||||
await handler(`${loadedModel.tableName}_morph`, attributes);
|
||||
}
|
||||
|
||||
// Equilize many to many releations
|
||||
const manyRelations = definition.associations.find((association) => {
|
||||
return association.nature === 'manyToMany';
|
||||
});
|
||||
|
||||
if (manyRelations && manyRelations.dominant) {
|
||||
const collection = manyRelations.plugin ?
|
||||
strapi.plugins[manyRelations.plugin].models[manyRelations.collection]:
|
||||
strapi.models[manyRelations.collection];
|
||||
|
||||
const attributes = {
|
||||
[`${pluralize.singular(manyRelations.collection)}_id`]: {
|
||||
type: 'integer'
|
||||
},
|
||||
[`${pluralize.singular(definition.globalId.toLowerCase())}_id`]: {
|
||||
type: 'integer'
|
||||
}
|
||||
};
|
||||
|
||||
const table = _.get(manyRelations, 'collectionName') ||
|
||||
_.map(
|
||||
_.sortBy(
|
||||
[
|
||||
collection.attributes[
|
||||
manyRelations.via
|
||||
],
|
||||
manyRelations
|
||||
],
|
||||
'collection'
|
||||
),
|
||||
table => {
|
||||
return _.snakeCase(
|
||||
pluralize.plural(table.collection) +
|
||||
' ' +
|
||||
pluralize.plural(table.via)
|
||||
);
|
||||
}
|
||||
).join('__');
|
||||
|
||||
await handler(table, attributes);
|
||||
}
|
||||
|
||||
// Remove from attributes (auto handled by bookshlef and not displayed on ctb)
|
||||
if (loadedModel.hasTimestamps) {
|
||||
delete definition.attributes['created_at'];
|
||||
delete definition.attributes['updated_at'];
|
||||
}
|
||||
|
||||
resolve();
|
||||
}));
|
||||
} catch (err) {
|
||||
strapi.log.error('Impossible to register the `' + model + '` model.');
|
||||
strapi.log.error(err);
|
||||
@ -602,6 +831,8 @@ module.exports = function(strapi) {
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(databaseUpdate);
|
||||
|
||||
cb();
|
||||
},
|
||||
|
||||
|
||||
@ -4,6 +4,9 @@
|
||||
const execSync = require('child_process').execSync;
|
||||
const path = require('path');
|
||||
|
||||
// Public node modules
|
||||
const inquirer = require('inquirer');
|
||||
|
||||
// Logger.
|
||||
const logger = require('strapi-utils').logger;
|
||||
|
||||
@ -17,15 +20,45 @@ module.exports = (scope, success, error) => {
|
||||
|
||||
knex.raw('select 1+1 as result').then(() => {
|
||||
logger.info('The app has been connected to the database successfully');
|
||||
knex.destroy();
|
||||
execSync(`rm -r "${scope.tmpPath}"`);
|
||||
|
||||
logger.info('Copying the dashboard...');
|
||||
knex.raw(scope.client.database === 'postgres' ? "SELECT tablename FROM pg_tables WHERE schemaname='public'" : 'SELECT * FROM information_schema.tables').then((tables) => {
|
||||
knex.destroy();
|
||||
|
||||
success();
|
||||
const next = () => {
|
||||
execSync(`rm -r "${scope.tmpPath}"`);
|
||||
|
||||
logger.info('Copying the dashboard...');
|
||||
|
||||
success();
|
||||
};
|
||||
|
||||
if (tables.rows.length !== 0) {
|
||||
logger.warn('It seems that your database is not empty. Be aware that Strapi is going to automatically creates tables & columns, and might update columns which can corrupt data or cause data loss.');
|
||||
|
||||
inquirer.prompt([{
|
||||
type: 'confirm',
|
||||
prefix: '',
|
||||
name: 'confirm',
|
||||
message: `Are you sure you want to continue with the ${scope.database.settings.database} database:`,
|
||||
}])
|
||||
.then(({confirm}) => {
|
||||
if (confirm) {
|
||||
next();
|
||||
} else {
|
||||
error();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
logger.warn('Database connection has failed! Make sure your database is running.');
|
||||
.catch((err) => {
|
||||
if (err.sql) {
|
||||
logger.warn('Server connection has failed! Make sure your database server is running.');
|
||||
} else {
|
||||
logger.warn(`Database connection has failed! Make sure your "${scope.database.settings.database}" database exist.`);
|
||||
}
|
||||
error();
|
||||
});
|
||||
};
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
"main": "./lib",
|
||||
"dependencies": {
|
||||
"bookshelf": "^0.12.1",
|
||||
"inquirer": "^5.2.0",
|
||||
"lodash": "^4.17.4",
|
||||
"pluralize": "^6.0.0",
|
||||
"strapi-knex": "3.0.0-alpha.12",
|
||||
|
||||
@ -83,21 +83,6 @@ module.exports = (scope, cb) => {
|
||||
connector: 'strapi-bookshelf',
|
||||
module: 'mysql'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Sqlite3',
|
||||
value: {
|
||||
database: 'sqlite3',
|
||||
connector: 'strapi-bookshelf',
|
||||
module: 'sqlite3'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Redis',
|
||||
value: {
|
||||
database: 'redis',
|
||||
connector: 'strapi-redis'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@ -169,9 +154,7 @@ module.exports = (scope, cb) => {
|
||||
const ports = {
|
||||
mongo: 27017,
|
||||
postgres: 5432,
|
||||
mysql: 3306,
|
||||
sqlite3: 1433,
|
||||
redis: 6379
|
||||
mysql: 3306
|
||||
};
|
||||
|
||||
return ports[scope.client.database];
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
const path = require('path');
|
||||
const shell = require('shelljs');
|
||||
|
||||
const pwd = shell.pwd();
|
||||
const isDevelopmentMode = path.resolve(pwd.stdout).indexOf('strapi-admin') !== -1;
|
||||
const appPath = isDevelopmentMode ? path.resolve(process.env.PWD, '..') : path.resolve(pwd.stdout, '..');
|
||||
const isSetup = path.resolve(process.env.PWD, '..', '..') === path.resolve(process.env.INIT_CWD);
|
||||
|
||||
// Load the app configurations only when :
|
||||
// - starting the app in dev mode
|
||||
// - building the admin from an existing app (`npm run setup` at the root of the project)
|
||||
if (!isSetup) {
|
||||
const strapi = require(path.join(appPath, 'node_modules', 'strapi'));
|
||||
strapi.config.appPath = appPath;
|
||||
strapi.log.level = 'silent';
|
||||
|
||||
(async () => {
|
||||
await strapi.load({
|
||||
environment: process.env.NODE_ENV,
|
||||
});
|
||||
})();
|
||||
}
|
||||
@ -12,6 +12,8 @@ const pkg = require(path.resolve(process.cwd(), 'package.json'));
|
||||
const pluginId = pkg.name.replace(/^strapi-/i, '');
|
||||
const isAdmin = process.env.IS_ADMIN === 'true';
|
||||
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
|
||||
const appPath = (() => {
|
||||
if (process.env.APP_PATH) {
|
||||
return process.env.APP_PATH;
|
||||
@ -28,25 +30,6 @@ const adminPath = (() => {
|
||||
return path.resolve(process.env.PWD);
|
||||
})();
|
||||
|
||||
if (!isSetup) {
|
||||
try {
|
||||
// Load app' configurations to update `plugins.json` automatically.
|
||||
const strapi = require(path.join(appPath, 'node_modules', 'strapi'));
|
||||
|
||||
strapi.config.appPath = appPath;
|
||||
strapi.log.level = 'silent';
|
||||
|
||||
(async () => {
|
||||
await strapi.load({
|
||||
environment: process.env.NODE_ENV,
|
||||
});
|
||||
})();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
throw new Error(`You need to start the WebPack server from the /admin or /plugins/**/admin directories in a Strapi's project.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Define remote and backend URLs.
|
||||
const URLs = {
|
||||
host: '/admin',
|
||||
@ -100,7 +83,9 @@ if (process.env.npm_lifecycle_event === 'start') {
|
||||
plugins.exist = true;
|
||||
}
|
||||
|
||||
// Read `plugins` directory and check if the plugin comes with an UI.
|
||||
// Read `plugins` directory and check if the plugin comes with an UI (it has an App container).
|
||||
// If we don't do this check webpack expects the plugin to have a containers/App/reducer.js to create
|
||||
// the plugin's store (redux).
|
||||
plugins.src = isAdmin && !plugins.exist ? fs.readdirSync(path.resolve(appPath, 'plugins')).filter(x => {
|
||||
let hasAdminFolder;
|
||||
|
||||
@ -121,166 +106,221 @@ if (process.env.npm_lifecycle_event === 'start') {
|
||||
}, {});
|
||||
}
|
||||
|
||||
module.exports = (options) => ({
|
||||
entry: options.entry,
|
||||
output: Object.assign({ // Compile into js/build.js
|
||||
path: path.join(adminPath, 'admin', 'build'),
|
||||
}, options.output), // Merge with env dependent settings
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.js$/, // Transform all .js files required somewhere with Babel,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: options.babelPresets,
|
||||
env: {
|
||||
production: {
|
||||
only: [
|
||||
'src',
|
||||
],
|
||||
plugins: [
|
||||
require.resolve('babel-plugin-transform-react-remove-prop-types'),
|
||||
require.resolve('babel-plugin-transform-react-constant-elements'),
|
||||
require.resolve('babel-plugin-transform-react-inline-elements'),
|
||||
require.resolve('babel-plugin-transform-es2015-destructuring'),
|
||||
require.resolve('babel-plugin-transform-es2015-parameters'),
|
||||
require.resolve('babel-plugin-transform-object-rest-spread'),
|
||||
],
|
||||
},
|
||||
test: {
|
||||
plugins: [
|
||||
'istanbul',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
include: [path.join(adminPath, 'admin', 'src')]
|
||||
.concat(plugins.src.reduce((acc, current) => {
|
||||
acc.push(path.resolve(appPath, 'plugins', current, 'admin', 'src'), plugins.folders[current]);
|
||||
// Tell webpack to use a loader only for those files
|
||||
const foldersToInclude = [path.join(adminPath, 'admin', 'src')]
|
||||
.concat(plugins.src.reduce((acc, current) => {
|
||||
acc.push(path.resolve(appPath, 'plugins', current, 'admin', 'src'), plugins.folders[current]);
|
||||
|
||||
return acc;
|
||||
}, []))
|
||||
.concat([path.join(adminPath, 'node_modules', 'strapi-helper-plugin', 'lib', 'src')]),
|
||||
}, {
|
||||
// Transform our own .scss files
|
||||
test: /\.scss$/,
|
||||
use: [{
|
||||
loader: 'style-loader',
|
||||
}, {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
localIdentName: `${pluginId}[local]__[path][name]__[hash:base64:5]`,
|
||||
modules: true,
|
||||
importLoaders: 1,
|
||||
sourceMap: true,
|
||||
minimize: process.env.NODE_ENV === 'production',
|
||||
},
|
||||
}, {
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
config: {
|
||||
path: path.resolve(__dirname, '..', 'postcss', 'postcss.config.js'),
|
||||
},
|
||||
},
|
||||
}, {
|
||||
loader: 'sass-loader',
|
||||
}],
|
||||
}, {
|
||||
// Do not transform vendor's CSS with CSS-modules
|
||||
// The point is that they remain in global scope.
|
||||
// Since we require these CSS files in our JS or CSS files,
|
||||
// they will be a part of our compilation either way.
|
||||
// So, no need for ExtractTextPlugin here.
|
||||
test: /\.css$/,
|
||||
include: /node_modules/,
|
||||
loaders: ['style-loader', {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
minimize: process.env.NODE_ENV === 'production',
|
||||
sourceMap: true,
|
||||
},
|
||||
}],
|
||||
}, {
|
||||
test: /\.(eot|svg|ttf|woff|woff2)$/,
|
||||
loader: 'file-loader',
|
||||
}, {
|
||||
test: /\.(jpg|png|gif)$/,
|
||||
loaders: [
|
||||
'file-loader',
|
||||
return acc;
|
||||
}, []))
|
||||
.concat([path.join(adminPath, 'node_modules', 'strapi-helper-plugin', 'lib', 'src')]);
|
||||
|
||||
module.exports = (options) => {
|
||||
// The disable option is only for production
|
||||
// Config from https://github.com/facebook/create-react-app/blob/next/packages/react-scripts/config/webpack.config.prod.js
|
||||
const extractSass = new ExtractTextPlugin({
|
||||
filename: '[name].[contenthash].css',
|
||||
disable: options.disableExtractTextPlugin || true,
|
||||
});
|
||||
|
||||
return {
|
||||
entry: options.entry,
|
||||
output: Object.assign({ // Compile into js/build.js
|
||||
path: path.join(adminPath, 'admin', 'build'),
|
||||
}, options.output), // Merge with env dependent settings
|
||||
module: {
|
||||
rules: [ // TODO: add eslint formatter
|
||||
{
|
||||
loader: 'image-webpack-loader',
|
||||
query: {
|
||||
mozjpeg: {
|
||||
progressive: true,
|
||||
// "oneOf" will traverse all following loaders until one will
|
||||
// match the requirements. When no loader matches it will fall
|
||||
// back to the "file" loader at the end of the loader list.
|
||||
oneOf: [
|
||||
{
|
||||
test: /\.js$/, // Transform all .js files required somewhere with Babel,
|
||||
loader: require.resolve('babel-loader'),
|
||||
include: foldersToInclude,
|
||||
options: {
|
||||
presets: options.babelPresets,
|
||||
env: {
|
||||
production: {
|
||||
only: [
|
||||
'src',
|
||||
],
|
||||
plugins: [
|
||||
require.resolve('babel-plugin-transform-react-remove-prop-types'),
|
||||
require.resolve('babel-plugin-transform-react-constant-elements'),
|
||||
require.resolve('babel-plugin-transform-react-inline-elements'),
|
||||
require.resolve('babel-plugin-transform-es2015-destructuring'),
|
||||
require.resolve('babel-plugin-transform-es2015-parameters'),
|
||||
require.resolve('babel-plugin-transform-object-rest-spread'),
|
||||
],
|
||||
},
|
||||
test: {
|
||||
plugins: [
|
||||
'istanbul',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
gifsicle: {
|
||||
interlaced: false,
|
||||
// The notation here is somewhat confusing.
|
||||
// "postcss" loader applies autoprefixer to our CSS.
|
||||
// "css" loader resolves paths in CSS and adds assets as dependencies.
|
||||
// "style" loader normally turns CSS into JS modules injecting <style>,
|
||||
// but unlike in development configuration, we do something different.
|
||||
// `ExtractTextPlugin` first applies the "postcss" and "css" loaders
|
||||
// (second argument), then grabs the result CSS and puts it into a
|
||||
// separate file in our build process. This way we actually ship
|
||||
// a single CSS file in production instead of JS code injecting <style>
|
||||
// tags. If you use code splitting, however, any async bundles will still
|
||||
// use the "style" loader inside the async code so CSS from them won't be
|
||||
// in the main CSS file.
|
||||
{
|
||||
test: /\.css$/,
|
||||
include: /node_modules/,
|
||||
use: extractSass.extract({
|
||||
fallback: require.resolve('style-loader'),
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('css-loader'),
|
||||
options: {
|
||||
minimize: process.env.NODE_ENV === 'production',
|
||||
sourceMap: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve('postcss-loader'),
|
||||
options: {
|
||||
config: {
|
||||
path: path.resolve(__dirname, '..', 'postcss', 'postcss.config.js'),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
optipng: {
|
||||
optimizationLevel: 4,
|
||||
{
|
||||
test: /\.scss$/,
|
||||
include: foldersToInclude,
|
||||
use: extractSass.extract({
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('css-loader'),
|
||||
options: {
|
||||
localIdentName: `${pluginId}[local]__[path][name]__[hash:base64:5]`,
|
||||
modules: true,
|
||||
importLoaders: 1,
|
||||
sourceMap: false,
|
||||
minimize: process.env.NODE_ENV === 'production',
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve('postcss-loader'),
|
||||
options: {
|
||||
config: {
|
||||
path: path.resolve(__dirname, '..', 'postcss', 'postcss.config.js'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
},
|
||||
],
|
||||
fallback: require.resolve('style-loader'),
|
||||
}),
|
||||
},
|
||||
pngquant: {
|
||||
quality: '65-90',
|
||||
speed: 4,
|
||||
{
|
||||
test: /\.(eot|svg|otf|ttf|woff|woff2)$/,
|
||||
use: 'file-loader',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(jpg|png|gif)$/,
|
||||
loaders: [
|
||||
require.resolve('file-loader'),
|
||||
{
|
||||
loader: require.resolve('image-webpack-loader'),
|
||||
query: {
|
||||
mozjpeg: {
|
||||
progressive: true,
|
||||
},
|
||||
gifsicle: {
|
||||
interlaced: false,
|
||||
},
|
||||
optipng: {
|
||||
optimizationLevel: 4,
|
||||
},
|
||||
pngquant: {
|
||||
quality: '65-90',
|
||||
speed: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
include: [path.join(adminPath, 'admin', 'src')],
|
||||
use: require.resolve('html-loader'),
|
||||
},
|
||||
{
|
||||
test: /\.(mp4|webm)$/,
|
||||
loader: require.resolve('url-loader'),
|
||||
options: {
|
||||
limit: 10000,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}, {
|
||||
test: /\.html$/,
|
||||
loader: 'html-loader',
|
||||
}, {
|
||||
test: /\.(mp4|webm)$/,
|
||||
loader: 'url-loader?limit=10000',
|
||||
}],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
// make fetch available
|
||||
fetch: 'exports-loader?self.fetch!whatwg-fetch',
|
||||
}),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
// make fetch available
|
||||
fetch: 'exports-loader?self.fetch!whatwg-fetch',
|
||||
}),
|
||||
|
||||
// Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV`
|
||||
// inside your code for any environment checks; UglifyJS will automatically
|
||||
// drop any unreachable code.
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
|
||||
REMOTE_URL: JSON.stringify(URLs.host),
|
||||
BACKEND_URL: JSON.stringify(URLs.backend),
|
||||
MODE: JSON.stringify(URLs.mode), // Allow us to define the public path for the plugins assets.
|
||||
},
|
||||
}),
|
||||
new webpack.NamedModulesPlugin(),
|
||||
].concat(options.plugins),
|
||||
resolve: {
|
||||
modules: [
|
||||
'admin/src',
|
||||
'node_modules/strapi-helper-plugin/lib/src',
|
||||
'node_modules/strapi-helper-plugin/node_modules',
|
||||
'node_modules',
|
||||
],
|
||||
alias: options.alias,
|
||||
symlinks: false,
|
||||
extensions: [
|
||||
'.js',
|
||||
'.jsx',
|
||||
'.react.js',
|
||||
],
|
||||
mainFields: [
|
||||
'browser',
|
||||
'jsnext:main',
|
||||
'main',
|
||||
],
|
||||
},
|
||||
externals: options.externals,
|
||||
resolveLoader: {
|
||||
modules: [
|
||||
path.join(__dirname, '..', '..', '..', 'node_modules'),
|
||||
path.join(process.cwd(), 'node_modules'),
|
||||
],
|
||||
},
|
||||
devtool: options.devtool,
|
||||
target: 'web', // Make web variables accessible to webpack, e.g. window,
|
||||
});
|
||||
// Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV`
|
||||
// inside your code for any environment checks; UglifyJS will automatically
|
||||
// drop any unreachable code.
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
|
||||
REMOTE_URL: JSON.stringify(URLs.host),
|
||||
BACKEND_URL: JSON.stringify(URLs.backend),
|
||||
MODE: JSON.stringify(URLs.mode), // Allow us to define the public path for the plugins assets.
|
||||
},
|
||||
}),
|
||||
new webpack.NamedModulesPlugin(),
|
||||
extractSass,
|
||||
].concat(options.plugins),
|
||||
resolve: {
|
||||
modules: [
|
||||
'admin/src',
|
||||
'node_modules/strapi-helper-plugin/lib/src',
|
||||
'node_modules/strapi-helper-plugin/node_modules',
|
||||
'node_modules',
|
||||
],
|
||||
alias: options.alias,
|
||||
symlinks: false,
|
||||
extensions: [
|
||||
'.js',
|
||||
'.jsx',
|
||||
'.react.js',
|
||||
],
|
||||
mainFields: [
|
||||
'browser',
|
||||
'jsnext:main',
|
||||
'main',
|
||||
],
|
||||
},
|
||||
externals: options.externals,
|
||||
resolveLoader: {
|
||||
modules: [
|
||||
path.join(__dirname, '..', '..', '..', 'node_modules'),
|
||||
path.join(process.cwd(), 'node_modules'),
|
||||
],
|
||||
},
|
||||
devtool: options.devtool,
|
||||
target: 'web', // Make web variables accessible to webpack, e.g. window,
|
||||
};
|
||||
};
|
||||
|
||||
@ -48,7 +48,8 @@ if (process.env.npm_lifecycle_event === 'start') {
|
||||
|
||||
plugins.src = process.env.IS_ADMIN === 'true' && !plugins.exist ? fs.readdirSync(path.resolve(appPath, 'plugins')).filter(x => {
|
||||
let hasAdminFolder;
|
||||
|
||||
|
||||
// Don't inject the plugins that don't have an admin into the app
|
||||
try {
|
||||
fs.accessSync(path.resolve(appPath, 'plugins', x, 'admin', 'src', 'containers', 'App'));
|
||||
hasAdminFolder = true;
|
||||
|
||||
@ -3,7 +3,6 @@ const path = require('path');
|
||||
const _ = require('lodash');
|
||||
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const cssnext = require('postcss-cssnext');
|
||||
const postcssFocus = require('postcss-focus');
|
||||
const postcssReporter = require('postcss-reporter');
|
||||
@ -14,10 +13,6 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
const base = require('./webpack.base.babel');
|
||||
|
||||
// const pkg = require(path.resolve(process.cwd(), 'package.json'));
|
||||
// const pluginId = pkg.name.replace(/^strapi-plugin-/i, '');
|
||||
// const dllPlugin = pkg.dllPlugin;
|
||||
|
||||
const isAdmin = process.env.IS_ADMIN === 'true';
|
||||
const isSetup = path.resolve(process.env.PWD, '..', '..') === path.resolve(process.env.INIT_CWD);
|
||||
const appPath = (() => {
|
||||
@ -105,7 +100,6 @@ if (isAdmin) {
|
||||
chunks: ['main'],
|
||||
inject: true,
|
||||
}));
|
||||
plugins.push(new ExtractTextPlugin('[name].[contenthash].css'));
|
||||
plugins.push(new AddAssetHtmlPlugin({
|
||||
filepath: path.resolve(__dirname, 'dist/*.dll.js'),
|
||||
}));
|
||||
@ -180,4 +174,5 @@ module.exports = base({
|
||||
},
|
||||
|
||||
devtool: 'cheap-module-source-map',
|
||||
disableExtractTextPlugin: false,
|
||||
});
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import PropTypes from 'prop-types';
|
||||
// import DateTimeStyle from 'react-datetime/css/react-datetime.css';
|
||||
import DateTimeStyle from 'react-datetime/css/react-datetime.css'; /* eslint-disable-line no-unused-vars */
|
||||
import DateTime from 'react-datetime';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { isEmpty, isObject } from 'lodash';
|
||||
|
||||
@ -14,8 +14,10 @@ import cn from 'classnames';
|
||||
import styles from './styles.scss';
|
||||
|
||||
function InputTextArea(props) {
|
||||
const placeholder = isEmpty(props.placeholder) ? 'app.utils.placeholder.defaultMessage' : props.placeholder;
|
||||
|
||||
return (
|
||||
<FormattedMessage id={props.placeholder} defaultMessage={props.placeholder}>
|
||||
<FormattedMessage id={placeholder} defaultMessage={placeholder}>
|
||||
{(message) => (
|
||||
<textarea
|
||||
autoFocus={props.autoFocus}
|
||||
|
||||
@ -590,22 +590,40 @@ class Wysiwyg extends React.Component {
|
||||
'X-Forwarded-Host': 'strapi',
|
||||
};
|
||||
|
||||
const newContentState = this.createNewContentStateFromBlock(
|
||||
createNewBlock(`![Uploading ${files[0].name}]()`),
|
||||
);
|
||||
const newEditorState = EditorState.push(this.getEditorState(), newContentState);
|
||||
this.setState({ editorState: newEditorState });
|
||||
let newEditorState = this.getEditorState();
|
||||
|
||||
const nextBlocks = getNextBlocksList(newEditorState, this.getSelection().getStartKey());
|
||||
// Loop to update each block after the inserted li
|
||||
nextBlocks.map((block, index) => {
|
||||
// Update the current block
|
||||
const nextBlockText = index === 0 ? `![Uploading ${files[0].name}]()` : nextBlocks.get(index - 1).getText();
|
||||
const newBlock = createNewBlock(nextBlockText, 'unstyled', block.getKey());
|
||||
// Update the contentState
|
||||
const newContentState = this.createNewContentStateFromBlock(
|
||||
newBlock,
|
||||
newEditorState.getCurrentContent(),
|
||||
);
|
||||
newEditorState = EditorState.push(newEditorState, newContentState);
|
||||
});
|
||||
|
||||
const offset = `![Uploading ${files[0].name}]()`.length;
|
||||
const updatedSelection = updateSelection(this.getSelection(), nextBlocks, offset);
|
||||
this.setState({ editorState: EditorState.acceptSelection(newEditorState, updatedSelection) });
|
||||
|
||||
return request('/upload', { method: 'POST', headers, body: formData }, false, false)
|
||||
.then(response => {
|
||||
const lastBlock = this.getEditorState()
|
||||
const nextBlockKey = newEditorState
|
||||
.getCurrentContent()
|
||||
.getLastBlock();
|
||||
.getKeyAfter(newEditorState.getSelection().getStartKey());
|
||||
const content = ``;
|
||||
const newContentState = this.createNewContentStateFromBlock(
|
||||
createNewBlock(``, 'unstyled', lastBlock.getKey()),
|
||||
createNewBlock(content, 'unstyled', nextBlockKey),
|
||||
);
|
||||
const newEditorState = EditorState.push(this.getEditorState(), newContentState);
|
||||
this.setState({ editorState: EditorState.moveFocusToEnd(newEditorState) });
|
||||
|
||||
newEditorState = EditorState.push(newEditorState, newContentState);
|
||||
const updatedSelection = updateSelection(this.getSelection(), nextBlocks, 2);
|
||||
|
||||
this.setState({ editorState: EditorState.acceptSelection(newEditorState, updatedSelection) });
|
||||
this.sendData(newEditorState);
|
||||
})
|
||||
.catch(() => {
|
||||
|
||||
@ -48,4 +48,4 @@
|
||||
"npm": ">= 5.3.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
{
|
||||
"plugin.description.short": "快速查看、编辑和删除数据库中的数据。",
|
||||
"plugin.description.long": "快速查看、编辑和删除数据库中的数据。",
|
||||
"containers.Home.pluginHeaderTitle": "内容管理器",
|
||||
"containers.Home.introduction": "要编辑您的条目,请转到左边菜单中的特定链接。这个插件没有合适的方法来编辑设置,它仍然在积极的开发中。",
|
||||
"containers.Home.pluginHeaderDescription": "通过一个强大而漂亮的界面管理你的条目。",
|
||||
"containers.Edit.submit": "保存",
|
||||
"containers.Edit.editing": "编辑...",
|
||||
"containers.Edit.delete": "删除",
|
||||
"containers.Edit.reset": "重置",
|
||||
"containers.Edit.returnList": "返回列表",
|
||||
"containers.List.addAnEntry": "增加新的 {entity}",
|
||||
"containers.List.pluginHeaderDescription": "找到 {label} 条目",
|
||||
"containers.List.pluginHeaderDescription.singular": "找到 {label} 条目",
|
||||
"components.LimitSelect.itemsPerPage": "每页显示数目",
|
||||
"containers.List.errorFetchRecords": "错误",
|
||||
|
||||
"EditRelations.title": "关系数据",
|
||||
|
||||
"emptyAttributes.title": "还没有字段",
|
||||
"emptyAttributes.description": "为你的内容类型添加第一个字段",
|
||||
"emptyAttributes.button": "转到内容类型生成器",
|
||||
|
||||
"error.schema.generation": "Schema生成过程中发生错误。",
|
||||
"error.records.count": "获取记录数count时发生错误。",
|
||||
"error.records.fetch": "获取记录时发生错误。",
|
||||
"error.record.fetch": "获取记录时发生错误。",
|
||||
"error.record.create": "创建记录时发生错误",
|
||||
"error.record.update": "更新记录时发生错误",
|
||||
"error.record.delete": "删除记录时发生错误",
|
||||
"error.model.fetch": "获取models配置时发生错误",
|
||||
"error.validation.required": "必填项",
|
||||
"error.validation.regex": "格式错误",
|
||||
"error.validation.max": "超过最大值",
|
||||
"error.validation.min": "小于最小值",
|
||||
"error.validation.maxLength": "长度太长",
|
||||
"error.validation.minLength": "长度太短",
|
||||
"error.contentTypeName.taken": "该名称已被使用",
|
||||
"error.attribute.taken": "该名称已被使用",
|
||||
"error.attribute.key.taken": "该值已存在",
|
||||
"error.attribute.sameKeyAndName": "不能相等",
|
||||
"error.validation.minSupMax": "最小值大于最大值。",
|
||||
|
||||
"notification.error.relationship.fetch": "获取关联数据时发生错误",
|
||||
|
||||
"success.record.delete": "删除",
|
||||
"success.record.save": "保存",
|
||||
|
||||
"pageNotFound": "页面未找到",
|
||||
|
||||
"popUpWarning.button.cancel": "取消",
|
||||
"popUpWarning.button.confirm": "确认",
|
||||
"popUpWarning.title": "请确认",
|
||||
"popUpWarning.bodyMessage.contentType.delete": "确实要删除此条目吗?"
|
||||
}
|
||||
@ -78,7 +78,7 @@ module.exports = {
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const entry = await this
|
||||
const request = await this
|
||||
.forge(values)
|
||||
.save()
|
||||
.catch((err) => {
|
||||
@ -90,6 +90,8 @@ module.exports = {
|
||||
throw err;
|
||||
});
|
||||
|
||||
const entry = request.toJSON ? request.toJSON() : request;
|
||||
|
||||
return module.exports.update.call(this, {
|
||||
[this.primaryKey]: entry[this.primaryKey],
|
||||
values: _.assign({
|
||||
@ -167,9 +169,7 @@ module.exports = {
|
||||
case 'oneToMany':
|
||||
case 'manyToOne':
|
||||
case 'manyToMany':
|
||||
if (association.nature === 'manyToMany' && details.dominant === true) {
|
||||
acc[current] = params.values[current];
|
||||
} else if (response[current] && _.isArray(response[current]) && current !== 'id') {
|
||||
if (response[current] && _.isArray(response[current]) && current !== 'id') {
|
||||
// Records to add in the relation.
|
||||
const toAdd = _.differenceWith(params.values[current], response[current], (a, b) =>
|
||||
a[this.primaryKey].toString() === b[this.primaryKey].toString()
|
||||
|
||||
@ -81,7 +81,9 @@ function formReducer(state = initialState, action) {
|
||||
case RESET_FORM_ERRORS:
|
||||
return state.set('formErrors', List());
|
||||
case RESET_IS_FORM_SET:
|
||||
return state.set('isFormSet', false);
|
||||
return state
|
||||
.set('isFormSet', false)
|
||||
.update('modifiedData', () => Map({}));
|
||||
case SET_ATTRIBUTE_FORM: {
|
||||
if (state.get('isFormSet')) {
|
||||
return state
|
||||
|
||||
@ -4,7 +4,8 @@ export default function checkAttributeValidations(errors) {
|
||||
|
||||
const attributeIndex = split(this.props.hash, '::')[3];
|
||||
const sameAttributes = filter(this.props.contentTypeData.attributes, (attr) => attr.name === this.props.modifiedDataAttribute.name);
|
||||
const sameParamsKey = filter(this.props.contentTypeData.attributes, (attr) => attr.params.key === this.props.modifiedDataAttribute.params.key);
|
||||
const sameParamsKey = filter(this.props.contentTypeData.attributes, (attr) =>
|
||||
attr.params.key === this.props.modifiedDataAttribute.params.key && attr.params.target === this.props.modifiedDataAttribute.params.target);
|
||||
const sameParamsKeyAndName = filter(this.props.contentTypeData.attributes, (attr) => attr.name === this.props.modifiedDataAttribute.params.key);
|
||||
const formErrors = concat(errors, hasNestedValue(this.props.modifiedDataAttribute));
|
||||
const isEditingParamsKey = this.props.modifiedDataAttribute.params.key !== get(this.props.contentTypeData.attributes, [attributeIndex, 'params', 'key']);
|
||||
|
||||
@ -50,6 +50,7 @@ import styles from './styles.scss';
|
||||
|
||||
// Array of attributes that the ctb can handle at the moment
|
||||
const availableAttributes = Object.keys(forms.attribute);
|
||||
availableAttributes.push('integer', 'decimal', 'float');
|
||||
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
/* eslint-disable react/jsx-wrap-multilines */
|
||||
@ -89,25 +90,6 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
|
||||
}
|
||||
}
|
||||
|
||||
// componentWillUpdate(nextProps) {
|
||||
// if (!isEmpty(nextProps.menu)) {
|
||||
// const allowedPaths = nextProps.menu.reduce((acc, current) => {
|
||||
// const models = current.items.reduce((acc, current) => {
|
||||
// acc.push(current.name);
|
||||
//
|
||||
// return acc;
|
||||
// }, []);
|
||||
// return acc.concat(models);
|
||||
// }, []);
|
||||
//
|
||||
// const shouldRedirect = allowedPaths.filter(el => el === this.props.match.params.modelName.split('&')[0]).length === 0;
|
||||
//
|
||||
// if (shouldRedirect) {
|
||||
// this.props.history.push('/404');
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.match.params.modelName !== this.props.match.params.modelName) {
|
||||
this.props.resetShowButtonsProps();
|
||||
|
||||
@ -0,0 +1,174 @@
|
||||
{
|
||||
"plugin.description.short": "给你的API的数据结构建模",
|
||||
"plugin.description.long":
|
||||
"给你的API的数据结构建模. 快速的创造新的字段(fields)和关系(relations)。将会自动在项目中创建和更新文件。",
|
||||
"attribute.string": "String",
|
||||
"attribute.text": "Text",
|
||||
"attribute.boolean": "Boolean",
|
||||
"attribute.float": "Float",
|
||||
"attribute.integer": "integer",
|
||||
"attribute.decimal": "Decimal",
|
||||
"attribute.date": "Date",
|
||||
"attribute.json": "JSON",
|
||||
"attribute.media": "Media",
|
||||
"attribute.email": "Email",
|
||||
"attribute.password": "Password",
|
||||
"attribute.relation": "Relation",
|
||||
"attribute.enumeration": "Enumeration",
|
||||
"attribute.WYSIWYG": "Text (WYSIWYG)",
|
||||
|
||||
"contentType.temporaryDisplay": "(未保存)",
|
||||
"from": "from",
|
||||
"home.contentTypeBuilder.name": "Content Types",
|
||||
"home.contentTypeBuilder.description": "创建和更新自己Content Types.",
|
||||
"home.emptyContentType.title": "没有可用的Content Types",
|
||||
"home.emptyContentType.description":
|
||||
"创建第一个Content Type,以便能够从API中检索数据。",
|
||||
|
||||
"home.emptyAttributes.title": "还没有字段",
|
||||
"home.emptyAttributes.description": "为你的新Content Type添加第一个字段",
|
||||
|
||||
"button.contentType.create": "创建Content Type",
|
||||
"button.contentType.add": "增加Content Type",
|
||||
"button.attributes.add": "增加New字段",
|
||||
|
||||
"error.validation.required": "必填项",
|
||||
"error.validation.regex": "格式错误",
|
||||
"error.validation.max": "超过最大值",
|
||||
"error.validation.min": "低于最小值",
|
||||
"error.validation.maxLength": "超过最大长度",
|
||||
"error.validation.minLength": "小于最小长度",
|
||||
"error.contentTypeName.taken": "名称已存在",
|
||||
"error.attribute.taken": "字段名称已存在",
|
||||
"error.attribute.key.taken": "该值已存在",
|
||||
"error.attribute.sameKeyAndName": "不能相等",
|
||||
"error.validation.minSupMax": "最小值大于最大值。",
|
||||
|
||||
"form.attribute.item.textarea.name": "Name",
|
||||
"form.attribute.item.number.name": "Name",
|
||||
"form.attribute.item.date.name": "Name",
|
||||
"form.attribute.item.media.name": "Name",
|
||||
"form.attribute.item.media.multiple": "允许多个文件",
|
||||
"form.attribute.item.json.name": "Name",
|
||||
"form.attribute.item.boolean.name": "Name",
|
||||
"form.attribute.item.string.name": "Name",
|
||||
"form.attribute.item.appearance.name": "Appearance",
|
||||
"form.attribute.item.appearance.label": "Display as a WYSIWYG",
|
||||
"form.attribute.item.appearance.description":
|
||||
"否则,该值将通过基本文本字段进行编辑。",
|
||||
"form.attribute.item.settings.name": "设置",
|
||||
"form.attribute.item.requiredField": "必须的",
|
||||
"form.attribute.item.uniqueField": "唯一的",
|
||||
"form.attribute.item.minimum": "最小值",
|
||||
"form.attribute.item.minimumLength": "最小长度",
|
||||
"form.attribute.item.maximumLength": "最大长度",
|
||||
"form.attribute.item.maximum": "最大值",
|
||||
"form.attribute.item.requiredField.description":
|
||||
"如果此字段为空,则无法创建条目。",
|
||||
"form.attribute.item.uniqueField.description":
|
||||
"如果存在具有相同内容的现有条目,则无法创建条目。",
|
||||
"form.attribute.item.defineRelation.fieldName": "Field name",
|
||||
"form.attribute.item.customColumnName": "Custom column names",
|
||||
"form.attribute.item.customColumnName.description":"修改数据库列名,使得API返回更容易理解。",
|
||||
"form.attribute.item.number.type": "Number format",
|
||||
"form.attribute.item.number.type.integer": "integer (ex: 10)",
|
||||
"form.attribute.item.number.type.float": "float (ex: 3.33333333)",
|
||||
"form.attribute.item.number.type.decimal": "decimal (ex: 2.22)",
|
||||
"form.attribute.settings.default": "默认值",
|
||||
"form.attribute.settings.default.checkboxLabel": "Set to true",
|
||||
|
||||
"form.button.cancel": "取消",
|
||||
"form.button.continue": "继续",
|
||||
"form.button.save": "保存",
|
||||
|
||||
"form.contentType.item.connections": "连接",
|
||||
"form.contentType.item.name": "名称",
|
||||
"form.contentType.item.name.description": "Content Type names should be singular: {link}",
|
||||
"form.contentType.item.name.link.description": "查看我们的文档",
|
||||
"form.contentType.item.description": "描述",
|
||||
"form.contentType.item.description.placeholder": "在这里写下你的描述...",
|
||||
"form.contentType.item.collectionName": "Collection Name",
|
||||
"form.contentType.item.collectionName.inputDescription":
|
||||
"当你的Content Type和你的数据库表名不一样的时候",
|
||||
|
||||
"menu.section.contentTypeBuilder.name.plural": "Content Types",
|
||||
"menu.section.contentTypeBuilder.name.singular": "Content Type",
|
||||
"menu.section.documentation.name": "Documentation",
|
||||
"menu.section.documentation.guide": "阅读更多关于我们的Content Types",
|
||||
"menu.section.documentation.guideLink": "guide.",
|
||||
"menu.section.documentation.tutorial": "看看我们的",
|
||||
"menu.section.documentation.tutorialLink": "教程视频。",
|
||||
|
||||
"modelPage.contentHeader.emptyDescription.description": "该Content Type没有任何描述",
|
||||
"modelPage.contentType.list.title.plural": "fields",
|
||||
"modelPage.contentType.list.title.singular": "field",
|
||||
"modelPage.contentType.list.title.including": "including",
|
||||
"modelPage.contentType.list.relationShipTitle.plural": "relationships",
|
||||
"modelPage.contentType.list.relationShipTitle.singular": "relationship",
|
||||
"modelPage.attribute.relationWith": "Relation with",
|
||||
|
||||
"noTableWarning.description": "不要忘了在数据库里创建表 `{modelName}`",
|
||||
"noTableWarning.infos": "更多信息",
|
||||
|
||||
"notification.error.message": "发生错误",
|
||||
"notification.info.contentType.creating.notSaved":
|
||||
"在创建新Content Type之前,请保存当前Content Type",
|
||||
"notification.info.optimized": "这个插件是用本地存储优化的",
|
||||
"notification.success.message.contentType.edit": "你的Content Type已更新",
|
||||
"notification.success.message.contentType.create": "你的Content Type已创建",
|
||||
"notification.success.contentTypeDeleted": "这个Content Type已被删除",
|
||||
"notification.info.enumeration": "这个字段暂时不可编辑...😮",
|
||||
|
||||
"popUpForm.attributes.string.description": "标题、名称、段落、名称列表",
|
||||
"popUpForm.attributes.text.description": "描述、文本段落、文章",
|
||||
"popUpForm.attributes.boolean.description": "Yes or no, 1 or 0, true or false",
|
||||
"popUpForm.attributes.number.description": "所有数字",
|
||||
"popUpForm.attributes.date.description": "活动日期、开放时间",
|
||||
"popUpForm.attributes.json.description": "JSON格式的数据",
|
||||
"popUpForm.attributes.media.description": "图像,视频,PDF文件和其他文件",
|
||||
"popUpForm.attributes.relation.description": "引用其它 Content Type",
|
||||
"popUpForm.attributes.email.description": "用户email...",
|
||||
"popUpForm.attributes.password.description": "用户密码...",
|
||||
|
||||
"popUpForm.attributes.string.name": "String",
|
||||
"popUpForm.attributes.text.name": "Text",
|
||||
"popUpForm.attributes.boolean.name": "Boolean",
|
||||
"popUpForm.attributes.date.name": "Date",
|
||||
"popUpForm.attributes.json.name": "JSON",
|
||||
"popUpForm.attributes.media.name": "Media",
|
||||
"popUpForm.attributes.number.name": "Number",
|
||||
"popUpForm.attributes.relation.name": "Relation",
|
||||
"popUpForm.attributes.email.name": "Email",
|
||||
"popUpForm.attributes.password.name": "Password",
|
||||
"popUpForm.create": "增加新的",
|
||||
"popUpForm.edit": "编辑",
|
||||
"popUpForm.field": "字段",
|
||||
"popUpForm.create.contentType.header.title": "增加新的 Content Type",
|
||||
"popUpForm.choose.attributes.header.title": "增加新字段",
|
||||
"popUpForm.edit.contentType.header.title": "编辑 Content Type",
|
||||
|
||||
"popUpForm.navContainer.relation": "定义关联关系",
|
||||
"popUpForm.navContainer.base": "基础设置",
|
||||
"popUpForm.navContainer.advanced": "高级设置",
|
||||
|
||||
"popUpRelation.title": "关联",
|
||||
|
||||
"popUpWarning.button.cancel": "取消",
|
||||
"popUpWarning.button.confirm": "确认",
|
||||
"popUpWarning.title": "请确认",
|
||||
"popUpWarning.bodyMessage.contentType.delete":
|
||||
"确实要删除此 Content Type 吗?",
|
||||
"popUpWarning.bodyMessage.attribute.delete": "确实要删除此字段吗?",
|
||||
|
||||
"table.contentType.title.plural": "Content Types 是可用的",
|
||||
"table.contentType.title.singular": "Content Type 是可用的",
|
||||
"table.contentType.head.name": "名称",
|
||||
"table.contentType.head.description": "描述",
|
||||
"table.contentType.head.fields": "字段",
|
||||
|
||||
"relation.oneToOne": "has one",
|
||||
"relation.oneToMany": "belongs to many",
|
||||
"relation.manyToOne": "has many",
|
||||
"relation.manyToMany": "has and belongs to many",
|
||||
"relation.attributeName.placeholder": "Ex: author, category, tag"
|
||||
}
|
||||
@ -51,7 +51,7 @@ module.exports = {
|
||||
if (name === 'file') {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
acc.push({
|
||||
icon: 'fa-cube',
|
||||
name: _.get(model, 'info.name', 'model.name.missing'),
|
||||
@ -214,7 +214,10 @@ module.exports = {
|
||||
|
||||
attr.via = relation.key;
|
||||
attr.dominant = relation.dominant;
|
||||
attr.plugin = relation.pluginValue;
|
||||
|
||||
if (relation.pluginValue) {
|
||||
attr.plugin = relation.pluginValue;
|
||||
}
|
||||
|
||||
attrs[attribute.name] = attr;
|
||||
}
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"plugin.description.short": "发送电子邮件。",
|
||||
"plugin.description.long": "发送电子邮件。"
|
||||
}
|
||||
@ -36,7 +36,6 @@
|
||||
"form.database.item.provider.mongo": "Mongo",
|
||||
"form.database.item.provider.postgres": "PostgreSQL",
|
||||
"form.database.item.provider.mysql": "MySQL",
|
||||
"form.database.item.provider.sqlite3": "SQlite3",
|
||||
"form.database.item.provider.redis": "Redis",
|
||||
|
||||
"form.application.name": "Anwendung",
|
||||
|
||||
@ -36,7 +36,6 @@
|
||||
"form.database.item.provider.mongo": "Mongo",
|
||||
"form.database.item.provider.postgres": "PostgresSQL",
|
||||
"form.database.item.provider.mysql": "MySQL",
|
||||
"form.database.item.provider.sqlite3": "SQlite3",
|
||||
"form.database.item.provider.redis": "Redis",
|
||||
|
||||
"form.application.name": "Application",
|
||||
|
||||
@ -34,7 +34,6 @@
|
||||
"form.database.item.provider.mongo": "Mongo",
|
||||
"form.database.item.provider.postgres": "PostgresSQL",
|
||||
"form.database.item.provider.mysql": "MySQL",
|
||||
"form.database.item.provider.sqlite3": "SQlite3",
|
||||
"form.database.item.provider.redis": "Redis",
|
||||
|
||||
"form.application.name": "Application",
|
||||
|
||||
@ -34,7 +34,6 @@
|
||||
"form.database.item.provider.mongo": "Mongo",
|
||||
"form.database.item.provider.postgres": "PostgresSQL",
|
||||
"form.database.item.provider.mysql": "MySQL",
|
||||
"form.database.item.provider.sqlite3": "SQlite3",
|
||||
"form.database.item.provider.redis": "Redis",
|
||||
|
||||
"form.application.name": "Aplikacja",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -34,7 +34,6 @@
|
||||
"form.database.item.provider.mongo": "Mongo",
|
||||
"form.database.item.provider.postgres": "PostgresSQL",
|
||||
"form.database.item.provider.mysql": "MySQL",
|
||||
"form.database.item.provider.sqlite3": "SQlite3",
|
||||
"form.database.item.provider.redis": "Redis",
|
||||
|
||||
"form.application.name": "Uygulama",
|
||||
|
||||
@ -0,0 +1,655 @@
|
||||
{
|
||||
"components.DownloadDb.download": "正在安装...",
|
||||
"components.DownloadDb.text": "这可能需要几分钟左右。谢谢你的耐心。",
|
||||
"plugin.description.short": "在几秒钟内配置你的项目。",
|
||||
"plugin.description.long": "在几秒钟内配置你的项目。",
|
||||
"menu.section.global-settings": "全局设置",
|
||||
"menu.item.application": "应用",
|
||||
"menu.item.languages": "语言",
|
||||
"menu.item.advanced": "高级",
|
||||
|
||||
"menu.section.environments": "环境",
|
||||
"menu.item.database": "数据库",
|
||||
"menu.item.request": "请求",
|
||||
"menu.item.response": "回复",
|
||||
"menu.item.security": "安全性",
|
||||
"menu.item.server": "服务器",
|
||||
|
||||
"form.button.cancel": "取消",
|
||||
"form.button.save": "保存",
|
||||
"form.button.confirm": "确认",
|
||||
|
||||
"form.databases.name": "数据库",
|
||||
"form.databases.description": "通过环境配置数据库设置。",
|
||||
|
||||
"form.database.item.name": "连接名",
|
||||
"form.database.item.client": "客户端",
|
||||
"form.database.item.connector": "连接器",
|
||||
"form.database.item.host": "主机",
|
||||
"form.database.item.port": "端口",
|
||||
"form.database.item.username": "用户名",
|
||||
"form.database.item.password": "密码",
|
||||
"form.database.item.database": "数据库",
|
||||
"form.database.item.default": "设置为默认连接",
|
||||
"form.database.item.provider.mongo": "Mongo",
|
||||
"form.database.item.provider.postgres": "PostgresSQL",
|
||||
"form.database.item.provider.mysql": "MySQL",
|
||||
"form.database.item.provider.sqlite3": "SQlite3",
|
||||
"form.database.item.provider.redis": "Redis",
|
||||
|
||||
"form.application.name": "应用",
|
||||
"form.application.description": "配置应用程序设置。",
|
||||
|
||||
"form.application.item.name": "名字",
|
||||
"form.application.item.description": "描述",
|
||||
"form.application.item.version": "版本",
|
||||
|
||||
"form.advanced.name": "先进的",
|
||||
"form.advanced.description": "配置高级设置。",
|
||||
|
||||
"form.advanced.item.admin": "Admin dashboard url",
|
||||
"form.advanced.item.prefix": "Prefix API",
|
||||
|
||||
"form.request.name": "Request",
|
||||
"form.request.description": "配置您的请求设置。",
|
||||
"form.request.item.parser": "Parser",
|
||||
"form.request.item.parser.multipart": "Parser Multipart",
|
||||
"form.request.item.prefix": "Prefix",
|
||||
"form.request.item.prefix.prefix": "Prefix",
|
||||
"form.request.item.logger": "Logger",
|
||||
"form.request.item.logger.level": "Level",
|
||||
"form.request.item.logger.exposeInContext": "Expose in context",
|
||||
"form.request.item.logger.requests": "Requests",
|
||||
"form.request.item.router": "Router",
|
||||
"form.request.item.router.prefix": "Prefix",
|
||||
|
||||
"form.response.name": "Response",
|
||||
"form.response.description": "配置您的响应设置。",
|
||||
"form.response.item.gzip.enabled": "Gzip",
|
||||
"form.response.item.responseTime.enabled": "Response Time",
|
||||
|
||||
"form.security.name": "Security",
|
||||
"form.security.description": "配置安全设置。",
|
||||
|
||||
"form.security.item.csrf": "CSRF",
|
||||
"form.security.item.p3p": "P3P",
|
||||
"form.security.item.p3p.value": "Value",
|
||||
"form.security.item.hsts": "HOSTS",
|
||||
"form.security.item.csrf.key": "Key",
|
||||
"form.security.item.csrf.secret": "Secret",
|
||||
"form.security.item.csrf.cookie": "Cookie",
|
||||
"form.security.item.csrf.angular": "Angular",
|
||||
"form.security.item.hsts.maxAge": "Max Age",
|
||||
"form.security.item.hsts.includeSubDomains": "包括子域",
|
||||
"form.security.item.hsts.preload": "Preload",
|
||||
|
||||
"form.security.item.session": "Session",
|
||||
"form.security.item.session.key": "Secret key",
|
||||
"form.security.item.session.maxAge": "Maximum age",
|
||||
|
||||
"form.security.item.xframe": "Xframe",
|
||||
"form.security.item.xframe.value": "Options",
|
||||
"form.security.item.xframe.deny": "DENY",
|
||||
"form.security.item.xframe.sameorigin": "SAMEORIGIN",
|
||||
"form.security.item.xframe.allow-from": "ALLOW-FROM",
|
||||
|
||||
"form.security.item.xssProtection": "xss Protection",
|
||||
"form.security.item.xssProtection.mode": "Mode",
|
||||
|
||||
"form.security.item.cors": "Cors",
|
||||
"form.security.item.cors.origin": "Origin",
|
||||
|
||||
"form.server.name": "Server",
|
||||
"form.server.description": "配置服务器设置。",
|
||||
|
||||
"form.server.item.host": "Host",
|
||||
"form.server.item.port": "Port",
|
||||
"form.server.item.cron": "Cron",
|
||||
|
||||
"form.language.name": "Languages",
|
||||
"form.language.description": "配置您的语言。",
|
||||
"form.language.choose": "选择一种语言:",
|
||||
|
||||
"request.error.database.exist": "此连接已经存在",
|
||||
"request.error.database.unknow": "没有这样的连接",
|
||||
"request.error.type.string": "需要一个文本。",
|
||||
"request.error.type.number": "需要一个数字。",
|
||||
"request.error.type.boolean": "需要一个布尔值。",
|
||||
"request.error.type.select": "该值必须在预定义列表中。",
|
||||
|
||||
"request.error.validation.required": "必选",
|
||||
"request.error.validation.regex": "格式错误",
|
||||
"request.error.validation.max": "超过最大值",
|
||||
"request.error.validation.min": "小于最小值",
|
||||
"request.error.validation.maxLength": "超过最大长度",
|
||||
"request.error.validation.minLength": "低于最小长度",
|
||||
|
||||
"request.error.config": "配置文件不存在。",
|
||||
"request.error.environment.required": "环境是必需的。",
|
||||
"request.error.environment.unknow": "环境未知。",
|
||||
"request.error.languages.exist": "这种语言已经存在。",
|
||||
"request.error.languages.unknow": "这种语言是不存在的。",
|
||||
"request.error.languages.incorrect": "这种语言不正确。",
|
||||
|
||||
"list.languages.button.label": "添加新语言",
|
||||
"list.languages.title.singular": "语言是可用的",
|
||||
"list.languages.title.plural": "可用的语言",
|
||||
"list.languages.default.languages": "缺省语言",
|
||||
"list.languages.set.languages": "设置为默认值",
|
||||
"list.databases.button.label": "添加新连接",
|
||||
"list.databases.title.singular": "在这种环境中的连接",
|
||||
"list.databases.title.plural": "在这种环境中的连接",
|
||||
|
||||
"popUpWarning.title": "请确认",
|
||||
"popUpWarning.databases.danger.message": "内容类型仍然与此连接连接。如果删除它,可能会对应用程序造成严重的问题。小心...",
|
||||
"popUpWarning.danger.ok.message": "我明白",
|
||||
"popUpWarning.databases.delete.message": "确实要删除此数据库吗?",
|
||||
"popUpWarning.languages.delete.message": "确实要删除此数据库吗?",
|
||||
"strapi.notification.info.settingsEqual": "设置相等",
|
||||
"strapi.notification.success.databaseDelete": "数据库已被成功删除。",
|
||||
"strapi.notification.success.languageDelete": "数据库已被成功删除。",
|
||||
"strapi.notification.success.languageAdd": "该语言已成功添加。",
|
||||
"strapi.notification.success.databaseAdd": "该语言已成功添加。",
|
||||
"strapi.notification.success.databaseEdit": "数据库设置已成功更新。",
|
||||
"strapi.notification.success.databaseDeleted": "数据库已被删除。",
|
||||
"strapi.notification.success.settingsEdit": "设置已成功更新。",
|
||||
"strapi.notification.error": "发生错误",
|
||||
"strapi.notification.info.serverRestart": "服务器将重新启动",
|
||||
|
||||
"language.af": "Afrikaans",
|
||||
"language.af_NA": "Afrikaans (Namibië)",
|
||||
"language.af_ZA": "Afrikaans (Suid-Afrika)",
|
||||
"language.agq": "Aghem",
|
||||
"language.agq_CM": "Aghem (Kàmàlûŋ)",
|
||||
"language.ak": "Akan",
|
||||
"language.ak_GH": "Akan (Gaana)",
|
||||
"language.am": "አማርኛ",
|
||||
"language.am_ET": "አማርኛ (ኢትዮጵያ)",
|
||||
"language.ar": "العربية",
|
||||
"language.ar_001": "العربية (العالم)",
|
||||
"language.ar_AE": "العربية (الإمارات العربية المتحدة)",
|
||||
"language.ar_BH": "العربية (البحرين)",
|
||||
"language.ar_DZ": "العربية (الجزائر)",
|
||||
"language.ar_EG": "العربية (مصر)",
|
||||
"language.ar_IQ": "العربية (العراق)",
|
||||
"language.ar_JO": "العربية (الأردن)",
|
||||
"language.ar_KW": "العربية (الكويت)",
|
||||
"language.ar_LB": "العربية (لبنان)",
|
||||
"language.ar_LY": "العربية (ليبيا)",
|
||||
"language.ar_MA": "العربية (المغرب)",
|
||||
"language.ar_OM": "العربية (عُمان)",
|
||||
"language.ar_QA": "العربية (قطر)",
|
||||
"language.ar_SA": "العربية (المملكة العربية السعودية)",
|
||||
"language.ar_SD": "العربية (السودان)",
|
||||
"language.ar_SY": "العربية (سوريا)",
|
||||
"language.ar_TN": "العربية (تونس)",
|
||||
"language.ar_YE": "العربية (اليمن)",
|
||||
"language.as": "অসমীয়া",
|
||||
"language.as_IN": "অসমীয়া (ভাৰত)",
|
||||
"language.asa": "Kipare",
|
||||
"language.asa_TZ": "Kipare (Tadhania)",
|
||||
"language.az": "azərbaycanca",
|
||||
"language.az_Cyrl": "Азәрбајҹан (kiril)",
|
||||
"language.az_Cyrl_AZ": "Азәрбајҹан (kiril, Азәрбајҹан)",
|
||||
"language.az_Latn": "azərbaycanca (latın)",
|
||||
"language.az_Latn_AZ": "azərbaycanca (latın, Azərbaycan)",
|
||||
"language.bas": "Ɓàsàa",
|
||||
"language.bas_CM": "Ɓàsàa (Kàmɛ̀rûn)",
|
||||
"language.be": "беларуская",
|
||||
"language.be_BY": "беларуская (Беларусь)",
|
||||
"language.bem": "Ichibemba",
|
||||
"language.bem_ZM": "Ichibemba (Zambia)",
|
||||
"language.bez": "Hibena",
|
||||
"language.bez_TZ": "Hibena (Hutanzania)",
|
||||
"language.bg": "български",
|
||||
"language.bg_BG": "български (България)",
|
||||
"language.bm": "bamanakan",
|
||||
"language.bm_ML": "bamanakan (Mali)",
|
||||
"language.bn": "বাংলা",
|
||||
"language.bn_BD": "বাংলা (বাংলাদেশ)",
|
||||
"language.bn_IN": "বাংলা (ভারত)",
|
||||
"language.bo": "པོད་སྐད་",
|
||||
"language.bo_CN": "པོད་སྐད་ (རྒྱ་ནག)",
|
||||
"language.bo_IN": "པོད་སྐད་ (རྒྱ་གར་)",
|
||||
"language.br": "brezhoneg",
|
||||
"language.br_FR": "brezhoneg (Frañs)",
|
||||
"language.brx": "बड़ो",
|
||||
"language.brx_IN": "बड़ो (भारत)",
|
||||
"language.bs": "bosanski",
|
||||
"language.bs_BA": "bosanski (Bosna i Hercegovina)",
|
||||
"language.ca": "català",
|
||||
"language.ca_ES": "català (Espanya)",
|
||||
"language.cgg": "Rukiga",
|
||||
"language.cgg_UG": "Rukiga (Uganda)",
|
||||
"language.chr": "ᏣᎳᎩ",
|
||||
"language.chr_US": "ᏣᎳᎩ (ᎠᎹᏰᏟ)",
|
||||
"language.cs": "čeština",
|
||||
"language.cs_CZ": "čeština (Česká republika)",
|
||||
"language.cy": "Cymraeg",
|
||||
"language.cy_GB": "Cymraeg (Prydain Fawr)",
|
||||
"language.da": "dansk",
|
||||
"language.da_DK": "dansk (Danmark)",
|
||||
"language.dav": "Kitaita",
|
||||
"language.dav_KE": "Kitaita (Kenya)",
|
||||
"language.de": "Deutsch",
|
||||
"language.de_AT": "Deutsch (Österreich)",
|
||||
"language.de_BE": "Deutsch (Belgien)",
|
||||
"language.de_CH": "Deutsch (Schweiz)",
|
||||
"language.de_DE": "Deutsch (Deutschland)",
|
||||
"language.de_LI": "Deutsch (Liechtenstein)",
|
||||
"language.de_LU": "Deutsch (Luxemburg)",
|
||||
"language.dje": "Zarmaciine",
|
||||
"language.dje_NE": "Zarmaciine (Nižer)",
|
||||
"language.dua": "duálá",
|
||||
"language.dua_CM": "duálá (Cameroun)",
|
||||
"language.dyo": "joola",
|
||||
"language.dyo_SN": "joola (Senegal)",
|
||||
"language.ebu": "Kĩembu",
|
||||
"language.ebu_KE": "Kĩembu (Kenya)",
|
||||
"language.ee": "eʋegbe",
|
||||
"language.ee_GH": "eʋegbe (Ghana nutome)",
|
||||
"language.ee_TG": "eʋegbe (Togo nutome)",
|
||||
"language.el": "Ελληνικά",
|
||||
"language.el_CY": "Ελληνικά (Κύπρος)",
|
||||
"language.el_GR": "Ελληνικά (Ελλάδα)",
|
||||
"language.en": "English",
|
||||
"language.en_AS": "English (American Samoa)",
|
||||
"language.en_AU": "English (Australia)",
|
||||
"language.en_BB": "English (Barbados)",
|
||||
"language.en_BE": "English (Belgium)",
|
||||
"language.en_BM": "English (Bermuda)",
|
||||
"language.en_BW": "English (Botswana)",
|
||||
"language.en_BZ": "English (Belize)",
|
||||
"language.en_CA": "English (Canada)",
|
||||
"language.en_GB": "English (United Kingdom)",
|
||||
"language.en_GU": "English (Guam)",
|
||||
"language.en_GY": "English (Guyana)",
|
||||
"language.en_HK": "English (Hong Kong SAR China)",
|
||||
"language.en_IE": "English (Ireland)",
|
||||
"language.en_IN": "English (India)",
|
||||
"language.en_JM": "English (Jamaica)",
|
||||
"language.en_MH": "English (Marshall Islands)",
|
||||
"language.en_MP": "English (Northern Mariana Islands)",
|
||||
"language.en_MT": "English (Malta)",
|
||||
"language.en_MU": "English (Mauritius)",
|
||||
"language.en_NA": "English (Namibia)",
|
||||
"language.en_NZ": "English (New Zealand)",
|
||||
"language.en_PH": "English (Philippines)",
|
||||
"language.en_PK": "English (Pakistan)",
|
||||
"language.en_SG": "English (Singapore)",
|
||||
"language.en_TT": "English (Trinidad and Tobago)",
|
||||
"language.en_UM": "English (U.S. Minor Outlying Islands)",
|
||||
"language.en_US": "English (United States)",
|
||||
"language.en_US_POSIX": "English (United States, Computer)",
|
||||
"language.en_VI": "English (U.S. Virgin Islands)",
|
||||
"language.en_ZA": "English (South Africa)",
|
||||
"language.en_ZW": "English (Zimbabwe)",
|
||||
"language.eo": "esperanto",
|
||||
"language.es": "español",
|
||||
"language.es_419": "español (Latinoamérica)",
|
||||
"language.es_AR": "español (Argentina)",
|
||||
"language.es_BO": "español (Bolivia)",
|
||||
"language.es_CL": "español (Chile)",
|
||||
"language.es_CO": "español (Colombia)",
|
||||
"language.es_CR": "español (Costa Rica)",
|
||||
"language.es_DO": "español (República Dominicana)",
|
||||
"language.es_EC": "español (Ecuador)",
|
||||
"language.es_ES": "español (España)",
|
||||
"language.es_GQ": "español (Guinea Ecuatorial)",
|
||||
"language.es_GT": "español (Guatemala)",
|
||||
"language.es_HN": "español (Honduras)",
|
||||
"language.es_MX": "español (México)",
|
||||
"language.es_NI": "español (Nicaragua)",
|
||||
"language.es_PA": "español (Panamá)",
|
||||
"language.es_PE": "español (Perú)",
|
||||
"language.es_PR": "español (Puerto Rico)",
|
||||
"language.es_PY": "español (Paraguay)",
|
||||
"language.es_SV": "español (El Salvador)",
|
||||
"language.es_US": "español (Estados Unidos)",
|
||||
"language.es_UY": "español (Uruguay)",
|
||||
"language.es_VE": "español (Venezuela)",
|
||||
"language.et": "eesti",
|
||||
"language.et_EE": "eesti (Eesti)",
|
||||
"language.eu": "euskara",
|
||||
"language.eu_ES": "euskara (Espainia)",
|
||||
"language.ewo": "ewondo",
|
||||
"language.ewo_CM": "ewondo (Kamǝrún)",
|
||||
"language.fa": "فارسی",
|
||||
"language.fa_AF": "دری (افغانستان)",
|
||||
"language.fa_IR": "فارسی (ایران)",
|
||||
"language.ff": "Pulaar",
|
||||
"language.ff_SN": "Pulaar (Senegaal)",
|
||||
"language.fi": "suomi",
|
||||
"language.fi_FI": "suomi (Suomi)",
|
||||
"language.fil": "Filipino",
|
||||
"language.fil_PH": "Filipino (Pilipinas)",
|
||||
"language.fo": "føroyskt",
|
||||
"language.fo_FO": "føroyskt (Føroyar)",
|
||||
"language.fr": "français",
|
||||
"language.fr_BE": "français (Belgique)",
|
||||
"language.fr_BF": "français (Burkina Faso)",
|
||||
"language.fr_BI": "français (Burundi)",
|
||||
"language.fr_BJ": "français (Bénin)",
|
||||
"language.fr_BL": "français (Saint-Barthélémy)",
|
||||
"language.fr_CA": "français (Canada)",
|
||||
"language.fr_CD": "français (République démocratique du Congo)",
|
||||
"language.fr_CF": "français (République centrafricaine)",
|
||||
"language.fr_CG": "français (Congo-Brazzaville)",
|
||||
"language.fr_CH": "français (Suisse)",
|
||||
"language.fr_CI": "français (Côte d’Ivoire)",
|
||||
"language.fr_CM": "français (Cameroun)",
|
||||
"language.fr_DJ": "français (Djibouti)",
|
||||
"language.fr_FR": "français (France)",
|
||||
"language.fr_GA": "français (Gabon)",
|
||||
"language.fr_GF": "français (Guyane française)",
|
||||
"language.fr_GN": "français (Guinée)",
|
||||
"language.fr_GP": "français (Guadeloupe)",
|
||||
"language.fr_GQ": "français (Guinée équatoriale)",
|
||||
"language.fr_KM": "français (Comores)",
|
||||
"language.fr_LU": "français (Luxembourg)",
|
||||
"language.fr_MC": "français (Monaco)",
|
||||
"language.fr_MF": "français (Saint-Martin)",
|
||||
"language.fr_MG": "français (Madagascar)",
|
||||
"language.fr_ML": "français (Mali)",
|
||||
"language.fr_MQ": "français (Martinique)",
|
||||
"language.fr_NE": "français (Niger)",
|
||||
"language.fr_RE": "français (Réunion)",
|
||||
"language.fr_RW": "français (Rwanda)",
|
||||
"language.fr_SN": "français (Sénégal)",
|
||||
"language.fr_TD": "français (Tchad)",
|
||||
"language.fr_TG": "français (Togo)",
|
||||
"language.fr_YT": "français (Mayotte)",
|
||||
"language.ga": "Gaeilge",
|
||||
"language.ga_IE": "Gaeilge (Éire)",
|
||||
"language.gl": "galego",
|
||||
"language.gl_ES": "galego (España)",
|
||||
"language.gsw": "Schwiizertüütsch",
|
||||
"language.gsw_CH": "Schwiizertüütsch (Schwiiz)",
|
||||
"language.gu": "ગુજરાતી",
|
||||
"language.gu_IN": "ગુજરાતી (ભારત)",
|
||||
"language.guz": "Ekegusii",
|
||||
"language.guz_KE": "Ekegusii (Kenya)",
|
||||
"language.gv": "Gaelg",
|
||||
"language.gv_GB": "Gaelg (Rywvaneth Unys)",
|
||||
"language.ha": "Hausa",
|
||||
"language.ha_Latn": "Hausa (Latn)",
|
||||
"language.ha_Latn_GH": "Hausa (Latn, Gana)",
|
||||
"language.ha_Latn_NE": "Hausa (Latn, Nijar)",
|
||||
"language.ha_Latn_NG": "Hausa (Latn, Najeriya)",
|
||||
"language.haw": "ʻŌlelo Hawaiʻi",
|
||||
"language.haw_US": "ʻŌlelo Hawaiʻi (ʻAmelika Hui Pū ʻIa)",
|
||||
"language.he": "עברית",
|
||||
"language.he_IL": "עברית (ישראל)",
|
||||
"language.hi": "हिन्दी",
|
||||
"language.hi_IN": "हिन्दी (भारत)",
|
||||
"language.hr": "hrvatski",
|
||||
"language.hr_HR": "hrvatski (Hrvatska)",
|
||||
"language.hu": "magyar",
|
||||
"language.hu_HU": "magyar (Magyarország)",
|
||||
"language.hy": "Հայերէն",
|
||||
"language.hy_AM": "Հայերէն (Հայաստանի Հանրապետութիւն)",
|
||||
"language.id": "Bahasa Indonesia",
|
||||
"language.id_ID": "Bahasa Indonesia (Indonesia)",
|
||||
"language.ig": "Igbo",
|
||||
"language.ig_NG": "Igbo (Nigeria)",
|
||||
"language.ii": "ꆈꌠꉙ",
|
||||
"language.ii_CN": "ꆈꌠꉙ (ꍏꇩ)",
|
||||
"language.is": "íslenska",
|
||||
"language.is_IS": "íslenska (Ísland)",
|
||||
"language.it": "italiano",
|
||||
"language.it_CH": "italiano (Svizzera)",
|
||||
"language.it_IT": "italiano (Italia)",
|
||||
"language.ja": "日本語",
|
||||
"language.ja_JP": "日本語(日本)",
|
||||
"language.jmc": "Kimachame",
|
||||
"language.jmc_TZ": "Kimachame (Tanzania)",
|
||||
"language.ka": "ქართული",
|
||||
"language.ka_GE": "ქართული (საქართველო)",
|
||||
"language.kab": "Taqbaylit",
|
||||
"language.kab_DZ": "Taqbaylit (Lezzayer)",
|
||||
"language.kam": "Kikamba",
|
||||
"language.kam_KE": "Kikamba (Kenya)",
|
||||
"language.kde": "Chimakonde",
|
||||
"language.kde_TZ": "Chimakonde (Tanzania)",
|
||||
"language.kea": "kabuverdianu",
|
||||
"language.kea_CV": "kabuverdianu (Kabu Verdi)",
|
||||
"language.khq": "Koyra ciini",
|
||||
"language.khq_ML": "Koyra ciini (Maali)",
|
||||
"language.ki": "Gikuyu",
|
||||
"language.ki_KE": "Gikuyu (Kenya)",
|
||||
"language.kk": "қазақ тілі",
|
||||
"language.kk_Cyrl": "қазақ тілі (кириллица)",
|
||||
"language.kk_Cyrl_KZ": "қазақ тілі (кириллица, Қазақстан)",
|
||||
"language.kl": "kalaallisut",
|
||||
"language.kl_GL": "kalaallisut (Kalaallit Nunaat)",
|
||||
"language.kln": "Kalenjin",
|
||||
"language.kln_KE": "Kalenjin (Emetab Kenya)",
|
||||
"language.km": "ភាសាខ្មែរ",
|
||||
"language.km_KH": "ភាសាខ្មែរ (កម្ពុជា)",
|
||||
"language.kn": "ಕನ್ನಡ",
|
||||
"language.kn_IN": "ಕನ್ನಡ (ಭಾರತ)",
|
||||
"language.ko": "한국어",
|
||||
"language.ko_KR": "한국어(대한민국)",
|
||||
"language.kok": "कोंकणी",
|
||||
"language.kok_IN": "कोंकणी (भारत)",
|
||||
"language.ksb": "Kishambaa",
|
||||
"language.ksb_TZ": "Kishambaa (Tanzania)",
|
||||
"language.ksf": "rikpa",
|
||||
"language.ksf_CM": "rikpa (kamɛrún)",
|
||||
"language.kw": "kernewek",
|
||||
"language.kw_GB": "kernewek (Rywvaneth Unys)",
|
||||
"language.lag": "Kɨlaangi",
|
||||
"language.lag_TZ": "Kɨlaangi (Taansanía)",
|
||||
"language.lg": "Luganda",
|
||||
"language.lg_UG": "Luganda (Yuganda)",
|
||||
"language.ln": "lingála",
|
||||
"language.ln_CD": "lingála (Repibiki demokratiki ya Kongó)",
|
||||
"language.ln_CG": "lingála (Kongo)",
|
||||
"language.lt": "lietuvių",
|
||||
"language.lt_LT": "lietuvių (Lietuva)",
|
||||
"language.lu": "Tshiluba",
|
||||
"language.lu_CD": "Tshiluba (Ditunga wa Kongu)",
|
||||
"language.luo": "Dholuo",
|
||||
"language.luo_KE": "Dholuo (Kenya)",
|
||||
"language.luy": "Luluhia",
|
||||
"language.luy_KE": "Luluhia (Kenya)",
|
||||
"language.lv": "latviešu",
|
||||
"language.lv_LV": "latviešu (Latvija)",
|
||||
"language.mas": "Maa",
|
||||
"language.mas_KE": "Maa (Kenya)",
|
||||
"language.mas_TZ": "Maa (Tansania)",
|
||||
"language.mer": "Kĩmĩrũ",
|
||||
"language.mer_KE": "Kĩmĩrũ (Kenya)",
|
||||
"language.mfe": "kreol morisien",
|
||||
"language.mfe_MU": "kreol morisien (Moris)",
|
||||
"language.mg": "Malagasy",
|
||||
"language.mg_MG": "Malagasy (Madagasikara)",
|
||||
"language.mgh": "Makua",
|
||||
"language.mgh_MZ": "Makua (Umozambiki)",
|
||||
"language.mk": "македонски",
|
||||
"language.mk_MK": "македонски (Македонија)",
|
||||
"language.ml": "മലയാളം",
|
||||
"language.ml_IN": "മലയാളം (ഇന്ത്യ)",
|
||||
"language.mr": "मराठी",
|
||||
"language.mr_IN": "मराठी (भारत)",
|
||||
"language.ms": "Bahasa Melayu",
|
||||
"language.ms_BN": "Bahasa Melayu (Brunei)",
|
||||
"language.ms_MY": "Bahasa Melayu (Malaysia)",
|
||||
"language.mt": "Malti",
|
||||
"language.mt_MT": "Malti (Malta)",
|
||||
"language.mua": "MUNDAŊ",
|
||||
"language.mua_CM": "MUNDAŊ (kameruŋ)",
|
||||
"language.my": "ဗမာ",
|
||||
"language.my_MM": "ဗမာ (မြန်မာ)",
|
||||
"language.naq": "Khoekhoegowab",
|
||||
"language.naq_NA": "Khoekhoegowab (Namibiab)",
|
||||
"language.nb": "norsk bokmål",
|
||||
"language.nb_NO": "norsk bokmål (Norge)",
|
||||
"language.nd": "isiNdebele",
|
||||
"language.nd_ZW": "isiNdebele (Zimbabwe)",
|
||||
"language.ne": "नेपाली",
|
||||
"language.ne_IN": "नेपाली (भारत)",
|
||||
"language.ne_NP": "नेपाली (नेपाल)",
|
||||
"language.nl": "Nederlands",
|
||||
"language.nl_AW": "Nederlands (Aruba)",
|
||||
"language.nl_BE": "Nederlands (België)",
|
||||
"language.nl_CW": "Nederlands (Curaçao)",
|
||||
"language.nl_NL": "Nederlands (Nederland)",
|
||||
"language.nl_SX": "Nederlands (Sint Maarten)",
|
||||
"language.nmg": "nmg",
|
||||
"language.nmg_CM": "nmg (Kamerun)",
|
||||
"language.nn": "nynorsk",
|
||||
"language.nn_NO": "nynorsk (Noreg)",
|
||||
"language.nus": "Thok Nath",
|
||||
"language.nus_SD": "Thok Nath (Sudan)",
|
||||
"language.nyn": "Runyankore",
|
||||
"language.nyn_UG": "Runyankore (Uganda)",
|
||||
"language.om": "Oromoo",
|
||||
"language.om_ET": "Oromoo (Itoophiyaa)",
|
||||
"language.om_KE": "Oromoo (Keeniyaa)",
|
||||
"language.or": "ଓଡ଼ିଆ",
|
||||
"language.or_IN": "ଓଡ଼ିଆ (ଭାରତ)",
|
||||
"language.pa": "ਪੰਜਾਬੀ",
|
||||
"language.pa_Arab": "پنجاب (العربية)",
|
||||
"language.pa_Arab_PK": "پنجاب (العربية, پکستان)",
|
||||
"language.pa_Guru": "ਪੰਜਾਬੀ (Guru)",
|
||||
"language.pa_Guru_IN": "ਪੰਜਾਬੀ (Guru, ਭਾਰਤ)",
|
||||
"language.pl": "polski",
|
||||
"language.pl_PL": "polski (Polska)",
|
||||
"language.ps": "پښتو",
|
||||
"language.ps_AF": "پښتو (افغانستان)",
|
||||
"language.pt": "português",
|
||||
"language.pt_AO": "português (Angola)",
|
||||
"language.pt_BR": "português (Brasil)",
|
||||
"language.pt_GW": "português (Guiné Bissau)",
|
||||
"language.pt_MZ": "português (Moçambique)",
|
||||
"language.pt_PT": "português (Portugal)",
|
||||
"language.pt_ST": "português (São Tomé e Príncipe)",
|
||||
"language.rm": "rumantsch",
|
||||
"language.rm_CH": "rumantsch (Svizra)",
|
||||
"language.rn": "Ikirundi",
|
||||
"language.rn_BI": "Ikirundi (Uburundi)",
|
||||
"language.ro": "română",
|
||||
"language.ro_MD": "română (Republica Moldova)",
|
||||
"language.ro_RO": "română (România)",
|
||||
"language.rof": "Kihorombo",
|
||||
"language.rof_TZ": "Kihorombo (Tanzania)",
|
||||
"language.ru": "русский",
|
||||
"language.ru_MD": "русский (Молдова)",
|
||||
"language.ru_RU": "русский (Россия)",
|
||||
"language.ru_UA": "русский (Украина)",
|
||||
"language.rw": "Kinyarwanda",
|
||||
"language.rw_RW": "Kinyarwanda (Rwanda)",
|
||||
"language.rwk": "Kiruwa",
|
||||
"language.rwk_TZ": "Kiruwa (Tanzania)",
|
||||
"language.saq": "Kisampur",
|
||||
"language.saq_KE": "Kisampur (Kenya)",
|
||||
"language.sbp": "Ishisangu",
|
||||
"language.sbp_TZ": "Ishisangu (Tansaniya)",
|
||||
"language.seh": "sena",
|
||||
"language.seh_MZ": "sena (Moçambique)",
|
||||
"language.ses": "Koyraboro senni",
|
||||
"language.ses_ML": "Koyraboro senni (Maali)",
|
||||
"language.sg": "Sängö",
|
||||
"language.sg_CF": "Sängö (Ködörösêse tî Bêafrîka)",
|
||||
"language.shi": "tamazight",
|
||||
"language.shi_Latn": "tamazight (Latn)",
|
||||
"language.shi_Latn_MA": "tamazight (Latn, lmɣrib)",
|
||||
"language.shi_Tfng": "ⵜⴰⵎⴰⵣⵉⵖⵜ (Tfng)",
|
||||
"language.shi_Tfng_MA": "ⵜⴰⵎⴰⵣⵉⵖⵜ (Tfng, ⵍⵎⵖⵔⵉⴱ)",
|
||||
"language.si": "සිංහල",
|
||||
"language.si_LK": "සිංහල (ශ්රී ලංකාව)",
|
||||
"language.sk": "slovenčina",
|
||||
"language.sk_SK": "slovenčina (Slovenská republika)",
|
||||
"language.sl": "slovenščina",
|
||||
"language.sl_SI": "slovenščina (Slovenija)",
|
||||
"language.sn": "chiShona",
|
||||
"language.sn_ZW": "chiShona (Zimbabwe)",
|
||||
"language.so": "Soomaali",
|
||||
"language.so_DJ": "Soomaali (Jabuuti)",
|
||||
"language.so_ET": "Soomaali (Itoobiya)",
|
||||
"language.so_KE": "Soomaali (Kiiniya)",
|
||||
"language.so_SO": "Soomaali (Soomaaliya)",
|
||||
"language.sq": "shqip",
|
||||
"language.sq_AL": "shqip (Shqipëria)",
|
||||
"language.sr": "Српски",
|
||||
"language.sr_Cyrl": "Српски (Ћирилица)",
|
||||
"language.sr_Cyrl_BA": "Српски (Ћирилица, Босна и Херцеговина)",
|
||||
"language.sr_Cyrl_ME": "Српски (Ћирилица, Црна Гора)",
|
||||
"language.sr_Cyrl_RS": "Српски (Ћирилица, Србија)",
|
||||
"language.sr_Latn": "Srpski (Latinica)",
|
||||
"language.sr_Latn_BA": "Srpski (Latinica, Bosna i Hercegovina)",
|
||||
"language.sr_Latn_ME": "Srpski (Latinica, Crna Gora)",
|
||||
"language.sr_Latn_RS": "Srpski (Latinica, Srbija)",
|
||||
"language.sv": "svenska",
|
||||
"language.sv_FI": "svenska (Finland)",
|
||||
"language.sv_SE": "svenska (Sverige)",
|
||||
"language.sw": "Kiswahili",
|
||||
"language.sw_KE": "Kiswahili (Kenya)",
|
||||
"language.sw_TZ": "Kiswahili (Tanzania)",
|
||||
"language.swc": "Kiswahili ya Kongo",
|
||||
"language.swc_CD": "Kiswahili ya Kongo (Jamhuri ya Kidemokrasia ya Kongo)",
|
||||
"language.ta": "தமிழ்",
|
||||
"language.ta_IN": "தமிழ் (இந்தியா)",
|
||||
"language.ta_LK": "தமிழ் (இலங்கை)",
|
||||
"language.te": "తెలుగు",
|
||||
"language.te_IN": "తెలుగు (భారత దేశం)",
|
||||
"language.teo": "Kiteso",
|
||||
"language.teo_KE": "Kiteso (Kenia)",
|
||||
"language.teo_UG": "Kiteso (Uganda)",
|
||||
"language.th": "ไทย",
|
||||
"language.th_TH": "ไทย (ไทย)",
|
||||
"language.ti": "ትግርኛ",
|
||||
"language.ti_ER": "ትግርኛ (ER)",
|
||||
"language.ti_ET": "ትግርኛ (ET)",
|
||||
"language.to": "lea fakatonga",
|
||||
"language.to_TO": "lea fakatonga (Tonga)",
|
||||
"language.tr": "Türkçe",
|
||||
"language.tr_TR": "Türkçe (Türkiye)",
|
||||
"language.twq": "Tasawaq senni",
|
||||
"language.twq_NE": "Tasawaq senni (Nižer)",
|
||||
"language.tzm": "Tamaziɣt",
|
||||
"language.tzm_Latn": "Tamaziɣt (Latn)",
|
||||
"language.tzm_Latn_MA": "Tamaziɣt (Latn, Meṛṛuk)",
|
||||
"language.uk": "українська",
|
||||
"language.uk_UA": "українська (Україна)",
|
||||
"language.ur": "اردو",
|
||||
"language.ur_IN": "اردو (بھارت)",
|
||||
"language.ur_PK": "اردو (پاکستان)",
|
||||
"language.uz": "Ўзбек",
|
||||
"language.uz_Arab": "اۉزبېک (Arab)",
|
||||
"language.uz_Arab_AF": "اۉزبېک (Arab, افغانستان)",
|
||||
"language.uz_Cyrl": "Ўзбек (Cyrl)",
|
||||
"language.uz_Cyrl_UZ": "Ўзбек (Cyrl, Ўзбекистон)",
|
||||
"language.uz_Latn": "oʼzbekcha (Lotin)",
|
||||
"language.uz_Latn_UZ": "oʼzbekcha (Lotin, Oʼzbekiston)",
|
||||
"language.vai": "ꕙꔤ",
|
||||
"language.vai_Latn": "Vai (Latn)",
|
||||
"language.vai_Latn_LR": "Vai (Latn, Laibhiya)",
|
||||
"language.vai_Vaii": "ꕙꔤ (Vaii)",
|
||||
"language.vai_Vaii_LR": "ꕙꔤ (Vaii, ꕞꔤꔫꕩ)",
|
||||
"language.vi": "Tiếng Việt",
|
||||
"language.vi_VN": "Tiếng Việt (Việt Nam)",
|
||||
"language.vun": "Kyivunjo",
|
||||
"language.vun_TZ": "Kyivunjo (Tanzania)",
|
||||
"language.xog": "Olusoga",
|
||||
"language.xog_UG": "Olusoga (Yuganda)",
|
||||
"language.yav": "nuasue",
|
||||
"language.yav_CM": "nuasue (Kemelún)",
|
||||
"language.yo": "Èdè Yorùbá",
|
||||
"language.yo_NG": "Èdè Yorùbá (Orílẹ́ède Nàìjíríà)",
|
||||
"language.zh": "中文",
|
||||
"language.zh_Hans": "中文(简体中文)",
|
||||
"language.zh_Hans_CN": "中文(简体中文、中国)",
|
||||
"language.zh_Hans_HK": "中文(简体中文、中国香港特别行政区)",
|
||||
"language.zh_Hans_MO": "中文(简体中文、中国澳门特别行政区)",
|
||||
"language.zh_Hans_SG": "中文(简体中文、新加坡)",
|
||||
"language.zh_Hant": "中文(繁體中文)",
|
||||
"language.zh_Hant_HK": "中文(繁體中文,中華人民共和國香港特別行政區)",
|
||||
"language.zh_Hant_MO": "中文(繁體中文,中華人民共和國澳門特別行政區)",
|
||||
"language.zh_Hant_TW": "中文(繁體中文,台灣)",
|
||||
"language.zu": "isiZulu",
|
||||
"language.zu_ZA": "isiZulu (iNingizimu Afrika)",
|
||||
|
||||
"pageNotFound": "Page not found"
|
||||
}
|
||||
@ -34,7 +34,6 @@
|
||||
"form.database.item.provider.mongo": "Mongo",
|
||||
"form.database.item.provider.postgres": "PostgresSQL",
|
||||
"form.database.item.provider.mysql": "MySQL",
|
||||
"form.database.item.provider.sqlite3": "SQlite3",
|
||||
"form.database.item.provider.redis": "Redis",
|
||||
|
||||
"form.application.name": "應用程式",
|
||||
|
||||
@ -137,6 +137,7 @@ module.exports = {
|
||||
fs.unlinkSync(filePath);
|
||||
|
||||
ctx.send({ ok: true });
|
||||
strapi.reload();
|
||||
} catch (e) {
|
||||
ctx.badRequest(null, Service.formatErrors([{
|
||||
target: 'name',
|
||||
|
||||
@ -520,11 +520,6 @@ module.exports = {
|
||||
value: 'mysql',
|
||||
port: 3306
|
||||
},
|
||||
{
|
||||
name: 'form.database.item.provider.sqlite3',
|
||||
value: 'sqlite3',
|
||||
port: 1433
|
||||
},
|
||||
{
|
||||
name: 'form.database.item.provider.redis',
|
||||
value: 'redis',
|
||||
@ -650,7 +645,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
getClientConnector: client => {
|
||||
const bookshelfClients = ['postgres', 'mysql', 'sqlite3'];
|
||||
const bookshelfClients = ['postgres', 'mysql'];
|
||||
const mongooseClients = ['mongo'];
|
||||
const redisClients = ['redis'];
|
||||
|
||||
@ -676,9 +671,6 @@ module.exports = {
|
||||
case 'mongo':
|
||||
return '#43b121';
|
||||
break;
|
||||
case 'sqlite3':
|
||||
return '#006fff';
|
||||
break;
|
||||
default:
|
||||
return '#000000';
|
||||
}
|
||||
@ -899,8 +891,7 @@ module.exports = {
|
||||
installDependency: (params, name) => {
|
||||
const clientsDependencies = {
|
||||
postgres: 'pg',
|
||||
mysql: 'mysql',
|
||||
sqlite3: 'sqlite3'
|
||||
mysql: 'mysql'
|
||||
};
|
||||
|
||||
const client = _.get(clientsDependencies, _.get(params, `database.connections.${name}.settings.client`));
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
{
|
||||
"ConfigPage.title": "上传-设置",
|
||||
"ConfigPage.description": "配置上传插件",
|
||||
|
||||
"EditForm.Input.number.label": "最大文件限制 (单位:MB)",
|
||||
"EditForm.Input.select.label": "Providers",
|
||||
"EditForm.Input.select.inputDescription": "文件可以上传到服务器上,也可以上传到外部存储服务供应商。",
|
||||
"EditForm.Input.toggle.label": "启用文件上传",
|
||||
|
||||
"EmptyLi.message": "没有上传的文件",
|
||||
|
||||
"EntriesNumber.number": "找到 {number} 个文件",
|
||||
"EntriesNumber.number.plural": "找到 {number} 个文件",
|
||||
|
||||
"HomePage.title": "上传",
|
||||
"HomePage.description": "发现所有上传的文件",
|
||||
"HomePage.InputSearch.placeholder": "搜索文件…",
|
||||
|
||||
"Li.linkCopied": "链接复制到剪贴板",
|
||||
|
||||
"ListHeader.type": "类型",
|
||||
"ListHeader.hash": "Hash",
|
||||
"ListHeader.name": "文件名",
|
||||
"ListHeader.updated": "更新时间",
|
||||
"ListHeader.size": "大小",
|
||||
"ListHeader.related": "Related to",
|
||||
|
||||
"PluginInputFile.text": "将文件拖放到该区域或 {link} 文件上传",
|
||||
"PluginInputFile.link": "浏览",
|
||||
"PluginInputFile.loading": "您的文件正在上传…",
|
||||
|
||||
"notification.config.success": "设置已更新",
|
||||
"notification.delete.success": "文件已被删除",
|
||||
"notification.dropFile.success": "您的文件已上传",
|
||||
"notification.dropFiles.success": "{number} 个文件已上传"
|
||||
}
|
||||
@ -13,48 +13,6 @@ const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = async cb => {
|
||||
const Model = strapi.plugins.upload.models.file;
|
||||
|
||||
if (Model.orm === 'bookshelf') {
|
||||
const hasTable = await strapi.connections[Model.connection].schema.hasTable(Model.tableName || Model.collectionName);
|
||||
|
||||
if (!hasTable) {
|
||||
const quote = Model.client === 'pg' ? '"' : '`';
|
||||
|
||||
strapi.log.warn(`
|
||||
⚠️ TABLE \`upload_file\` DOESN'T EXIST
|
||||
⚠️ TABLE \`upload_file_morph\` DOESN'T EXIST
|
||||
|
||||
CREATE TABLE ${quote}${Model.tableName || Model.collectionName}${quote} (
|
||||
id ${Model.client === 'pg' ? 'SERIAL' : 'INT AUTO_INCREMENT'} NOT NULL PRIMARY KEY,
|
||||
name text,
|
||||
hash text,
|
||||
ext text,
|
||||
mime text,
|
||||
size text,
|
||||
url text,
|
||||
provider text,
|
||||
updated_at ${Model.client === 'pg' ? 'timestamp with time zone' : 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'},
|
||||
created_at ${Model.client === 'pg' ? 'timestamp with time zone' : 'timestamp DEFAULT CURRENT_TIMESTAMP'}
|
||||
);
|
||||
|
||||
CREATE TABLE ${quote}upload_file_morph${quote} (
|
||||
id ${Model.client === 'pg' ? 'SERIAL' : 'INT AUTO_INCREMENT'} NOT NULL PRIMARY KEY,
|
||||
upload_file_id ${Model.client === 'pg' ? 'integer' : 'int'},
|
||||
related_id ${Model.client === 'pg' ? 'integer' : 'int'},
|
||||
related_type text,
|
||||
field text
|
||||
);
|
||||
|
||||
1️⃣ EXECUTE THE FOLLOWING SQL QUERY
|
||||
|
||||
2️⃣ RESTART YOUR SERVER
|
||||
`);
|
||||
|
||||
strapi.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// set plugin store
|
||||
const pluginStore = strapi.store({
|
||||
environment: strapi.config.environment,
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
"required": true
|
||||
},
|
||||
"size": {
|
||||
"type": "float",
|
||||
"type": "string",
|
||||
"configurable": false,
|
||||
"required": true
|
||||
},
|
||||
|
||||
@ -0,0 +1,177 @@
|
||||
{
|
||||
"Auth.form.button.register-success": "再次发送",
|
||||
"Auth.form.button.forgot-password.success": "再次发送",
|
||||
"Auth.form.button.forgot-password": "发送电子邮件",
|
||||
"Auth.form.button.reset-password": "修改密码",
|
||||
"Auth.form.button.login": "登陆",
|
||||
"Auth.form.button.register": "准备开始",
|
||||
"Auth.form.error.noAdminAccess": "你无法访问管理面板。",
|
||||
|
||||
"Auth.form.forgot-password.email.label": "输入你的电子邮件",
|
||||
"Auth.form.forgot-password.email.label.success": "电子邮件已成功发送到",
|
||||
"Auth.form.forgot-password.email.placeholder": "mysuperemail@gmail.com",
|
||||
|
||||
"Auth.header.register.description": "要完成安装并保护您的应用程序,请通过输入必要的信息来创建第一个用户(root管理员)。",
|
||||
"Auth.form.header.login": "strapi",
|
||||
"Auth.form.header.forgot-password": "strapi",
|
||||
"Auth.form.header.register": "欢迎!",
|
||||
"Auth.form.header.register-success": "strapi",
|
||||
|
||||
"Auth.form.login.password.label": "密码",
|
||||
"Auth.form.login.rememberMe.label": "记住我",
|
||||
"Auth.form.login.username.label": "用户名",
|
||||
"Auth.form.login.username.placeholder": "John Doe",
|
||||
|
||||
"Auth.form.register.email.label": "邮箱",
|
||||
"Auth.form.register.email.placeholder": "johndoe@gmail.com",
|
||||
"Auth.form.register.username.label": "用户名",
|
||||
"Auth.form.register.username.placeholder": "John Doe",
|
||||
"Auth.form.register.password.label": "密码",
|
||||
"Auth.form.register.confirmPassword.label": "确认密码",
|
||||
"Auth.form.register.news.label": "让我了解新特性和即将到来的改进。",
|
||||
|
||||
"Auth.form.register-success.email.label": "电子邮件已发送成功到",
|
||||
"Auth.form.register-success.email.placeholder": "mysuperemail@gmail.com",
|
||||
|
||||
"Auth.form.error.email.provide": "请提供您的用户名或电子邮件。",
|
||||
"Auth.form.error.email.invalid": "此电子邮件无效。",
|
||||
"Auth.form.error.password.provide": "请提供您的密码。",
|
||||
"Auth.form.error.invalid": "标识符或密码无效。",
|
||||
"Auth.form.error.password.local": "此用户从未设置本地密码,请通过帐户创建过程中使用的第三方供应商登录。",
|
||||
"Auth.form.error.password.format": "您的密码不能包含符号 `$` 超过三次。",
|
||||
"Auth.form.error.user.not-exist": "此电子邮件不存在。",
|
||||
"Auth.form.error.code.provide": "提供代码的代码不正确。",
|
||||
"Auth.form.error.password.matching": "密码不匹配。",
|
||||
"Auth.form.error.params.provide": "提供错误的参数。",
|
||||
"Auth.form.error.username.taken": "用户名已被使用",
|
||||
"Auth.form.error.email.taken": "邮箱已被使用",
|
||||
|
||||
"Auth.link.forgot-password": "忘记密码了吗?",
|
||||
"Auth.link.ready": "准备好登陆?",
|
||||
|
||||
"BoundRoute.title": "绑定路由到",
|
||||
|
||||
"components.Input.error.password.noMatch": "密码不匹配",
|
||||
|
||||
"Controller.input.label": "{label} ",
|
||||
"Controller.selectAll": "选择全部",
|
||||
|
||||
"EditForm.inputSelect.label.role": "认证用户的默认角色",
|
||||
"EditForm.inputSelect.description.role": "新验证身份的用户将被赋予所选角色。",
|
||||
"EditForm.inputSelect.subscriptions.label": "管理订阅配额",
|
||||
"EditForm.inputSelect.subscriptions.description": "限制每个每小时IP的订阅数。",
|
||||
"EditForm.inputSelect.durations.label": "持续时间",
|
||||
"EditForm.inputSelect.durations.description": "用户无法订阅的时间段。",
|
||||
|
||||
"EditForm.inputToggle.label.email": "每个电子邮件地址一个帐户",
|
||||
"EditForm.inputToggle.label.sign-up": "启用注册",
|
||||
"EditForm.inputToggle.description.email": "不允许用户使用不同的认证提供者(绑定的相同的电子邮件地址)来创建多个帐户。",
|
||||
"EditForm.inputToggle.description.sign-up": "当禁用(OFF)时,注册过程将被禁止。任何人无论使用任何的供应商都不可以订阅。",
|
||||
|
||||
"EditPage.cancel": "取消",
|
||||
"EditPage.submit": "保存",
|
||||
"EditPage.form.roles": "角色详情",
|
||||
"EditPage.form.roles.label.description": "描述",
|
||||
"EditPage.form.roles.label.name": "名称",
|
||||
"EditPage.form.roles.label.users": "与此角色相关联的用户数: ({number})",
|
||||
"EditPage.form.roles.name.error": "此值是必需的。",
|
||||
"EditPage.header.title": "{name} ",
|
||||
"EditPage.header.title.create": "创建一个新角色",
|
||||
"EditPage.header.description": "{description} ",
|
||||
"EditPage.header.description.create": " ",
|
||||
|
||||
"EditPage.notification.permissions.error": "获取权限(permission)时出错",
|
||||
"EditPage.notification.policies.error": "获取鉴权策略(policies)时出错",
|
||||
"EditPage.notification.role.error": "获取角色时出错",
|
||||
|
||||
"HeaderNav.link.advancedSettings": "高级设置",
|
||||
"HeaderNav.link.emailTemplates": "电子邮件模板",
|
||||
"HeaderNav.link.providers": "提供者",
|
||||
"HeaderNav.link.roles": "角色和权限",
|
||||
|
||||
"HomePage.header.title": "角色和权限",
|
||||
"HomePage.header.description": "定义用户的角色和权限。",
|
||||
|
||||
"InputSearch.placeholder": "搜索用户",
|
||||
|
||||
"List.button.roles": "添加新角色",
|
||||
"List.button.providers": "添加新供应商",
|
||||
|
||||
"List.title.emailTemplates.singular": "{number} 电子邮件模板是可用的",
|
||||
"List.title.emailTemplates.plural": "{number} 电子邮件模板是可用的",
|
||||
|
||||
"List.title.providers.disabled.singular": "{number} 被禁用",
|
||||
"List.title.providers.disabled.plural": "{number} 被禁用",
|
||||
"List.title.providers.enabled.singular": "{number} 个供应商被启用, ",
|
||||
"List.title.providers.enabled.plural": "{number} 个供应商被启用, ",
|
||||
|
||||
"List.title.roles.singular": "{number} 个角色可用",
|
||||
"List.title.roles.plural": "{number} 角色可用",
|
||||
|
||||
"notification.error.delete": "删除数据时出错",
|
||||
"notification.error.fetch": "获取数据时出错",
|
||||
"notification.error.fetchUser": "获取用户时出错",
|
||||
"notification.info.emailSent": "电子邮件已发送",
|
||||
"notification.success.delete": "该项已被删除",
|
||||
"notification.success.submit": "设置已被更新",
|
||||
|
||||
"plugin.description.short": "使用基于JWT的完整身份验证过程保护API",
|
||||
"plugin.description.long": "使用基于JWT的完整身份验证过程来保护API。这个插件还有一个ACL策略,允许你管理用户组之间的权限。",
|
||||
|
||||
"Plugin.permissions.application.description": "定义所有项目的允许操作。",
|
||||
"Plugin.permissions.plugins.description": "定义 {name} 插件所有允许的操作。",
|
||||
|
||||
"Plugins.header.title": "权限",
|
||||
"Plugins.header.description": "下面只列出路由绑定的操作。",
|
||||
|
||||
"Policies.InputSelect.empty": "没有",
|
||||
"Policies.InputSelect.label": "允许执行以下操作:",
|
||||
"Policies.header.hint": "选择应用程序或插件的操作,然后点击COG图标显示绑定的路由",
|
||||
"Policies.header.title": "高级设置",
|
||||
|
||||
"Email.template.validation_email": "电子邮件地址验证",
|
||||
"Email.template.reset_password": "重置密码",
|
||||
"Email.template.success_register": "注册成功",
|
||||
|
||||
"Auth.advanced.allow_register": "",
|
||||
|
||||
"PopUpForm.button.cancel": "取消",
|
||||
"PopUpForm.button.save": "保存",
|
||||
"PopUpForm.header.add.providers": "增加新的供应商(Provider)",
|
||||
"PopUpForm.header.edit.email-templates": "编辑电子邮件模版",
|
||||
"PopUpForm.header.edit.providers": "编译 {provider} 供应商",
|
||||
"PopUpForm.inputSelect.providers.label": "选择供应商",
|
||||
"PopUpForm.Email.options.from.name.label": "Shipper name",
|
||||
"PopUpForm.Email.options.from.email.label": "Shipper email",
|
||||
"PopUpForm.Email.options.response_email.label": "Response email",
|
||||
"PopUpForm.Email.options.object.label": "主题",
|
||||
"PopUpForm.Email.options.message.label": "消息",
|
||||
"PopUpForm.Email.validation_email.options.object.placeholder": "请确认您的电子邮件地址 %APP_NAME%",
|
||||
"PopUpForm.Email.reset_password.options.object.placeholder": "请确认您的电子邮件地址 %APP_NAME%",
|
||||
"PopUpForm.Email.success_register.options.object.placeholder": "请确认您的电子邮件地址 %APP_NAME%",
|
||||
"PopUpForm.Email.validation_email.options.message.placeholder": "<p>请点击此链接验证您的帐户</p>",
|
||||
"PopUpForm.Email.reset_password.options.message.placeholder": "<p>请点击此链接验证您的帐户</p>",
|
||||
"PopUpForm.Email.success_register.options.message.placeholder": "<p>请点击此链接验证您的帐户</p>",
|
||||
"PopUpForm.Email.options.from.email.placeholder": "johndoe@gmail.com",
|
||||
"PopUpForm.Email.options.response_email.placeholder": "johndoe@gmail.com",
|
||||
"PopUpForm.Email.options.from.name.placeholder": "John Doe",
|
||||
"PopUpForm.Providers.enabled.label": "启用",
|
||||
"PopUpForm.Providers.enabled.description": "如果禁用,用户将无法使用此供应商。",
|
||||
"PopUpForm.Providers.key.label": "Client ID",
|
||||
"PopUpForm.Providers.key.placeholder": "TEXT",
|
||||
"PopUpForm.Providers.secret.label": "Client Secret",
|
||||
"PopUpForm.Providers.secret.placeholder": "TEXT",
|
||||
"PopUpForm.Providers.redirectURL.front-end.label": "重定向URL",
|
||||
|
||||
|
||||
|
||||
"PopUpForm.Providers.facebook.providerConfig.redirectURL": "Facebook应用中配置的重定向URL",
|
||||
"PopUpForm.Providers.google.providerConfig.redirectURL": "Google应用中配置的重定向URL",
|
||||
"PopUpForm.Providers.github.providerConfig.redirectURL": "GitHub应用中配置的重定向URL",
|
||||
"PopUpForm.Providers.linkedin2.providerConfig.redirectURL": "Linkedin应用中配置的重定向URL",
|
||||
"PopUpForm.Providers.twitter.providerConfig.redirectURL": "Twitter应用中配置的重定向URL",
|
||||
|
||||
"PopUpForm.Providers.callback.placeholder": "TEXT",
|
||||
"PopUpForm.Email.email_templates.inputDescription": "如果你不确定如何使用变量, {link}",
|
||||
"PopUpForm.Email.link.documentation": "看看我们的文档。"
|
||||
}
|
||||
@ -124,7 +124,5 @@ module.exports = async cb => {
|
||||
await pluginStore.set({key: 'advanced', value});
|
||||
}
|
||||
|
||||
strapi.plugins['users-permissions'].services.userspermissions.syncSchema(() => {
|
||||
strapi.plugins['users-permissions'].services.userspermissions.initialize(cb);
|
||||
});
|
||||
strapi.plugins['users-permissions'].services.userspermissions.initialize(cb);
|
||||
};
|
||||
|
||||
@ -4,7 +4,13 @@ module.exports = {
|
||||
find: async function (params = {}, populate) {
|
||||
const records = await this.query(function(qb) {
|
||||
_.forEach(params.where, (where, key) => {
|
||||
qb.where(key, where[0].symbol, where[0].value);
|
||||
if (_.isArray(where.value)) {
|
||||
for (const value in where.value) {
|
||||
qb[value ? 'where' : 'orWhere'](key, where.symbol, where.value[value])
|
||||
}
|
||||
} else {
|
||||
qb.where(key, where.symbol, where.value);
|
||||
}
|
||||
});
|
||||
|
||||
if (params.sort) {
|
||||
|
||||
@ -401,179 +401,6 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
syncSchema: async (cb) => {
|
||||
if (strapi.plugins['users-permissions'].models.user.orm !== 'bookshelf') {
|
||||
return cb();
|
||||
}
|
||||
|
||||
// Extract necessary information from plugin's models.
|
||||
const {
|
||||
user: { collectionName: userTableName, connection: userConnection, client: userClient },
|
||||
role: { collectionName: roleTableName, connection: roleConnection, client: roleClient },
|
||||
permission: { collectionName: permissionTableName, connection: permissionConnection, client: permissionClient }
|
||||
} = strapi.plugins['users-permissions'].models;
|
||||
|
||||
const details = {
|
||||
user: {
|
||||
tableName: userTableName,
|
||||
connection: userConnection,
|
||||
client: userClient
|
||||
},
|
||||
role: {
|
||||
tableName: roleTableName,
|
||||
connection: roleConnection,
|
||||
client: roleClient
|
||||
},
|
||||
permission: {
|
||||
tableName: permissionTableName,
|
||||
connection: permissionConnection,
|
||||
client: permissionClient
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the tables are existing.
|
||||
const hasTables = await Promise.all(Object.keys(details).map(name =>
|
||||
strapi.connections[details[name].connection].schema.hasTable(details[name].tableName)
|
||||
));
|
||||
|
||||
const missingTables = [];
|
||||
const tablesToCreate = [];
|
||||
|
||||
for (let index = 0; index < hasTables.length; ++index) {
|
||||
const hasTable = hasTables[index];
|
||||
const currentModel = Object.keys(details)[index];
|
||||
const quote = details[currentModel].client === 'pg' ? '"' : '`';
|
||||
|
||||
if (!hasTable) {
|
||||
missingTables.push(`
|
||||
⚠️ TABLE \`${details[currentModel].tableName}\` DOESN'T EXIST`);
|
||||
|
||||
switch (currentModel) {
|
||||
case 'user':
|
||||
tablesToCreate.push(`
|
||||
|
||||
CREATE TABLE ${quote}${details[currentModel].tableName}${quote} (
|
||||
id ${details[currentModel].client === 'pg' ? 'SERIAL' : 'INT AUTO_INCREMENT'} NOT NULL PRIMARY KEY,
|
||||
username text,
|
||||
email text,
|
||||
provider text,
|
||||
role ${details[currentModel].client === 'pg' ? 'integer' : 'int'},
|
||||
${quote}resetPasswordToken${quote} text,
|
||||
password text,
|
||||
updated_at ${details[currentModel].client === 'pg' ? 'timestamp with time zone' : 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'},
|
||||
created_at ${details[currentModel].client === 'pg' ? 'timestamp with time zone' : 'timestamp DEFAULT CURRENT_TIMESTAMP'}
|
||||
);`);
|
||||
break;
|
||||
case 'role':
|
||||
tablesToCreate.push(`
|
||||
|
||||
CREATE TABLE ${quote}${details[currentModel].tableName}${quote} (
|
||||
id ${details[currentModel].client === 'pg' ? 'SERIAL' : 'INT AUTO_INCREMENT'} NOT NULL PRIMARY KEY,
|
||||
name text,
|
||||
description text,
|
||||
type text
|
||||
);`);
|
||||
break;
|
||||
case 'permission':
|
||||
tablesToCreate.push(`
|
||||
|
||||
CREATE TABLE ${quote}${details[currentModel].tableName}${quote} (
|
||||
id ${details[currentModel].client === 'pg' ? 'SERIAL' : 'INT AUTO_INCREMENT'} NOT NULL PRIMARY KEY,
|
||||
role ${details[currentModel].client === 'pg' ? 'integer' : 'int'},
|
||||
type text,
|
||||
controller text,
|
||||
action text,
|
||||
enabled boolean,
|
||||
policy text
|
||||
);`);
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!_.isEmpty(tablesToCreate)) {
|
||||
tablesToCreate.unshift(`
|
||||
|
||||
1️⃣ EXECUTE THE FOLLOWING SQL QUERY`);
|
||||
|
||||
tablesToCreate.push(`
|
||||
|
||||
2️⃣ RESTART YOUR SERVER`)
|
||||
strapi.log.warn(missingTables.concat(tablesToCreate).join(''));
|
||||
|
||||
// Stop the server.
|
||||
strapi.stop();
|
||||
}
|
||||
|
||||
const missingColumns = [];
|
||||
const tablesToAlter = [];
|
||||
|
||||
for (let index = 0; index < hasTables.length; ++index) {
|
||||
const currentModel = Object.keys(details)[index];
|
||||
const quote = details[currentModel].client === 'pg' ? '"' : '`';
|
||||
const attributes = {
|
||||
id: {
|
||||
type: details[currentModel].client === 'pg' ? 'integer' : 'int'
|
||||
},
|
||||
..._.cloneDeep(strapi.plugins['users-permissions'].models[currentModel].attributes)
|
||||
};
|
||||
|
||||
// Add created_at and updated_at attributes for the model User.
|
||||
if (currentModel === 'user') {
|
||||
Object.assign(attributes, {
|
||||
created_at: {
|
||||
type: details[currentModel].client === 'pg' ? 'timestamp with time zone' : 'timestamp'
|
||||
},
|
||||
updated_at: {
|
||||
type: details[currentModel].client === 'pg' ? 'timestamp with time zone' : 'timestamp'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const columns = Object.keys(attributes);
|
||||
|
||||
// Check if there are the required attributes.
|
||||
const hasColumns = await Promise.all(columns.map(attribute =>
|
||||
strapi.connections[details[currentModel].connection].schema.hasColumn(details[currentModel].tableName, attribute)
|
||||
));
|
||||
|
||||
hasColumns.forEach((hasColumn, index) => {
|
||||
const currentColumn = columns[index];
|
||||
const attribute = attributes[currentColumn];
|
||||
|
||||
if (!hasColumn && !attribute.collection) {
|
||||
const currentType = attribute.model ? 'integer' : attribute.type;
|
||||
const type = currentType === 'string' ? 'text' : currentType;
|
||||
|
||||
missingColumns.push(`
|
||||
⚠️ TABLE \`${details[currentModel].tableName}\` HAS MISSING COLUMNS`);
|
||||
|
||||
tablesToAlter.push(`
|
||||
|
||||
ALTER TABLE ${quote}${details[currentModel].tableName}${quote} ADD ${details[currentModel].client === 'pg' ? `${quote}${currentColumn}${quote}` : `${currentColumn}`} ${type};`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!_.isEmpty(tablesToAlter)) {
|
||||
tablesToAlter.unshift(`
|
||||
|
||||
1️⃣ EXECUTE THE FOLLOWING SQL QUERIES`);
|
||||
|
||||
tablesToAlter.push(`
|
||||
|
||||
2️⃣ RESTART YOUR SERVER`)
|
||||
strapi.log.warn(missingColumns.concat(tablesToAlter).join(''));
|
||||
|
||||
// Stop the server.
|
||||
return strapi.stop();
|
||||
}
|
||||
|
||||
cb();
|
||||
},
|
||||
|
||||
template: (layout, data) => {
|
||||
const compiledObject = _.template(layout);
|
||||
return compiledObject(data);
|
||||
|
||||
@ -140,30 +140,16 @@ module.exports = {
|
||||
types.current = 'modelD';
|
||||
|
||||
// We have to find if they are a model linked to this key
|
||||
_.forIn(_.omit(models, currentModelName || ''), model => {
|
||||
Object.keys(model.attributes)
|
||||
.filter(key => key === association.via)
|
||||
.forEach(attr => {
|
||||
const attribute = model.attributes[attr];
|
||||
const model = models[association.model]
|
||||
const attribute = model.attributes[association.via];
|
||||
|
||||
if (attribute.hasOwnProperty('via') && attribute.via === key && attribute.hasOwnProperty('collection') && attribute.collection !== '*') {
|
||||
types.other = 'collection';
|
||||
|
||||
// Break loop
|
||||
return false;
|
||||
} else if (attribute.hasOwnProperty('model') && attribute.model !== '*') {
|
||||
types.other = 'model';
|
||||
|
||||
// Break loop
|
||||
return false;
|
||||
} else if (attribute.hasOwnProperty('collection') || attribute.hasOwnProperty('model')) {
|
||||
types.other = 'morphTo';
|
||||
|
||||
// Break loop
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
if (attribute.hasOwnProperty('via') && attribute.via === key && attribute.hasOwnProperty('collection') && attribute.collection !== '*') {
|
||||
types.other = 'collection';
|
||||
} else if (attribute.hasOwnProperty('model') && attribute.model !== '*') {
|
||||
types.other = 'model';
|
||||
} else if (attribute.hasOwnProperty('collection') || attribute.hasOwnProperty('model')) {
|
||||
types.other = 'morphTo';
|
||||
}
|
||||
} else if (association.hasOwnProperty('model')) {
|
||||
types.current = 'model';
|
||||
|
||||
@ -454,7 +440,7 @@ module.exports = {
|
||||
if (_.includes(['_start', '_limit'], key)) {
|
||||
result = convertor(value, key);
|
||||
} else if (key === '_sort') {
|
||||
const [attr, order] = value.split(':');
|
||||
const [attr, order = 'ASC'] = value.split(':');
|
||||
result = convertor(order, key, attr);
|
||||
} else {
|
||||
const suffix = key.split('_');
|
||||
|
||||
@ -176,6 +176,8 @@ class Strapi extends EventEmitter {
|
||||
}
|
||||
|
||||
async load() {
|
||||
await this.enhancer();
|
||||
|
||||
this.app.use(async (ctx, next) => {
|
||||
if (ctx.request.url === '/_health' && ctx.request.method === 'HEAD') {
|
||||
ctx.set('strapi', 'You are so French!');
|
||||
@ -199,8 +201,8 @@ class Strapi extends EventEmitter {
|
||||
// Usage.
|
||||
await utils.usage.call(this);
|
||||
|
||||
// Init core store manager
|
||||
await store.pre.call(this);
|
||||
// Init core store
|
||||
await store.call(this);
|
||||
|
||||
// Initialize hooks and middlewares.
|
||||
await Promise.all([
|
||||
@ -208,9 +210,6 @@ class Strapi extends EventEmitter {
|
||||
initializeHooks.call(this)
|
||||
]);
|
||||
|
||||
// Core store post middleware and hooks init validation.
|
||||
await store.post.call(this);
|
||||
|
||||
// Harmonize plugins configuration.
|
||||
await plugins.call(this);
|
||||
}
|
||||
|
||||
@ -327,6 +327,7 @@ module.exports.app = async function() {
|
||||
}, {});
|
||||
|
||||
this.config.port = get(this.config.currentEnvironment, 'server.port') || this.config.port;
|
||||
this.config.host = get(this.config.currentEnvironment, 'server.host') || this.config.host;
|
||||
this.config.url = `http://${this.config.host}:${this.config.port}`;
|
||||
};
|
||||
|
||||
|
||||
@ -1,178 +1,137 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
pre: function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.models['core_store'] = {
|
||||
connection: 'default',
|
||||
info: {
|
||||
name: 'core_store',
|
||||
description: ''
|
||||
module.exports = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.models['core_store'] = {
|
||||
connection: 'default',
|
||||
info: {
|
||||
name: 'core_store',
|
||||
description: ''
|
||||
},
|
||||
attributes: {
|
||||
key: {
|
||||
type: 'string'
|
||||
},
|
||||
attributes: {
|
||||
key: {
|
||||
type: 'string'
|
||||
},
|
||||
value: {
|
||||
type: 'string'
|
||||
},
|
||||
type: {
|
||||
type: 'string'
|
||||
},
|
||||
environment: {
|
||||
type: 'string'
|
||||
},
|
||||
tag: {
|
||||
type: 'string'
|
||||
value: {
|
||||
type: 'text'
|
||||
},
|
||||
type: {
|
||||
type: 'string'
|
||||
},
|
||||
environment: {
|
||||
type: 'string'
|
||||
},
|
||||
tag: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
globalId: 'StrapiConfigs',
|
||||
collectionName: 'core_store'
|
||||
};
|
||||
|
||||
this.store = (source = {}) => {
|
||||
const get = async (params = {}) => {
|
||||
Object.assign(source, params);
|
||||
|
||||
const {
|
||||
key,
|
||||
environment = strapi.config.environment,
|
||||
type = 'core',
|
||||
name = '',
|
||||
tag = ''
|
||||
} = source;
|
||||
|
||||
const prefix = `${type}${name ? `_${name}` : ''}`;
|
||||
|
||||
const findAction = strapi.models['core_store'].orm === 'mongoose' ? 'findOne' : 'forge';
|
||||
|
||||
const where = {
|
||||
key: `${prefix}_${key}`,
|
||||
environment,
|
||||
tag
|
||||
};
|
||||
|
||||
const data = strapi.models['core_store'].orm === 'mongoose'
|
||||
? await strapi.models['core_store'].findOne(where)
|
||||
: await strapi.models['core_store'].forge(where).fetch().then(config => {
|
||||
if (config) {
|
||||
return config.toJSON();
|
||||
}
|
||||
},
|
||||
globalId: 'StrapiConfigs',
|
||||
collectionName: 'core_store'
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.type === 'object' || data.type === 'array' || data.type === 'boolean') {
|
||||
try {
|
||||
return JSON.parse(data.value);
|
||||
} catch (err) {
|
||||
return new Date(data.value);
|
||||
}
|
||||
} else if (data.type === 'number') {
|
||||
return parseFloat(data.value);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
this.store = (source = {}) => {
|
||||
const get = async (params = {}) => {
|
||||
Object.assign(source, params);
|
||||
const set = async (params = {}) => {
|
||||
Object.assign(source, params);
|
||||
|
||||
const {
|
||||
key,
|
||||
environment = strapi.config.environment,
|
||||
type = 'core',
|
||||
name = '',
|
||||
tag = ''
|
||||
} = source;
|
||||
const {
|
||||
key,
|
||||
value,
|
||||
environment = strapi.config.environment,
|
||||
type,
|
||||
name,
|
||||
tag = ''
|
||||
} = source;
|
||||
|
||||
const prefix = `${type}${name ? `_${name}` : ''}`;
|
||||
const prefix = `${type}${name ? `_${name}` : ''}`;
|
||||
|
||||
const findAction = strapi.models['core_store'].orm === 'mongoose' ? 'findOne' : 'forge';
|
||||
|
||||
const where = {
|
||||
key: `${prefix}_${key}`,
|
||||
environment,
|
||||
tag
|
||||
};
|
||||
|
||||
const data = strapi.models['core_store'].orm === 'mongoose'
|
||||
? await strapi.models['core_store'].findOne(where)
|
||||
: await strapi.models['core_store'].forge(where).fetch().then(config => {
|
||||
if (config) {
|
||||
return config.toJSON();
|
||||
}
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.type === 'object' || data.type === 'array' || data.type === 'boolean') {
|
||||
try {
|
||||
return JSON.parse(data.value);
|
||||
} catch (err) {
|
||||
return new Date(data.value);
|
||||
}
|
||||
} else if (data.type === 'number') {
|
||||
return parseFloat(data.value);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
const where = {
|
||||
key: `${prefix}_${key}`,
|
||||
environment,
|
||||
tag
|
||||
};
|
||||
|
||||
const set = async (params = {}) => {
|
||||
Object.assign(source, params);
|
||||
let data = strapi.models['core_store'].orm === 'mongoose'
|
||||
? await strapi.models['core_store'].findOne(where)
|
||||
: await strapi.models['core_store'].forge(where).fetch().then(config => {
|
||||
if (config) {
|
||||
return config.toJSON();
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
key,
|
||||
value,
|
||||
environment = strapi.config.environment,
|
||||
type,
|
||||
name,
|
||||
tag = ''
|
||||
} = source;
|
||||
|
||||
const prefix = `${type}${name ? `_${name}` : ''}`;
|
||||
|
||||
const where = {
|
||||
key: `${prefix}_${key}`,
|
||||
environment,
|
||||
tag
|
||||
};
|
||||
|
||||
let data = strapi.models['core_store'].orm === 'mongoose'
|
||||
? await strapi.models['core_store'].findOne(where)
|
||||
: await strapi.models['core_store'].forge(where).fetch().then(config => {
|
||||
if (config) {
|
||||
return config.toJSON();
|
||||
}
|
||||
if (data) {
|
||||
Object.assign(data, {
|
||||
value: JSON.stringify(value) || value.toString(),
|
||||
type: (typeof value).toString()
|
||||
});
|
||||
|
||||
if (data) {
|
||||
Object.assign(data, {
|
||||
value: JSON.stringify(value) || value.toString(),
|
||||
type: (typeof value).toString()
|
||||
});
|
||||
strapi.models['core_store'].orm === 'mongoose'
|
||||
? await strapi.models['core_store'].update({ _id: data._id }, data, { strict: false })
|
||||
: await strapi.models['core_store'].forge({ id: data.id }).save(data, { patch: true });
|
||||
} else {
|
||||
Object.assign(where, {
|
||||
value: JSON.stringify(value) || value.toString(),
|
||||
type: (typeof value).toString(),
|
||||
tag
|
||||
});
|
||||
|
||||
strapi.models['core_store'].orm === 'mongoose'
|
||||
? await strapi.models['core_store'].update({ _id: data._id }, data, { strict: false })
|
||||
: await strapi.models['core_store'].forge({ id: data.id }).save(data, { patch: true });
|
||||
} else {
|
||||
Object.assign(where, {
|
||||
value: JSON.stringify(value) || value.toString(),
|
||||
type: (typeof value).toString(),
|
||||
tag
|
||||
});
|
||||
|
||||
strapi.models['core_store'].orm === 'mongoose'
|
||||
? await strapi.models['core_store'].create(where)
|
||||
: await strapi.models['core_store'].forge().save(where);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
get,
|
||||
set
|
||||
strapi.models['core_store'].orm === 'mongoose'
|
||||
? await strapi.models['core_store'].create(where)
|
||||
: await strapi.models['core_store'].forge().save(where);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
get,
|
||||
set
|
||||
}
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
post: function () {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const Model = this.models['core_store'];
|
||||
|
||||
if (Model.orm !== 'bookshelf') {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
const hasTable = await this.connections[Model.connection].schema.hasTable(Model.tableName || Model.collectionName);
|
||||
|
||||
if (!hasTable) {
|
||||
const quote = Model.client === 'pg' ? '"' : '`';
|
||||
|
||||
this.log.warn(`
|
||||
⚠️ TABLE \`core_store\` DOESN'T EXIST
|
||||
|
||||
CREATE TABLE ${quote}${Model.tableName || Model.collectionName}${quote} (
|
||||
id ${Model.client === 'pg' ? 'SERIAL' : 'INT AUTO_INCREMENT'} NOT NULL PRIMARY KEY,
|
||||
${quote}key${quote} text,
|
||||
${quote}value${quote} text,
|
||||
environment text,
|
||||
type text,
|
||||
tag text
|
||||
);
|
||||
|
||||
ALTER TABLE ${quote}${Model.tableName || Model.collectionName}${quote} ADD COLUMN ${quote}parent${quote} integer, ADD FOREIGN KEY (${quote}parent${quote}) REFERENCES ${quote}${Model.tableName || Model.collectionName}${quote}(${quote}id${quote});
|
||||
|
||||
1️⃣ EXECUTE THE FOLLOWING SQL QUERY
|
||||
|
||||
2️⃣ RESTART YOUR SERVER
|
||||
`);
|
||||
|
||||
// Stop the server.
|
||||
return this.stop();
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user