Merge branch 'master' into translation/fr

This commit is contained in:
Jim LAURIE 2018-08-06 09:27:28 +02:00 committed by GitHub
commit 1dbbea458d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 790 additions and 123 deletions

1
.gitignore vendored
View File

@ -78,6 +78,7 @@ $RECYCLE.BIN/
*#
.idea
nbproject
.vscode/
############################

View File

@ -59,7 +59,8 @@ $.ajax({
Thanks to [Grant](https://github.com/simov/grant) and [Purest](https://github.com/simov/purest), you can easily use OAuth and OAuth2
providers to enable authentication in your application. By default,
Strapi comes with four providers:
Strapi comes with the following providers:
- [Discord](https://github.com/strapi/strapi-examples/blob/master/login-react/doc/discord_setup.md)
- [Facebook](https://github.com/strapi/strapi-examples/blob/master/login-react/doc/fb_setup.md)
- [Google](https://github.com/strapi/strapi-examples/blob/master/login-react/doc/google_setup.md)
- [Github](https://github.com/strapi/strapi-examples/blob/master/login-react/doc/github_setup.md)

View File

@ -11,7 +11,7 @@ There are several ways to create a policy.
**Path —** `./config/policies/isAuthenticated.js`.
```js
module.exports = async (ctx, next) => {
if (ctx.session.isAuthenticated === true) {
if (ctx.state.user) {
// Go to next policy or will reach the controller's action.
return await next();
}
@ -87,7 +87,7 @@ The scoped policies can only be associated to the routes defined in the API wher
**Path —** `./api/car/config/policies/isAdmin.js`.
```js
module.exports = async (ctx, next) => {
if (ctx.session.user.role === 'administrator') {
if (ctx.state.user.role.name === 'Administrator') {
// Go to next policy or will reach the controller's action.
return await next();
}

View File

@ -149,6 +149,8 @@
"Users & Permissions": "Usuários & Permissões",
"Content Manager": "Gestão de conteúdo",
"Content Type Builder": "Construtor de Conteúdo",
"Files Upload": "Enviar arquivos",
"Roles & Permissions": "Papéis e permissões",
"Settings Manager": "Gerenciador de configurações",
"Email": "E-mail",
"Password": "Senha",

View File

@ -1,7 +1,7 @@
'use strict';
const path = require('path');
const exec = require('child_process').execSync;
const exec = require('child_process').spawnSync;
const _ = require('lodash');
/**
@ -53,8 +53,7 @@ module.exports = {
strapi.reload.isWatching = false;
strapi.log.info(`Installing ${plugin}...`);
exec(`node "${strapiBin}" install ${plugin} ${port === '4000' ? '--dev' : ''}`);
exec('node', [strapiBin, 'install', plugin, (port === '4000') ? '--dev' : '']);
ctx.send({ ok: true });
@ -87,7 +86,7 @@ module.exports = {
strapi.reload.isWatching = false;
strapi.log.info(`Uninstalling ${plugin}...`);
exec(`node "${strapiBin}" uninstall ${plugin}`);
exec('node', [strapiBin, 'uninstall', plugin]);
ctx.send({ ok: true });

View File

@ -51,4 +51,4 @@
"npm": ">= 5.0.0"
},
"license": "MIT"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "strapi-email-amazon-ses",
"version": "3.0.0-alpha.13",
"version": "3.0.0-alpha.13.0.1",
"description": "Amazon SES provider for strapi email",
"homepage": "http://strapi.io",
"keywords": [
@ -42,4 +42,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}

View File

@ -42,4 +42,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}

View File

@ -42,4 +42,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}

View File

@ -41,4 +41,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}

View File

@ -42,4 +42,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}

View File

@ -43,4 +43,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}

View File

@ -115,4 +115,4 @@
"webpack-hot-middleware": "^2.18.2",
"whatwg-fetch": "^2.0.3"
}
}
}

View File

@ -43,4 +43,4 @@
"npm": ">= 5.0.0"
},
"license": "MIT"
}
}

View File

@ -45,4 +45,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}

View File

@ -44,4 +44,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}

View File

@ -41,4 +41,4 @@
"babel-eslint": "^8.2.3",
"prettier": "^1.12.1"
}
}
}

View File

@ -43,4 +43,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="8px" height="8px" viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
<title>Icon grab</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Pages" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Content-Manager---Settings-view---Single" transform="translate(-335.000000, -698.000000)" fill="#B3B5B9">
<g id="Container" transform="translate(261.000000, 84.000000)">
<g id="Forms" transform="translate(3.000000, 77.000000)">
<g id="Settings">
<g id="Attributes" transform="translate(4.000000, 456.000000)">
<g id="Order-attributes" transform="translate(0.000000, 23.000000)">
<g id="Link" transform="translate(0.000000, 19.000000)">
<g id="Icon-grab" transform="translate(67.000000, 39.000000)">
<rect id="Rectangle-4" x="0" y="0" width="2" height="2"></rect>
<rect id="Rectangle-4" x="3" y="0" width="2" height="2"></rect>
<rect id="Rectangle-4" x="6" y="0" width="2" height="2"></rect>
<rect id="Rectangle-4" x="0" y="3" width="2" height="2"></rect>
<rect id="Rectangle-4" x="3" y="3" width="2" height="2"></rect>
<rect id="Rectangle-4" x="6" y="3" width="2" height="2"></rect>
<rect id="Rectangle-4" x="0" y="6" width="2" height="2"></rect>
<rect id="Rectangle-4" x="3" y="6" width="2" height="2"></rect>
<rect id="Rectangle-4" x="6" y="6" width="2" height="2"></rect>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
<title>Icon remove</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Pages" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Content-Manager---List-view" transform="translate(-279.000000, -165.000000)">
<g id="Container" transform="translate(234.000000, 0.000000)">
<g id="Add-filters" transform="translate(0.000000, 60.000000)">
<g id="Icon-remove" transform="translate(45.000000, 105.000000)">
<rect id="Rectangle-12" stroke="#E3E9F3" x="0.5" y="0.5" width="19" height="19" rx="9.5"></rect>
<path d="M6,10 L14,10" id="Line-4" stroke="#007EFF" stroke-width="2" stroke-linecap="round"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -5,7 +5,6 @@
*/
import React from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import { get, map } from 'lodash';
@ -26,9 +25,6 @@ const filterRelationsUpload = (data) => Object.keys(data).reduce((acc, current)
function EditRelations(props) {
return (
<div className={styles.editFormRelations}>
<FormattedMessage id="content-manager.EditRelations.title">
{(message) => <h3>{message}</h3>}
</FormattedMessage>
{map(filterRelationsUpload(props.schema.relations), (relation, key) => {
if (relation.nature.toLowerCase().includes('morph') && relation[key]) return '';
@ -43,6 +39,7 @@ function EditRelations(props) {
schema={props.schema}
setRecordAttribute={props.changeData}
location={props.location}
onRedirect={props.onRedirect}
/>
);
})}
@ -59,6 +56,7 @@ EditRelations.propTypes = {
changeData: PropTypes.func.isRequired,
currentModelName: PropTypes.string.isRequired,
location: PropTypes.object.isRequired,
onRedirect: PropTypes.func.isRequired,
record: PropTypes.object,
schema: PropTypes.object,
};

View File

@ -1,13 +1,3 @@
.editFormRelations { /* stylelint-disable */
h3{
height: 41px;
margin: 0 -25px 14px;
padding: 0 25px;
background: #F3F3F3;
line-height: 41px;
border-radius: 2px 2px 0 0;
letter-spacing: 0.03rem;
font-size: 1.3rem;
font-weight: bold;
}
padding-top: 19px;
}

View File

@ -6,32 +6,87 @@
import React from 'react';
import Select from 'react-select';
import { FormattedMessage } from 'react-intl';
import { SortableContainer, SortableElement, arrayMove } from 'react-sortable-hoc';
import PropTypes from 'prop-types';
import 'react-select/dist/react-select.css';
import { cloneDeep, isArray, isNull, isUndefined, get, findIndex, includes } from 'lodash';
import cn from 'classnames';
import { cloneDeep, isArray, isNull, isUndefined, get, findIndex, includes, isEmpty } from 'lodash';
// Utils.
import request from 'utils/request';
import templateObject from 'utils/templateObject';
// CSS.
import 'react-select/dist/react-select.css';
// Icons.
import IconRemove from '../../assets/images/icon_remove.svg';
import styles from './styles.scss';
class SelectMany extends React.Component {
// eslint-disable-line react/prefer-stateless-function
constructor(props) {
super(props);
const SortableItem = SortableElement(({idx, onRemove, item, onClick}) => {
return (
<li className={styles.sortableListItem}>
<div>
<div className={styles.dragHandle}><span></span></div>
<FormattedMessage id='content-manager.containers.Edit.clickToJump'>
{title => (
<span
className='sortable-item--value'
onClick={() => onClick(item)}
title={title}
>
{item.label}
</span>
)}
</FormattedMessage>
</div>
<div className={styles.sortableListItemActions}>
<img src={IconRemove} alt="Remove Icon" onClick={() => onRemove(idx)} />
</div>
</li>
);
});
this.state = {
isLoading: true,
options: [],
toSkip: 0,
};
}
const SortableList = SortableContainer(({items, onRemove, onClick}) => {
const shadowList = (items.length > 4 ? <div className={styles.sortableListLong}></div> : '');
return (
<div className={cn(styles.sortableList)}>
<ul>
{items.map((item, index) => (
<SortableItem key={`item-${index}`} index={index} idx={index} item={item} onRemove={onRemove} onClick={onClick} />
))}
</ul>
{shadowList}
</div>
);
});
class SelectMany extends React.PureComponent {
state = {
isLoading: true,
options: [],
toSkip: 0,
};
componentDidMount() {
this.getOptions('');
}
componentDidUpdate(prevProps, prevState) {
if (isEmpty(prevProps.record) && !isEmpty(this.props.record)) {
const values = (get(this.props.record, this.props.relation.alias) || [])
.map(el => (el.id || el._id));
const options = this.state.options.filter(el => {
return !values.includes(el.value.id || el.value._id);
});
this.state.options = options;
}
if (prevState.toSkip !== this.state.toSkip) {
this.getOptions('');
}
@ -92,15 +147,23 @@ class SelectMany extends React.Component {
};
handleChange = value => {
const filteredValue = value.filter(
(data, index) => findIndex(value, o => o.value.id === data.value.id) === index
);
const values = get(this.props.record, this.props.relation.alias) || [];
const target = {
name: `record.${this.props.relation.alias}`,
type: 'select',
value: filteredValue,
value: [...values, value.value],
};
// Remove new added value from available option;
this.state.options = this.state.options.filter(el => {
if (el.value._id || el.value.id === value.value.id || value.value._id) {
return false;
}
return true;
});
this.props.setRecordAttribute({ target });
};
@ -121,6 +184,44 @@ class SelectMany extends React.Component {
}
}
handleSortEnd = ({oldIndex, newIndex}) => {
const values = get(this.props.record, this.props.relation.alias);
const target = {
name: `record.${this.props.relation.alias}`,
type: 'select',
value: arrayMove(values, oldIndex, newIndex),
};
this.props.setRecordAttribute({ target });
};
handleRemove = (index) => {
const values = get(this.props.record, this.props.relation.alias);
const target = {
name: `record.${this.props.relation.alias}`,
type: 'select',
value: values.filter( (item, idx) => idx !== index),
};
// Add removed value from available option;
this.state.options.push({
value: values[index],
label: templateObject({ mainField: this.props.relation.displayedAttribute }, values[index])
.mainField,
});
this.props.setRecordAttribute({ target });
}
// Redirect to the edit page
handleClick = (item = {}) => {
this.props.onRedirect({
model: this.props.relation.collection || this.props.relation.model,
id: item.value.id || item.value._id,
source: this.props.relation.plugin,
});
}
render() {
const description = this.props.relation.description ? (
<p>{this.props.relation.description}</p>
@ -128,11 +229,12 @@ class SelectMany extends React.Component {
''
);
const value = get(this.props.record, this.props.relation.alias);
const value = get(this.props.record, this.props.relation.alias) || [];
/* eslint-disable jsx-a11y/label-has-for */
return (
<div className={`form-group ${styles.selectMany}`}>
<label htmlFor={this.props.relation.alias}>{this.props.relation.alias}</label>
<div className={`form-group ${styles.selectMany} ${value.length > 4 && styles.selectManyUpdate}`}>
<label htmlFor={this.props.relation.alias}>{this.props.relation.alias} <span>({value.length})</span></label>
{description}
<Select
onChange={this.handleChange}
@ -140,10 +242,10 @@ class SelectMany extends React.Component {
id={this.props.relation.alias}
isLoading={this.state.isLoading}
onMenuScrollToBottom={this.handleBottomScroll}
onInputChange={this.handleInputChange}
onSelectResetsInput={false}
multi
value={
placeholder={<FormattedMessage id='content-manager.containers.Edit.addAnItem' />}
/>
<SortableList
items={
isNull(value) || isUndefined(value) || value.size === 0
? null
: value.map(item => {
@ -159,6 +261,10 @@ class SelectMany extends React.Component {
}
})
}
onSortEnd={this.handleSortEnd}
onRemove={this.handleRemove}
distance={1}
onClick={this.handleClick}
/>
</div>
);
@ -167,6 +273,7 @@ class SelectMany extends React.Component {
}
SelectMany.propTypes = {
onRedirect: PropTypes.func.isRequired,
record: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired,
relation: PropTypes.object.isRequired,
setRecordAttribute: PropTypes.func.isRequired,

View File

@ -1,13 +1,22 @@
.selectMany { /* stylelint-disable */
padding-bottom: 19px;
margin-bottom: 0px;
overflow-x: hidden;
label{
font-size: 1.3rem;
font-weight: 500;
text-transform: capitalize;
margin-top: 3px;
> span {
font-weight: 400;
font-size: 1.2rem;
}
}
label + div{
margin: 5px 0 26px;
margin: 3px 0 26px;
&:focus{
outline: none;
@ -25,3 +34,145 @@
}
}
}
.selectManyUpdate{
padding-bottom: 18px !important;
}
.sortableList {
max-height: 110px;
> ul {
margin: -21px -20px 0;
padding: 0 20px !important;
list-style: none !important;
overflow: scroll;
max-height: 110px;
}
}
.sortableListLong {
position: relative;
display: inline-block;
width: 100%;
height: 0px;
&:after {
position: absolute;
top: -15px;
left: -5px;
content: '';
display: inline-block;
width: calc(100% + 10px);
height: 1px;
margin-bottom: -25px;
box-shadow: 0px -2px 4px 0px rgba(227, 233, 243, .5);
}
}
.sortableListItem {
display: flex;
flex-wrap: nowrap;
align-content: center;
justify-content: space-between;
height: 27px;
&:hover{
cursor: pointer;
}
&:active{
.dragHandle{
cursor: pointer;
> span {
background: #AED4FB;
}
}
}
.dragHandle{
outline: none;
text-decoration: none;
margin-top: -1px;
> span {
vertical-align: middle;
position: relative;
display: inline-block;
width: 6px;
height: 1px;
padding: 0px !important;
background: #B3B5B9;
overflow: visible !important;
transition: background .25s ease-out;
&:before, &:after{
content: '';
display: inline-block;
width: 6px;
height: 1px;
background: inherit;
}
&:before{
position: absolute;
top: -2px;
left: 0;
}
&:after{
position: absolute;
bottom: -2px;
left: 0;
}
}
}
> div {
span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&:first-of-type{
display: flex;
align-items: center;
transition: color .25s ease-out;
&:hover{
.dragHandle{
> span {
background: #007EFF;
}
}
color: #007EFF;
}
span {
&:last-of-type{
padding-left: 10px;
}
}
}
&:last-of-type{
display: inline-block;
height: 100%;
line-height: 27px;
text-align: right;
padding-right: 0px;
img{
display: inline-block;
height: 14px;
}
}
}
}

View File

@ -6,6 +6,7 @@
import React from 'react';
import Select from 'react-select';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import 'react-select/dist/react-select.css';
import { cloneDeep, map, includes, isArray, isNull, isUndefined, isFunction, get, findIndex } from 'lodash';
@ -106,6 +107,15 @@ class SelectOne extends React.Component { // eslint-disable-line react/prefer-st
});
}
// Redirect to the edit page
handleClick = (item = {}) => {
this.props.onRedirect({
model: this.props.relation.collection || this.props.relation.model,
id: item.value.id || item.value._id,
source: this.props.relation.plugin,
});
}
handleInputChange = (value) => {
const clonedOptions = this.state.options;
const filteredValues = clonedOptions.filter(data => includes(data.label, value));
@ -121,11 +131,25 @@ class SelectOne extends React.Component { // eslint-disable-line react/prefer-st
: '';
const value = get(this.props.record, this.props.relation.alias);
const excludeModel = ['role', 'permission', 'file'].includes(this.props.relation.model || this.props.relation.collection); // Temporary.
const entryLink = (isNull(value) || isUndefined(value) || excludeModel ?
'' :
(
<FormattedMessage id='content-manager.containers.Edit.clickToJump'>
{title => (
<a onClick={() => this.handleClick({value})} title={title}><FormattedMessage id='content-manager.containers.Edit.seeDetails' /></a>
)}
</FormattedMessage>
)
);
/* eslint-disable jsx-a11y/label-has-for */
return (
<div className={`form-group ${styles.selectOne}`}>
<label htmlFor={this.props.relation.alias}>{this.props.relation.alias}</label>
<nav className={styles.headline}>
<label htmlFor={this.props.relation.alias}>{this.props.relation.alias}</label>
{entryLink}
</nav>
{description}
<Select
onChange={this.handleChange}
@ -140,6 +164,7 @@ class SelectOne extends React.Component { // eslint-disable-line react/prefer-st
label: templateObject({ mainField: this.props.relation.displayedAttribute }, isFunction(value.toJS) ? value.toJS() : value).mainField || (isFunction(value.toJS) ? get(value.toJS(), 'id') : get(value, 'id')),
}}
/>
</div>
);
/* eslint-disable jsx-a11y/label-has-for */
@ -147,6 +172,7 @@ class SelectOne extends React.Component { // eslint-disable-line react/prefer-st
}
SelectOne.propTypes = {
onRedirect: PropTypes.func.isRequired,
record: PropTypes.oneOfType([
PropTypes.object,
PropTypes.bool,

View File

@ -1,4 +1,6 @@
.selectOne { /* stylelint-disable */
position: relative;
label{
font-size: 1.3rem;
font-weight: 500;
@ -6,7 +8,7 @@
margin-top: 3px;
}
label + div{
nav + div{
height: 34px;
margin: 3px 0 26px;
@ -28,3 +30,19 @@
}
}
}
.headline{
display: flex;
justify-content: space-between;
a{
color: #007EFF !important;
font-size: 1.3rem;
padding-top: 3px;
&:hover{
text-decoration: underline !important;
cursor: pointer;
}
}
}

View File

@ -72,7 +72,10 @@ export function initModelProps(modelName, isCreating, source, attributes) {
const record = Object.keys(attributes).reduce((acc, current) => {
if (attributes[current].default) {
acc[current] = attributes[current].default;
} else if (attributes[current].type === 'json') {
acc[current] = {};
}
return acc;
}, {});

View File

@ -33,6 +33,7 @@ import injectSaga from 'utils/injectSaga';
import getQueryParameters from 'utils/getQueryParameters';
import { bindLayout } from 'utils/bindLayout';
import inputValidations from 'utils/inputsValidations';
import { generateRedirectURI } from 'containers/ListPage/utils';
import { checkFormValidity } from 'utils/formValidations';
@ -57,35 +58,15 @@ export class EditPage extends React.Component {
state = { showWarning: false };
componentDidMount() {
this.props.initModelProps(this.getModelName(), this.isCreating(), this.getSource(), this.getModelAttributes());
if (!this.isCreating()) {
const mainField = get(this.getModel(), 'info.mainField') || this.getModel().primaryKey;
this.props.getData(this.props.match.params.id, this.getSource(), mainField);
} else {
this.props.getLayout(this.getSource());
}
// Get all relations made with the upload plugin
const fileRelations = Object.keys(get(this.getSchema(), 'relations', {})).reduce((acc, current) => {
const association = get(this.getSchema(), ['relations', current], {});
if (association.plugin === 'upload' && association[association.type] === 'file') {
const relation = {
name: current,
multiple: association.nature === 'manyToManyMorph',
};
acc.push(relation);
}
return acc;
}, []);
// Update the reducer so we can use it to create the appropriate FormData in the saga
this.props.setFileRelations(fileRelations);
this.initComponent(this.props);
}
componentDidUpdate(prevProps) {
if (prevProps.location.pathname !== this.props.location.pathname) {
this.props.resetProps();
this.initComponent(this.props);
}
if (prevProps.editPage.submitSuccess !== this.props.editPage.submitSuccess) {
if (!isEmpty(this.props.location.search) && includes(this.props.location.search, '?redirectUrl')) {
const redirectUrl = this.props.location.search.split('?redirectUrl=')[1];
@ -168,6 +149,38 @@ export class EditPage extends React.Component {
*/
getSource = () => getQueryParameters(this.props.location.search, 'source');
/**
* Initialize component
*/
initComponent = (props) => {
this.props.initModelProps(this.getModelName(), this.isCreating(), this.getSource(), this.getModelAttributes());
if (!this.isCreating()) {
const mainField = get(this.getModel(), 'info.mainField') || this.getModel().primaryKey;
this.props.getData(props.match.params.id, this.getSource(), mainField);
} else {
this.props.getLayout(this.getSource());
}
// Get all relations made with the upload plugin
const fileRelations = Object.keys(get(this.getSchema(), 'relations', {})).reduce((acc, current) => {
const association = get(this.getSchema(), ['relations', current], {});
if (association.plugin === 'upload' && association[association.type] === 'file') {
const relation = {
name: current,
multiple: association.nature === 'manyToManyMorph',
};
acc.push(relation);
}
return acc;
}, []);
// Update the reducer so we can use it to create the appropriate FormData in the saga
this.props.setFileRelations(fileRelations);
}
handleBlur = ({ target }) => {
const defaultValue = get(this.getModelAttribute(target.name), 'default');
@ -212,6 +225,27 @@ export class EditPage extends React.Component {
this.props.changeData({ target });
}
handleRedirect = ({ model, id, source = 'content-manager'}) => {
/* eslint-disable */
switch (model) {
case 'permission':
case 'role':
case 'file':
// Exclude special models which are handled by plugins.
if (source !== 'content-manager') {
break;
}
default:
const pathname = `${this.props.match.path.replace(':slug', model).replace(':id', id)}`;
this.props.history.push({
pathname,
search: `?source=${source}&redirectURI=${generateRedirectURI({ model, search: `?source=${source}` })}`,
});
}
/* eslint-enable */
}
handleSubmit = (e) => {
e.preventDefault();
const formErrors = checkFormValidity(this.generateFormFromRecord(), this.props.editPage.formValidations);
@ -330,6 +364,7 @@ export class EditPage extends React.Component {
changeData={this.props.changeData}
record={editPage.record}
schema={this.getSchema()}
onRedirect={this.handleRedirect}
/>
)}
</div>

View File

@ -87,9 +87,10 @@ export function* submit() {
// Show button loader
yield put(setLoader());
const recordCleaned = Object.keys(record).reduce((acc, current) => {
const attrType = source !== 'content-manager' ? get(schema, ['plugins', source, currentModelName, 'fields', current, 'type'], null) : get(schema, [currentModelName, 'fields', current, 'type'], null);
const attrType = source !== 'content-manager' ? get(schema, ['models', 'plugins', source, currentModelName, 'fields', current, 'type'], null) : get(schema, ['models', currentModelName, 'fields', current, 'type'], null);
const cleanedData = attrType === 'json' ? record[current] : cleanData(record[current], 'value', 'id');
if (isString(cleanedData) || isNumber(cleanedData)) {
acc.append(current, cleanedData);
} else if (findIndex(fileRelations, ['name', current]) !== -1) {
@ -114,6 +115,11 @@ export function* submit() {
return acc;
}, new FormData());
// Helper to visualize FormData
// for(var pair of recordCleaned.entries()) {
// console.log(pair[0]+ ', '+ pair[1]);
// }
const id = isCreating ? '' : record.id || record._id;
const params = { source };
// Change the request helper default headers so we can pass a FormData

View File

@ -62,6 +62,7 @@ import {
generateFiltersFromSearch,
generateSearchFromFilters,
generateSearchFromParams,
generateRedirectURI,
} from './utils';
import styles from './styles.scss';
@ -177,9 +178,7 @@ export class ListPage extends React.Component {
* Generate the redirect URI when editing an entry
* @type {String}
*/
generateRedirectURI = () => (
`?redirectUrl=/plugins/content-manager/${this.getCurrentModelName().toLowerCase()}${this.generateSearch()}`
);
generateRedirectURI = generateRedirectURI.bind(this);
generateSearch = () => {
const {

View File

@ -50,8 +50,17 @@ const generateSearchFromParams = params =>
return acc;
}, '');
/**
* Generate the redirect URI when editing an entry
* @type {String}
*/
const generateRedirectURI = function ({ model, search } = {}) {
return `?redirectUrl=/plugins/content-manager/${(model || this.getCurrentModelName()).toLowerCase()}${(search || this.generateSearch())}`;
};
export {
generateFiltersFromSearch,
generateSearchFromFilters,
generateSearchFromParams,
generateRedirectURI,
};

View File

@ -25,6 +25,7 @@
"containers.SettingsPage.Block.generalSettings.title" : "Allgemeines",
"containers.SettingsPage.Block.contentType.title": "Inhaltstypen",
"containers.SettingsPage.Block.contentType.description": "Konfiguriere die spezifischen Einstellungen",
"containers.SettingsPage.pluginHeaderDescription": "Konfigurieren Sie die Standardeinstellungen für alle Ihre Inhaltstypen.",
"components.AddFilterCTA.add": "Filter",
"components.AddFilterCTA.hide": "Filter",
@ -52,6 +53,7 @@
"components.TableEmpty.withSearch": "Es gibt keinen {contentType}, der der Suche entspricht ({search})...",
"form.Input.label": "Label",
"form.Input.label.inputDescription": "Dieser Wert überschreibt das im Kopf der Tabelle angezeigte Label.",
"form.Input.search": "Suche aktivieren",
"form.Input.search.field": "Suche in diesem Feld aktivieren",
"form.Input.filters": "Filter aktivieren",

View File

@ -9,6 +9,9 @@
"containers.Edit.delete": "Delete",
"containers.Edit.reset": "Reset",
"containers.Edit.returnList": "Return to list",
"containers.Edit.addAnItem": "Add an item...",
"containers.Edit.clickToJump": "Click to jump to the entry",
"containers.Edit.seeDetails": "Details",
"containers.List.addAnEntry": "Add New {entity}",
"containers.List.pluginHeaderDescription": "{label} entries found",
"containers.List.pluginHeaderDescription.singular": "{label} entry found",

View File

@ -9,6 +9,9 @@
"containers.Edit.delete": "Supprimer",
"containers.Edit.reset": "Annuler",
"containers.Edit.returnList": "Retourner à la liste",
"containers.Edit.addAnItem": "Ajouter un élément...",
"containers.Edit.clickToJump": "Cliquer pour voir l'entrée",
"containers.Edit.seeDetails": "Détails",
"containers.List.addAnEntry": "Ajouter {entity}",
"containers.List.pluginHeaderDescription": "{label} entrées trouvées",
"containers.List.pluginHeaderDescription.singular": "{label} entrée trouvée",

View File

@ -15,15 +15,31 @@
"components.LimitSelect.itemsPerPage": "항목 수 / 페이지",
"containers.List.errorFetchRecords": "에러",
"containers.SettingPage.addField": "새 필드 추가",
"containers.SettingPage.attributes": "속성",
"containers.SettingPage.attributes.description": "속성의 순서를 지정합니다",
"containers.SettingPage.listSettings.title": "목록 — 설정",
"containers.SettingPage.listSettings.description": "이 콘텐츠 유형의 옵션을 구성합니다.",
"containers.SettingPage.pluginHeaderDescription": "이 콘텐츠 유형에 특정한 설정을 구성합니다.",
"containers.SettingsPage.pluginHeaderDescription": "모든 콘텐츠 유형에 대한 기본 설정을 구성합니다.",
"containers.SettingsPage.Block.generalSettings.description": "콘텐츠 유형에 대한 기본 옵션을 구성합니다.",
"containers.SettingsPage.Block.generalSettings.title": "일반",
"containers.SettingsPage.Block.contentType.title": "콘텐츠 유형",
"containers.SettingsPage.Block.contentType.description": "특정 설정을 구성합니다.",
"components.AddFilterCTA.add": "필터",
"components.AddFilterCTA.hide": "필터",
"components.FilterOptions.button.apply": "적용",
"components.DraggableAttr.edit": "클릭하여 수정",
"components.FiltersPickWrapper.PluginHeader.actions.apply": "적용",
"components.FiltersPickWrapper.PluginHeader.actions.clearAll": "모두 재설정",
"components.FiltersPickWrapper.PluginHeader.description": "필터링 조건을 설정하세요.",
"components.FiltersPickWrapper.PluginHeader.title.filter": "필터",
"components.FiltersPickWrapper.hide": "숨김",
"components.FilterOptions.button.apply": "적용",
"components.FilterOptions.FILTER_TYPES.=": "같음",
"components.FilterOptions.FILTER_TYPES._ne": "같지 않음",
"components.FilterOptions.FILTER_TYPES._lt": "작음",
@ -44,11 +60,11 @@
"components.TableEmpty.withoutFilter": "{contentType} 목록이 없습니다.",
"components.TableEmpty.withSearch": "\"{search}\" 검색. {contentType} 목록이 없습니다.",
"EditRelations.title": "Relational 데이터",
"EditRelations.title": "관계 데이터",
"emptyAttributes.title": "아직 필드가 없습니다.",
"emptyAttributes.description": "첫번째 필드를 추가하세요.",
"emptyAttributes.button": "콘텐츠 타입 빌더",
"emptyAttributes.button": "콘텐츠 유형 빌더",
"error.schema.generation": "스키마를 생성하는 도중 에러가 발생했습니다.",
"error.records.count": "데이터 수를 가져오는 도중 에러가 발생했습니다.",
@ -64,14 +80,26 @@
"error.validation.min": "입력한 내용이 너무 작습니다.",
"error.validation.maxLength": "입력한 내용이 너무 깁니다.",
"error.validation.minLength": "입력한 내용이 너무 짧습니다.",
"error.contentTypeName.taken": "이미 사용중인 이름입니다..",
"error.attribute.taken": "이미 사용중인 이름입니다..",
"error.contentTypeName.taken": "이미 사용중인 이름입니다.",
"error.attribute.taken": "이미 사용중인 이름입니다.",
"error.attribute.key.taken": "이미 사용중인 키입니다.",
"error.attribute.sameKeyAndName": "같은 값을 사용할 수 없습니다.",
"error.validation.minSupMax": "이 보다 더 클 수 없습니다.",
"error.validation.json": "JSON 형식이 아닙니다.",
"form.Input.label.inputDescription": "이 값은 테이블 머리에 표시된 라벨을 덮어씌웁니다.",
"form.Input.label": "라벨",
"form.Input.search": "검색 활성화",
"form.Input.search.field": "이 필드에 검색 활성화",
"form.Input.filters": "필더 활성화",
"form.Input.sort.field": "이 필드에 정렬 활성화",
"form.Input.bulkActions": "대규모 액션 활성화",
"form.Input.pageEntries": "페이지 당 요소",
"form.Input.pageEntries.inputDescription": "참고 : 콘텐츠 유형 설정에서 이 값을 덮어씌울 수 있습니다.",
"form.Input.defaultSort": "기본 정렬 속성",
"notification.error.relationship.fetch": "데이터 관계를 가져오는 도중 에러가 발생했습니다.",
"notification.info.SettingPage.disableSort": "정렬이 활성화된 한 개의 속성이 필요합니다.",
"success.record.delete": "삭제",
"success.record.save": "저장",
@ -81,6 +109,8 @@
"popUpWarning.button.cancel": "취소",
"popUpWarning.button.confirm": "확인",
"popUpWarning.title": "확인",
"popUpWarning.bodyMessage.contentType.delete": "삭제 하시겠습니까?",
"popUpWarning.bodyMessage.contentType.delete.all": "모두 삭제 하시겠습니까?"
"popUpWarning.bodyMessage.contentType.delete": "삭제하시겠습니까?",
"popUpWarning.bodyMessage.contentType.delete.all": "모두 삭제하시겠습니까?",
"popUpWarning.warning.cancelAllSettings": "수정 사항을 취소하시겠습니까?",
"popUpWarning.warning.updateAllSettings": "모든 설정에 적용됩니다."
}

View File

@ -15,8 +15,24 @@
"components.LimitSelect.itemsPerPage": "Registros por página",
"containers.List.errorFetchRecords": "Erro",
"containers.SettingPage.addField": "Adicionar campo",
"containers.SettingPage.attributes": "Atributos",
"containers.SettingPage.attributes.description": "Define a ordem dos atributos",
"containers.SettingPage.listSettings.title": "Lista — Configurações",
"containers.SettingPage.listSettings.description": "Configure as opções para este Tipo de Conteúdo",
"containers.SettingPage.pluginHeaderDescription": "Defina as configurações específicas para este Tipo de Conteúdo",
"containers.SettingsPage.pluginHeaderDescription": "Configure as opções padrões para todos os seus Tipos de Conteúdo",
"containers.SettingsPage.Block.generalSettings.description": "Configure as opções padrões para seu Tipo de Conteúdo",
"containers.SettingsPage.Block.generalSettings.title": "Geral",
"containers.SettingsPage.Block.contentType.title": "Tipos de Conteúdo",
"containers.SettingsPage.Block.contentType.description": "Defina as configurações específicas",
"components.AddFilterCTA.add": "Filtros",
"components.AddFilterCTA.hide": "Filtros",
"components.DraggableAttr.edit": "Clique para editar",
"components.FilterOptions.button.apply": "Aplicar",
"components.FiltersPickWrapper.PluginHeader.actions.apply": "Aplicar",
"components.FiltersPickWrapper.PluginHeader.actions.clearAll": "Limpar tudo",
@ -71,7 +87,19 @@
"error.validation.minSupMax": "Não pode ser superior",
"error.validation.json": "Isto não corresponde com o formato JSON",
"form.Input.label.inputDescription": "Este valor substitui o rótulo apresentado no cabeçalho da tabela",
"form.Input.label": "Rótulo",
"form.Input.search": "Habilitar busca",
"form.Input.search.field": "Habilitar busca neste campo",
"form.Input.filters": "Habilitar filtros",
"form.Input.sort.field": "Habilitar ordenação neste campo",
"form.Input.bulkActions": "Habilitar ações em lote",
"form.Input.pageEntries": "Entradas por página",
"form.Input.pageEntries.inputDescription": "Nota: Você pode substituir este valor na página de configurações do Tipo de Conteúdo.",
"form.Input.defaultSort": "Atributo de ordenação padrão",
"notification.error.relationship.fetch": "Ocorreu um erro durante a busca da relação.",
"notification.info.SettingPage.disableSort": "Você precisa de um atributo com permissão de ordenação",
"success.record.delete": "Removido",
"success.record.save": "Salvo",
@ -82,5 +110,7 @@
"popUpWarning.button.confirm": "Confirmar",
"popUpWarning.title": "Por favor, confirme",
"popUpWarning.bodyMessage.contentType.delete": "Tem a certeza de que deseja remover este registro?",
"popUpWarning.bodyMessage.contentType.delete.all": "Tem a certeza de que deseja remover estes registros?"
"popUpWarning.bodyMessage.contentType.delete.all": "Tem a certeza de que deseja remover estes registros?",
"popUpWarning.warning.cancelAllSettings": "Você tem certeza de que deseja cancelar suas modificações?",
"popUpWarning.warning.updateAllSettings": "Isto irá modificar todas as suas configurações"
}

View File

@ -27,6 +27,7 @@
"react-dnd": "^5.0.0",
"react-dnd-html5-backend": "^5.0.1",
"react-select": "^1.2.1",
"react-sortable-hoc": "^0.8.3",
"showdown": "^1.8.6",
"strapi-helper-plugin": "3.0.0-alpha.13.0.1"
},

View File

@ -49,4 +49,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}

View File

@ -3,7 +3,7 @@
const fs = require('fs');
const path = require('path');
const _ = require('lodash');
const exec = require('child_process').execSync;
const exec = require('child_process').spawnSync;
module.exports = {
menu: {
@ -901,12 +901,12 @@ module.exports = {
if (connector && !installedConnector) {
strapi.log.info(`Installing ${connector} dependency ...`);
exec(`npm install ${connector}@alpha`);
exec('npm', ['install', `${connector}@alpha`]);
}
if (client && !installedClient) {
strapi.log.info(`Installing ${client} dependency ...`);
exec(`npm install ${client}`);
exec('npm', ['install', client]);
}
},

View File

@ -42,7 +42,7 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
inputDescription={{ id: 'users-permissions.EditForm.inputSelect.description.role' }}
inputClassName={styles.inputStyle}
label={{ id: 'users-permissions.EditForm.inputSelect.label.role' }}
name="settings.default_role"
name="advanced.settings.default_role"
onChange={this.props.onChange}
selectOptions={this.generateSelectOptions()}
type="select"
@ -54,7 +54,7 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
<Input
label={{ id: 'users-permissions.EditForm.inputToggle.label.email' }}
inputDescription={{ id: 'users-permissions.EditForm.inputToggle.description.email' }}
name="settings.unique_email"
name="advanced.settings.unique_email"
onChange={this.props.onChange}
type="toggle"
value={get(this.props.values.settings, 'unique_email')}
@ -91,7 +91,7 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
<Input
label={{ id: 'users-permissions.EditForm.inputToggle.label.sign-up' }}
inputDescription={{ id: 'users-permissions.EditForm.inputToggle.description.sign-up' }}
name="settings.allow_register"
name="advanced.settings.allow_register"
onChange={this.props.onChange}
type="toggle"
value={get(this.props.values.settings, 'allow_register')}

View File

@ -43,6 +43,8 @@ class PopUpForm extends React.Component { // eslint-disable-line react/prefer-st
getRedirectURIProviderConf = () => { // NOTE: Still testings providers so the switch statement is likely to change
switch (this.props.dataToEdit) {
case 'discord':
return `${strapi.backendURL}/connect/discord/callback`;
case 'facebook':
return `${strapi.backendURL}/connect/facebook/callback`;
case 'google':

View File

@ -1,5 +1,5 @@
import { LOCATION_CHANGE } from 'react-router-redux';
import { findIndex } from 'lodash';
import { findIndex, get } from 'lodash';
import { takeLatest, put, fork, take, cancel, select, call } from 'redux-saga/effects';
import request from 'utils/request';
@ -64,7 +64,7 @@ export function* dataFetch(action) {
export function* submitData(action) {
try {
const body = yield select(makeSelectModifiedData());
const opts = { method: 'PUT', body: (action.endPoint === 'advanced') ? body.settings : body };
const opts = { method: 'PUT', body: (action.endPoint === 'advanced') ? get(body, ['advanced', 'settings'], {}) : body };
yield call(request, `/users-permissions/${action.endPoint}`, opts);
yield put(submitSucceeded());

View File

@ -163,6 +163,7 @@
"PopUpForm.Providers.secret.placeholder": "TEXT",
"PopUpForm.Providers.redirectURL.front-end.label": "The redirect URL to your front-end app",
"PopUpForm.Providers.discord.providerConfig.redirectURL": "The redirect URL to add in your Discord application configurations",
"PopUpForm.Providers.facebook.providerConfig.redirectURL": "The redirect URL to add in your Facebook application configurations",
"PopUpForm.Providers.google.providerConfig.redirectURL": "The redirect URL to add in your Google application configurations",
"PopUpForm.Providers.github.providerConfig.redirectURL": "The redirect URL to add in your GitHub application configurations",

View File

@ -1 +1,144 @@
{}
{
"Auth.form.button.register-success" : "Invia di nuovo",
"Auth.form.button.forgot-password.success" : "Invia di nuovo",
"Auth.form.button.forgot-password" : "Invia Email",
"Auth.form.button.reset-password" : "Cambia password",
"Auth.form.button.login" : "Accedi",
"Auth.form.button.register" : "Inizia adesso",
"Auth.form.error.noAdminAccess" : "Non puoi accedere al pannello di amministrazione.",
"Auth.form.forgot-password.email.label" : "Inserisci la tua email",
"Auth.form.forgot-password.email.label.success" : "Email inviata correttamente",
"Auth.form.forgot-password.email.placeholder" : "mysuperemail@gmail.com",
"Auth.header.register.description" : "Per terminare l'installazione e mettere in sicurezza la tua applicazione è necessario creare il primo utente (root admin) inserendo le informazioni necessarie di seguito riportate",
"Auth.form.header.login" : "strapi",
"Auth.form.header.forgot-password" : "strapi",
"Auth.form.header.register" : "Benvenuto!",
"Auth.form.header.register-success" : "strapi",
"Auth.form.login.password.label" : "Password",
"Auth.form.login.rememberMe.label" : "Ricordati di me",
"Auth.form.login.username.label" : "Username",
"Auth.form.login.username.placeholder" : "John Doe",
"Auth.form.register.email.label" : "Email",
"Auth.form.register.email.placeholder" : "johndoe@gmail.com",
"Auth.form.register.username.label" : "Username",
"Auth.form.register.username.placeholder" : "John Doe",
"Auth.form.register.password.label" : "Password",
"Auth.form.register.confirmPassword.label" : "Password di conferma",
"Auth.form.register.news.label" : "Tienimi aggiornato su nuove funzionalità e futuri miglioramenti",
"Auth.form.register-success.email.label" : "Email inviata con successo a",
"Auth.form.register-success.email.placeholder" : "mysuperemail@gmail.com",
"Auth.form.error.email.provide" : "Per favore fornisci il tuo username o la tua password",
"Auth.form.error.email.invalid" : "Questa email non è valida.",
"Auth.form.error.password.provide" : "Per favore fornisci la tua password",
"Auth.form.error.invalid" : "Identificatore o password non valida.",
"Auth.form.error.password.local" : "Questo utente non ha mai impostato una password locale, accedi gentilmente tramite il provider usato durante la creazione dell'account",
"Auth.form.error.password.format" : "La tua password non può contenere il simbolo $ per più di tre volte.",
"Auth.form.error.user.not-exist" : "Questa email non esiste.",
"Auth.form.error.code.provide" : "Codice fornito non corretto.",
"Auth.form.error.password.matching" : "La password non corrisponde.",
"Auth.form.error.params.provide" : "I parametri forniti non sono corretti.",
"Auth.form.error.username.taken" : "Username in uso",
"Auth.form.error.email.taken" : "Email in uso",
"Auth.link.forgot-password" : "Password dimenticata?",
"Auth.link.ready" : "Sei pronto per accedere?",
"BoundRoute.title" : "Percorso vincolato a",
"components.Input.error.password.noMatch" : "La password non corrisponde",
"Controller.input.label" : "{label}",
"Controller.selectAll" : "Seleziona tutto",
"EditForm.inputSelect.label.role" : "Ruolo di default per gli utenti autenticati",
"EditForm.inputSelect.description.role" : "Questa operazione associerà i nuovi utenti autenticati al ruolo selezionato.",
"EditForm.inputSelect.subscriptions.label" : "Gestisci le sottoscrizioni di quota",
"EditForm.inputSelect.subscriptions.description" : "Limita il numero di sottoscrizioni per IP per ora.",
"EditForm.inputSelect.durations.label" : "Durata",
"EditForm.inputSelect.durations.description" : "Numero di ore in cui l'utente non può registrarsi",
"EditForm.inputToggle.label.email" : "Un account per indirizzo email",
"EditForm.inputToggle.label.sign-up" : "Abilita iscrizioni",
"EditForm.inputToggle.description.email" : "Non consentire all'utente di creare account multipli usando lo stesso indirizzo email con fornitori di autenticazione diversi.",
"EditForm.inputToggle.description.sign-up" : "Quando disabilitata (OFF), il processo di registrazione è proibito. Nessuno può iscriversi indipendentemente dal fornitore utilizzato.",
"EditPage.cancel" : "Cancella",
"EditPage.submit" : "Salva",
"EditPage.form.roles" : "Dettagli del ruolo",
"EditPage.form.roles.label.description" : "Descrizione",
"EditPage.form.roles.label.name" : "Nome",
"EditPage.form.roles.label.users" : "Utente associato con questo ruolo ({number})",
"EditPage.form.roles.name.error" : "Questo valore è obbligatorio",
"EditPage.header.title" : "{name}",
"EditPage.header.title.create" : "Crea un nuovo ruolo",
"EditPage.header.description" : "{description}",
"EditPage.header.description.create" : "Crea",
"EditPage.notification.permissions.error" : "Si è verificato un errore durante il recupero dei permessi",
"EditPage.notification.policies.error" : "Si è verificato un errore durante il recupero delle policy",
"EditPage.notification.role.error" : "Si è verificato un errore durante il recupero del ruolo",
"eaderNav.link.advancedSettings" : "Impostazioni avanzate",
"HeaderNav.link.emailTemplates" : "Template delle Email",
"HeaderNav.link.providers" : "Providers",
"HeaderNav.link.roles" : "Ruoli e permessi",
"HomePage.header.title" : "Ruoli e permessi",
"HomePage.header.description" : "Definisci i ruoli e i permessi per i tuoi utenti.",
"InputSearch.placeholder" : "Cerca un utente",
"List.button.roles" : "Aggiungi un ruolo",
"List.button.providers" : "Aggiungi un nuovo Provider",
"List.title.emailTemplates.singular" : "{number} template per le email disponibile",
"List.title.emailTemplates.plural" : "{number} template per le email disponibili",
"List.title.providers.disabled.singular" : "{number} disabilitato",
"List.title.providers.disabled.plural" : "{number} sono disabilitati",
"List.title.providers.enabled.singular" : "{number} provider è abilitato e",
"List.title.providers.enabled.plural" : "{number} providers sono disponibili e",
"List.title.roles.singular" : "{number} ruolo disponibile",
"List.title.roles.plural" : "{number} ruoli disponibili",
"notification.error.delete" : "Si è verificato un errore mentre si stava cercando di eliminare l'elemento",
"notification.error.fetch" : "Si è verificato un errore mentre si stava cercando di recuperare i dati",
"notification.error.fetchUser" : "Si è verificato un errore mentre si stava cercando di recuperare gli utenti",
"notification.info.emailSent" : "Email inviata",
"notification.success.delete" : "L'elemento è stato eliminato",
"notification.success.submit" : "Impostazioni aggiornate",
"plugin.description.short" : "Proteggi le tue API con un processo completo di autenticazione basato su JWT",
"plugin.description.long" : "Proteggi le tue API con un processo completo di autenticazione basato su JWT. Questo plugin è implementato con una strategia ACL che ti consente di gestire i permessi tra i gruppi di utenti.",
"Plugin.permissions.application.description" : "Definire tutte le azioni consentite per il tuo progetto.",
"Plugin.permissions.plugins.description" : "Definire tutte le azioni consentite per il plugin {name}.",
"Plugins.header.title" : "Permessi",
"Plugins.header.description" : "Solo le azioni guidate da un percorso sono elencate sotto.",
"Policies.InputSelect.empty" : "Nessuno",
"Policies.InputSelect.label" : "Consenti di eseguire questa azione per:",
"Policies.header.hint" : "Seleziona le azioni dell'applicazione o del plugin e clicca sull'icona cog per mostrare il percorso corrispondente",
"Policies.header.title" : "Impostazioni avanzate",
"Email.template.validation_email" : "Validazione dell'indirizzo Email",
"Email.template.reset_password" : "Reset password",
"Email.template.success_register" : "Registrazione avvenuta",
"Auth.advanced.allow_register" : "Registrazione consentita",
"PopUpForm.button.cancel" : "Cancella",
"PopUpForm.button.save" : "Salva",
"PopUpForm.header.add.providers" : "Aggiungi nuovo Provider",
"PopUpForm.header.edit.email-templates" : "Modifica il template delle Email",
"PopUpForm.header.edit.providers" : "Modifica {provider} Provider",
"PopUpForm.inputSelect.providers.label" : "Scegli il provider",
"PopUpForm.Email.options.from.name.label" : "Nome del mittente",
"PopUpForm.Email.options.from.email.label" : "Email del mittente",
"PopUpForm.Email.options.response_email.label" : "Email di risposta",
"PopUpForm.Email.options.object.label" : "Soggetto",
"PopUpForm.Email.options.message.label" : "Messaggio",
"PopUpForm.Email.validation_email.options.object.placeholder" : "Conferma gentilmente il tuo indirizzo email per %APP_NAME%",
"PopUpForm.Email.reset_password.options.object.placeholder" : "Conferma gentilmente il tuo indirizzo email per %APP_NAME%",
"PopUpForm.Email.success_register.options.object.placeholder" : "Conferma gentilmente il tuo indirizzo email per %APP_NAME%",
"PopUpForm.Email.validation_email.options.message.placeholder" : "Clicca su questo link per validare il tuo account",
"PopUpForm.Email.reset_password.options.message.placeholder" : "Clicca su questo link per validare il tuo account",
"PopUpForm.Email.success_register.options.message.placeholder" : "Clicca su questo link per validare il tuo account",
"PopUpForm.Email.options.from.email.placeholder" : "johndoe@gmail.com",
"PopUpForm.Email.options.response_email.placeholder" : "johndoe@gmail.com",
"PopUpForm.Email.options.from.name.placeholder" : "John Doe",
"PopUpForm.Providers.enabled.label" : "Abilita",
"PopUpForm.Providers.enabled.description" : "Se disabilitato, gli utenti non potranno usare questo provider.",
"opUpForm.Providers.key.label" : "Client ID",
"PopUpForm.Providers.key.placeholder" : "TEXT",
"PopUpForm.Providers.secret.label" : "Client Secret",
"PopUpForm.Providers.secret.placeholder" : "TEXT",
"PopUpForm.Providers.redirectURL.front-end.label" : "L'URL di redirect per la tua app di front-end",
"PopUpForm.Providers.facebook.providerConfig.redirectURL" : "L'URL di redirect per aggiungere la tua configurazione dell'applicazione Facebook",
"PopUpForm.Providers.google.providerConfig.redirectURL" : "L'URL di redirect per aggiungere la tua configurazione dell'applicazione Google",
"PopUpForm.Providers.github.providerConfig.redirectURL" : "L'URL di redirect per aggiungere la tua configurazione dell'applicazione Github",
"PopUpForm.Providers.linkedin2.providerConfig.redirectURL" : "L'URL di redirect per aggiungere la tua configurazione dell'applicazione Linkdin",
"PopUpForm.Providers.twitter.providerConfig.redirectURL" : "L'URL di redirect per aggiungere la tua configurazione dell'applicazione Twitter",
"PopUpForm.Providers.callback.placeholder" : "TEXT",
"PopUpForm.Email.email_templates.inputDescription" : "Se non sai bene come usare le variabili, {link}",
"PopUpForm.Email.link.documentation" : "controlla la documentazione"
}

View File

@ -39,6 +39,17 @@ module.exports = async cb => {
enabled: true,
icon: 'envelope'
},
discord: {
enabled: false,
icon: 'comments',
key: '',
secret: '',
callback: '/auth/discord/callback',
scope: [
'identify',
'email'
]
},
facebook: {
enabled: false,
icon: 'facebook-official',

View File

@ -14,6 +14,12 @@ module.exports = strapi => {
},
initialize: function(cb) {
_.forEach(strapi.admin.config.routes, value => {
if (_.get(value.config, 'policies')) {
value.config.policies.unshift('plugins.users-permissions.permissions');
}
});
_.forEach(strapi.config.routes, value => {
if (_.get(value.config, 'policies')) {
value.config.policies.unshift('plugins.users-permissions.permissions');

View File

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

View File

@ -109,6 +109,40 @@ const getProfile = async (provider, query, callback) => {
}).get();
switch (provider) {
case 'discord': {
const discord = new Purest({
provider: 'discord',
config: {
'discord': {
'https://discordapp.com/api/': {
'__domain': {
'auth': {
'auth': {'bearer': '[0]'}
}
},
'{endpoint}': {
'__path': {
'alias': '__default'
}
}
}
}
}
});
discord.query().get('users/@me').auth(access_token).request((err, res, body) => {
if (err) {
callback(err);
} else {
// Combine username and discriminator because discord username is not unique
var username = body.username + '#' + body.discriminator;
callback(null, {
username: username,
email: body.email
});
}
});
break;
}
case 'facebook': {
const facebook = new Purest({
provider: 'facebook'

View File

@ -116,7 +116,9 @@ module.exports = {
}, {}));
const appControllers = Object.keys(strapi.api || {}).reduce((acc, key) => {
acc.controllers[key] = generateActions(strapi.api[key].controllers[key]);
Object.keys(strapi.api[key].controllers).forEach((controller) => {
acc.controllers[controller] = generateActions(strapi.api[key].controllers[controller]);
});
return acc;
}, { controllers: {} });
@ -203,7 +205,7 @@ module.exports = {
const databasePermissions = await strapi.query('permission', 'users-permissions').find();
const actions = databasePermissions
.map(permission => `${permission.type}.${permission.controller}.${permission.action}`);
// Aggregate first level actions.
const appActions = Object.keys(strapi.api || {}).reduce((acc, api) => {

View File

@ -43,4 +43,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}

View File

@ -43,4 +43,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}

View File

@ -39,4 +39,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}

View File

@ -13,4 +13,4 @@
"pkgcloud": "^1.5.0",
"streamifier": "^0.1.1"
}
}
}

View File

@ -49,4 +49,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}

View File

@ -60,7 +60,7 @@ module.exports = function (plugin, cliArguments) {
if (!isStrapiInstalledWithNPM) {
// Create the directory yarn doesn't do it it
shell.exec(`mkdir ${pluginPath}`);
shell.exec('mkdir', [pluginPath]);
// Add a package.json so it installs the dependencies
shell.touch(`${pluginPath}/package.json`);
fs.writeFileSync(`${pluginPath}/package.json`, JSON.stringify({}), 'utf8');

View File

@ -66,7 +66,8 @@ module.exports = function(appPath = '') {
};
const setFilesToWatch = (src) => {
var files = fs.readdirSync(src);
let files = _.includes(src, '/admin') || _.includes(src, 'components') ? [] : fs.readdirSync(src);
_.forEach(files, file => {
if (
_.startsWith(file, '.') ||

View File

@ -91,4 +91,4 @@
},
"preferGlobal": true,
"license": "MIT"
}
}