Commit b286fb49 authored by Daniel Caillibaud's avatar Daniel Caillibaud

Merge branch 'fixGestionnairesGroupes' into 'master'

Fix gestionnaires groupes

See merge request !191
parents 1d27ad5f 62548cf1
......@@ -39,10 +39,7 @@ const saveGroupeAction = (groupe, isNew) => ({
}
})
export const saveGroupe = (
groupe,
success = () => {}
) => dispatch => {
export const saveGroupe = (groupe, success) => dispatch => {
const requestSuccess = responseGroup => {
dispatch(
addNotification({
......
......@@ -19,7 +19,8 @@
* @todo throw si un arg est une chaîne vide. Il faut d'abord s'assurer que le front ne le fait jamais
* @returns {routeGetter}
*/
const wrapper = (getRoute, argsNeeded = []) => (params, baseUrl) => {
const makeGetter = (getRoute, argsNeeded = []) => (params, baseUrl) => {
if (argsNeeded.length && !params) throw Error(`Il faut passer les paramètres ${argsNeeded.join(' et ')} pour obtenir cette route`)
argsNeeded.forEach(arg => {
if (params[arg] === undefined) throw Error(`${arg} est un argument obligatoire pour cette route`)
// les autres valeurs falsy seront castées en string dans l'url
......@@ -30,23 +31,23 @@ const wrapper = (getRoute, argsNeeded = []) => (params, baseUrl) => {
const liste = ({search}) => `liste?${search}`
/**
* Liste de ressource
* @type {Function}
* @type {routeGetter}
* @param {object} urlParams
* @param {string} urlParams.search La queryString de la recherche (sans le ?)
* @param {string} [baseUrl]
*/
export const getRessourceListUrl = wrapper(liste, ['search'])
export const getRessourceListUrl = makeGetter(liste, ['search'])
const personneByOid = ({oid}) => `personne/byOid/${oid}`
const checkPid = ({nom, pid}) => `personne/checkPid?nom=${encodeURIComponent(nom)}&pid=${pid}`
/**
* Personne d'après son oid
* @type {routeGetter}
* @param {object} urlParams
* @param {string} urlParams.oid
* @param {string} urlParams.pid
* @param {string} [baseUrl]
* @returns {string}
*/
export const getPersonneByOidUrl = wrapper(personneByOid, ['oid'])
export const getCheckPidUrl = makeGetter(checkPid, ['nom', 'pid'])
const groupesOuverts = () => `groupes/ouverts`
/**
......@@ -55,7 +56,7 @@ const groupesOuverts = () => `groupes/ouverts`
* @param {string} [baseUrl]
* @type {routeGetter}
*/
export const getGroupesOuvertsUrl = wrapper(groupesOuverts)
export const getGroupesOuvertsUrl = makeGetter(groupesOuverts)
const groupesPublics = () => `groupes/publics`
/**
......@@ -64,7 +65,7 @@ const groupesPublics = () => `groupes/publics`
* @param {string} [baseUrl]
* @type {routeGetter}
*/
export const getGroupesPublicsUrl = wrapper(groupesPublics)
export const getGroupesPublicsUrl = makeGetter(groupesPublics)
const personneCurrent = () => `personne/current`
/**
......@@ -73,7 +74,7 @@ const personneCurrent = () => `personne/current`
* @param {string} [baseUrl]
* @type {routeGetter}
*/
export const getCurrentPersonneUrl = wrapper(personneCurrent)
export const getCurrentPersonneUrl = makeGetter(personneCurrent)
const clone = ({oid}) => `clone/${oid}`
/**
......@@ -83,7 +84,7 @@ const clone = ({oid}) => `clone/${oid}`
* @param {string} [baseUrl]
* @type {routeGetter}
*/
export const getRessourceCloneUrl = wrapper(clone, ['oid'])
export const getRessourceCloneUrl = makeGetter(clone, ['oid'])
const ressourceUrl = ({oid, format}) => 'ressource' + (oid ? `/${oid}` : '') + (format ? `?format=${format}` : '')
/**
......@@ -95,7 +96,7 @@ const ressourceUrl = ({oid, format}) => 'ressource' + (oid ? `/${oid}` : '') + (
* @param {string} [baseUrl]
* @type {routeGetter}
*/
export const getRessourceUrl = wrapper(ressourceUrl)
export const getRessourceUrl = makeGetter(ressourceUrl)
const createAlias = ({baseId, oid}) => `createAlias/${baseId}/${oid}`
/**
......@@ -107,7 +108,7 @@ const createAlias = ({baseId, oid}) => `createAlias/${baseId}/${oid}`
* @param {string} [baseUrl]
* @type {routeGetter}
*/
export const getCreateAliasUrl = wrapper(createAlias, ['baseId', 'oid'])
export const getCreateAliasUrl = makeGetter(createAlias, ['baseId', 'oid'])
const forkAlias = ({oid}) => `forkAlias/${oid}`
/**
......@@ -117,7 +118,7 @@ const forkAlias = ({oid}) => `forkAlias/${oid}`
* @param {string} [baseUrl]
* @type {routeGetter}
*/
export const getForkAliasUrl = wrapper(forkAlias, ['oid'])
export const getForkAliasUrl = makeGetter(forkAlias, ['oid'])
const groupesPerso = () => `groupes/perso`
/**
......@@ -126,7 +127,7 @@ const groupesPerso = () => `groupes/perso`
* @param {string} [baseUrl]
* @type {routeGetter}
*/
export const getGroupesPersoUrl = wrapper(groupesPerso)
export const getGroupesPersoUrl = makeGetter(groupesPerso)
const saveGroupe = () => `groupe`
/**
......@@ -135,7 +136,7 @@ const saveGroupe = () => `groupe`
* @param {string} [baseUrl]
* @type {routeGetter}
*/
export const getSaveGroupeUrl = wrapper(saveGroupe)
export const getSaveGroupeUrl = makeGetter(saveGroupe)
const groupe = ({nom}) => `groupe/${encodeURIComponent(nom)}`
/**
......@@ -145,7 +146,7 @@ const groupe = ({nom}) => `groupe/${encodeURIComponent(nom)}`
* @param {string} [baseUrl]
* @type {routeGetter}
*/
export const getGroupeUrl = wrapper(groupe, ['nom'])
export const getGroupeUrl = makeGetter(groupe, ['nom'])
const groupeJoin = ({nom}) => `groupe/rejoindre/${encodeURIComponent(nom)}`
/**
......@@ -155,7 +156,7 @@ const groupeJoin = ({nom}) => `groupe/rejoindre/${encodeURIComponent(nom)}`
* @param {string} [baseUrl]
* @type {routeGetter}
*/
export const getGroupeJoinUrl = wrapper(groupeJoin, ['nom'])
export const getGroupeJoinUrl = makeGetter(groupeJoin, ['nom'])
const groupeFollow = ({nom}) => `groupe/suivre/${encodeURIComponent(nom)}`
/**
......@@ -165,7 +166,7 @@ const groupeFollow = ({nom}) => `groupe/suivre/${encodeURIComponent(nom)}`
* @param {string} [baseUrl]
* @type {routeGetter}
*/
export const getGroupeFollowUrl = wrapper(groupeFollow, ['nom'])
export const getGroupeFollowUrl = makeGetter(groupeFollow, ['nom'])
const groupeLeave = ({nom}) => `groupe/quitter/${encodeURIComponent(nom)}`
/**
......@@ -175,7 +176,7 @@ const groupeLeave = ({nom}) => `groupe/quitter/${encodeURIComponent(nom)}`
* @param {string} [baseUrl]
* @type {routeGetter}
*/
export const getGroupeLeaveUrl = wrapper(groupeLeave, ['nom'])
export const getGroupeLeaveUrl = makeGetter(groupeLeave, ['nom'])
const groupeIgnore = ({nom}) => `groupe/ignorer/${encodeURIComponent(nom)}`
/**
......@@ -185,4 +186,4 @@ const groupeIgnore = ({nom}) => `groupe/ignorer/${encodeURIComponent(nom)}`
* @param {string} [baseUrl]
* @type {routeGetter}
*/
export const getGroupeIgnoreUrl = wrapper(groupeIgnore, ['nom'])
export const getGroupeIgnoreUrl = makeGetter(groupeIgnore, ['nom'])
......@@ -8,12 +8,13 @@ const InputField = ({
className,
label,
name,
placeholder,
disabled,
type = 'text'
}) => (
<Field
className={className}
placeholder={label}
placeholder={placeholder || label}
name={name}
component={Input}
type={type}
......@@ -24,9 +25,21 @@ const InputField = ({
InputField.propTypes = {
className: PropTypes.string,
label: PropTypes.string,
placeholder: PropTypes.string,
name: PropTypes.string,
disabled: PropTypes.bool,
type: PropTypes.string
}
/**
* Composant d'input avec les props classiques
* - [className] une ou des classes css
* - [label] un label qui sera affiché dans un <label> lié au field
* - [info] un éventuel complément de label (affiché dans un <i>)
* - name le nom de l'input (variable du form)
* mais aussi
* - [type=text]
* - [disabled=false]
* - [placeholder=${label}]
*/
export default addLabel(InputField)
......@@ -27,6 +27,7 @@ const addLabel = (WrappedComponent, hasForAttr = true) => {
}
}
// pour le debug, cf https://reactjs.org/docs/higher-order-components.html#convention-wrap-the-display-name-for-easy-debugging
AddLabel.displayName = `addLabel(${getDisplayName(WrappedComponent)})`
AddLabel.propTypes = {
......
import {push} from 'connected-react-router'
import {debounce} from 'lodash'
import PropTypes from 'prop-types'
import React, {Fragment} from 'react'
import {components} from 'react-select'
import {withProps} from 'recompose'
import {formValues, reduxForm} from 'redux-form'
import {GET} from '../../utils/httpMethods'
import {
SwitchField,
InputField,
TextareaField,
SelectField
TextareaField
} from '../fields'
import {saveGroupe} from '../../actions/groupes'
import {saveGroupe} from 'client-react/actions/groupes'
import onSubmitFail from 'client-react/utils/onSubmitFail'
import validate, {asyncBlurFields, asyncValidate} from 'client-react/utils/groupeValidate'
import groupesLoader from './hoc/groupesLoader'
import {getPersonneByOidUrl} from '../../apiRoutes'
import onSubmitFail from '../../utils/onSubmitFail'
import validate from '../../utils/groupeValidate'
const {MultiValueRemove: DefaultMultiValueRemove} = components
const MultiValueRemove = ({
data,
...props
}) => (data.isUndeletable ? null : (
<DefaultMultiValueRemove
data={data}
{...props}
/>
))
MultiValueRemove.propTypes = {
data: PropTypes.shape({})
}
const debouncedGET = debounce((input, setOptions) => {
GET(getPersonneByOidUrl({oid: input}))
.then(({user}) => {
if (user === null) {
return setOptions([])
}
const {oid, prenom, nom} = user
return setOptions([
{
value: oid,
label: `${prenom} ${nom} (${oid})`
}
])
})
.catch(error => {
console.error(error)
setOptions([])
})
}, 500)
const emptyInputRe = /^\s*$/
const getOptions = (input, setOptions) => {
if (emptyInputRe.test(input)) return setOptions([])
debouncedGET(input, setOptions)
}
const LabelGestionnaires = () => (
<Fragment>
Ajouter des gestionnaires
<span className="remarque"> (saisir lidentifiant dun utilisateur, ATTENTION lajout est irrévocable)</span>
</Fragment>
)
// Cf versions avant 2019-05-20 pour l'autocomplete MultiValueRemove
// (ajout de gestionnaires, qu'on a viré car ça permettait d'interroger la base user
// en itérant sur sesasso/NN, mais le composant fonctionnait très bien)
/**
* Formulaire d'édition de groupe
* Doit être dans un redux-form
* @type {PureComponent}
* @param {object} props
* @param {boolean} props.detailed
*/
const GroupeEdition = ({
initialValues: {oid, gestionnaires},
initialValues: {oid, gestionnairesNames},
handleSubmit,
isOuvert,
isPublic,
......@@ -115,15 +64,22 @@ const GroupeEdition = ({
: 'il faut être membre pour suivre les publications du groupe'
})</span>
</div>
<SelectField
label={<LabelGestionnaires />}
components={{ MultiValueRemove }}
placeholder="Saisir un oid"
name="gestionnaires"
loadOptions={getOptions}
isMulti
isClearable={false}
/>
<div>
<label className="field">Gestionnaire(s)</label>
<ul>
{gestionnairesNames.map((name, index) => (
<li key={index}>{name}</li>
))}
</ul>
<label className="field">Ajouter un gestionnaire <i>(en le faisant vous vous engagez à lui avoir demandé son avis avant, toutes les modifications de gestionnaires sont enregistrées)</i></label>
<p>ATTENTION : lajout sera effectif lors du clic sur « Modifier » et il est irrévocable.</p>
<div className="grid-2">
<InputField name="newGestionnairePid" label="Son identifiant" placeholder="celui qui apparaît dans « mes informations personnelles »"/>
<InputField name="newGestionnaireNom" label="Son nom" placeholder="Entrez également le nom (insensible à la casse)" />
</div>
</div>
</fieldset>
<hr />
<div className="buttons-area">
......@@ -147,50 +103,42 @@ GroupeEdition.propTypes = {
isPublic: PropTypes.bool
}
// fourni une prop initialValues (en le passant à withProps)
const getInitialValues = ({
// tous les groupes chargés dans le store (appel de groupes/perso, fourni par le hoc groupesLoader)
groupes,
// le nom du groupe édité d'après l'url
match: {params: {groupe: groupeNom}},
// vient de la session (via ensureLogged appelé par groupesLoader)
personne: {
oid,
nom,
prenom
}
}) => {
// valeurs par défaut à la création
const newGroupe = {
ouvert: false,
public: true,
gestionnairesNames: [`${prenom} ${nom}`]
}
let groupe
if (groupeNom) {
groupe = groupes[groupeNom] || {
ouvert: false,
public: true,
gestionnaires: [oid],
gestionnairesNames: [`${prenom} ${nom}`],
nom: groupeNom
if (groupes[groupeNom]) {
// groupe existant
groupe = groupes[groupeNom]
} else {
// un nouveau groupe que l'on vient de créer, pas encore dans le state
groupe = {...newGroupe}
groupe.nom = groupeNom
}
} else {
groupe = {
ouvert: false,
public: true,
gestionnaires: [oid],
gestionnairesNames: [`${prenom} ${nom}`]
}
groupe = newGroupe
}
const {gestionnaires, gestionnairesNames, ...others} = groupe
const gestionnairesItems = gestionnaires.map((oid, index) => ({
value: oid,
label: `${gestionnairesNames[index]} (${oid})`,
isUndeletable: true
}))
const initialValues = {
...others,
gestionnaires: gestionnairesItems
}
return {initialValues}
return {initialValues: groupe}
}
const onSubmit = ({gestionnaires, ...others}, dispatch) => {
const groupe = {
...others,
gestionnaires: gestionnaires.map(({value}) => value)
}
const onSubmit = ({gestionnairesNames, ...groupe}, dispatch) => {
const onSaveSuccess = () => dispatch(push('/groupes/perso'))
const action = saveGroupe(groupe, onSaveSuccess)
dispatch(action)
......@@ -198,22 +146,27 @@ const onSubmit = ({gestionnaires, ...others}, dispatch) => {
const formDefinition = {
form: 'groupe-edition',
asyncValidate,
asyncBlurFields,
onSubmit,
validate,
onSubmitFail
}
const propsFromForm = {
// le mapping props => values que formValues apporte
// le render en a besoin dynamiquement
const formValuesToProps = {
// public est un mot clé js, on préfixe avec is
isPublic: 'public',
// pour ouvert aussi par cohérence
isOuvert: 'ouvert'
}
// groupesLoader contient ensureLogged
// on a besoin de groupesLoader pour aller chercher les props du groupe courant
// dans la liste des groupes persos (s'il s'y trouve pas c'est une création)
export default groupesLoader(
withProps(getInitialValues)(
reduxForm(formDefinition)(
formValues(propsFromForm)(GroupeEdition))
formValues(formValuesToProps)(GroupeEdition))
)
)
......@@ -7,7 +7,13 @@
list-style-type: none;
li {
display: inline;
padding-left: 5px;
padding-left: 0.4em; /* à peu près la taille de l'espace ajouté après le | qui se retrouvera en limite de padding */
}
li::before {
content: '| ';
}
li:first-child::before {
content: ''
}
}
.links {
......
......@@ -30,7 +30,6 @@ const GroupesOuverts = ({
nom,
description,
public: publicStatus,
gestionnaires,
gestionnairesNames
}) => {
return (
......@@ -55,12 +54,9 @@ const GroupesOuverts = ({
</NavLink>
</span>
<pre>{description}</pre>
<ul>
<ul className="admins">
Gestionnaire(s) :&nbsp;
{gestionnaires.map((oid, index) => (
<li key={oid}>{gestionnairesNames[index]} <span className="remarque">({oid})</span>
</li>
))}
{gestionnairesNames.map((name, index) => (<li key={index}>{name}</li>))}
</ul>
</li>
)
......
......@@ -17,28 +17,24 @@ import './Groupes.scss'
* Composant qui liste les gestionnaires d'un groupe
* @type {PureComponent}
* @param {object} props
* @param {string[]} props.gestionnaires liste d'oids
* @param {string[]} props.gestionnairesNames liste des noms
*/
const Admins = ({
gestionnaires,
gestionnairesNames
}) => {
if (!gestionnaires.length) return (<p>Aucun gestionnaire</p>)
if (gestionnaires.length === 1) return (<p>Gestionnaire&nbsp;: {gestionnairesNames[0]} <span className="remarque">({gestionnaires[0]})</span></p>)
// cf versions avant 2019-05-17 pour avoir aussi l'affichage des oid (il vaudrait mieux les pids
// vu que c'est ça qu'il faut saisir pour ajouter des gestionnaires, mais on préfère ne jamais
// les afficher pour obliger à demander le pid à qqun avant de le mettre gestionnaire)
if (!gestionnairesNames || !gestionnairesNames.length) return (<p>Aucun gestionnaire</p>)
if (gestionnairesNames.length === 1) return (<p>Gestionnaire&nbsp;: {gestionnairesNames[0]}</p>)
return (
<ul className="admins">Gestionnaires :&nbsp;
{gestionnaires.map((oid, index) => (
<li key={oid}>
{gestionnairesNames[index]} <span className="remarque">({oid})</span>
</li>
))}
{gestionnairesNames.map((name, index) => (<li key={index}>{name}</li>))}
</ul>
)
}
Admins.propTypes = {
gestionnaires: PropTypes.arrayOf(PropTypes.string),
gestionnairesNames: PropTypes.arrayOf(PropTypes.string)
}
......@@ -172,10 +168,7 @@ const Groupe = ({
<li key={nom}>
<strong>{nom}</strong> ({ouvert ? 'ouvert' : 'fermé'} {isPublic ? 'public' : 'privé'})
<pre>{description}</pre>
<Admins
gestionnaires={gestionnaires}
gestionnairesNames={gestionnairesNames}
/>
<Admins gestionnairesNames={gestionnairesNames} />
<GroupeLinks nom={nom} {...others} />
</li>
)
......
......@@ -30,18 +30,14 @@ const GroupesPublics = ({
nom,
description,
ouvert,
gestionnaires,
gestionnairesNames
}) => {
return (
<li key={nom}><strong>{nom}</strong> ({ouvert ? 'ouvert' : 'fermé'})
<pre>{description}</pre>
<ul>
<ul className="admins">
Gestionnaire(s) :&nbsp;
{gestionnaires.map((oid, index) => (
<li key={oid}>{gestionnairesNames[index]} <span className="remarque">({oid})</span>
</li>
))}
{gestionnairesNames.map((name, index) => (<li key={index}>{name}</li>))}
</ul>
<span className="links">
......
const groupesReducer = (state = null, {type, payload}) => {
switch (type) {
case 'LOAD_GROUPES':
// c'est l'init du state "groupes" géré ici
return payload.groupes
case 'DELETE_GROUPE': {
......@@ -16,7 +17,7 @@ const groupesReducer = (state = null, {type, payload}) => {
case 'SAVE_GROUPE':
case 'JOIN_GROUPE':
case 'FOLLOW_GROUPE': {
if (state === null) { return state }
if (state === null) return state
const {groupe} = payload
const {nom} = groupe
......
......@@ -6,6 +6,11 @@ import session from './session'
import ressource from './ressource'
import groupes from './groupes'
/**
* Combine tous nos reducers
* @param history
* @return {Reducer<any>} le reducer à passer au createStore de redux
*/
const createRootReducer = history => combineReducers({
form,
groupes,
......
import validate from './validate'
import {addNotification} from 'client-react/actions/notifications'
import {GET} from 'client-react/utils/httpMethods'
import {getCheckPidUrl} from 'client-react/apiRoutes'
import {looksLikePid} from 'utils/validators'
const requiredFields = ['nom']
const groupeValidate = validate({requiredFields})
export default groupeValidate
const groupeValidate = validate({
requiredFields
})
export const asyncBlurFields = ['newGestionnairePid', 'newGestionnaireNom']
export default groupeValidate
// un objet pour stocker les appels déjà faits, pour éviter de le refaire plusieurs fois
// sinon on a une notif à chaque blur d'un des deux champs même si on ne les modifie pas
const alreadyCalled = {}
/**
* Appelé au blur sur un des asyncBlurFields (si la validation sync est ok)
* @see https://redux-form.com/6.0.5/examples/asyncvalidation/
* @param {Object} values Les values du form GroupeEdition
* @return {Promise}
*/
export const asyncValidate = (values, dispatch) => {
// on regarde d'abord si les deux champs qui nous intéressent sont non-vide
if (asyncBlurFields.every(key => !values[key])) return Promise.resolve()
const errors = {}
asyncBlurFields.forEach(key => {
if (!values[key] || !values[key].trim()) errors[key] = 'Champ requis pour ajouter un gestionnaire'
})
if (values.newGestionnairePid && !looksLikePid(values.newGestionnairePid)) errors.newGestionnairePid = 'identifiant invalide'
if (Object.keys(errors).length) return Promise.reject(errors)
const pid = values.newGestionnairePid
const nom = values.newGestionnaireNom
if (alreadyCalled[pid] && alreadyCalled[pid][nom]) return Promise.resolve()
const url = getCheckPidUrl({pid, nom})
return GET(url)
.then(({nom: realNom, prenom}) => {
// on a pas besoin de récupérer l'oid car il sera revérifié coté serveur avant l'ajout
if (!alreadyCalled[pid]) alreadyCalled[pid] = {}
alreadyCalled[pid][nom] = true
dispatch(
addNotification({
level: 'info',
message: `${prenom} ${realNom} a bien l'identifiant (${pid})`
})
)
return Promise.resolve()
}).catch((error) => {
console.error(error)
const errors = {newGestionnairePid: error.message}
return Promise.reject(errors)
})
}
......@@ -4,16 +4,24 @@
* @param {Response} response
*/
const handleResponse = (response) => {
const {status, statusText, ok} = response
const {status, statusText, ok} = response // ok est ajouté par l'api fetch
// on tente quand même le json avant de gérer les erreurs, car l'erreur peut être explicitée dans le json renvoyé
return response.json()
.then(result => {
// à priori avec status > 400 on devrait aller direct dans le catch…
if (!ok || status >= 400) throw Error(result.message || `Erreur ${status} ${statusText}`)
if (!ok || status >= 400) {
const error = Error(result.message || `Erreur ${status} ${statusText}`)
error.userFriendly = true
error.status = status
throw error
}
return result.data || result.message
}).catch((error) => {
if (error.userFriendly) throw error
// l'erreur ne vient pas du then précédent, à priori un pb json,
// mais ça peut être une erreur 500 ou une absence de réponse (que fetch traduit en 504 si la connexion est refusée)
console.error(error)
if (!ok || status >= 400) throw Error(`Erreur ${status} ${statusText}`)
if (status >= 400) throw Error(`Erreur ${status} ${statusText}`)
throw Error('La réponse n’était pas au format attendu (json)')
})
}
......
......@@ -6,7 +6,7 @@ const errorsMessage = 'La sauvegarde a échoué car des champs contiennent des e
const ArrayFrom = Array.prototype.slice
const onSubmitFail = (_, dispatch, error) => {
const onSubmitFail = (errors, dispatch, error) => {
if (error) {
dispatch(addNotification({
level: 'error',
......@@ -15,7 +15,10 @@ const onSubmitFail = (_, dispatch, error) => {
throw error
}
// pourquoi aller chercher les messages d'erreur dans le dom alors qu'ils sont déjà dans l'objet errors ?
const elements = ArrayFrom.call(document.querySelectorAll('.validation-error'))
// à creuser…
// console.log('onSubmitFail avec les erreurs', errors, 'qui devraient déjà être affichées dans le dom dans les éléments', elements)
if (elements.length) {
const element = elements[0]
if (element && element.scrollIntoView) element.scrollIntoView(false)
......
import editors from './generatedEditors'
const getEditor = (type) => {
return editors[type] || {}
}
/**
* Retourne un objet avec une proprité éditor, ou un objet vide si le plugin du type ne fourni pas d'éditeur
* @param {string} type
* @return {Object}
*/
const getEditor = (type) => editors[type] || {}
export default getEditor
......@@ -66,19 +66,24 @@ module.exports = function (mainComponent) {