Merge branch 'master' into improve-ctb-ux

This commit is contained in:
Jim LAURIE 2018-06-13 11:38:48 +02:00 committed by GitHub
commit ddad496f87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 1410 additions and 470 deletions

View File

@ -7,6 +7,7 @@ language: node_js
node_js:
- "9"
- "10"
before_install:
- export CHROME_BIN=chromium-browser
@ -21,4 +22,3 @@ install:
script:
- npm run doc
- npm run test

View File

@ -64,8 +64,9 @@ Every folder that follows this name pattern `strapi-*` in your `./node_modules`
A hook needs to follow the structure below:
```
/lib
- index.js
/hook
└─── lib
- index.js
- LICENSE.md
- package.json
- README.md

View File

@ -1,6 +1,7 @@
{
"private": true,
"version": "3.0.0-alpha.12.3",
"dependencies": {},
"devDependencies": {
"assert": "~1.3.0",
"axios": "^0.18.0",

View File

@ -20,11 +20,14 @@ const localStorageKey = 'strapi-admin-language';
// Detect user language.
const userLanguage = window.localStorage.getItem(localStorageKey) || window.navigator.language || window.navigator.userLanguage;
// Split user language in a correct format.
const userLanguageShort = get(split(userLanguage, '-'), '0');
let foundLanguage = includes(languages, userLanguage) && userLanguage;
if (!foundLanguage) {
// Split user language in a correct format.
const userLanguageShort = get(split(userLanguage, '-'), '0');
// Check that the language is included in the admin configuration.
const foundLanguage = includes(languages, userLanguageShort) && userLanguageShort;
// Check that the language is included in the admin configuration.
foundLanguage = includes(languages, userLanguageShort) && userLanguageShort;
}
const initialState = fromJS({
locale: foundLanguage || first(languages) || 'en',

View File

@ -918,6 +918,13 @@ module.exports = function(strapi) {
value: `%${value}%`
};
break;
case '_in':
result.key = `where.${key}`;
result.value = {
symbol: 'IN',
value,
};
break;
default:
return undefined;
}

View File

@ -179,7 +179,7 @@ module.exports = {
);
});
} else if (_.get(this._attributes, `${current}.isVirtual`) !== true) {
if (typeof params.values[current] === 'object') {
if (params.values[current] && typeof params.values[current] === 'object') {
acc[current] = _.get(params.values[current], this.primaryKey);
} else {
acc[current] = params.values[current];

View File

@ -28,15 +28,15 @@ module.exports = scope => {
}
}, {
method: 'GET',
path: '/' + scope.humanizeId + '/:' + tokenID,
handler: scope.globalID + '.findOne',
path: '/' + scope.humanizeId + '/count',
handler: scope.globalID + '.count',
config: {
policies: []
}
}, {
method: 'GET',
path: '/' + scope.humanizeId + '/count',
handler: scope.globalID + '.count',
path: '/' + scope.humanizeId + '/:' + tokenID,
handler: scope.globalID + '.findOne',
config: {
policies: []
}

View File

@ -12,7 +12,6 @@ const { execSync } = require('child_process');
const _ = require('lodash');
const fs = require('fs-extra');
const npm = require('enpeem');
const getInstalledPath = require('get-installed-path');
// Logger.
const logger = require('strapi-utils').logger;
@ -39,16 +38,17 @@ module.exports = (scope, cb) => {
const dependencies = _.get(packageJSON, 'dependencies');
const strapiDependencies = Object.keys(dependencies).filter(key => key.indexOf('strapi') !== -1);
const othersDependencies = Object.keys(dependencies).filter(key => key.indexOf('strapi') === -1);
const globalRootPath = execSync('npm root -g');
// Verify if the dependencies are available into the global
_.forEach(strapiDependencies, (key) => {
try {
const isInstalled = getInstalledPath.sync(key);
fs.accessSync(path.resolve(_.trim(globalRootPath.toString()), key), fs.constants.R_OK | fs.constants.F_OK);
availableDependencies.push({
key,
global: true,
path: isInstalled
path: path.resolve(_.trim(globalRootPath.toString()), key)
});
} catch (e) {
othersDependencies.push(key);
@ -104,9 +104,6 @@ module.exports = (scope, cb) => {
},{
name: 'upload',
core: true
}, {
name: 'analytics',
core: false
}];
// Install each plugin.
@ -143,6 +140,12 @@ module.exports = (scope, cb) => {
logger.info('Your new application `' + scope.name + '` is ready at `' + scope.rootPath + '`.');
logger.info('We are almost there !!!');
logger.info('cd ' + scope.name);
logger.info('strapi start');
logger.info('Open your browser to http://localhost:1337');
logger.info('Enjoy your strapi project :)');
cb();
}
};

View File

@ -15,7 +15,6 @@
"dependencies": {
"enpeem": "^2.2.0",
"fs-extra": "^4.0.0",
"get-installed-path": "^3.0.1",
"inquirer": "^4.0.2",
"lodash": "^4.17.4",
"strapi-utils": "3.0.0-alpha.12.3",

View File

@ -144,6 +144,7 @@ InputSelectWithErrors.defaultProps = {
labelStyle: {},
onBlur: false,
onFocus: () => {},
selectOptions: [],
style: {},
tabIndex: '0',
validations: {},
@ -198,7 +199,7 @@ InputSelectWithErrors.propTypes = {
}),
PropTypes.string,
]),
).isRequired,
),
style: PropTypes.object,
tabIndex: PropTypes.string,
validations: PropTypes.object,

View File

@ -510,6 +510,10 @@ module.exports = function (strapi) {
result.key = `where.${key}.$regex`;
result.value = value;
break;
case '_in':
result.key = `where.${key}.$in`;
result.value = value;
break;
default:
result = undefined;
}

View File

@ -0,0 +1,54 @@
/**
*
* CustomInputCheckbox
*/
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './styles.scss';
function CustomInputCheckbox({ isAll, name, onChange, value }) {
return (
<span className={cn('form-check', styles.customSpan)}>
<label
className={cn(
'form-check-label',
styles.customLabel,
isAll ? styles.customLabelHeader : styles.customLabelRow,
value && isAll && styles.customLabelCheckedHeader,
value && !isAll && styles.customLabelCheckedRow,
)}
htmlFor={name}
>
<input
className="form-check-input"
checked={value}
id={name}
name={name}
onChange={onChange}
type="checkbox"
/>
</label>
</span>
);
}
CustomInputCheckbox.defaultProps = {
isAll: false,
name: '',
value: false,
};
CustomInputCheckbox.propTypes = {
isAll: PropTypes.bool,
name: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
]),
onChange: PropTypes.func.isRequired,
value: PropTypes.bool,
};
export default CustomInputCheckbox;

View File

@ -0,0 +1,62 @@
.customSpan {
margin-left: -15px;
}
.customLabel {
cursor: pointer;
> input {
display: none;
}
}
.customLabelHeader {
&:before {
content: '';
position: absolute;
left:15px; top: 5px;
width: 14px; height: 14px;
border: 1px solid rgba(16,22,34,0.15);
background-color: #FDFDFD;
border-radius: 3px;
}
}
.customLabelRow {
&:before {
content: '';
position: absolute;
left:15px; top: 19px;
width: 14px; height: 14px;
border: 1px solid rgba(16,22,34,0.15);
background-color: #FDFDFD;
border-radius: 3px;
}
}
.customLabelCheckedHeader {
&:after {
content: '\f00c';
position: absolute;
top: 5px; left: 17px;
font-size: 10px;
font-family: 'FontAwesome';
font-weight: 100;
color: #1C5DE7;
transition: all .2s;
}
}
.customLabelCheckedRow {
&:after {
content: '\f00c';
position: absolute;
top: 0px; left: 17px;
font-size: 10px;
font-family: 'FontAwesome';
font-weight: 100;
color: #1C5DE7;
transition: all .2s;
}
}

View File

@ -1,36 +1,86 @@
const FILTER_TYPES = [
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES.=',
value: '=',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._ne',
value: '_ne',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._lt',
value: '_lt',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._lte',
value: '_lte',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._gt',
value: '_gt',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._gte',
value: '_gte',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._contains',
value: '_contains',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._containss',
value: '_containss',
},
];
const getFilters = (type) => {
switch(type) {
case 'string':
case 'text':
case 'password':
case 'email':
return [
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES.=',
value: '=',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._ne',
value: '_ne',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._lt',
value: '_lt',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._lte',
value: '_lte',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._gt',
value: '_gt',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._gte',
value: '_gte',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._contains',
value: '_contains',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._containss',
value: '_containss',
},
];
case 'integer':
case 'float':
case 'decimal':
case 'date':
return [
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES.=',
value: '=',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._ne',
value: '_ne',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._lt',
value: '_lt',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._lte',
value: '_lte',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._gt',
value: '_gt',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._gte',
value: '_gte',
},
];
default:
return [
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES.=',
value: '=',
},
{
id: 'content-manager.components.FilterOptions.FILTER_TYPES._ne',
value: '_ne',
},
];
}
};
export default FILTER_TYPES;
export default getFilters;

View File

@ -17,7 +17,7 @@ import InputWithAutoFocus from './InputWithAutoFocus';
import Remove from './Remove';
import styles from './styles.scss';
import FILTER_TYPES from './filterTypes';
import getFilters from './filterTypes';
const defaultInputStyle = { width: '210px', marginRight: '10px', paddingTop: '4px' };
const midSelectStyle = { minWidth: '130px', maxWidth: '200px', marginLeft: '10px', marginRight: '10px' };
@ -51,7 +51,7 @@ function FilterOptions({ filter, filterToFocus, index, onChange, onClickAdd, onC
onChange={onChange}
name={`${index}.filter`}
value={get(filter, 'filter', '=')}
selectOptions={FILTER_TYPES}
selectOptions={getFilters(attrType)}
style={midSelectStyle}
/>
<div className={cn(isDate ? styles.filterOptionsInputWrapper : '')}>

View File

@ -6,10 +6,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import { toString } from 'lodash';
import TableHeader from '../TableHeader';
import TableRow from '../TableRow';
import TableEmpty from '../TableEmpty';
import TableDelete from 'components/TableDelete';
import TableHeader from 'components/TableHeader';
import TableRow from 'components/TableRow';
import TableEmpty from 'components/TableEmpty';
import styles from './styles.scss';
@ -19,12 +21,13 @@ class Table extends React.Component {
(
<TableEmpty
filters={this.props.filters}
colspan={this.props.headers.length}
colspan={this.props.headers.length + 1}
contentType={this.props.routeParams.slug}
/>
) :
this.props.records.map((record, key) => (
<TableRow
onChange={this.props.onClickSelect}
key={key}
destination={`${this.props.route.path.replace(':slug', this.props.routeParams.slug)}/${record[this.props.primaryKey]}`}
headers={this.props.headers}
@ -33,18 +36,29 @@ class Table extends React.Component {
primaryKey={this.props.primaryKey}
onDelete={this.props.handleDelete}
redirectUrl={this.props.redirectUrl}
value={this.props.entriesToDelete.indexOf(toString(record.id)) !== -1}
/>
));
const entriesToDeleteNumber = this.props.entriesToDelete.length;
return (
<table className={`table ${styles.table}`}>
<TableHeader
onClickSelectAll={this.props.onClickSelectAll}
value={this.props.deleteAllValue}
headers={this.props.headers}
onChangeSort={this.props.onChangeSort}
sort={this.props.sort}
primaryKey={this.props.primaryKey}
/>
<tbody>
{ entriesToDeleteNumber > 0 && (
<TableDelete
colspan={this.props.headers.length + 1}
number={entriesToDeleteNumber}
onToggleDeleteAll={this.props.onToggleDeleteAll}
/>
)}
{rows}
</tbody>
</table>
@ -56,12 +70,22 @@ Table.contextTypes = {
router: PropTypes.object.isRequired,
};
Table.defaultProps = {
entriesToDelete: [],
handleDelete: () => {},
};
Table.propTypes = {
deleteAllValue: PropTypes.bool.isRequired,
entriesToDelete: PropTypes.array,
filters: PropTypes.array.isRequired,
handleDelete: PropTypes.func,
headers: PropTypes.array.isRequired,
history: PropTypes.object.isRequired,
onChangeSort: PropTypes.func.isRequired,
onClickSelect: PropTypes.func.isRequired,
onClickSelectAll: PropTypes.func.isRequired,
onToggleDeleteAll: PropTypes.func.isRequired,
primaryKey: PropTypes.string.isRequired,
records: PropTypes.oneOfType([
PropTypes.array,
@ -73,8 +97,4 @@ Table.propTypes = {
sort: PropTypes.string.isRequired,
};
Table.defaultProps = {
handleDelete: () => {},
};
export default Table;

View File

@ -0,0 +1,45 @@
/**
*
* TableDelete
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import styles from './styles.scss';
function TableDelete({ colspan, number, onToggleDeleteAll }) {
const suffix = number > 1 ? 'plural' : 'singular';
return (
<tr className={styles.tableDelete}>
<td colSpan={colspan + 1}>
<FormattedMessage
id={`content-manager.components.TableDelete.entries.${suffix}`}
values={{ number }}
>
{message => <span className={styles.tableDeleteSpan}>{message}</span>}
</FormattedMessage>
<FormattedMessage
id="content-manager.components.TableDelete.delete"
>
{message => <span className={styles.deleteAll} onClick={onToggleDeleteAll}>{message}</span>}
</FormattedMessage>
</td>
</tr>
);
}
TableDelete.defaultProps = {
colspan: 0,
};
TableDelete.propTypes = {
colspan: PropTypes.number,
number: PropTypes.number.isRequired,
onToggleDeleteAll: PropTypes.func.isRequired,
};
export default TableDelete;

View File

@ -0,0 +1,44 @@
.tableDelete {
width: 100%;
height: 36px;
background: #F7F8F8;
td{
height: 36px;
line-height: 36px;
font-size: 1.3rem;
font-weight: 400;
color: #333740;
text-align: left;
border-collapse: collapse;
border-top: 1px solid #F1F1F2 !important;
}
}
.tableDeleteSpan {
font-weight: 600;
&:after {
content: '';
margin: 0 7px;
font-size: 13px;
font-weight: 600;
}
-webkit-font-smoothing: antialiased;
}
.deleteAll {
position: absolute;
color: #F64D0A;
font-weight: 500;
cursor: pointer;
&:after {
position: relative;
top: -1px;
content: '\f1f8';
margin-left: 7px;
// margin-top: -10px;
font-size: 13px;
font-family: FontAwesome;
-webkit-font-smoothing: antialiased;
}
}

View File

@ -7,6 +7,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import CustomInputCheckbox from 'components/CustomInputCheckbox';
import styles from './styles.scss';
class TableHeader extends React.Component {
@ -20,6 +22,17 @@ class TableHeader extends React.Component {
}
}
renderBulk = () => (
<th key="bulk_action">
<CustomInputCheckbox
isAll
name="all"
onChange={this.props.onClickSelectAll}
value={this.props.value}
/>
</th>
);
render() {
// Generate headers list
const headers = this.props.headers.map((header, i) => {
@ -52,18 +65,24 @@ class TableHeader extends React.Component {
return (
<thead className={styles.tableHeader}>
<tr >
{headers}
{[this.renderBulk()].concat(headers)}
</tr>
</thead>
);
}
}
TableHeader.defaultProps = {
value: false,
};
TableHeader.propTypes = {
headers: PropTypes.array.isRequired,
onChangeSort: PropTypes.func.isRequired,
onClickSelectAll: PropTypes.func.isRequired,
primaryKey: PropTypes.string.isRequired,
sort: PropTypes.string.isRequired,
value: PropTypes.bool,
};
export default TableHeader;

View File

@ -14,6 +14,12 @@
cursor: pointer;
}
}
> tr {
th:first-child {
width: 50px;
}
}
}
.iconAsc{

View File

@ -9,6 +9,7 @@ import PropTypes from 'prop-types';
import moment from 'moment';
import { isEmpty, isObject, toString } from 'lodash';
import CustomInputCheckbox from 'components/CustomInputCheckbox';
import IcoContainer from 'components/IcoContainer';
import styles from './styles.scss';
@ -68,31 +69,51 @@ class TableRow extends React.Component {
this.context.router.history.push(`${this.props.destination}${this.props.redirectUrl}`);
}
renderAction = () => (
<td key='action' className={styles.actions}>
<IcoContainer
icons={[
{ icoType: 'pencil', onClick: () => this.handleClick(this.props.destination) },
{ id: this.props.record.id, icoType: 'trash', onClick: this.props.onDelete },
]}
/>
</td>
);
renderCells = () => {
const { headers } = this.props;
return [this.renderDelete()]
.concat(
headers.map((header, i) => (
<td key={i}>
<div className={styles.truncate}>
<div className={styles.truncated}>
{this.getDisplayedValue(
header.type,
this.props.record[header.name],
header.name,
)}
</div>
</div>
</td>
)))
.concat([this.renderAction()]);
}
renderDelete = () => (
<td onClick={(e) => e.stopPropagation()} key="i">
<CustomInputCheckbox
name={this.props.record.id}
onChange={this.props.onChange}
value={this.props.value}
/>
</td>
);
render() {
// Generate cells
const cells = this.props.headers.map((header, i) => (
<td key={i}>
<div className={styles.truncate}>
<div className={styles.truncated}>
{this.getDisplayedValue(
header.type,
this.props.record[header.name],
header.name,
)}
</div>
</div>
</td>
));
cells.push(
<td key='action' className={styles.actions}>
<IcoContainer icons={[{ icoType: 'pencil', onClick: () => this.handleClick(this.props.destination) }, { id: this.props.record.id, icoType: 'trash', onClick: this.props.onDelete }]} />
</td>
);
return (
<tr className={styles.tableRow} onClick={() => this.handleClick(this.props.destination)}>
{cells}
{this.renderCells()}
</tr>
);
}
@ -102,12 +123,18 @@ TableRow.contextTypes = {
router: PropTypes.object.isRequired,
};
TableRow.defaultProps = {
value: false,
};
TableRow.propTypes = {
destination: PropTypes.string.isRequired,
headers: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
onDelete: PropTypes.func,
record: PropTypes.object.isRequired,
redirectUrl: PropTypes.string.isRequired,
value: PropTypes.bool,
};
TableRow.defaultProps = {

View File

@ -16,6 +16,9 @@
border-collapse: collapse;
border-top: 1px solid #F1F1F2 !important;
}
> td:first-child {
width: 50px;
}
}
.truncate {

View File

@ -9,10 +9,15 @@ import {
CHANGE_PARAMS,
DELETE_DATA,
DELETE_DATA_SUCCESS,
DELETE_SEVERAL_DATA,
DELETE_SEVERAL_DATA_SUCCESS,
GET_DATA,
GET_DATA_SUCCEEDED,
ON_CHANGE,
ON_CLICK_REMOVE,
ON_CLICK_SELECT,
ON_CLICK_SELECT_ALL,
ON_TOGGLE_DELETE_ALL,
ON_TOGGLE_FILTERS,
OPEN_FILTERS_WITH_SELECTION,
REMOVE_ALL_FILTERS,
@ -52,6 +57,21 @@ export function deleteDataSuccess(id) {
};
}
export function deleteSeveralData(entriesToDelete, model, source) {
return {
type: DELETE_SEVERAL_DATA,
entriesToDelete,
model,
source,
};
}
export function deleteSeveralDataSuccess() {
return {
type: DELETE_SEVERAL_DATA_SUCCESS,
};
}
export function getData(currentModel, source) {
return {
type: GET_DATA,
@ -83,6 +103,19 @@ export function onClickRemove(index) {
};
}
export function onClickSelect({ target }) {
return {
type: ON_CLICK_SELECT,
id: target.name,
};
}
export function onClickSelectAll() {
return {
type: ON_CLICK_SELECT_ALL,
};
}
export function openFiltersWithSelections(index) {
return {
type: OPEN_FILTERS_WITH_SELECTION,
@ -90,6 +123,12 @@ export function openFiltersWithSelections(index) {
};
}
export function onToggleDeleteAll() {
return {
type: ON_TOGGLE_DELETE_ALL,
};
}
export function onToggleFilters() {
return {
type: ON_TOGGLE_FILTERS,

View File

@ -9,10 +9,15 @@ export const ADD_FILTER = 'ContentManager/ListPage/ADD_FILTER';
export const CHANGE_PARAMS = 'ContentManager/ListPage/CHANGE_PARAMS';
export const DELETE_DATA = 'ContentManager/ListPage/DELETE_DATA';
export const DELETE_DATA_SUCCESS = 'ContentManager/ListPage/DELETE_DATA_SUCCESS';
export const DELETE_SEVERAL_DATA = 'ContentManager/ListPage/DELETE_SEVERAL_DATA';
export const DELETE_SEVERAL_DATA_SUCCESS = 'ContentManager/ListPage/DELETE_SEVERAL_DATA_SUCCESS';
export const GET_DATA = 'ContentManager/ListPage/GET_DATA';
export const GET_DATA_SUCCEEDED = 'ContentManager/ListPage/GET_DATA_SUCCEEDED';
export const ON_CHANGE = 'ContentManager/ListPage/ON_CHANGE';
export const ON_CLICK_REMOVE = 'ContentManager/ListPage/ON_CLICK_REMOVE';
export const ON_CLICK_SELECT = 'ContentManager/ListPage/ON_CLICK_SELECT';
export const ON_CLICK_SELECT_ALL = 'ContentManager/ListPage/ON_CLICK_SELECT_ALL';
export const ON_TOGGLE_DELETE_ALL = 'ContentManager/ListPage/ON_TOGGLE_DELETE_ALL';
export const ON_TOGGLE_FILTERS = 'ContentManager/ListPage/ON_TOGGLE_FILTERS';
export const OPEN_FILTERS_WITH_SELECTION = 'ContentManager/ListPage/OPEN_FILTERS_WITH_SELECTION';
export const REMOVE_ALL_FILTERS = 'ContentManager/ListPage/REMOVE_ALL_FILTERS';

View File

@ -39,9 +39,13 @@ import {
addFilter,
changeParams,
deleteData,
deleteSeveralData,
getData,
onChange,
onClickRemove,
onClickSelect,
onClickSelectAll,
onToggleDeleteAll,
onToggleFilters,
openFiltersWithSelections,
removeAllFilters,
@ -141,11 +145,11 @@ export class ListPage extends React.Component {
get(this.props.schema, [this.getCurrentModelName(), 'fields']) ||
get(this.props.schema, ['plugins', this.getSource(), this.getCurrentModelName(), 'fields']);
shouldHideFilters = () => {
if (this.props.listPage.showFilter) {
this.props.onToggleFilters();
}
};
getPopUpDeleteAllMsg = () => (
this.props.listPage.entriesToDelete.length > 1 ?
'content-manager.popUpWarning.bodyMessage.contentType.delete.all'
: 'content-manager.popUpWarning.bodyMessage.contentType.delete'
);
/**
* Generate the redirect URI when editing an entry
@ -185,6 +189,12 @@ export class ListPage extends React.Component {
return tableHeaders;
};
areAllEntriesSelected = () => {
const { listPage: { entriesToDelete, records } } = this.props;
return entriesToDelete.length === records.length && records.length > 0;
};
/**
* [findPageSort description]
* @param {Object} props [description]
@ -268,6 +278,12 @@ export class ListPage extends React.Component {
}
};
shouldHideFilters = () => {
if (this.props.listPage.showFilter) {
this.props.onToggleFilters();
}
};
toggleModalWarning = e => {
if (!isUndefined(e)) {
e.preventDefault();
@ -277,16 +293,32 @@ export class ListPage extends React.Component {
});
}
this.setState({ showWarning: !this.state.showWarning });
if (this.props.listPage.entriesToDelete.length > 0) {
this.props.onClickSelectAll();
}
this.setState(prevState => ({ showWarning: !prevState.showWarning }));
};
render() {
const {
addFilter,
deleteSeveralData,
listPage,
listPage: { appliedFilters, filters, filterToFocus, params, showFilter },
listPage: {
appliedFilters,
entriesToDelete,
filters,
filterToFocus,
params,
showFilter,
showWarningDeleteAll,
},
onChange,
onClickRemove,
onClickSelect,
onClickSelectAll,
onToggleDeleteAll,
onToggleFilters,
openFiltersWithSelections,
removeAllFilters,
@ -363,17 +395,22 @@ export class ListPage extends React.Component {
<div className={cn('row', styles.row)}>
<div className="col-md-12">
<Table
deleteAllValue={this.areAllEntriesSelected()}
entriesToDelete={entriesToDelete}
filters={filters}
handleDelete={this.toggleModalWarning}
headers={this.generateTableHeaders()}
history={this.props.history}
onChangeSort={this.handleChangeSort}
onClickSelectAll={onClickSelectAll}
onClickSelect={onClickSelect}
onToggleDeleteAll={onToggleDeleteAll}
primaryKey={this.getCurrentModel().primaryKey || 'id'}
records={listPage.records}
redirectUrl={this.generateRedirectURI()}
route={this.props.match}
routeParams={this.props.match.params}
headers={this.generateTableHeaders()}
filters={filters}
onChangeSort={this.handleChangeSort}
sort={params._sort}
history={this.props.history}
primaryKey={this.getCurrentModel().primaryKey || 'id'}
handleDelete={this.toggleModalWarning}
redirectUrl={this.generateRedirectURI()}
/>
<PopUpWarning
isOpen={this.state.showWarning}
@ -387,6 +424,20 @@ export class ListPage extends React.Component {
popUpWarningType="danger"
onConfirm={this.handleDelete}
/>
<PopUpWarning
isOpen={showWarningDeleteAll}
toggleModal={onToggleDeleteAll}
content={{
title: 'content-manager.popUpWarning.title',
message: this.getPopUpDeleteAllMsg(),
cancel: 'content-manager.popUpWarning.button.cancel',
confirm: 'content-manager.popUpWarning.button.confirm',
}}
popUpWarningType="danger"
onConfirm={() => {
deleteSeveralData(entriesToDelete, this.getCurrentModelName(), this.getSource());
}}
/>
<PageFooter
count={listPage.count}
onChangeParams={this.handleChangeParams}
@ -406,6 +457,7 @@ ListPage.propTypes = {
addFilter: PropTypes.func.isRequired,
changeParams: PropTypes.func.isRequired,
deleteData: PropTypes.func.isRequired,
deleteSeveralData: PropTypes.func.isRequired,
getData: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
listPage: PropTypes.object.isRequired,
@ -414,6 +466,9 @@ ListPage.propTypes = {
models: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
onClickRemove: PropTypes.func.isRequired,
onClickSelect: PropTypes.func.isRequired,
onClickSelectAll: PropTypes.func.isRequired,
onToggleDeleteAll: PropTypes.func.isRequired,
onToggleFilters: PropTypes.func.isRequired,
openFiltersWithSelections: PropTypes.func.isRequired,
removeAllFilters: PropTypes.func.isRequired,
@ -429,9 +484,13 @@ function mapDispatchToProps(dispatch) {
addFilter,
changeParams,
deleteData,
deleteSeveralData,
getData,
onChange,
onClickRemove,
onClickSelect,
onClickSelectAll,
onToggleDeleteAll,
onToggleFilters,
openFiltersWithSelections,
removeAllFilters,

View File

@ -5,26 +5,32 @@
*/
import { fromJS, List, Map } from 'immutable';
import { toString } from 'lodash';
// ListPage constants
import {
ADD_FILTER,
CHANGE_PARAMS,
DELETE_DATA_SUCCESS,
DELETE_SEVERAL_DATA_SUCCESS,
GET_DATA_SUCCEEDED,
ON_CHANGE,
ON_CLICK_REMOVE,
ON_CLICK_SELECT,
ON_CLICK_SELECT_ALL,
ON_TOGGLE_FILTERS,
OPEN_FILTERS_WITH_SELECTION,
REMOVE_ALL_FILTERS,
REMOVE_FILTER,
SET_PARAMS,
SUBMIT,
ON_TOGGLE_DELETE_ALL,
} from './constants';
const initialState = fromJS({
appliedFilters: List([]),
count: 0,
entriesToDelete: List([]),
filters: List([]),
filtersUpdated: false,
filterToFocus: null,
@ -35,6 +41,7 @@ const initialState = fromJS({
}),
records: List([]),
showFilter: false,
showWarningDeleteAll: false,
});
function listPageReducer(state = initialState, action) {
@ -53,10 +60,15 @@ function listPageReducer(state = initialState, action) {
})
))
.update('count', (v) => v = v - 1);
case DELETE_SEVERAL_DATA_SUCCESS:
return state
.update('showWarningDeleteAll', () => false)
.update('entriesToDelete', () => List([]));
case CHANGE_PARAMS:
return state.updateIn(action.keys, () => action.value);
case GET_DATA_SUCCEEDED:
return state
.update('entriesToDelete', () => List([]))
.update('count', () => action.data[0].count)
.update('records', () => List(action.data[1]));
case ON_CHANGE:
@ -66,6 +78,26 @@ function listPageReducer(state = initialState, action) {
.update('appliedFilters', list => list.splice(action.index, 1))
.update('filters', list => list.splice(action.index, 1))
.update('filtersUpdated', v => v = !v);
case ON_CLICK_SELECT:
return state.update('entriesToDelete', list => {
const index = state.get('entriesToDelete').indexOf(toString(action.id));
if (index !== -1) {
return list.splice(index, 1);
}
return list.concat(toString(action.id));
});
case ON_CLICK_SELECT_ALL:
return state.update('entriesToDelete', () => {
if (state.get('entriesToDelete').size === 0) {
return state
.get('records')
.reduce((acc, current) => acc.concat(List([toString(current.id)])), List([]));
}
return List([]);
});
case ON_TOGGLE_FILTERS:
return state
.update('filterToFocus', () => null)
@ -94,6 +126,8 @@ function listPageReducer(state = initialState, action) {
.update('appliedFilters', (list) => list.filter(filter => filter.get('value') !== ''))
.update('showFilter', () => false)
.update('filtersUpdated', v => v = !v);
case ON_TOGGLE_DELETE_ALL:
return state.update('showWarningDeleteAll', v => v = !v);
default:
return state;
}

View File

@ -16,11 +16,13 @@ import request from 'utils/request';
// Actions
import {
deleteDataSuccess,
deleteSeveralDataSuccess,
getDataSucceeded,
} from './actions';
// Constants
import {
DELETE_DATA,
DELETE_SEVERAL_DATA,
GET_DATA,
} from './constants';
// Selectors
@ -68,7 +70,7 @@ export function* dataGet(action) {
}
}
export function* dataDelete({ id, modelName, source}) {
export function* dataDelete({ id, modelName, source }) {
try {
const requestUrl = `/content-manager/explorer/${modelName}/${id}`;
const params = {};
@ -90,10 +92,26 @@ export function* dataDelete({ id, modelName, source}) {
}
}
export function* dataDeleteAll({ entriesToDelete, model, source }) {
try {
const params = Object.assign(entriesToDelete, source !== undefined ? { source } : {});
yield call(request, `/content-manager/explorer/deleteAll/${model}`, {
method: 'DELETE',
params,
});
yield put(deleteSeveralDataSuccess());
yield call(dataGet, { currentModel: model, source });
} catch(err) {
strapi.notification.error('content-manager.error.record.delete');
}
}
// All sagas to be loaded
function* defaultSaga() {
const loadDataWatcher = yield fork(takeLatest, GET_DATA, dataGet);
yield fork(takeLatest, DELETE_DATA, dataDelete);
yield fork(takeLatest, DELETE_SEVERAL_DATA, dataDeleteAll);
yield take(LOCATION_CHANGE);

View File

@ -33,6 +33,11 @@
"components.FilterOptions.FILTER_TYPES._contains": "contains",
"components.FilterOptions.FILTER_TYPES._containss": "contains (case sensitive)",
"components.TableDelete.entries.plural": "{number} entries selected",
"components.TableDelete.entries.singular": "{number} entry selected",
"components.TableDelete.delete": "Delete all",
"components.TableEmpty.withFilters": "There is no {contentType} with the applied filters...",
"components.TableEmpty.withoutFilter": "There is no {contentType}...",
@ -72,5 +77,6 @@
"popUpWarning.button.cancel": "Cancel",
"popUpWarning.button.confirm": "Confirm",
"popUpWarning.title": "Please confirm",
"popUpWarning.bodyMessage.contentType.delete": "Are you sure you want to delete this entry?"
"popUpWarning.bodyMessage.contentType.delete": "Are you sure you want to delete this entry?",
"popUpWarning.bodyMessage.contentType.delete.all": "Are you sure you want to delete theses entries?"
}

View File

@ -25,6 +25,10 @@
"components.FiltersPickWrapper.PluginHeader.title.filter": "Filtres",
"components.FiltersPickWrapper.hide": "Fermer",
"components.TableDelete.entries.plural": "{number} entrées sélectionnées",
"components.TableDelete.entries.singular": "{number} entrée sélectionnée",
"components.TableDelete.delete": "Tout supprimer",
"components.TableEmpty.withFilters": "Aucun {contentType} n'a été trouvé avec ces filtres...",
"components.TableEmpty.withoutFilter": "Aucun {contentType} n'a été trouvé...",
@ -71,5 +75,6 @@
"popUpWarning.button.cancel": "Annuler",
"popUpWarning.button.confirm": "Confirmer",
"popUpWarning.title": "Confirmation requise",
"popUpWarning.bodyMessage.contentType.delete": "Êtes-vous sûr de vouloir supprimer cette entrée ?"
"popUpWarning.bodyMessage.contentType.delete": "Êtes-vous sûr de vouloir supprimer cette entrée ?",
"popUpWarning.bodyMessage.contentType.delete.all": "Êtes-vous sûr de vouloir supprimer ces entrées ?"
}

View File

@ -1,10 +1,10 @@
const _ = require('lodash');
module.exports = {
find: async function (params, populate) {
find: async function (params, populate, raw = false) {
return this.query(function(qb) {
_.forEach(params.where, (where, key) => {
if (_.isArray(where.value)) {
if (_.isArray(where.value) && where.symbol !== 'IN') {
for (const value in where.value) {
qb[value ? 'where' : 'orWhere'](key, where.symbol, where.value[value]);
}
@ -26,7 +26,7 @@ module.exports = {
}
}).fetchAll({
withRelated: populate || this.associations.map(x => x.alias)
});
}).then(data => raw ? data.toJSON() : data);
},
count: async function (params = {}) {
@ -128,5 +128,13 @@ module.exports = {
[this.primaryKey]: params.id
})
.destroy();
},
deleteMany: async function (params) {
return await this
.query(function(qb) {
return qb.whereIn('id', params.id);
})
.destroy();
}
};

View File

@ -1,13 +1,15 @@
const _ = require('lodash');
module.exports = {
find: async function (params, populate) {
return this
find: async function (params, populate, raw = false) {
const query = this
.find(params.where)
.limit(Number(params.limit))
.sort(params.sort)
.skip(Number(params.skip))
.populate(populate || this.associations.map(x => x.alias).join(' '));
return raw ? query.lean() : query;
},
count: async function (params) {
@ -77,5 +79,14 @@ module.exports = {
.remove({
[this.primaryKey]: params.id
});
},
deleteMany: async function (params) {
return this
.remove({
[this.primaryKey]: {
$in: params[this.primaryKey] || params.id
}
});
}
};

View File

@ -55,6 +55,14 @@
"policies": ["routing"]
}
},
{
"method": "DELETE",
"path": "/explorer/deleteAll/:model",
"handler": "ContentManager.deleteAll",
"config": {
"policies": ["routing"]
}
},
{
"method": "DELETE",
"path": "/explorer/:model/:id",

View File

@ -98,4 +98,8 @@ module.exports = {
delete: async ctx => {
ctx.body = await strapi.plugins['content-manager'].services['contentmanager'].delete(ctx.params, ctx.request.query);
},
deleteAll: async ctx => {
ctx.body = await strapi.plugins['content-manager'].services['contentmanager'].deleteMany(ctx.params, ctx.request.query);
}
};

View File

@ -159,4 +159,53 @@ module.exports = {
id: params.id
});
},
deleteMany: async (params, query) => {
const { source } = query;
const { model } = params;
const primaryKey = strapi.query(model, source).primaryKey;
const toRemove = Object.keys(query).reduce((acc, curr) => {
if (curr !== 'source') {
return acc.concat([query[curr]]);
}
return acc;
}, []);
const filters = strapi.utils.models.convertParams(model, { [`${primaryKey}_in`]: toRemove });
const entries = await strapi.query(model, source).find({ where: filters.where }, null, true);
const associations = strapi.query(model, source).associations;
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
associations.forEach(association => {
if (entry[association.alias]) {
switch (association.nature) {
case 'oneWay':
case 'oneToOne':
case 'manyToOne':
case 'oneToManyMorph':
entry[association.alias] = null;
break;
case 'oneToMany':
case 'manyToMany':
case 'manyToManyMorph':
entry[association.alias] = [];
break;
default:
}
}
});
await strapi.query(model, source).update({
[primaryKey]: entry[primaryKey],
values: _.pick(entry, associations.map(a => a.alias))
});
}
return strapi.query(model, source).deleteMany({
[primaryKey]: toRemove,
});
}
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="70" height="40" viewBox="0 0 70 40"><style>.st1{fill:#fff}</style><path fill="#f8b195" d="M67 40H3c-1.6 0-3-1.3-3-3V3c0-1.6 1.4-3 3-3h64c1.7 0 3 1.4 3 3v34c0 1.7-1.3 3-3 3z"/><circle cx="22" cy="10" r="2" class="st1"/><circle cx="22" cy="20" r="2" class="st1"/><circle cx="22" cy="30" r="2" class="st1"/><path d="M49 12H28c-1.1 0-2-.9-2-2s.9-2 2-2h21c1.1 0 2 .9 2 2s-.9 2-2 2zM49 22H28c-1.1 0-2-.9-2-2s.9-2 2-2h21c1.1 0 2 .9 2 2s-.9 2-2 2zM49 32H28c-1.1 0-2-.9-2-2s.9-2 2-2h21c1.1 0 2 .9 2 2s-.9 2-2 2z" class="st1"/></svg>

After

Width:  |  Height:  |  Size: 571 B

View File

@ -18,6 +18,7 @@ import IcoNumber from '../../assets/images/icon_number.png';
import IcoRelation from '../../assets/images/icon_relation.png';
import IcoString from '../../assets/images/icon_string.png';
import IcoText from '../../assets/images/icon_text.png';
import IcoEnum from '../../assets/images/icon_enum.svg';
import styles from './styles.scss';
@ -34,6 +35,7 @@ const asset = {
'relation': IcoRelation,
'string': IcoString,
'text': IcoText,
'enum': IcoEnum,
};

View File

@ -22,6 +22,7 @@ import IcoPassword from '../../assets/images/icon_password.png';
import IcoRelation from '../../assets/images/icon_relation.png';
import IcoString from '../../assets/images/icon_string.png';
import IcoText from '../../assets/images/icon_text.png';
import IcoEnum from '../../assets/images/icon_enum.svg';
import styles from './styles.scss';
/* eslint-disable jsx-a11y/no-static-element-interactions */
@ -45,8 +46,7 @@ class AttributeRow extends React.Component {
decimal: IcoNumber,
email: IcoEmail,
password: IcoPassword,
// TODO add Enumeration icon
enumeration: IcoJson,
enumeration: IcoEnum,
};
this.state = {
showWarning: false,

View File

@ -163,7 +163,7 @@ class PopUpForm extends React.Component { // eslint-disable-line react/prefer-st
<ModalBody className={styles.modalBody} style={modalBodyStyle}>
<form onSubmit={this.props.onSubmit}>
<div className="container-fluid">
<div className={`row ${this.props.renderModalBody ? 'justify-content-center' : ''}`}>
<div className="row">
{modalBody}
</div>
</div>

View File

@ -115,6 +115,10 @@
{
"type": "relation",
"description": "content-type-builder.popUpForm.attributes.relation.description"
},
{
"type": "enumeration",
"description": "content-type-builder.popUpForm.attributes.enumeration.description"
}
]
},
@ -912,6 +916,84 @@
}
]
}
},
"enumeration": {
"baseSettings": {
"items": [
{
"label": {
"id": "content-type-builder.form.attribute.item.enumeration.name"
},
"name": "name",
"type": "string",
"value": ""
},
{
"label": {
"id": "content-type-builder.form.attribute.item.enumeration.rules"
},
"name": "params.enumValue",
"type": "textarea",
"placeholder": "content-type-builder.form.attribute.item.enumeration.placeholder",
"value": false,
"validations": {
"required": true
}
}
]
},
"advancedSettings": {
"items": [
{
"label": {
"id": "content-type-builder.form.attribute.settings.default"
},
"name": "params.default",
"type": "string",
"value": "",
"validations": {}
},
{
"label": {
"id": "content-type-builder.form.attribute.item.enumeration.graphql"
},
"name": "params.enumName",
"type": "string",
"value": "",
"validations": {},
"inputDescription": {
"id": "content-type-builder.form.attribute.item.enumeration.graphql.description"
}
},
{
"title": {
"id": "content-type-builder.form.attribute.item.settings.name"
},
"label": {
"id": "content-type-builder.form.attribute.item.requiredField"
},
"name": "params.required",
"type": "checkbox",
"value": false,
"validations": {},
"inputDescription": {
"id": "content-type-builder.form.attribute.item.requiredField.description"
}
},
{
"label": {
"id": "content-type-builder.form.attribute.item.uniqueField"
},
"name": "params.unique",
"type": "checkbox",
"value": false,
"validations": {},
"inputDescription": {
"id": "content-type-builder.form.attribute.item.uniqueField.description"
}
}
]
}
}
}
}

View File

@ -387,6 +387,11 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
handleChange = ({ target }) => {
let value = target.type === 'number' && target.value !== '' ? toNumber(target.value) : target.value;
// Parse enumeration textarea to transform it into a array
if (target.name === 'params.enumValue') {
value = target.value.split(',');
}
if (isObject(target.value) && target.value._isAMomentObject === true) {
value = moment(target.value, 'YYYY-MM-DD HH:mm:ss').format();
}
@ -394,11 +399,11 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
if (includes(this.props.hash.split('::')[1], 'attribute')) {
this.props.changeInputAttribute(target.name, value);
if (target.name === 'params.nature' && target.value === "manyToMany") {
if (target.name === 'params.nature' && target.value === 'manyToMany') {
this.props.changeInputAttribute('params.dominant', true);
}
if (target.name === 'params.nature' && target.value === "oneWay") {
if (target.name === 'params.nature' && target.value === 'oneWay') {
this.props.changeInputAttribute('params.key', '-');
}

View File

@ -146,9 +146,12 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
}
handleDelete = (attributeName) => {
const index = findIndex(this.props.modelPage.model.attributes, ['name', attributeName]);
const parallelAttributeIndex = findIndex(this.props.modelPage.model.attributes, (attr) => attr.params.key === attributeName);
const { modelPage: { model } } = this.props;
const index = findIndex(model.attributes, ['name', attributeName]);
const attributeToRemove = get(model, ['attributes', index]);
const parallelAttributeIndex = attributeToRemove.name === attributeToRemove.params.key ?
-1 : findIndex(model.attributes, (attr) => attr.params.key === attributeName);
this.props.deleteAttribute(index, this.props.match.params.modelName, parallelAttributeIndex !== -1);
}
@ -158,7 +161,7 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
// Display a notification if the attribute is not present in the ones that the ctb handles
if (!has(attribute.params, 'nature') && !includes(availableAttributes, attribute.params.type)) {
return strapi.notification.info('content-type-builder.notification.info.enumeration');
return strapi.notification.info('content-type-builder.notification.info.disable');
}
const settingsType = attribute.params.type ? 'baseSettings' : 'defineRelation';
const parallelAttributeIndex = findIndex(this.props.modelPage.model.attributes, ['name', attribute.params.key]);

View File

@ -1,164 +1,173 @@
{
"plugin.description.short": "Modelliere die Datenstruktur deiner API.",
"plugin.description.long": "Modelliere die Datenstruktur deiner API. Lege neue Felder und Beziehungen innerhalb von einer Minute an. Erforderliche Dateien werden automatisch in deinem Projekt angelegt und aktualisiert.",
"attribute.string": "String",
"attribute.text": "Text",
"attribute.boolean": "Boolean",
"attribute.float": "Float",
"attribute.integer": "Integer",
"attribute.decimal": "Decimal",
"attribute.date": "Datum",
"attribute.json": "JSON",
"attribute.media": "Medien",
"attribute.email": "E-Mail",
"attribute.password": "Passwort",
"attribute.relation": "Beziehung",
"attribute.enumeration": "Enumeration",
"plugin.description.short": "Modelliere die Datenstruktur deiner API.",
"plugin.description.long":
"Modelliere die Datenstruktur deiner API. Lege neue Felder und Beziehungen innerhalb von einer Minute an. Erforderliche Dateien werden automatisch in deinem Projekt angelegt und aktualisiert.",
"attribute.string": "String",
"attribute.text": "Text",
"attribute.boolean": "Boolean",
"attribute.float": "Float",
"attribute.integer": "Integer",
"attribute.decimal": "Decimal",
"attribute.date": "Datum",
"attribute.json": "JSON",
"attribute.media": "Medien",
"attribute.email": "E-Mail",
"attribute.password": "Passwort",
"attribute.relation": "Beziehung",
"attribute.enumeration": "Enumeration",
"attribute.WYSIWYG": "Text (WYSIWYG)",
"contentType.temporaryDisplay": "(Nicht gespeichert)",
"from": "aus",
"home.contentTypeBuilder.name": "Content-Typen",
"home.contentTypeBuilder.description": "Verwalte deine Content-Typen.",
"home.emptyContentType.title": "Es sind keine Content-Typen verfügbar",
"home.emptyContentType.description": "Lege deinen ersten Content-Typ an, Daten deiner API abrufen zu können.",
"contentType.temporaryDisplay": "(Nicht gespeichert)",
"from": "aus",
"home.contentTypeBuilder.name": "Content-Typen",
"home.contentTypeBuilder.description": "Verwalte deine Content-Typen.",
"home.emptyContentType.title": "Es sind keine Content-Typen verfügbar",
"home.emptyContentType.description": "Lege deinen ersten Content-Typ an, Daten deiner API abrufen zu können.",
"home.emptyAttributes.title": "Es gibt noch keine Felder",
"home.emptyAttributes.description": "Füge deinem Content-Typen das erste Feld hinzu",
"home.emptyAttributes.title": "Es gibt noch keine Felder",
"home.emptyAttributes.description": "Füge deinem Content-Typen das erste Feld hinzu",
"button.contentType.create": "Lege einen Content-Typ an",
"button.contentType.add": "Neuer Content-Typ",
"button.attributes.add": "Neues Feld",
"button.contentType.create": "Lege einen Content-Typ an",
"button.contentType.add": "Neuer Content-Typ",
"button.attributes.add": "Neues Feld",
"error.validation.required": "Dieser Wert ist erforderlich.",
"error.validation.regex": "Dieser Wert entspricht nicht dem RegEx.",
"error.validation.max": "Dieser Wert ist zu hoch.",
"error.validation.min": "Dieser Wert ist zu niedrig.",
"error.validation.maxLength": "Dieser Wert ist zu lang.",
"error.validation.minLength": "Dieser Wert ist zu kurz.",
"error.contentTypeName.taken": "Dieser Name existiert bereits",
"error.attribute.taken": "Dieser Feldname ist bereits vergeben",
"error.attribute.key.taken": "Dieser Wert existiert bereits",
"error.attribute.sameKeyAndName": "Darf nicht gleich sein",
"error.validation.minSupMax": "Darf nicht höher sein",
"error.validation.required": "Dieser Wert ist erforderlich.",
"error.validation.regex": "Dieser Wert entspricht nicht dem RegEx.",
"error.validation.max": "Dieser Wert ist zu hoch.",
"error.validation.min": "Dieser Wert ist zu niedrig.",
"error.validation.maxLength": "Dieser Wert ist zu lang.",
"error.validation.minLength": "Dieser Wert ist zu kurz.",
"error.contentTypeName.taken": "Dieser Name existiert bereits",
"error.attribute.taken": "Dieser Feldname ist bereits vergeben",
"error.attribute.key.taken": "Dieser Wert existiert bereits",
"error.attribute.sameKeyAndName": "Darf nicht gleich sein",
"error.validation.minSupMax": "Darf nicht höher sein",
"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": "Erlaube mehrere Dateien",
"form.attribute.item.json.name": "Name",
"form.attribute.item.boolean.name": "Name",
"form.attribute.item.string.name": "Name",
"form.attribute.item.settings.name": "Einstellungen",
"form.attribute.item.requiredField": "Benötigtes Feld",
"form.attribute.item.uniqueField": "Einzigartiges Feld",
"form.attribute.item.minimum": "Mindestwert",
"form.attribute.item.minimumLength": "Mindestlänge",
"form.attribute.item.maximumLength": "Maximallänge",
"form.attribute.item.maximum": "Maximalwert",
"form.attribute.item.requiredField.description": "Du wirst keinen Eintrag anlegen können, wenn dieses Feld leer ist",
"form.attribute.item.uniqueField.description": "Du wirst keinen Eintrag anlegen können, wenn es bereits einen Eintrag mit identischem Inhalt gibt",
"form.attribute.item.defineRelation.fieldName": "Feldname",
"form.attribute.item.customColumnName": "Eigener Spaltenname",
"form.attribute.item.customColumnName.description": "Dies ist nützlich, um Spalten in der Datenbank für Antworten der API umzubenennen",
"form.attribute.item.number.type": "Zahlenformat",
"form.attribute.item.number.type.integer": "integer (z.B.: 10)",
"form.attribute.item.number.type.float": "float (z.B.: 3.33333333)",
"form.attribute.item.number.type.decimal": "decimal (z.B.: 2.22)",
"form.attribute.settings.default": "Standardwert",
"form.attribute.settings.default.checkboxLabel": "Set to true",
"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": "Erlaube mehrere Dateien",
"form.attribute.item.json.name": "Name",
"form.attribute.item.boolean.name": "Name",
"form.attribute.item.string.name": "Name",
"form.attribute.item.enumeration.name": "Name",
"form.attribute.item.enumeration.rules": "Werte (trennen Sie sie mit einem Komma)",
"form.attribute.item.enumeration.placeholder": "Ex: Morgen, Mittag, Abend",
"form.attribute.item.enumeration.graphql": "Name override for GraphQL",
"form.attribute.item.enumeration.graphql.description": "Allows you to override the default generated name for GraphQL",
"form.attribute.item.settings.name": "Einstellungen",
"form.attribute.item.requiredField": "Benötigtes Feld",
"form.attribute.item.uniqueField": "Einzigartiges Feld",
"form.attribute.item.minimum": "Mindestwert",
"form.attribute.item.minimumLength": "Mindestlänge",
"form.attribute.item.maximumLength": "Maximallänge",
"form.attribute.item.maximum": "Maximalwert",
"form.attribute.item.requiredField.description": "Du wirst keinen Eintrag anlegen können, wenn dieses Feld leer ist",
"form.attribute.item.uniqueField.description": "Du wirst keinen Eintrag anlegen können, wenn es bereits einen Eintrag mit identischem Inhalt gibt",
"form.attribute.item.defineRelation.fieldName": "Feldname",
"form.attribute.item.customColumnName": "Eigener Spaltenname",
"form.attribute.item.customColumnName.description": "Dies ist nützlich, um Spalten in der Datenbank für Antworten der API umzubenennen",
"form.attribute.item.number.type": "Zahlenformat",
"form.attribute.item.number.type.integer": "integer (z.B.: 10)",
"form.attribute.item.number.type.float": "float (z.B.: 3.33333333)",
"form.attribute.item.number.type.decimal": "decimal (z.B.: 2.22)",
"form.attribute.settings.default": "Standardwert",
"form.attribute.settings.default.checkboxLabel": "Set to true",
"form.button.cancel": "Abbrechen",
"form.button.continue": "Weiter",
"form.button.save": "Speichern",
"form.button.cancel": "Abbrechen",
"form.button.continue": "Weiter",
"form.button.save": "Speichern",
"form.contentType.item.connections": "Verbindung",
"form.contentType.item.name": "Name",
"form.contentType.item.name.description": "Der Name des Content-Typs sollte Singular sein. {link}",
"form.contentType.item.name.link.description": "Schau dir unsere Dokumentation an.",
"form.contentType.item.description": "Beschreibung",
"form.contentType.item.description.placeholder": "Beschreibe deinen Content-Typ",
"form.contentType.item.collectionName": "Name des Dokuments in der Datenbank",
"form.contentType.item.collectionName.inputDescription": "Nützlich, wenn Content-Typ und Datenbankname unterschiedlich sind",
"form.contentType.item.connections": "Verbindung",
"form.contentType.item.name": "Name",
"form.contentType.item.name.description": "Der Name des Content-Typs sollte Singular sein. {link}",
"form.contentType.item.name.link.description": "Schau dir unsere Dokumentation an.",
"form.contentType.item.description": "Beschreibung",
"form.contentType.item.description.placeholder": "Beschreibe deinen Content-Typ",
"form.contentType.item.collectionName": "Name des Dokuments in der Datenbank",
"form.contentType.item.collectionName.inputDescription": "Nützlich, wenn Content-Typ und Datenbankname unterschiedlich sind",
"menu.section.contentTypeBuilder.name.plural": "Content-Typen",
"menu.section.contentTypeBuilder.name.singular": "Content-Typ",
"menu.section.documentation.name": "Dokumentation",
"menu.section.documentation.guide": "Mehr über Content-Typen findest du in unserer",
"menu.section.documentation.guideLink": "Anleitung.",
"menu.section.documentation.tutorial": "Schau dir unser",
"menu.section.documentation.tutorialLink": "Tutorial an.",
"menu.section.contentTypeBuilder.name.plural": "Content-Typen",
"menu.section.contentTypeBuilder.name.singular": "Content-Typ",
"menu.section.documentation.name": "Dokumentation",
"menu.section.documentation.guide": "Mehr über Content-Typen findest du in unserer",
"menu.section.documentation.guideLink": "Anleitung.",
"menu.section.documentation.tutorial": "Schau dir unser",
"menu.section.documentation.tutorialLink": "Tutorial an.",
"modelPage.contentHeader.emptyDescription.description": "Dieser Content-Typ hat keine Beschreibung",
"modelPage.contentType.list.title.plural": "Felder",
"modelPage.contentType.list.title.singular": "Feld",
"modelPage.contentType.list.title.including": "schließt ein",
"modelPage.contentType.list.relationShipTitle.plural": "Beziehungen",
"modelPage.contentType.list.relationShipTitle.singular": "Beziehung",
"modelPage.attribute.relationWith": "Beziehung mit",
"modelPage.contentHeader.emptyDescription.description": "Dieser Content-Typ hat keine Beschreibung",
"modelPage.contentType.list.title.plural": "Felder",
"modelPage.contentType.list.title.singular": "Feld",
"modelPage.contentType.list.title.including": "schließt ein",
"modelPage.contentType.list.relationShipTitle.plural": "Beziehungen",
"modelPage.contentType.list.relationShipTitle.singular": "Beziehung",
"modelPage.attribute.relationWith": "Beziehung mit",
"noTableWarning.description": "Vergiss nicht, die Tabelle `{modelName}` in deiner Datenbank zu erstellen",
"noTableWarning.infos": "Mehr Informationen",
"noTableWarning.description": "Vergiss nicht, die Tabelle `{modelName}` in deiner Datenbank zu erstellen",
"noTableWarning.infos": "Mehr Informationen",
"notification.error.message": "Ein Fehler ist aufgetreten",
"notification.info.contentType.creating.notSaved": "Bitte speichere zuerst diesen Content-Typ bevor du einen neuen anlegst",
"notification.info.optimized": "Dieses Plugin ist auf deinen localStorage optimiert",
"notification.success.message.contentType.edit": "Der Content-Typ wurde aktualisiert",
"notification.success.message.contentType.create": "Der Content-Typ wurde angelegt",
"notification.success.contentTypeDeleted": "Der Content-Typ wurde gelöscht",
"notification.info.enumeration": "Dieses Feld ist momentan nicht editierbar...😮",
"notification.error.message": "Ein Fehler ist aufgetreten",
"notification.info.contentType.creating.notSaved": "Bitte speichere zuerst diesen Content-Typ bevor du einen neuen anlegst",
"notification.info.disable": "Dieses Feld ist momentan nicht editierbar...😮",
"notification.info.optimized": "Dieses Plugin ist auf deinen localStorage optimiert",
"notification.success.message.contentType.edit": "Der Content-Typ wurde aktualisiert",
"notification.success.message.contentType.create": "Der Content-Typ wurde angelegt",
"notification.success.contentTypeDeleted": "Der Content-Typ wurde gelöscht",
"popUpForm.attributes.string.description": "Titel, Namen, Namenslisten",
"popUpForm.attributes.text.description": "Beschreibungen, Paragraphen, Artikel",
"popUpForm.attributes.boolean.description": "Ja/Nein, 1 oder 0, Wahr/Falsch",
"popUpForm.attributes.number.description": "Jegliche Zahlen",
"popUpForm.attributes.date.description": "Event-Daten, Öffnungszeiten",
"popUpForm.attributes.json.description": "Daten in JSON-Format",
"popUpForm.attributes.media.description": "Bilder, Videos, PDFs und andere",
"popUpForm.attributes.relation.description": "Bezieht sich auf einen Content-Typ",
"popUpForm.attributes.email.description": "E-Mail-Adressen von Benutzern",
"popUpForm.attributes.password.description": "Passwörter von Benutzers",
"popUpForm.attributes.string.description": "Titel, Namen, Namenslisten",
"popUpForm.attributes.text.description": "Beschreibungen, Paragraphen, Artikel",
"popUpForm.attributes.boolean.description": "Ja/Nein, 1 oder 0, Wahr/Falsch",
"popUpForm.attributes.number.description": "Jegliche Zahlen",
"popUpForm.attributes.date.description": "Event-Daten, Öffnungszeiten",
"popUpForm.attributes.json.description": "Daten in JSON-Format",
"popUpForm.attributes.media.description": "Bilder, Videos, PDFs und andere",
"popUpForm.attributes.relation.description": "Bezieht sich auf einen Content-Typ",
"popUpForm.attributes.email.description": "E-Mail-Adressen von Benutzern",
"popUpForm.attributes.password.description": "Passwörter von Benutzers",
"popUpForm.attributes.enumeration.description": "Liste der Auswahlmöglichkeiten",
"popUpForm.attributes.string.name": "String",
"popUpForm.attributes.text.name": "Text",
"popUpForm.attributes.boolean.name": "Boolean",
"popUpForm.attributes.date.name": "Datum",
"popUpForm.attributes.json.name": "JSON",
"popUpForm.attributes.media.name": "Medien",
"popUpForm.attributes.number.name": "Zahl",
"popUpForm.attributes.relation.name": "Beziehung",
"popUpForm.attributes.email.name": "E-Mail",
"popUpForm.attributes.password.name": "Passwort",
"popUpForm.attributes.enumeration.name": "Enumeration",
"popUpForm.create": "Neu",
"popUpForm.edit": "Bearbeiten",
"popUpForm.field": "Feld",
"popUpForm.create.contentType.header.title": "Neuer Content-Typ",
"popUpForm.choose.attributes.header.title": "Neues Feld hinzufügen",
"popUpForm.edit.contentType.header.title": "Content-Typen bearbeiten",
"popUpForm.attributes.string.name": "String",
"popUpForm.attributes.text.name": "Text",
"popUpForm.attributes.boolean.name": "Boolean",
"popUpForm.attributes.date.name": "Datum",
"popUpForm.attributes.json.name": "JSON",
"popUpForm.attributes.media.name": "Medien",
"popUpForm.attributes.number.name": "Zahl",
"popUpForm.attributes.relation.name": "Beziehung",
"popUpForm.attributes.email.name": "E-Mail",
"popUpForm.attributes.password.name": "Passwort",
"popUpForm.create": "Neu",
"popUpForm.edit": "Bearbeiten",
"popUpForm.field": "Feld",
"popUpForm.create.contentType.header.title": "Neuer Content-Typ",
"popUpForm.choose.attributes.header.title": "Neues Feld hinzufügen",
"popUpForm.edit.contentType.header.title": "Content-Typen bearbeiten",
"popUpForm.navContainer.relation": "Beziehung definieren",
"popUpForm.navContainer.base": "Grundeinstellungen",
"popUpForm.navContainer.advanced": "Fortgeschrittene Einstellungen",
"popUpForm.navContainer.relation": "Beziehung definieren",
"popUpForm.navContainer.base": "Grundeinstellungen",
"popUpForm.navContainer.advanced": "Fortgeschrittene Einstellungen",
"popUpRelation.title": "Beziehung",
"popUpRelation.title": "Beziehung",
"popUpWarning.button.cancel": "Abbrechen",
"popUpWarning.button.confirm": "Bestätigen",
"popUpWarning.title": "Bitte bestätigen",
"popUpWarning.bodyMessage.contentType.delete": "Bist du sicher, dass du diesen Content-Typ löschen willst?",
"popUpWarning.bodyMessage.attribute.delete": "Bist du sicher, dass du dieses Feld löschen willst?",
"popUpWarning.button.cancel": "Abbrechen",
"popUpWarning.button.confirm": "Bestätigen",
"popUpWarning.title": "Bitte bestätigen",
"popUpWarning.bodyMessage.contentType.delete": "Bist du sicher, dass du diesen Content-Typ löschen willst?",
"popUpWarning.bodyMessage.attribute.delete": "Bist du sicher, dass du dieses Feld löschen willst?",
"table.contentType.title.plural": "Content-Typen sind verfügbar",
"table.contentType.title.singular": "Content-Typ ist verfügbar",
"table.contentType.head.name": "Name",
"table.contentType.head.description": "Beschreibung",
"table.contentType.head.fields": "Felder",
"table.contentType.title.plural": "Content-Typen sind verfügbar",
"table.contentType.title.singular": "Content-Typ ist verfügbar",
"table.contentType.head.name": "Name",
"table.contentType.head.description": "Beschreibung",
"table.contentType.head.fields": "Felder",
"relation.oneToOne": "hat ein(en)",
"relation.oneToMany": "gehört zu vielen",
"relation.manyToOne": "hat viele",
"relation.manyToMany": "hat und gehört zu vielen",
"relation.attributeName.placeholder": "z.B.: Autor, Kategorie"
"relation.oneToOne": "hat ein(en)",
"relation.oneToMany": "gehört zu vielen",
"relation.manyToOne": "hat viele",
"relation.manyToMany": "hat und gehört zu vielen",
"relation.attributeName.placeholder": "z.B.: Autor, Kategorie"
}
}

View File

@ -53,6 +53,11 @@
"form.attribute.item.json.name": "Name",
"form.attribute.item.boolean.name": "Name",
"form.attribute.item.string.name": "Name",
"form.attribute.item.enumeration.name": "Name",
"form.attribute.item.enumeration.rules": "Values (separate them with a comma)",
"form.attribute.item.enumeration.graphql": "Name override for GraphQL",
"form.attribute.item.enumeration.graphql.description": "Allows you to override the default generated name for GraphQL",
"form.attribute.item.enumeration.placeholder": "Ex: morning,noon,evening",
"form.attribute.item.appearance.name": "Appearance",
"form.attribute.item.appearance.label": "Display as a WYSIWYG",
"form.attribute.item.appearance.description":
@ -116,11 +121,11 @@
"notification.error.message": "An error occurred",
"notification.info.contentType.creating.notSaved":
"Please save your current Content Type before creating a new one",
"notification.info.disable": "This field is not editable for the moment...😮",
"notification.info.optimized": "This plugin is optimized with your localStorage",
"notification.success.message.contentType.edit": "Your Content Type has been updated",
"notification.success.message.contentType.create": "Your Content Type has been created",
"notification.success.contentTypeDeleted": "The Content Type has been deleted",
"notification.info.enumeration": "This field is not editable for the moment...😮",
"popUpForm.attributes.string.description": "Titles, names, paragraphs, list of names",
"popUpForm.attributes.text.description": "Descriptions, text paragraphs, articles ",
@ -132,6 +137,7 @@
"popUpForm.attributes.relation.description": "Refers to a Content Type",
"popUpForm.attributes.email.description": "User's email...",
"popUpForm.attributes.password.description": "User password...",
"popUpForm.attributes.enumeration.description": "List of choices",
"popUpForm.attributes.string.name": "String",
"popUpForm.attributes.text.name": "Text",
@ -143,6 +149,7 @@
"popUpForm.attributes.relation.name": "Relation",
"popUpForm.attributes.email.name": "Email",
"popUpForm.attributes.password.name": "Password",
"popUpForm.attributes.enumeration.name": "Enumeration",
"popUpForm.create": "Add New",
"popUpForm.edit": "Edit",
"popUpForm.field": "Field",

View File

@ -1,20 +1,20 @@
{
"plugin.description.short": "Modelisez la structure de données de votre API.",
"plugin.description.short": "Modélisez la structure de données de votre API.",
"plugin.description.long":
"Modelisez la structure de données de votre API. Créer des nouveaux champs et relations en un instant. Les fichiers se créent et se mettent à jour automatiquement.",
"Modélisez la structure de données de votre API. Créer des nouveaux champs et relations en un instant. Les fichiers se créent et se mettent à jour automatiquement.",
"attribute.string": "Chaîne de caractères",
"attribute.text": "Text",
"attribute.text": "Texte",
"attribute.boolean": "Booléen",
"attribute.float": "Décimal approximatif",
"attribute.integer": "Entier",
"attribute.decimal": "Décimal",
"attribute.date": "Date",
"attribute.json": "JSON",
"attribute.media": "Media",
"attribute.media": "Média",
"attribute.password": "Mot de passe",
"attribute.email": "Email",
"attribute.relation": "Relation",
"attribute.enumeration": "Enumération",
"attribute.enumeration": "Énumération",
"attribute.WYSIWYG": "Text (WYSIWYG)",
"contentType.temporaryDisplay": "(Non sauvegardé)",
@ -26,7 +26,7 @@
"home.emptyContentType.title": "Il n'y a pas de model disponible",
"home.emptyContentType.description": "Créez votre premier modèle...",
"home.emptyAttributes.title": "Il n'y a pas encore de champs",
"home.emptyAttributes.title": "Il n'y a pas encore de champ",
"home.emptyAttributes.description": "Ajoutez votre premier champ a votre modèle",
"button.contentType.create": "Créer un modèle",
@ -54,6 +54,11 @@
"form.attribute.item.media.name": "Nom",
"form.attribute.item.media.multiple": "Peut être relié à plusieurs fichiers",
"form.attribute.item.string.name": "Nom",
"form.attribute.item.enumeration.name": "Nom",
"form.attribute.item.enumeration.rules": "Valeurs (les séparer par une virgule)",
"form.attribute.item.enumeration.graphql": "Surchage du nom pour GraphQL",
"form.attribute.item.enumeration.graphql.description": "Vous permet de remplacer le nom généré par défaut pour GraphQL",
"form.attribute.item.enumeration.placeholder": "Ex: matin,midi,soir",
"form.attribute.item.appearance.name": "Apparence",
"form.attribute.item.appearance.label": "Editable avec un WYSIWYG",
"form.attribute.item.appearance.description":
@ -118,11 +123,11 @@
"notification.error.message": "Une erreur est survenue",
"notification.info.contentType.creating.notSaved":
"Sauvegardez votre Modèle en cours avant d'en créer un nouveau",
"notification.info.disable": "Ce champ n'est pas modifiable pour le moment...😮",
"notification.info.optimized": "Ce plugin est optimisé pour votre localStorage",
"notification.success.message.contentType.edit": "Votre modèle a bien été modifié",
"notification.success.message.contentType.create": "Votre modèle a bien été créée",
"notification.success.contentTypeDeleted": "Le modèle a bien été supprimé.",
"notification.info.enumeration": "Ce champs n'est pas modifiable pour le moment...😮",
"popUpForm.attributes.string.description": "Titres, noms,...",
"popUpForm.attributes.text.description": "Descriptions, paragraphes texte, articles ",
@ -134,22 +139,24 @@
"popUpForm.attributes.relation.description": "Pointe vers un autre Modèle",
"popUpForm.attributes.password.description": "Mot de passe utilisateur...",
"popUpForm.attributes.email.description": "Email utilisateurs",
"popUpForm.attributes.enumeration.description": "Liste de choix",
"popUpForm.attributes.string.name": "Chaîne de caractères",
"popUpForm.attributes.text.name": "Text",
"popUpForm.attributes.text.name": "Texte",
"popUpForm.attributes.boolean.name": "Booléen",
"popUpForm.attributes.number.name": "Nombre",
"popUpForm.attributes.date.name": "Date",
"popUpForm.attributes.json.name": "JSON",
"popUpForm.attributes.media.name": "Media",
"popUpForm.attributes.media.name": "Média",
"popUpForm.attributes.relation.name": "Relation",
"popUpForm.attributes.email.name": "Email",
"popUpForm.attributes.password.name": "Mot de passe",
"popUpForm.attributes.enumeration": "Énumération",
"popUpForm.create": "Ajouter un Nouveau",
"popUpForm.edit": "Modifer",
"popUpForm.field": "Champ",
"popUpForm.create.contentType.header.title": "Ajouter un Nouveau Modèle",
"popUpForm.choose.attributes.header.title": "Ajouter un Nouveau Champs",
"popUpForm.choose.attributes.header.title": "Ajouter un Nouveau Champ",
"popUpForm.edit.contentType.header.title": "Modifier un Modèle",
"popUpForm.navContainer.relation": "Définir relation",
"popUpForm.navContainer.base": "Réglages de base",

View File

@ -52,6 +52,11 @@
"form.attribute.item.json.name": "Nazwa",
"form.attribute.item.boolean.name": "Nazwa",
"form.attribute.item.string.name": "Nazwa",
"form.attribute.item.enumeration.name": "Nazwa",
"form.attribute.item.enumeration.rules": "Values (separate them with a comma)",
"form.attribute.item.enumeration.placeholder": "Ex: morning,noon,evening",
"form.attribute.item.enumeration.graphql": "Name override for GraphQL",
"form.attribute.item.enumeration.graphql.description": "Allows you to override the default generated name for GraphQL",
"form.attribute.item.settings.name": "Ustawienia",
"form.attribute.item.requiredField": "Wymagany",
"form.attribute.item.uniqueField": "Unikalny",
@ -110,11 +115,11 @@
"notification.error.message": "Wystąpił błąd",
"notification.info.contentType.creating.notSaved":
"Zapisz proszę aktualny model zanim stworzysz nowy",
"notification.info.disable": "Tego pola nie można obecnie edytować... 😮",
"notification.info.optimized": "Ta wtyczka jest zoptymalizowana z localStorage",
"notification.success.message.contentType.edit": "Model został zmieniony",
"notification.success.message.contentType.create": "Model został utworzony",
"notification.success.contentTypeDeleted": "Model został usunięty",
"notification.info.enumeration": "Tego pola nie można obecnie edytować... 😮",
"popUpForm.attributes.string.description": "Tytuły, nazwy, paragrafy, lista nazwisk",
"popUpForm.attributes.text.description": "Opisy, paragrafy, artykuły ",
@ -126,7 +131,8 @@
"popUpForm.attributes.relation.description": "Odnosi się do modelu",
"popUpForm.attributes.email.description": "Email użytkownika...",
"popUpForm.attributes.password.description": "Hasło użytkownika...",
"popUpForm.attributes.enumeration.description": "List of choices",
"popUpForm.attributes.string.name": "Ciąg",
"popUpForm.attributes.text.name": "Tekst",
"popUpForm.attributes.boolean.name": "Typ logiczny",
@ -137,6 +143,7 @@
"popUpForm.attributes.relation.name": "Relacja",
"popUpForm.attributes.email.name": "Email",
"popUpForm.attributes.password.name": "Hasło",
"popUpForm.attributes.enumeration.name": "Wyliczenie",
"popUpForm.create": "Nowy",
"popUpForm.edit": "Zmień",
"popUpForm.field": "Atrybut",

View File

@ -1,176 +1,183 @@
{
"plugin.description.short": "Моделируйте структуру данных вашего API.",
"plugin.description.long":
"Моделируйте структуру данных вашего API. Создавайте новые поля и связи всего за минуту. Файлы автоматически создаются и обновляются в вашем проекте.",
"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": "Связь",
"attribute.enumeration": "Enumeration",
"attribute.WYSIWYG": "Text (WYSIWYG)",
"plugin.description.short": "Моделируйте структуру данных вашего API.",
"plugin.description.long":
"Моделируйте структуру данных вашего API. Создавайте новые поля и связи всего за минуту. Файлы автоматически создаются и обновляются в вашем проекте.",
"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": "Связь",
"attribute.enumeration": "Enumeration",
"attribute.WYSIWYG": "Text (WYSIWYG)",
"contentType.temporaryDisplay": "(Не сохранено)",
"from": "from",
"home.contentTypeBuilder.name": "Типы Данных",
"home.contentTypeBuilder.description": "Создавайте и обновляйте ваш Тип Данных.",
"home.emptyContentType.title": "Нет Типов Данных",
"home.emptyContentType.description":
"Создайте ваш первый Тип Данных и у вас появится возможность загружать ваши данные при помощи API.",
"home.emptyAttributes.title": "Пока ни одного поля не создано",
"home.emptyAttributes.description": "Добавте первое поле в ваш новый Тип Данных",
"button.contentType.create": "Создать Тип Данных",
"button.contentType.add": "Добавить Тип Данных",
"button.attributes.add": "Добавить Новое Поле",
"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": "Название",
"form.attribute.item.number.name": "Название",
"form.attribute.item.date.name": "Название",
"form.attribute.item.media.name": "Название",
"form.attribute.item.media.multiple": "Возможно несколько файлов",
"form.attribute.item.json.name": "Название",
"form.attribute.item.boolean.name": "Название",
"form.attribute.item.string.name": "Название",
"form.attribute.item.enumeration.name": "Название",
"form.attribute.item.enumeration.rules": "Values (separate them with a comma)",
"form.attribute.item.enumeration.graphql": "Name override for GraphQL",
"form.attribute.item.enumeration.graphql.description": "Allows you to override the default generated name for GraphQL",
"form.attribute.item.enumeration.placeholder": "Ex: morning,noon,evening",
"form.attribute.item.appearance.name": "Отображение",
"form.attribute.item.appearance.label": "Показывать 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": "Название поля",
"form.attribute.item.customColumnName": "Настраиваемые названия столбца",
"form.attribute.item.customColumnName.description":
"Это удобно иметь возможность переименовывать название столбцов для настройки ответов от API.",
"form.attribute.item.number.type": "Числовой формат",
"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": "Установить значение — true",
"form.button.cancel": "Отменить",
"form.button.continue": "Продолжить",
"form.button.save": "Сохранить",
"form.contentType.item.connections": "Соединение",
"form.contentType.item.name": "Название",
"form.contentType.item.name.description": "Название Типов Данных должны быть уникальными: {link}",
"form.contentType.item.name.link.description": "Ознакомьтесь с нашей документацией",
"form.contentType.item.description": "Описание",
"form.contentType.item.description.placeholder": "Добавте ваше короткое описание...",
"form.contentType.item.collectionName": "Название коллекции",
"form.contentType.item.collectionName.inputDescription":
"Полезно, когда название вашего Типа Данных и название вашей таблицы различаются",
"menu.section.contentTypeBuilder.name.plural": "Типы Данных",
"menu.section.contentTypeBuilder.name.singular": "Тип Данных",
"menu.section.documentation.name": "Документация",
"menu.section.documentation.guide": "Прочтите больше о Типах Данных в нашем",
"menu.section.documentation.guideLink": "руководстве.",
"menu.section.documentation.tutorial": "Посмотрите наши",
"menu.section.documentation.tutorialLink": "обучающие видео.",
"modelPage.contentHeader.emptyDescription.description":
"Нет описания для этого Типа Данных",
"modelPage.contentType.list.title.plural": "поля",
"modelPage.contentType.list.title.singular": "поле",
"modelPage.contentType.list.title.including": "включает",
"modelPage.contentType.list.relationShipTitle.plural": "связи",
"modelPage.contentType.list.relationShipTitle.singular": "связь",
"modelPage.attribute.relationWith": "Связан с",
"noTableWarning.description": "Не забудте создать таблицу `{modelName}` в вашей базе данных",
"noTableWarning.infos": "Больше информации",
"notification.error.message": "Возникла ошибка",
"notification.info.contentType.creating.notSaved":
"Пожалуйста сохраните ваш текущий Тип Данных перед тем как создавать новый",
"notification.info.disable": "Это поле в данный момент не редактируемо...😮",
"notification.info.optimized": "Плагин оптимизирован с вашим localstorage",
"notification.success.message.contentType.edit": "Ваш Тип Данных обновлен",
"notification.success.message.contentType.create": "Ваш Тип Данных создан",
"notification.success.contentTypeDeleted": "Ваш Тип Данных удален",
"popUpForm.attributes.string.description": "Загаловки, названия, имена, перечень названий",
"popUpForm.attributes.text.description": "Описания, текстовые параграфы, статьи",
"popUpForm.attributes.boolean.description": "Yes или no, 1 или 0, true или false",
"popUpForm.attributes.number.description": "Все что является числом",
"popUpForm.attributes.date.description": "Дата события, рабочие часы",
"popUpForm.attributes.json.description": "Данные в JSON формате",
"popUpForm.attributes.media.description": "Картинки, видео, PDF и другие виды файлов",
"popUpForm.attributes.relation.description": "Связан с Типом Данных",
"popUpForm.attributes.email.description": "Пользовательский email...",
"popUpForm.attributes.password.description": "Пароль пользователя...",
"popUpForm.attributes.enumeration.description": "List of choices",
"contentType.temporaryDisplay": "(Не сохранено)",
"from": "from",
"home.contentTypeBuilder.name": "Типы Данных",
"home.contentTypeBuilder.description": "Создавайте и обновляйте ваш Тип Данных.",
"home.emptyContentType.title": "Нет Типов Данных",
"home.emptyContentType.description":
"Создайте ваш первый Тип Данных и у вас появится возможность загружать ваши данные при помощи API.",
"home.emptyAttributes.title": "Пока ни одного поля не создано",
"home.emptyAttributes.description": "Добавте первое поле в ваш новый Тип Данных",
"button.contentType.create": "Создать Тип Данных",
"button.contentType.add": "Добавить Тип Данных",
"button.attributes.add": "Добавить Новое Поле",
"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": "Название",
"form.attribute.item.number.name": "Название",
"form.attribute.item.date.name": "Название",
"form.attribute.item.media.name": "Название",
"form.attribute.item.media.multiple": "Возможно несколько файлов",
"form.attribute.item.json.name": "Название",
"form.attribute.item.boolean.name": "Название",
"form.attribute.item.string.name": "Название",
"form.attribute.item.appearance.name": "Отображение",
"form.attribute.item.appearance.label": "Показывать 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": "Название поля",
"form.attribute.item.customColumnName": "Настраиваемые названия столбца",
"form.attribute.item.customColumnName.description":
"Это удобно иметь возможность переименовывать название столбцов для настройки ответов от API.",
"form.attribute.item.number.type": "Числовой формат",
"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": "Установить значение — true",
"form.button.cancel": "Отменить",
"form.button.continue": "Продолжить",
"form.button.save": "Сохранить",
"form.contentType.item.connections": "Соединение",
"form.contentType.item.name": "Название",
"form.contentType.item.name.description": "Название Типов Данных должны быть уникальными: {link}",
"form.contentType.item.name.link.description": "Ознакомьтесь с нашей документацией",
"form.contentType.item.description": "Описание",
"form.contentType.item.description.placeholder": "Добавте ваше короткое описание...",
"form.contentType.item.collectionName": "Название коллекции",
"form.contentType.item.collectionName.inputDescription":
"Полезно, когда название вашего Типа Данных и название вашей таблицы различаются",
"menu.section.contentTypeBuilder.name.plural": "Типы Данных",
"menu.section.contentTypeBuilder.name.singular": "Тип Данных",
"menu.section.documentation.name": "Документация",
"menu.section.documentation.guide": "Прочтите больше о Типах Данных в нашем",
"menu.section.documentation.guideLink": "руководстве.",
"menu.section.documentation.tutorial": "Посмотрите наши",
"menu.section.documentation.tutorialLink": "обучающие видео.",
"modelPage.contentHeader.emptyDescription.description":
"Нет описания для этого Типа Данных",
"modelPage.contentType.list.title.plural": "поля",
"modelPage.contentType.list.title.singular": "поле",
"modelPage.contentType.list.title.including": "включает",
"modelPage.contentType.list.relationShipTitle.plural": "связи",
"modelPage.contentType.list.relationShipTitle.singular": "связь",
"modelPage.attribute.relationWith": "Связан с",
"noTableWarning.description": "Не забудте создать таблицу `{modelName}` в вашей базе данных",
"noTableWarning.infos": "Больше информации",
"notification.error.message": "Возникла ошибка",
"notification.info.contentType.creating.notSaved":
"Пожалуйста сохраните ваш текущий Тип Данных перед тем как создавать новый",
"notification.info.optimized": "Плагин оптимизирован с вашим localstorage",
"notification.success.message.contentType.edit": "Ваш Тип Данных обновлен",
"notification.success.message.contentType.create": "Ваш Тип Данных создан",
"notification.success.contentTypeDeleted": "Ваш Тип Данных удален",
"notification.info.enumeration": "Это поле в данный момент не редактируемо...😮",
"popUpForm.attributes.string.description": "Загаловки, названия, имена, перечень названий",
"popUpForm.attributes.text.description": "Описания, текстовые параграфы, статьи",
"popUpForm.attributes.boolean.description": "Yes или no, 1 или 0, true или false",
"popUpForm.attributes.number.description": "Все что является числом",
"popUpForm.attributes.date.description": "Дата события, рабочие часы",
"popUpForm.attributes.json.description": "Данные в JSON формате",
"popUpForm.attributes.media.description": "Картинки, видео, PDF и другие виды файлов",
"popUpForm.attributes.relation.description": "Связан с Типом Данных",
"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": "Добавить новый Тип Данных",
"popUpForm.choose.attributes.header.title": "Добавить новое поле",
"popUpForm.edit.contentType.header.title": "Отредактировать Тип Данных",
"popUpForm.navContainer.relation": "Определить связь",
"popUpForm.navContainer.base": "Базовый настройки",
"popUpForm.navContainer.advanced": "Расширенные настройки",
"popUpRelation.title": "Связь",
"popUpWarning.button.cancel": "Отменить",
"popUpWarning.button.confirm": "Подтвердить",
"popUpWarning.title": "Пожалуйста подтвердите",
"popUpWarning.bodyMessage.contentType.delete":
"Вы уверены, что хотите удалить этот Тип Данных?",
"popUpWarning.bodyMessage.attribute.delete": "Вы уверены, что хотите удалить это поле?",
"table.contentType.title.plural": "Типы Данных доступны",
"table.contentType.title.singular": "Тип Данных доступен",
"table.contentType.head.name": "Название",
"table.contentType.head.description": "Описание",
"table.contentType.head.fields": "Поля",
"relation.oneToOne": "имеет один",
"relation.oneToMany": "принадлежит многим",
"relation.manyToOne": "имеет много",
"relation.manyToMany": "имеет и принадлежит многим",
"relation.attributeName.placeholder": "Пример: автор, категория, тег"
}
"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.attributes.enumeration.name": "Enumeration",
"popUpForm.create": "Добавить новое",
"popUpForm.edit": "Отредактировать",
"popUpForm.field": "Поле",
"popUpForm.create.contentType.header.title": "Добавить новый Тип Данных",
"popUpForm.choose.attributes.header.title": "Добавить новое поле",
"popUpForm.edit.contentType.header.title": "Отредактировать Тип Данных",
"popUpForm.navContainer.relation": "Определить связь",
"popUpForm.navContainer.base": "Базовый настройки",
"popUpForm.navContainer.advanced": "Расширенные настройки",
"popUpRelation.title": "Связь",
"popUpWarning.button.cancel": "Отменить",
"popUpWarning.button.confirm": "Подтвердить",
"popUpWarning.title": "Пожалуйста подтвердите",
"popUpWarning.bodyMessage.contentType.delete":
"Вы уверены, что хотите удалить этот Тип Данных?",
"popUpWarning.bodyMessage.attribute.delete": "Вы уверены, что хотите удалить это поле?",
"table.contentType.title.plural": "Типы Данных доступны",
"table.contentType.title.singular": "Тип Данных доступен",
"table.contentType.head.name": "Название",
"table.contentType.head.description": "Описание",
"table.contentType.head.fields": "Поля",
"relation.oneToOne": "имеет один",
"relation.oneToMany": "принадлежит многим",
"relation.manyToOne": "имеет много",
"relation.manyToMany": "имеет и принадлежит многим",
"relation.attributeName.placeholder": "Пример: автор, категория, тег"
}

View File

@ -52,6 +52,11 @@
"form.attribute.item.json.name": "İsim",
"form.attribute.item.boolean.name": "İsim",
"form.attribute.item.string.name": "İsim",
"form.attribute.item.enumeration.name": "İsim",
"form.attribute.item.enumeration.rules": "Values (separate them with a comma)",
"form.attribute.item.enumeration.placeholder": "Ex: morning,noon,evening",
"form.attribute.item.enumeration.graphql": "Name override for GraphQL",
"form.attribute.item.enumeration.graphql.description": "Allows you to override the default generated name for GraphQL",
"form.attribute.item.appearance.name": "Görünüm",
"form.attribute.item.appearance.label": "WYSIWYG olarak görüntüle",
"form.attribute.item.appearance.description":
@ -114,11 +119,11 @@
"notification.error.message": "Bir hata oluştu.",
"notification.info.contentType.creating.notSaved":
"Lütfen yeni bir tane oluşturmadan önce mevcut İçerik Türünüzü kaydedin",
"notification.info.disable": "This field is not editable for the moment...😮",
"notification.info.optimized": "Bu eklenti, localStorage ile optimize edilmiştir",
"notification.success.message.contentType.edit": "İçerik Türünüz güncellendi",
"notification.success.message.contentType.create": "İçerik Türünüz oluşturuldu",
"notification.success.contentTypeDeleted": "İçerik Türü silindi",
"notification.info.enumeration": "This field is not editable for the moment...😮",
"popUpForm.attributes.string.description": "Başlıklar, adlar, paragraflar, isim listesi",
"popUpForm.attributes.text.description": "Tanımlar, metin paragrafları, makaleler ",
@ -130,7 +135,8 @@
"popUpForm.attributes.relation.description": "Bir İçerik Türünü Belirtiyor",
"popUpForm.attributes.email.description": "Kullanıcının e-postası...",
"popUpForm.attributes.password.description": "Kullanıcı şifresi...",
"popUpForm.attributes.enumeration.description": "List of choices",
"popUpForm.attributes.string.name": "String",
"popUpForm.attributes.text.name": "Yazı",
"popUpForm.attributes.boolean.name": "Mantıksal",
@ -141,6 +147,7 @@
"popUpForm.attributes.relation.name": "İlişki",
"popUpForm.attributes.email.name": "E-posta",
"popUpForm.attributes.password.name": "Parola",
"popUpForm.attributes.enumeration.name": "Enumeration",
"popUpForm.create": "Yeni ekle",
"popUpForm.edit": "Düzenle",
"popUpForm.field": "Alan",

View File

@ -52,6 +52,11 @@
"form.attribute.item.json.name": "Name",
"form.attribute.item.boolean.name": "Name",
"form.attribute.item.string.name": "Name",
"form.attribute.item.enumeration.name": "Name",
"form.attribute.item.enumeration.rules": "Values (separate them with a comma)",
"form.attribute.item.enumeration.placeholder": "Ex: morning,noon,evening",
"form.attribute.item.enumeration.graphql": "Name override for GraphQL",
"form.attribute.item.enumeration.graphql.description": "Allows you to override the default generated name for GraphQL",
"form.attribute.item.appearance.name": "Appearance",
"form.attribute.item.appearance.label": "Display as a WYSIWYG",
"form.attribute.item.appearance.description":
@ -113,11 +118,11 @@
"notification.error.message": "发生错误",
"notification.info.contentType.creating.notSaved":
"在创建新Content Type之前请保存当前Content Type",
"notification.info.disable": "这个字段暂时不可编辑...😮",
"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": "描述、文本段落、文章",
@ -129,7 +134,8 @@
"popUpForm.attributes.relation.description": "引用其它 Content Type",
"popUpForm.attributes.email.description": "用户email...",
"popUpForm.attributes.password.description": "用户密码...",
"popUpForm.attributes.enumeration.description": "List of choices",
"popUpForm.attributes.string.name": "String",
"popUpForm.attributes.text.name": "Text",
"popUpForm.attributes.boolean.name": "Boolean",
@ -140,6 +146,7 @@
"popUpForm.attributes.relation.name": "Relation",
"popUpForm.attributes.email.name": "Email",
"popUpForm.attributes.password.name": "Password",
"popUpForm.attributes.enumeration.name": "Enumeration",
"popUpForm.create": "增加新的",
"popUpForm.edit": "编辑",
"popUpForm.field": "字段",

View File

@ -14,6 +14,7 @@
"attribute.email": "Email",
"attribute.password": "密碼",
"attribute.relation": "關聯其他結構",
"attribute.enumeration": "Enumeration",
"attribute.WYSIWYG": "Text (WYSIWYG)",
"contentType.temporaryDisplay": "(未儲存)",
@ -49,6 +50,11 @@
"form.attribute.item.json.name": "名稱",
"form.attribute.item.boolean.name": "名稱",
"form.attribute.item.string.name": "名稱",
"form.attribute.item.enumeration.name": "名稱",
"form.attribute.item.enumeration.rules": "Values (separate them with a comma)",
"form.attribute.item.enumeration.placeholder": "Ex: morning,noon,evening",
"form.attribute.item.enumeration.graphql": "Name override for GraphQL",
"form.attribute.item.enumeration.graphql.description": "Allows you to override the default generated name for GraphQL",
"form.attribute.item.settings.name": "設定",
"form.attribute.item.requiredField": "必填欄位",
"form.attribute.item.uniqueField": "唯一欄位",
@ -105,6 +111,7 @@
"notification.error.message": "有錯誤發生了",
"notification.info.contentType.creating.notSaved": "建立新的資料結構前,請先儲存現在的。",
"notification.info.disable": "这个字段暂时不可编辑...😮",
"notification.info.optimized": "這個擴充功能使用您的 localstorage 來最佳化",
"notification.success.message.contentType.edit": "您的資料結構已更新",
"notification.success.message.contentType.create": "您的資料結構已建立",
@ -120,7 +127,8 @@
"popUpForm.attributes.relation.description": "關聯到一個資料結構",
"popUpForm.attributes.email.description": "使用者的 email...",
"popUpForm.attributes.password.description": "使用者密碼...",
"popUpForm.attributes.enumeration.description": "List of choices",
"popUpForm.attributes.string.name": "字串",
"popUpForm.attributes.text.name": "文字",
"popUpForm.attributes.boolean.name": "是/否",
@ -131,6 +139,7 @@
"popUpForm.attributes.relation.name": "關聯其他結構",
"popUpForm.attributes.email.name": "Email",
"popUpForm.attributes.password.name": "密碼",
"popUpForm.attributes.enumeration.name": "Enumeration",
"popUpForm.create": "增加新的",
"popUpForm.edit": "編輯",
"popUpForm.field": "欄位",

View File

@ -86,7 +86,7 @@ module.exports = {
updateModel: async ctx => {
const { model } = ctx.params;
const { name, description, connection, collectionName, attributes = [], plugin } = ctx.request.body;
const { name, description, mainField, connection, collectionName, attributes = [], plugin } = ctx.request.body;
if (!name) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.name.missing' }] }]);
if (!_.includes(Service.getConnections(), connection)) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.connection.unknow' }] }]);
@ -122,6 +122,10 @@ module.exports = {
};
modelJSON.attributes = formatedAttributes;
if (mainField) {
modelJSON.info.mainField = mainField;
}
const clearRelationsErrors = Service.clearRelations(model, plugin);
if (!_.isEmpty(clearRelationsErrors)) {

View File

@ -105,6 +105,7 @@ module.exports = {
return {
name: _.get(model, 'info.name', 'model.name.missing'),
description: _.get(model, 'info.description', 'model.description.missing'),
mainField: _.get(model, 'info.mainField', ''),
connection: model.connection,
collectionName: model.collectionName,
attributes: attributes

View File

@ -144,12 +144,15 @@ module.exports = {
/**
* Convert Strapi type to GraphQL type.
*
* @param {Object} attribute Information about the attribute.
* @param {Object} attribute.definition Definition of the attribute.
* @param {String} attribute.modelName Name of the model which owns the attribute.
* @param {String} attribute.attributeName Name of the attribute.
* @return String
*/
convertType: (definition = {}) => {
// Type.
convertType: function ({ definition = {}, modelName = '', attributeName = '' }) {
// Type
if (definition.type) {
let type = 'String';
@ -167,6 +170,9 @@ module.exports = {
case 'float':
type = 'Float';
break;
case 'enumeration':
type = this.convertEnumType(definition, modelName, attributeName);
break;
}
if (definition.required) {
@ -178,9 +184,9 @@ module.exports = {
const ref = definition.model || definition.collection;
// Association.
// Association
if (ref && ref !== '*') {
// Add bracket or not.
// Add bracket or not
const globalId = definition.plugin ?
strapi.plugins[definition.plugin].models[ref].globalId:
strapi.models[ref].globalId;
@ -196,6 +202,16 @@ module.exports = {
return definition.model ? 'Morph' : '[Morph]';
},
/**
* Convert Strapi enumeration to GraphQL Enum.
* @param {Object} definition Definition of the attribute.
* @param {String} model Name of the model which owns the attribute.
* @param {String} field Name of the attribute.
* @return String
*/
convertEnumType: (definition, model, field) => definition.enumName ? definition.enumName : `ENUM_${model.toUpperCase()}_${field.toUpperCase()}`,
/**
* Execute policies before the specified resolver.
*
@ -459,11 +475,26 @@ module.exports = {
.filter(attribute => model.attributes[attribute].private !== true)
.reduce((acc, attribute) => {
// Convert our type to the GraphQL type.
acc[attribute] = this.convertType(model.attributes[attribute]);
acc[attribute] = this.convertType({
definition: model.attributes[attribute],
modelName: globalId,
attributeName: attribute,
});
return acc;
}, initialState);
// Detect enum and generate it for the schema definition
const enums = Object.keys(model.attributes)
.filter(definition => definition.type === 'enumeration')
.map((attribute) => {
const definition = model.attributes[attribute];
return `enum ${this.convertEnumType(definition, globalId, attribute)} { ${definition.enum.join(' \n ')} }`;
}).join(' ');
acc.definition += enums;
// Add parameters to optimize association query.
(model.associations || [])
.filter(association => association.type === 'collection')

View File

@ -3,7 +3,8 @@ module.exports = {
actions: {
create: 'User.create', // Use the User plugin's controller.
update: 'User.update',
destroy: 'User.destroy'
destroy: 'User.destroy',
deleteall: 'User.destroyAll',
},
attributes: {
username: {

View File

@ -70,12 +70,14 @@ module.exports = {
*/
create: async (ctx) => {
if ((await strapi.store({
const advanced = await strapi.store({
environment: '',
type: 'plugin',
name: 'users-permissions',
key: 'advanced'
}).get()).unique_email && ctx.request.body.email) {
}).get();
if (advanced.unique_email && ctx.request.body.email) {
const user = await strapi.query('user', 'users-permissions').findOne({ email: ctx.request.body.email });
if (user) {
@ -83,6 +85,12 @@ module.exports = {
}
}
if (!ctx.request.body.role) {
const defaultRole = await strapi.query('role', 'users-permissions').findOne({ type: advanced.default_role }, []);
ctx.request.body.role = defaultRole._id || defaultRole.id;
}
try {
const data = await strapi.plugins['users-permissions'].services.user.add(ctx.request.body);
// Send 201 `created`
@ -153,6 +161,13 @@ module.exports = {
destroy: async (ctx) => {
const data = await strapi.plugins['users-permissions'].services.user.remove(ctx.params);
// Send 200 `ok`
ctx.send(data);
},
destroyAll: async (ctx) => {
const data = await strapi.plugins['users-permissions'].services.user.removeAll(ctx.params, ctx.request.query);
// Send 200 `ok`
ctx.send(data);
}

View File

@ -40,4 +40,4 @@
"configurable": false
}
}
}
}

View File

@ -70,13 +70,13 @@ exports.connect = (provider, query) => {
return resolve([null, [{ messages: [{ id: 'Auth.form.error.email.taken' }] }], 'Email is already taken.']);
}
// Retrieve role `public`.
const publicRole = await strapi.query('role', 'users-permissions').findOne({ type: 'public' }, []);
// Retrieve default role.
const defaultRole = await strapi.query('role', 'users-permissions').findOne({ type: advanced.default_role }, []);
// Create the new user.
const params = _.assign(profile, {
provider: provider,
role: publicRole._id || publicRole.id
role: defaultRole._id || defaultRole.id
});
const createdUser = await strapi.query('user', 'users-permissions').create(params);

View File

@ -115,6 +115,30 @@ module.exports = {
return strapi.query('user', 'users-permissions').delete(params);
},
removeAll: async (params, query) => {
// Use Content Manager business logic to handle relation.
if (strapi.plugins['content-manager']) {
params.model = 'user';
query.source = 'users-permissions';
return await strapi.plugins['content-manager'].services['contentmanager'].deleteMany(params, query);
}
// TODO remove this logic when we develop plugins' dependencies
const primaryKey = strapi.query('user', 'users-permissions').primaryKey;
const toRemove = Object.keys(query).reduce((acc, curr) => {
if (curr !== 'source') {
return acc.concat([query[curr]]);
}
return acc;
}, []);
return strapi.query('user', 'users-permissions').deleteMany({
[primaryKey]: toRemove,
});
},
validatePassword: (password, hash) => {
return bcrypt.compareSync(password, hash);
}

View File

@ -474,7 +474,7 @@ module.exports = {
let type;
if (_.includes(['ne', 'lt', 'gt', 'lte', 'gte', 'contains', 'containss'], _.last(suffix))) {
if (_.includes(['ne', 'lt', 'gt', 'lte', 'gte', 'contains', 'containss', 'in'], _.last(suffix))) {
type = `_${_.last(suffix)}`;
key = _.dropRight(suffix).join('_');
} else {

View File

@ -125,5 +125,43 @@
"name":"reference",
"description":"",
"collectionName":""
},
"product": {
"attributes":[
{
"name":"name",
"params":{
"appearance":{
"WYSIWYG":false
},
"multiple":false,
"type":"string"
}
},
{
"name":"description",
"params":{
"appearance":{
"WYSIWYG":true
},
"multiple":false,
"type":"text"
}
},
{
"name":"published",
"params":{
"appearance":{
"WYSIWYG":false
},
"multiple":false,
"type":"boolean"
}
}
],
"connection":"default",
"name":"product",
"description":"",
"collectionName":""
}
}

View File

@ -736,6 +736,84 @@ describe('Test oneToOne relation (article - reference) with Content Manager', ()
);
});
describe('Test route /count from generated API', () => {
beforeAll(() => {
data = {
articles: [],
tags: []
};
});
beforeEach(async () => {
await restart(rq);
}, 60000);
test(
'Create new product API',
async () => {
await rq({
url: `/content-type-builder/models`,
method: 'POST',
body: form.product,
json: true
});
}
);
test(
'Create product1',
async () => {
let body = await rq({
url: `/content-manager/explorer/product/?source=content-manager`,
method: 'POST',
formData: {
name: 'product1'
}
});
body = JSON.parse(body);
data.tags.push(body);
expect(body.id);
expect(body.name).toBe('product1');
}
);
test(
'Create product2',
async () => {
let body = await rq({
url: `/content-manager/explorer/product/?source=content-manager`,
method: 'POST',
formData: {
name: 'product2'
}
});
body = JSON.parse(body);
data.tags.push(body);
expect(body.id);
expect(body.name).toBe('product2');
}
);
test(
'Count products',
async () => {
let body = await rq({
url: `/product/count`,
method: 'GET'
});
body = JSON.parse(body);
data.tags.push(body);
expect(body).toBe(2);
}
);
});
describe('Delete test APIs', () => {
beforeEach(async () => {
await restart(rq);