Merge branch 'master' into feat/modal/add_position

This commit is contained in:
Jalil Arfaoui 2016-12-20 16:40:30 +01:00
commit ac9e7c3e12
65 changed files with 982 additions and 934 deletions

View file

@ -31,38 +31,53 @@
"jsx": true "jsx": true
}, },
"globals": { "globals": {
"document": false,
"window": false, "window": false,
"global": false, "global": false,
"require": false, "require": false,
"expect": false, "expect": false,
"should": false,
"chai": false,
"sinon": false,
"Power2": false,
"Sine": false,
"__DEVELOPMENT__": false "__DEVELOPMENT__": false
}, },
"rules": { "rules": {
"max-len": [0], "max-len": [0],
"new-cap": ["warn", {"capIsNewExceptions": [ "new-cap": ["warn", {"capIsNewExceptions": [
"Immutable", "Moment"
"Map",
"List",
"Set",
"OrderedSet",
"Range",
"Moment",
"Linker",
]}], ]}],
"comma-dangle": ["warn", "always-multiline"], // disallow or enforce trailing commas "comma-dangle": ["warn", "always-multiline"], // disallow or enforce trailing commas
"curly": ["warn", "multi-or-nest"], // specify curly brace conventions for all control statements "curly": ["warn", "multi-or-nest"], // specify curly brace conventions for all control statements
"indent": ["warn", 2, {"SwitchCase": 1}], // this option sets a specific tab width for your code (off by default) "indent": ["warn", 2, {
"SwitchCase": 1,
"MemberExpression": 1,
"FunctionDeclaration": {"parameters": "first"},
"FunctionExpression": {"parameters": "first"},
"CallExpression": {"arguments": 1}
}], // this option sets a specific tab width for your code (off by default)
"brace-style": ["warn", "1tbs", { "allowSingleLine": true }], // enforce one true brace style (off by default) "brace-style": ["warn", "1tbs", { "allowSingleLine": true }], // enforce one true brace style (off by default)
"key-spacing": ["warn", {"beforeColon": false, "afterColon": true}], // enforces spacing between keys and values in object literal properties "key-spacing": ["warn", {"beforeColon": false, "afterColon": true}], // enforces spacing between keys and values in object literal properties
"quotes": ["warn", "single", "avoid-escape"], // specify whether double or single quotes should be used "quotes": ["warn", "single", "avoid-escape"], // specify whether double or single quotes should be used
"keyword-spacing": ["warn", {"before": true, "after": true}], // require a space after certain keywords (off by default) "keyword-spacing": ["warn", {"before": true, "after": true}], // require a space after certain keywords (off by default)
"no-underscore-dangle": [0],
"import/no-extraneous-dependencies": [0], // because broken
"import/no-unresolved": [0], // because broken
"import/extensions": [0],
"import/prefer-default-export": [0],
"global-require": [0],
"linebreak-style": "off",
// React // React
//"computed-property-spacing": ["warn", "never"], // disallow spaces inside computed properties //"computed-property-spacing": ["warn", "never"], // disallow spaces inside computed properties
"react/jsx-indent-props": ["warn", 2] "react/jsx-indent-props": ["warn", 2],
"react/forbid-prop-types": [0],
"react/no-unescaped-entities": [0],
"react/no-unused-prop-types": [0], // because broken
"react/sort-comp": [1, {
"order": [
"static-methods",
"lifecycle",
"/^on.+$/",
"everything-else",
"/^render.+$/",
"render"
]
}]
} }
} }

View file

@ -1,3 +1,4 @@
/* eslint-disable import/imports-first */
import jquery from 'jquery'; import jquery from 'jquery';
global.$ = jquery; global.$ = jquery;
global.jQuery = jquery; global.jQuery = jquery;

View file

@ -12,8 +12,8 @@
"copy-mocks": "node scripts/copy-mocks.js", "copy-mocks": "node scripts/copy-mocks.js",
"renderer": "node ./renderer/renderer.js", "renderer": "node ./renderer/renderer.js",
"clean": "./node_modules/.bin/rimraf ./build integration/main.css integration/static && echo --- Cleaned ---", "clean": "./node_modules/.bin/rimraf ./build integration/main.css integration/static && echo --- Cleaned ---",
"lint": "eslint src", "lint": "eslint --ext .js --ext .jsx src webpack stories",
"lint-fix": "eslint src --fix", "lint-fix": "npm run lint -- --fix",
"test": "jest --coverage", "test": "jest --coverage",
"test-watch": "jest --watch", "test-watch": "jest --watch",
"e2e-test": "./node_modules/.bin/nightwatch", "e2e-test": "./node_modules/.bin/nightwatch",
@ -77,7 +77,7 @@
"@kadira/storybook-addon-knobs": "^1.4.1", "@kadira/storybook-addon-knobs": "^1.4.1",
"babel-cli": "<6.3.0", "babel-cli": "<6.3.0",
"babel-core": "<6.3.0", "babel-core": "<6.3.0",
"babel-eslint": "^6.0.0", "babel-eslint": "^7.1.0",
"babel-loader": "~6.2.1", "babel-loader": "~6.2.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-es2015-modules-umd": "^6.8.0", "babel-plugin-transform-es2015-modules-umd": "^6.8.0",
@ -88,11 +88,11 @@
"copy-webpack-plugin": "^2.1.3", "copy-webpack-plugin": "^2.1.3",
"css-loader": "^0.23.0", "css-loader": "^0.23.0",
"enzyme": "^2.3.0", "enzyme": "^2.3.0",
"eslint": "^2.5.3", "eslint": "^3.9.1",
"eslint-config-airbnb": "^6.2.0", "eslint-config-airbnb": "^12.0.0",
"eslint-plugin-import": "^1.10.2", "eslint-plugin-import": "^1.16.0",
"eslint-plugin-jsx-a11y": "^1.5.5", "eslint-plugin-jsx-a11y": "^2.2.3",
"eslint-plugin-react": "^4.2.3", "eslint-plugin-react": "^6.5.0",
"extract-text-webpack-plugin": "^0.9.1", "extract-text-webpack-plugin": "^0.9.1",
"file-loader": "^0.8.4", "file-loader": "^0.8.4",
"html-webpack-plugin": "^2.15.0", "html-webpack-plugin": "^2.15.0",

View file

@ -1,27 +1,28 @@
/* eslint-disable jsx-a11y/href-no-hash */
import React from 'react'; import React from 'react';
import paths from 'constants/paths'; import paths from 'constants/paths';
import { Link } from 'react-router'; import { Link } from 'react-router';
const Footer = () => ( const Footer = () => (
<footer className="footer"> <footer className="footer">
<div className="col-md-1"></div> <div className="col-md-1" />
<div className="col-md-3"> <div className="col-md-3">
<small> <small>
<a href="#">Crédits</a> <a href="#">Crédits</a>
| |
<a href="#">Mentions Légales</a> <a href="#">Mentions Légales</a>
</small></div> </small></div>
<div className="col-md-7"> <div className="col-md-7">
<nav> <nav>
<ul> <ul>
<li><Link to={paths.about}>À propos</Link></li> <li><Link to={paths.about}>À propos</Link></li>
<li><Link to={paths.contact}>Contact</Link></li> <li><Link to={paths.contact}>Contact</Link></li>
<li><Link to={paths.external.twitter} target="_blank">Twitter</Link></li> <li><Link to={paths.external.twitter} target="_blank">Twitter</Link></li>
</ul> </ul>
</nav> </nav>
</div> </div>
<div className="col-md-1"></div> <div className="col-md-1" />
</footer> </footer>
); );
export default Footer; export default Footer;

View file

@ -1,23 +1,25 @@
/* eslint-disable jsx-a11y/href-no-hash */
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import paths from 'constants/paths'; import paths from 'constants/paths';
const ConnectedUserMenu = ({ user, onLogOut }) => ( const ConnectedUserMenu = ({ user, onLogOut }) => (
<li className="dropdown"> <li className="dropdown">
<a href="#" className="dropdown-toggle" data-toggle="dropdown"> <a href="#" className="dropdown-toggle" data-toggle="dropdown">
{user.name} {user.name}
<b className="caret"></b> <b className="caret" />
</a> </a>
<ul className="dropdown-menu"> <ul className="dropdown-menu">
<li><Link to={paths.getFor.user(user)}>Profil</Link></li> <li><Link to={paths.getFor.user(user)}>Profil</Link></li>
<li className="divider"></li> <li className="divider" />
<li><a onClick={onLogOut}>Déconnexion</a></li> <li><a href="#" onClick={onLogOut}>Déconnexion</a></li>
</ul> </ul>
</li> </li>
); );
ConnectedUserMenu.propTypes = { ConnectedUserMenu.propTypes = {
user: PropTypes.object.isRequired, user: PropTypes.object.isRequired,
onLogOut: PropTypes.func.isRequired, onLogOut: PropTypes.func.isRequired,
}; };
export default ConnectedUserMenu; export default ConnectedUserMenu;

View file

@ -4,18 +4,19 @@ import cssModules from 'react-css-modules';
import { Navbar, Nav, NavItem } from 'react-bootstrap'; import { Navbar, Nav, NavItem } from 'react-bootstrap';
import { LinkContainer, IndexLinkContainer } from 'react-router-bootstrap'; import { LinkContainer, IndexLinkContainer } from 'react-router-bootstrap';
import paths from 'constants/paths';
import ConnectedUserMenu from './'; import ConnectedUserMenu from './';
import paths from 'constants/paths';
import logoImg from './images/logo_header.png'; import logoImg from './images/logo_header.png';
import styles from './Header.css'; import styles from './Header.css';
const renderUserMenu = (isConnected) => ( const renderUserMenu = isConnected => (
isConnected isConnected
? <ConnectedUserMenu /> ? <ConnectedUserMenu />
: [ : [
<li><a>Connexion</a></li>, /* modal ! */ <li><a>Connexion</a></li>, /* modal ! */
<li><a>Inscription</a></li>, /* modal ! */ <li><a>Inscription</a></li>, /* modal ! */
] ]
); );
@ -25,7 +26,7 @@ const Header = () => (
<Navbar.Brand> <Navbar.Brand>
<IndexLinkContainer to="/" styleName="logo"> <IndexLinkContainer to="/" styleName="logo">
<NavItem> <NavItem>
<img src={logoImg} /> <img alt="Brand Logo" src={logoImg} />
</NavItem> </NavItem>
</IndexLinkContainer> </IndexLinkContainer>
</Navbar.Brand> </Navbar.Brand>

View file

@ -1,5 +1,6 @@
/* eslint-disable no-unused-vars */
import React from 'react'; import React from 'react';
// eslint-disable-next-line no-unused-vars
import { storiesOf, action, linkTo } from '@kadira/storybook'; import { storiesOf, action, linkTo } from '@kadira/storybook';
import { withKnobs, text, boolean, number } from '@kadira/storybook-addon-knobs'; import { withKnobs, text, boolean, number } from '@kadira/storybook-addon-knobs';
@ -10,9 +11,9 @@ const stories = storiesOf('Header', module);
stories.addDecorator(withKnobs); stories.addDecorator(withKnobs);
stories.addWithInfo( stories.addWithInfo(
'default header', 'default header',
'Description of the story', 'Description of the story',
() => ( () => (
<Header /> <Header />
) )
); );

View file

@ -5,18 +5,18 @@ import Footer from './Footer';
import GoogleAnalytics from './GoogleAnalytics'; import GoogleAnalytics from './GoogleAnalytics';
const Main = ({ children }) => ( const Main = ({ children }) => (
<div className="container-fluid"> <div className="container-fluid">
<Header /> <Header />
<FlashMessages /> <FlashMessages />
<div className="main-content"> <div className="main-content">
{children} {children}
</div>
<Footer />
<GoogleAnalytics />
</div> </div>
<Footer />
<GoogleAnalytics />
</div>
); );
Main.propTypes = { Main.propTypes = {
children: PropTypes.node, children: PropTypes.node,
}; };
export default Main; export default Main;

View file

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
import { curry } from 'ramda'; import { curry } from 'ramda';
import axios from 'axios'; import axios from 'axios';
import Config from 'Config'; import Config from 'Config';
@ -13,4 +15,3 @@ export const getStatements = () => get('statements');
export const getPublicFiguresAutocomplete = typed => get(`autocomplete/public_figure/${typed}`); export const getPublicFiguresAutocomplete = typed => get(`autocomplete/public_figure/${typed}`);
export const getSubjectsAutocomplete = typed => get(`autocomplete/subject/${typed}`); export const getSubjectsAutocomplete = typed => get(`autocomplete/subject/${typed}`);
export const getPositions = subjectId => get(`subjects/${subjectId}/positions`); export const getPositions = subjectId => get(`subjects/${subjectId}/positions`);

View file

@ -14,45 +14,45 @@ const getId = prop('id');
const getAttributesPair = objectWithAttributes => toPairs(objectWithAttributes.attributes); const getAttributesPair = objectWithAttributes => toPairs(objectWithAttributes.attributes);
const mergeAttributes = entityWithAttributes => compose( const mergeAttributes = entityWithAttributes => compose(
reduce( reduce(
(entity, attributePair) => (assoc(attributePair[0], attributePair[1], entity)), (entity, attributePair) => (assoc(attributePair[0], attributePair[1], entity)),
entityWithAttributes entityWithAttributes
), ),
getAttributesPair, getAttributesPair,
)(entityWithAttributes); )(entityWithAttributes);
const overAttributes = over(lensProp('attributes')); const overAttributes = over(lensProp('attributes'));
const toCamelCase = replace( const toCamelCase = replace(
/-[a-z]/g, /-[a-z]/g,
compose(replace('-', ''), toUpper) compose(replace('-', ''), toUpper)
); );
const toCamelCaseAttributes = overAttributes(pipe( const toCamelCaseAttributes = overAttributes(pipe(
toPairs, toPairs,
map( map(
([k, v]) => ([toCamelCase(k), v]) ([k, v]) => ([toCamelCase(k), v])
), ),
fromPairs, fromPairs,
)); ));
export const flattenAttributes = pipe( export const flattenAttributes = pipe(
map(pipe( map(pipe(
toCamelCaseAttributes, toCamelCaseAttributes,
mergeAttributes, mergeAttributes,
dissoc('attributes'), dissoc('attributes'),
)), )),
); );
export const index = pipe( export const index = pipe(
when(isNotArray, of), when(isNotArray, of),
flattenAttributes, flattenAttributes,
map(indexBy(getId)) map(indexBy(getId))
); );
export const indexAndGroup = pipe( export const indexAndGroup = pipe(
when(isNotArray, of), when(isNotArray, of),
flattenAttributes, flattenAttributes,
groupBy(getType), groupBy(getType),
map(indexBy(getId)) map(indexBy(getId))
); );

View file

@ -1,5 +1,5 @@
require('bootstrap-loader');
import moment from 'moment'; import moment from 'moment';
import 'bootstrap-loader';
const locale = 'fr'; const locale = 'fr';
@ -16,7 +16,8 @@ moment.locale(locale, {
// Install ImmutableDevTools // Install ImmutableDevTools
if (process.env.NODE_ENV !== 'production') { // DEBUG/DEV MODE if (process.env.NODE_ENV !== 'production') { // DEBUG/DEV MODE
const Immutable = require('immutable'); const Immutable = require('immutable');
const installDevTools = require('immutable-devtools').default; const installDevTools = require('immutable-devtools').default;
installDevTools(Immutable);
installDevTools(Immutable);
} }

View file

@ -1,29 +1,29 @@
import React, { PropTypes, Component } from 'react'; import React, { Component } from 'react';
import { Button } from 'react-bootstrap'; import { Button } from 'react-bootstrap';
import AddStatementModal from 'components/AddStatementModal'; import AddStatementModal from 'components/AddStatementModal';
class AddStatementButton extends Component { class AddStatementButton extends Component {
state = { state = {
showModal: false, showModal: false,
} }
close = () => this.setState({ showModal: false }); close = () => this.setState({ showModal: false });
open = () => this.setState({ showModal: true }); open = () => this.setState({ showModal: true });
render() { render() {
return ( return (
<div> <div>
<Button <Button
bsStyle="primary" bsStyle="primary"
bsSize="xsmall" bsSize="xsmall"
onClick={this.open} onClick={this.open}
> >
Nouvelle prise de position Nouvelle prise de position
</Button> </Button>
<AddStatementModal show={this.state.showModal} onHide={this.close} /> <AddStatementModal show={this.state.showModal} onHide={this.close} />
</div> </div>
); );
} }
} }
export default AddStatementButton; export default AddStatementButton;

View file

@ -5,6 +5,7 @@ import AddSubjectFrom from './AddSubjectForm';
const SubjectStep = ({ selected, onSelection }) => ( const SubjectStep = ({ selected, onSelection }) => (
<Well> <Well>
<<<<<<< HEAD
<FormGroup controlId="subjectSelect" validationState={selected && !selected.customOption ? 'success' : undefined}> <FormGroup controlId="subjectSelect" validationState={selected && !selected.customOption ? 'success' : undefined}>
<ControlLabel>Quel est le sujet qui fait débat ?</ControlLabel> <ControlLabel>Quel est le sujet qui fait débat ?</ControlLabel>
{(!selected || !selected.customOption) && {(!selected || !selected.customOption) &&
@ -16,6 +17,11 @@ const SubjectStep = ({ selected, onSelection }) => (
onChange={onSelection} onChange={onSelection}
onCancel={() => onSelection(null)} onCancel={() => onSelection(null)}
/>} />}
=======
<FormGroup controlId="subjectSelect" validationState={!!selected ? 'success' : undefined}>
<ControlLabel>Quel est le sujet qui fait débat ?</ControlLabel>
<SubjectAutocompleteInput selected={selected} onSelection={onSelection} />
>>>>>>> master
</FormGroup> </FormGroup>
</Well> </Well>
); );

View file

@ -1,7 +1,7 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
getAddStatementPublicFigure, getAddStatementSubject, getAddStatementPosition, getAddStatementPublicFigure, getAddStatementSubject, getAddStatementPosition,
getAddStatementEvidenceUrl, getAddStatementEvidenceFile getAddStatementEvidenceUrl, getAddStatementEvidenceFile,
} from 'store/selectors'; } from 'store/selectors';
import { import {
onAddStatementPublicFigureSelection, onAddStatementSubjectSelection, onAddStatementPositionSelection, onAddStatementPublicFigureSelection, onAddStatementSubjectSelection, onAddStatementPositionSelection,
@ -10,20 +10,20 @@ import {
} from 'store/actions'; } from 'store/actions';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
selectedPublicFigure: getAddStatementPublicFigure(state), selectedPublicFigure: getAddStatementPublicFigure(state),
selectedSubject: getAddStatementSubject(state), selectedSubject: getAddStatementSubject(state),
selectedPosition: getAddStatementPosition(state), selectedPosition: getAddStatementPosition(state),
evidenceUrl: getAddStatementEvidenceUrl(state), evidenceUrl: getAddStatementEvidenceUrl(state),
evidenceFile: getAddStatementEvidenceFile(state), evidenceFile: getAddStatementEvidenceFile(state),
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
onPublicFigureSelection: publicFigureId => dispatch(onAddStatementPublicFigureSelection(publicFigureId)), onPublicFigureSelection: publicFigureId => dispatch(onAddStatementPublicFigureSelection(publicFigureId)),
onSubjectSelection: publicFigureId => dispatch(onAddStatementSubjectSelection(publicFigureId)), onSubjectSelection: publicFigureId => dispatch(onAddStatementSubjectSelection(publicFigureId)),
onPositionSelection: publicFigureId => dispatch(onAddStatementPositionSelection(publicFigureId)), onPositionSelection: publicFigureId => dispatch(onAddStatementPositionSelection(publicFigureId)),
onUpdateEvidenceUrl: url => dispatch(onAddStatementUpdateEvidenceUrl(url)), onUpdateEvidenceUrl: url => dispatch(onAddStatementUpdateEvidenceUrl(url)),
onUpdateEvidenceFile: file => dispatch(onAddStatementUpdateEvidenceFile(file)), onUpdateEvidenceFile: file => dispatch(onAddStatementUpdateEvidenceFile(file)),
onValidate: () => dispatch(onAddStatementValidate()), onValidate: () => dispatch(onAddStatementValidate()),
}); });
export default connect(mapStateToProps, mapDispatchToProps); export default connect(mapStateToProps, mapDispatchToProps);

View file

@ -1,20 +1,20 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
const getText = nb => { const getText = (nb) => {
if (nb === 0) if (nb === 0)
return "Aucun sujet actif"; return 'Aucun sujet actif';
else if (nb > 1) else if (nb > 1)
return nb + "sujets actifs"; return `${nb}sujets actifs`;
return "1 sujet actif"; return '1 sujet actif';
}; };
const AssociatedSubjects = ({ publicFigure }) => ( const AssociatedSubjects = ({ publicFigure }) => (
<h6 style={{ color: "#f21e40 !important" }}>{getText(publicFigure.nbActiveSubjects)}</h6> <h6 style={{ color: '#f21e40 !important' }}>{getText(publicFigure.nbActiveSubjects)}</h6>
); );
AssociatedSubjects.propTypes = { AssociatedSubjects.propTypes = {
publicFigure: PropTypes.object.isRequired publicFigure: PropTypes.object.isRequired,
}; };
export default AssociatedSubjects; export default AssociatedSubjects;

View file

@ -5,7 +5,7 @@ import React, { PropTypes } from 'react';
const Identity = ({ children }) => <div>{children}</div>; const Identity = ({ children }) => <div>{children}</div>;
Identity.propTypes = { Identity.propTypes = {
children: PropTypes.node, children: PropTypes.node,
}; };
export default Identity; export default Identity;

View file

@ -1,3 +1,4 @@
/* eslint-disable no-unused-vars */
import React from 'react'; import React from 'react';
import { storiesOf, action, linkTo } from '@kadira/storybook'; import { storiesOf, action, linkTo } from '@kadira/storybook';
import Identity from './'; import Identity from './';

View file

@ -6,28 +6,28 @@ import cssModules from 'react-css-modules';
import style from './Statement.css'; import style from './Statement.css';
const Statement = ({ statement }) => ( const Statement = ({ statement }) => (
<li id={`statement-${statement.id}`} styleName="wrapper" > <li id={`statement-${statement.id}`} styleName="wrapper" >
<PublicFigureAvatar publicFigure={statement.publicFigure} /> <PublicFigureAvatar publicFigure={statement.publicFigure} />
<div styleName="public-figure-text"> <div styleName="public-figure-text">
<strong> <strong>
<Link to={paths.getFor.publicFigure(statement.publicFigure)}> <Link to={paths.getFor.publicFigure(statement.publicFigure)}>
{statement.publicFigure.name} {statement.publicFigure.name}
</Link> </Link>
</strong> </strong>
&nbsp; s'est déclaré pour &nbsp; s'est déclaré pour
"<strong>{statement.position.title}</strong>" "<strong>{statement.position.title}</strong>"
dans le débat sur dans le débat sur
<strong><Link to={paths.getFor.subject(statement.subject)}> <strong><Link to={paths.getFor.subject(statement.subject)}>
{statement.subject.title} {statement.subject.title}
</Link></strong> </Link></strong>
</div> </div>
</li> </li>
); );
Statement.propTypes = { Statement.propTypes = {
statement: PropTypes.shape({ statement: PropTypes.shape({
publicFigure: PropTypes.object, publicFigure: PropTypes.object,
position: PropTypes.object, position: PropTypes.object,
}).isRequired, }).isRequired,
}; };
export default cssModules(Statement, style); export default cssModules(Statement, style);

View file

@ -3,11 +3,11 @@ import { getLatestStatements } from 'store/selectors';
import { onLastStatementsAccess } from 'store/actions/entities'; import { onLastStatementsAccess } from 'store/actions/entities';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
statements: getLatestStatements(state), statements: getLatestStatements(state),
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
onAccess: () => dispatch(onLastStatementsAccess()), onAccess: () => dispatch(onLastStatementsAccess()),
}); });
export default connect(mapStateToProps, mapDispatchToProps); export default connect(mapStateToProps, mapDispatchToProps);

View file

@ -1,35 +1,35 @@
import React, { PropTypes, Component } from 'react'; import React, { PropTypes, Component } from 'react';
import CSSModules from 'react-css-modules'; import cssModules from 'react-css-modules';
import Statement from './Statement'; import Statement from './Statement';
import LastStatementsStyle from './LastStatements.css'; import LastStatementsStyle from './LastStatements.css';
import connect from './connector'; import connect from './connector';
class LastStatements extends Component { class LastStatements extends Component {
static propTypes = { static propTypes = {
statements: PropTypes.arrayOf(PropTypes.object), statements: PropTypes.arrayOf(PropTypes.object),
onAccess: PropTypes.func.isRequired, onAccess: PropTypes.func.isRequired,
}; };
componentWillMount() { componentWillMount() {
this.props.onAccess(); this.props.onAccess();
} }
renderStatements = () => this.props.statements.map( renderStatements = () => this.props.statements.map(
(s, i) => <Statement key={i} statement={s} /> (s, i) => <Statement key={i} statement={s} />
); );
render() { render() {
if (!this.props.statements) return <span>loading last statements ...</span>; if (!this.props.statements) return <span>loading last statements ...</span>;
return ( return (
<div styleName="wrapper"> <div styleName="wrapper">
<h2 styleName="title">Les dernières prises de positions</h2> <h2 styleName="title">Les dernières prises de positions</h2>
<ul styleName="wrapper"> <ul styleName="wrapper">
{ this.renderStatements() }; { this.renderStatements() };
</ul> </ul>
</div> </div>
); );
} }
} }
export default connect(CSSModules(LastStatements, LastStatementsStyle)); export default connect(cssModules(LastStatements, LastStatementsStyle));

View file

@ -1,8 +1,8 @@
import React, { Component, PropTypes } from 'react'; import React, {Component, PropTypes} from 'react';
import { head, of, compose, when, prop, not, isNil } from 'ramda'; import {head, of, compose, when, prop, not, isNil} from 'ramda';
import Typeahead from 'react-bootstrap-typeahead'; import Typeahead from 'react-bootstrap-typeahead';
import { getPublicFiguresAutocomplete } from 'api/debats'; import {getPublicFiguresAutocomplete} from 'api/debats';
import { flattenAttributes } from 'api/jsonApiParser'; import {flattenAttributes} from 'api/jsonApiParser';
import PublicFigureAvatar from 'components/PublicFigureAvatar'; import PublicFigureAvatar from 'components/PublicFigureAvatar';
import { makeCancelable } from 'helpers/promises'; import { makeCancelable } from 'helpers/promises';

View file

@ -3,17 +3,17 @@ import cssModules from 'react-css-modules';
import styles from './PublicFigureAvatar.css'; import styles from './PublicFigureAvatar.css';
const PublicFigureAvatar = ({ publicFigure }) => ( const PublicFigureAvatar = ({ publicFigure }) => (
<div <div
styleName="wrapper" styleName="wrapper"
style={{ style={{
backgroundImage: !!publicFigure.picture backgroundImage: publicFigure.picture
? `url(${publicFigure.picture.url})` ? `url(${publicFigure.picture.url})`
: undefined, : undefined,
}} }}
/> />
); );
PublicFigureAvatar.propTypes = { PublicFigureAvatar.propTypes = {
publicFigure: PropTypes.object.isRequired, publicFigure: PropTypes.object.isRequired,
}; };
export default cssModules(PublicFigureAvatar, styles); export default cssModules(PublicFigureAvatar, styles);

View file

@ -1,5 +1,6 @@
/* eslint-disable no-unused-vars */
import React from 'react'; import React from 'react';
// eslint-disable-next-line no-unused-vars
import { storiesOf, action, linkTo } from '@kadira/storybook'; import { storiesOf, action, linkTo } from '@kadira/storybook';
import { withKnobs, text, boolean, number } from '@kadira/storybook-addon-knobs'; import { withKnobs, text, boolean, number } from '@kadira/storybook-addon-knobs';
import withReadme from 'storybook-readme/with-readme'; import withReadme from 'storybook-readme/with-readme';
@ -12,15 +13,15 @@ const stories = storiesOf('PublicFigureAvatar', module);
stories.addDecorator(withKnobs); stories.addDecorator(withKnobs);
stories.addWithInfo( stories.addWithInfo(
'pony avatar', 'pony avatar',
'Description of the story', 'Description of the story',
withReadme(README, withReadme(README,
() => ( () => (
<PublicFigureAvatar publicFigure={{ picture: { url: text('image url', 'http://tinyurl.com/jucz8b9') } }} /> <PublicFigureAvatar publicFigure={{ picture: { url: text('image url', 'http://tinyurl.com/jucz8b9') } }} />
) )
) )
); );
stories.add('jake avatar', () => ( stories.add('jake avatar', () => (
<PublicFigureAvatar publicFigure={{ picture: { url: 'http://tinyurl.com/jb286jq' } }} /> <PublicFigureAvatar publicFigure={{ picture: { url: 'http://tinyurl.com/jb286jq' } }} />
)); ));

View file

@ -13,7 +13,6 @@ const parseSubjects = raw => pipe(
) )
)(raw.data); )(raw.data);
class SubjectAutocompleteInput extends Component { class SubjectAutocompleteInput extends Component {
static propTypes = { static propTypes = {
@ -57,23 +56,41 @@ class SubjectAutocompleteInput extends Component {
onSelection = compose(this.props.onSelection, head); onSelection = compose(this.props.onSelection, head);
render() { loadSuggestions = (typed) => {
return ( if (this.props.selected) this.props.onSelection(null);
<Typeahead if (typed.length) {
name="subject" getSubjectsAutocomplete(typed)
options={this.state.suggestions} .then((response) => {
selected={of(this.props.selected)} this.setState({
emptyLabel="Aucun sujet correspondante" suggestions: flattenAttributes(response.data.data),
labelKey="title" });
minLength={3} }); }
allowNew };
newSelectionPrefix="Ajouter "
onChange={this.onSelection} renderMenuItemChildren = (typeaheadProps, subject) => (
onInputChange={this.loadSuggestions} <div>
renderMenuItemChildren={this.renderMenuItemChildren} <p>{subject.title} </p>
/> <small>{take(100)(subject.presentation)}</small>
); </div>
} );
render() {
return (
<Typeahead
name="subject"
options={this.state.suggestions}
selected={of(this.props.selected)}
emptyLabel="Aucun sujet correspondante"
labelKey="title"
minLength={3}
allowNew
newSelectionPrefix="Ajouter "
onChange={this.onSelection}
onInputChange={this.loadSuggestions}
renderMenuItemChildren={this.renderMenuItemChildren}
/>
);
}
} }

View file

@ -1,4 +0,0 @@
import React from 'react';
const Empty = () => <span />;
export default Empty;

View file

@ -1,15 +1,15 @@
export default { export default {
root: '/', root: '/',
subjects: '/s', subjects: '/s',
publicFigures: '/p', publicFigures: '/p',
getFor: { getFor: {
subject: (s) => `/s/${s.slug}`, subject: s => `/s/${s.slug}`,
publicFigure: (pf) => `/p/${pf.slug}`, publicFigure: pf => `/p/${pf.slug}`,
}, },
manual: '/guide', manual: '/guide',
about: '/about', about: '/about',
contact: '/contact', contact: '/contact',
external: { external: {
twitter: 'https://twitter.com/debatsco', twitter: 'https://twitter.com/debatsco',
}, },
}; };

View file

@ -1,14 +1,14 @@
import { pipe, always } from 'ramda'; import { pipe } from 'ramda';
const print = message => x => { const print = message => (x) => {
if (!!message) console.warn(message); if (message) console.warn(message);
return x; return x;
}; };
const printWithPrefix = (prefix) => (x) => { const printWithPrefix = prefix => (x) => {
console.warn('-------------------------------'); console.warn('-------------------------------');
console.warn(`---- ${prefix}: `, x); console.warn(`---- ${prefix}: `, x);
return x; return x;
}; };
const printBefore = printWithPrefix('BEFORE'); const printBefore = printWithPrefix('BEFORE');
@ -16,5 +16,5 @@ const printAfter = printWithPrefix('AFTER');
export const withConsole = (f, message) => pipe(printBefore, print(message), f, printAfter); export const withConsole = (f, message) => pipe(printBefore, print(message), f, printAfter);
export const log = x => { console.log(x); return x; }; /* eslint no-console: 0 */ export const log = (x) => { console.log(x); return x; }; /* eslint no-console: 0 */
export const warn = x => { console.warn(x); return x; }; /* eslint no-console: 0 */ export const warn = (x) => { console.warn(x); return x; }; /* eslint no-console: 0 */

View file

@ -1,4 +1,5 @@
import { complement } from 'ramda'; import { complement } from 'ramda';
export const hasWindow = () => (typeof window !== 'undefined'); export const hasWindow = () => (typeof window !== 'undefined');
export const isClientSide = hasWindow; export const isClientSide = hasWindow;
export const isServerSide = complement(hasWindow); export const isServerSide = complement(hasWindow);

View file

@ -1,9 +1,6 @@
import 'react-hot-loader/patch'; // https://github.com/gaearon/redux-devtools/commit/64f58b7010a1b2a71ad16716eb37ac1031f93915 import 'react-hot-loader/patch'; // https://github.com/gaearon/redux-devtools/commit/64f58b7010a1b2a71ad16716eb37ac1031f93915
import 'babel-polyfill'; import 'babel-polyfill';
// First boot side effects
import './boot.js';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader'; import { AppContainer } from 'react-hot-loader';
@ -11,6 +8,10 @@ import { match } from 'react-router';
// Styles // Styles
import 'styles/_main.css'; import 'styles/_main.css';
// First boot side effects
import './boot.js';
// Redux // Redux
import { store, history } from './store'; // Redux store import { store, history } from './store'; // Redux store
import Root from './root'; // App root (Router, Provider, Hot reload ... import Root from './root'; // App root (Router, Provider, Hot reload ...
@ -18,31 +19,31 @@ import routes from './routes'; // React-router routes
// Init app function // Init app function
const loadApplication = (DOMElementId) => { const loadApplication = (DOMElementId) => {
const DOMElement = document.getElementById(DOMElementId); const DOMElement = document.getElementById(DOMElementId);
match({ history, routes, location }, () => { match({ history, routes }, () => {
ReactDOM.render( ReactDOM.render(
<AppContainer> <AppContainer>
<Root store={store} history={history} /> <Root store={store} history={history} />
</AppContainer>, </AppContainer>,
DOMElement DOMElement
); );
}); });
if (module.hot) { if (module.hot) {
module.hot.accept('./root/index.jsx', () => { module.hot.accept('./root/index.jsx', () => {
// If you use Webpack 2 in ES modules mode, you can // If you use Webpack 2 in ES modules mode, you can
// use <App /> here rather than require() a <NextApp />. // use <App /> here rather than require() a <NextApp />.
const NextApp = require('./root/index').default; const NextApp = require('./root/index').default;
ReactDOM.render( ReactDOM.render(
<AppContainer> <AppContainer>
<NextApp store={store} history={history} /> <NextApp store={store} history={history} />
</AppContainer>, </AppContainer>,
DOMElement DOMElement
); );
}); });
} }
}; };
export { store, history }; export { store, history };

View file

@ -3,31 +3,31 @@ import cssModules from 'react-css-modules';
import styles from './About.css'; import styles from './About.css';
const About = () => ( const About = () => (
<div> <div>
<h1>Pourquoi Débats.co ?</h1> <h1>Pourquoi Débats.co ?</h1>
<p> <p>
<i>Débats</i> est un projet francophone et participatif, ayant pour objectif doffrir une synthèse ouverte, impartiale et vérifiable, des sujets clivants de notre société. <i>Débats</i> est un projet francophone et participatif, ayant pour objectif doffrir une synthèse ouverte, impartiale et vérifiable, des sujets clivants de notre société.
</p> </p>
<p> <p>
Dans un monde de plus en plus complexe, les discours simplistes sont légion. Régulièrement, des controverses se construisent sur la base d'arguments à peine vérifiés, ou simplement invérifiables. Dans un monde de plus en plus complexe, les discours simplistes sont légion. Régulièrement, des controverses se construisent sur la base d'arguments à peine vérifiés, ou simplement invérifiables.
Cest ainsi que semblent se développer des idées confuses et des discussions stériles. Cest ainsi que semblent se développer des idées confuses et des discussions stériles.
</p> </p>
<p> <p>
L'intelligibilité du débat public et son accessibilité par tous ceux qui aspirent à y participer déterminent la qualité de notre système démocratique. Or, aujourdhui, aucun outil, simple dutilisation, ne permet daccéder à la pluralité des idées de celles et ceux qui font le choix de sexprimer publiquement. Prendre connaissance de leurs arguments, comprendre ce quils prétendent ou ce quils laissent entendre est un véritable parcours du combattant. Nous cédons ainsi régulièrement à la facilité, en accordant une valeur démesurée à des postures démagogiques ou en laissant à dautres le soin de les produire. L'intelligibilité du débat public et son accessibilité par tous ceux qui aspirent à y participer déterminent la qualité de notre système démocratique. Or, aujourdhui, aucun outil, simple dutilisation, ne permet daccéder à la pluralité des idées de celles et ceux qui font le choix de sexprimer publiquement. Prendre connaissance de leurs arguments, comprendre ce quils prétendent ou ce quils laissent entendre est un véritable parcours du combattant. Nous cédons ainsi régulièrement à la facilité, en accordant une valeur démesurée à des postures démagogiques ou en laissant à dautres le soin de les produire.
</p> </p>
<p> <p>
Pourtant, les ressources sont , sous nos yeux, elles existent ! Les blogs, les tweets, les journaux télévisés, les discours ou les livres sont un trésor démocratique laissé en jachère, une réserve politique fertile jusquici largement sous-exploitée. Ces traces essaimées tout autour de nous constituent une des clés pour y voir plus clair. Pourtant, les ressources sont , sous nos yeux, elles existent ! Les blogs, les tweets, les journaux télévisés, les discours ou les livres sont un trésor démocratique laissé en jachère, une réserve politique fertile jusquici largement sous-exploitée. Ces traces essaimées tout autour de nous constituent une des clés pour y voir plus clair.
</p> </p>
<p> <p>
Lambition de <i>Débats</i> est simple : recenser, sur chaque grand thème qui fait débat, les prises de position de celles et ceux qui décident de sengager. Cartographier lévolution de leur pensée, de leurs opinions, des combats quils ont menés. Donner à voir la constance, la progression ou lincohérence dun engagement dans le temps. Lambition de <i>Débats</i> est simple : recenser, sur chaque grand thème qui fait débat, les prises de position de celles et ceux qui décident de sengager. Cartographier lévolution de leur pensée, de leurs opinions, des combats quils ont menés. Donner à voir la constance, la progression ou lincohérence dun engagement dans le temps.
</p> </p>
<p> <p>
Rédigé par des volontaires sur une plateforme en ligne, et fonctionnant sur le principe du wiki, <i>Débats</i> a pour objectif d'offrir un contenu libre, impartial et vérifiable des prises de position de celles et ceux qui participent aux débats publics. Rédigé par des volontaires sur une plateforme en ligne, et fonctionnant sur le principe du wiki, <i>Débats</i> a pour objectif d'offrir un contenu libre, impartial et vérifiable des prises de position de celles et ceux qui participent aux débats publics.
</p> </p>
<p> <p>
Face à ce que nous considérons comme un dévoiement démocratique, nous partageons une conviction : linformation est notre première arme. Redonner à lengagement la crédibilité et la valeur qui doivent être les siennes : voilà la raison dêtre de <i>Débats</i>. Face à ce que nous considérons comme un dévoiement démocratique, nous partageons une conviction : linformation est notre première arme. Redonner à lengagement la crédibilité et la valeur qui doivent être les siennes : voilà la raison dêtre de <i>Débats</i>.
</p> </p>
</div> </div>
); );
export default cssModules(About, styles); export default cssModules(About, styles);

View file

@ -13,12 +13,12 @@ const Contact = () => (
<ul className="list-inline banner-social-buttons"> <ul className="list-inline banner-social-buttons">
<li> <li>
<a href="mailto:contact@debats.co" className="btn btn-default btn-lg"> <a href="mailto:contact@debats.co" className="btn btn-default btn-lg">
<i className="fa fa-envelope-o fa-fw"></i><span className="network-name">&nbsp;Courriel</span> <i className="fa fa-envelope-o fa-fw" /><span className="network-name">&nbsp;Courriel</span>
</a> </a>
</li> </li>
<li> <li>
<a href="https://twitter.com/debatsco" className="btn btn-default btn-lg"> <a href="https://twitter.com/debatsco" className="btn btn-default btn-lg">
<i className="fa fa-twitter fa-fw"></i> <span className="network-name">Twitter</span> <i className="fa fa-twitter fa-fw" /> <span className="network-name">Twitter</span>
</a> </a>
</li> </li>
</ul> </ul>

View file

@ -3,14 +3,14 @@ import cssModules from 'react-css-modules';
import styles from './Guide.css'; import styles from './Guide.css';
const Guide = () => ( const Guide = () => (
<div> <div>
<div className="col-md-1"></div> <div className="col-md-1" />
<div className="col-md-10"> <div className="col-md-10">
<h1>Mode d'emploi</h1> <h1>Mode d'emploi</h1>
<h2>Panel de discussion</h2> <h2>Panel de discussion</h2>
<p> <p>
La confiance La confiance
La plateforme Débats.co est basée sur la confiance. Cest-à-dire que le contenu est systématiquement considéré comme a priori valide, mais peut être retiré en cas de signalement. Plusieurs motifs peuvent pousser les autres membres à signaler votre contenu : La plateforme Débats.co est basée sur la confiance. Cest-à-dire que le contenu est systématiquement considéré comme a priori valide, mais peut être retiré en cas de signalement. Plusieurs motifs peuvent pousser les autres membres à signaler votre contenu :
Le contenu ne respecte pas les règles de rédaction Le contenu ne respecte pas les règles de rédaction
@ -18,142 +18,142 @@ const Guide = () => (
Le contenu pré-existe autrepart sur la plateforme Le contenu pré-existe autrepart sur la plateforme
</p> </p>
<h2>Comment éviter dêtre signalé ?</h2> <h2>Comment éviter dêtre signalé ?</h2>
<div className="panel-group" id="accordion"> <div className="panel-group" id="accordion">
<div className="panel panel-default"> <div className="panel panel-default">
<div className="panel-heading"> <div className="panel-heading">
<h2 className="panel-title"> <h2 className="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse1"> <a data-toggle="collapse" data-parent="#accordion" href="#collapse1">
Débats.co, quest-ce que cest ?</a> Débats.co, quest-ce que cest ?</a>
</h2> </h2>
</div> </div>
<div id="collapse1" className="panel-collapse collapse"> <div id="collapse1" className="panel-collapse collapse">
<div className="panel-body"> <div className="panel-body">
<p> <p>
Débats.co nest pas un site de débats. Débats.co nest pas un site de débats.
<br /> <br />
Débats.co a pour objectif de référencer et cartographier les positions prises par des personnalités publiques au cours des débats qui font controverse en France. Ainsi, les contributeurs ne sont pas appelés à donner leur avis mais plutôt à rechercher, collecter et recenser les positions exprimées sur un sujet particulier. Débats.co a pour objectif de référencer et cartographier les positions prises par des personnalités publiques au cours des débats qui font controverse en France. Ainsi, les contributeurs ne sont pas appelés à donner leur avis mais plutôt à rechercher, collecter et recenser les positions exprimées sur un sujet particulier.
Nous invitons les contributeurs de Débats.co à lire ce mode demploi et à sy référer en cas de doute lors de lutilisation du site. Nous invitons les contributeurs de Débats.co à lire ce mode demploi et à sy référer en cas de doute lors de lutilisation du site.
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<div className="panel panel-default"> <div className="panel panel-default">
<div className="panel-heading"> <div className="panel-heading">
<h2 className="panel-title"> <h2 className="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse2">Comment fonctionne Débats.co ?</a> <a data-toggle="collapse" data-parent="#accordion" href="#collapse2">Comment fonctionne Débats.co ?</a>
</h2> </h2>
</div> </div>
<div id="collapse2" className="panel-collapse collapse"> <div id="collapse2" className="panel-collapse collapse">
<div className="panel-body"> <div className="panel-body">
<p> <p>
Le premier usage de Débats.co consiste à consulter la plateforme, en explorant les différents sujets enregistrés et le positionnement des personnalités publiques au sein de ces sujets. Le premier usage de Débats.co consiste à consulter la plateforme, en explorant les différents sujets enregistrés et le positionnement des personnalités publiques au sein de ces sujets.
Le deuxième usage consiste à contribuer à la plateforme en y apportant le contenu nécessaire pour une synthèse des différents sujets et prises de positions. Le deuxième usage consiste à contribuer à la plateforme en y apportant le contenu nécessaire pour une synthèse des différents sujets et prises de positions.
<br /> <br />
Dans les deux cas, il est important de bien comprendre la structure du site. Sur Débats.co, linformation est articulée autour de plusieurs éléments : Dans les deux cas, il est important de bien comprendre la structure du site. Sur Débats.co, linformation est articulée autour de plusieurs éléments :
</p> </p>
<ul id="reputation"> <ul id="reputation">
<li>Le sujet</li> <li>Le sujet</li>
<li>La position</li> <li>La position</li>
<li>La personnalité</li> <li>La personnalité</li>
<li>La prise de position</li> <li>La prise de position</li>
<li>Largument</li> <li>Largument</li>
<li>La source</li> <li>La source</li>
</ul> </ul>
<p> <p>
Chaque <b>sujet</b> comporte au moins deux <b>positions</b>. Une <b>position</b> peut être "prise" par plusieurs <b>personnalités</b>. A chaque <b>prise de position</b> sont attachés une <b>personnalité</b> et un ensemble d<b>arguments</b>. Pour que la <b>prise de position</b> puisse être recensée, elle doit être justifiée par au moins une <b>source</b>. Chaque <b>sujet</b> comporte au moins deux <b>positions</b>. Une <b>position</b> peut être "prise" par plusieurs <b>personnalités</b>. A chaque <b>prise de position</b> sont attachés une <b>personnalité</b> et un ensemble d<b>arguments</b>. Pour que la <b>prise de position</b> puisse être recensée, elle doit être justifiée par au moins une <b>source</b>.
<br /> <br />
En somme, pour quun <b>sujet</b> puisse être référencé, il est nécessaire d'y recenser au moins une <b>prise de position</b> par au moins une <b>personnalité</b> dans chacune des <b>positions</b>. En somme, pour quun <b>sujet</b> puisse être référencé, il est nécessaire d'y recenser au moins une <b>prise de position</b> par au moins une <b>personnalité</b> dans chacune des <b>positions</b>.
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<div className="panel panel-default"> <div className="panel panel-default">
<div className="panel-heading"> <div className="panel-heading">
<h2 className="panel-title"> <h2 className="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse5"> <a data-toggle="collapse" data-parent="#accordion" href="#collapse5">
Qu'est-ce qu'une source ?</a> Qu'est-ce qu'une source ?</a>
</h2> </h2>
</div> </div>
<div id="collapse5" className="panel-collapse collapse"> <div id="collapse5" className="panel-collapse collapse">
<div className="panel-body"> <div className="panel-body">
<p> <p>
Une <b>source</b> est un élément permettant dappuyer une prise de position. La source peut être constituée à partir de toute référence audio, vidéo, article, ou livre dont la diffusion est, ou a été, publique. Une <b>source</b> est un élément permettant dappuyer une prise de position. La source peut être constituée à partir de toute référence audio, vidéo, article, ou livre dont la diffusion est, ou a été, publique.
Il sera demandé au contributeur de retranscrire la citation qui soutient le propos en question, la date à laquelle ce propos a été tenu, et, si possible, de fournir un lien qui permet d'y accéder directement. Il sera demandé au contributeur de retranscrire la citation qui soutient le propos en question, la date à laquelle ce propos a été tenu, et, si possible, de fournir un lien qui permet d'y accéder directement.
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<div className="panel panel-default"> <div className="panel panel-default">
<div className="panel-heading"> <div className="panel-heading">
<h2 className="panel-title"> <h2 className="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse3">Comment référencer et recenser un contenu de qualité ?</a> <a data-toggle="collapse" data-parent="#accordion" href="#collapse3">Comment référencer et recenser un contenu de qualité ?</a>
</h2> </h2>
</div> </div>
<div id="collapse3" className="panel-collapse collapse"> <div id="collapse3" className="panel-collapse collapse">
<div className="panel-body"> <div className="panel-body">
<p> <p>
<b> <b>
Préexistence d'un contenu : Préexistence d'un contenu :
</b> </b>
<br /> <br />
Tout d'abord, le contributeur peut se demander si le contenu qu'il s'apprête à référencer existe déjà. Tout d'abord, le contributeur peut se demander si le contenu qu'il s'apprête à référencer existe déjà.
Lorsqu'il référence un nouveau sujet, il peut vérifier si celui-ci est déjà traité, ou partiellement traité, dans un autre sujet : auquel cas, il pourra enrichir ou modifier le débat déjà existant. Lorsqu'il référence un nouveau sujet, il peut vérifier si celui-ci est déjà traité, ou partiellement traité, dans un autre sujet : auquel cas, il pourra enrichir ou modifier le débat déjà existant.
<br /> <br />
Lors du recensement d'une prise de position, l'exercice est similaire : il pourra vérifier la pré-existence d'une prise de position pour la personnalité en question, et l'enrichir avec de nouvelles sources le cas échant. Les arguments, matérialisés sur Débats.co par des tags, peuvent être infinis. Le formulaire vous suggérera néanmoins les tags proposés par d'autres utilisateurs. Lors du recensement d'une prise de position, l'exercice est similaire : il pourra vérifier la pré-existence d'une prise de position pour la personnalité en question, et l'enrichir avec de nouvelles sources le cas échant. Les arguments, matérialisés sur Débats.co par des tags, peuvent être infinis. Le formulaire vous suggérera néanmoins les tags proposés par d'autres utilisateurs.
<br /> <br />
<br /> <br />
<b> <b>
Notoriété et véracité : Notoriété et véracité :
</b> </b>
<br /> <br />
Il est nécessaire pour le contributeur de se demander si le contenu est suffisamment important, fiable et pertinent pour être référencé ou recensé sur Débats.co. Basé sur une logique collaborative, le site comporte plusieurs mécanismes d'autorégulation, permettant déviter la publication de contenu considérés comme fallacieux ou hors-sujet. Chaque entrée, notamment lors du référencement dune nouvelle personnalité ou dun nouveau sujet, sont soumis à l'approbation des utilisateurs expérimentés de la plateforme. Il est nécessaire pour le contributeur de se demander si le contenu est suffisamment important, fiable et pertinent pour être référencé ou recensé sur Débats.co. Basé sur une logique collaborative, le site comporte plusieurs mécanismes d'autorégulation, permettant déviter la publication de contenu considérés comme fallacieux ou hors-sujet. Chaque entrée, notamment lors du référencement dune nouvelle personnalité ou dun nouveau sujet, sont soumis à l'approbation des utilisateurs expérimentés de la plateforme.
<br /> <br />
<br /> <br />
<b>Neutralité et cohérence du point de vue</b> <b>Neutralité et cohérence du point de vue</b>
<br /> <br />
Parler de "cohérence" lorsque l'on tente de référencer des sujets controversées est presque en soi contradictoire. Débats.co a néanmoins comme volonté de tendre un maximum vers l'objectivité. Parler de "cohérence" lorsque l'on tente de référencer des sujets controversées est presque en soi contradictoire. Débats.co a néanmoins comme volonté de tendre un maximum vers l'objectivité.
<br /> <br />
Afin de s'en rapprocher, il est nécessaire pour chaque utilisateur de se demander comment exprimer de façon objective lintitulé des sujets ou les positions des personnalités. Dans un premier temps, et pour simplifier cette démarche "objective", les sujets ainsi que les pages de personnalités peuvent être référencées directement depuis la base de données de Wikipedia. Il suffira donc, après la composition des premières lettres de l'intitulé de la personnalité ou du sujet, de sélectionner l'élément correspondant. Afin de s'en rapprocher, il est nécessaire pour chaque utilisateur de se demander comment exprimer de façon objective lintitulé des sujets ou les positions des personnalités. Dans un premier temps, et pour simplifier cette démarche "objective", les sujets ainsi que les pages de personnalités peuvent être référencées directement depuis la base de données de Wikipedia. Il suffira donc, après la composition des premières lettres de l'intitulé de la personnalité ou du sujet, de sélectionner l'élément correspondant.
<br /> <br />
Lors de la recension d'une prise d'une position ensuite, il est important d'attacher à la source une citation qui justifie la prise de position. La citation doit être isolée, entre guillemets, ne pas être modifiée, et se suffir à elle-même pour être comprise (ne pas être sortie d'un contexte qui peut altérer sa compréhensioin). Pour être valide dans le cadre dun sujet, un argument doit être explicitement mobilisé par la personnalité publique et répondre à la problématique initiale du débat. Lors de la recension d'une prise d'une position ensuite, il est important d'attacher à la source une citation qui justifie la prise de position. La citation doit être isolée, entre guillemets, ne pas être modifiée, et se suffir à elle-même pour être comprise (ne pas être sortie d'un contexte qui peut altérer sa compréhensioin). Pour être valide dans le cadre dun sujet, un argument doit être explicitement mobilisé par la personnalité publique et répondre à la problématique initiale du débat.
<br /> <br />
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<div className="panel panel-default"> <div className="panel panel-default">
<div className="panel-heading"> <div className="panel-heading">
<h2 className="panel-title"> <h2 className="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse4">Comment avoir accès à toutes les fonctions de Débats.co ?</a> <a data-toggle="collapse" data-parent="#accordion" href="#collapse4">Comment avoir accès à toutes les fonctions de Débats.co ?</a>
</h2> </h2>
</div> </div>
<div id="collapse4" className="panel-collapse collapse"> <div id="collapse4" className="panel-collapse collapse">
<div className="panel-body"> <div className="panel-body">
<p> <p>
La contribution au site est simple. Toutefois, avant de pouvoir accéder à toutes les fonctions de référencement, de recension et de modification, une phase dapprentissage est nécessaire. Pour apprendre, il faut sexercer. Ainsi, pour chaque contribution au site, un nombre de points est attribué à lutilisateur. Laccumulation de ces points lui donne progressivement accès aux fonctionnalités les plus puissantes de Débats.co. Le signalement par d'autres utilisateurs de la violation des principes inscrits dans ce mode d'emploi peut entraîner la perte de points. Ainsi 5 status existent sur Débats.co : La contribution au site est simple. Toutefois, avant de pouvoir accéder à toutes les fonctions de référencement, de recension et de modification, une phase dapprentissage est nécessaire. Pour apprendre, il faut sexercer. Ainsi, pour chaque contribution au site, un nombre de points est attribué à lutilisateur. Laccumulation de ces points lui donne progressivement accès aux fonctionnalités les plus puissantes de Débats.co. Le signalement par d'autres utilisateurs de la violation des principes inscrits dans ce mode d'emploi peut entraîner la perte de points. Ainsi 5 status existent sur Débats.co :
</p> </p>
<ul id="reputation"> <ul id="reputation">
<li><b>Le Métèque</b> : Son score est neutre. Il vient d'arriver sur la plateforme, et doit encore faire ses preuves. Il peut soumettre des arguments et des sources. </li> <li><b>Le Métèque</b> : Son score est neutre. Il vient d'arriver sur la plateforme, et doit encore faire ses preuves. Il peut soumettre des arguments et des sources. </li>
<li><b>L'Eloquent</b> : Son score est positif, il peut ajouter de nouvelles personnalités, de nouveau sujets, mais également suggérer l'approbation ou le rejet de modifications proposées par d'autres utilisateurs. Par défaut, tous les membres fondateurs inscrits lors de la bêta de Débats.co sont considérés comme Eloquents.</li> <li><b>L'Eloquent</b> : Son score est positif, il peut ajouter de nouvelles personnalités, de nouveau sujets, mais également suggérer l'approbation ou le rejet de modifications proposées par d'autres utilisateurs. Par défaut, tous les membres fondateurs inscrits lors de la bêta de Débats.co sont considérés comme Eloquents.</li>
<li><b>L'Idéaliste</b> : Son score est élevé, il peut instantanément supprimer la plupart des modifications faites par d'autres utilisateurs.</li> <li><b>L'Idéaliste</b> : Son score est élevé, il peut instantanément supprimer la plupart des modifications faites par d'autres utilisateurs.</li>
<li><b>Le Sophiste</b> : son score est négatif, il a tout autant de droits que le nouvel arrivant, mais part avec autant de points de retard pour redevenir Métèque. </li> <li><b>Le Sophiste</b> : son score est négatif, il a tout autant de droits que le nouvel arrivant, mais part avec autant de points de retard pour redevenir Métèque. </li>
</ul> </ul>
</div>
</div>
</div>
</div> </div>
</div>
</div> </div>
<div className="col-md-1"></div> </div>
</div> </div>
<div className="col-md-1" />
</div>
); );
export default cssModules(Guide, styles); export default cssModules(Guide, styles);

View file

@ -6,40 +6,40 @@ import PublicFigureAvatar from 'components/PublicFigureAvatar';
const PublicFigureAvatarMapper = pf => <PublicFigureAvatar key={pf.id} publicFigure={pf} />; const PublicFigureAvatarMapper = pf => <PublicFigureAvatar key={pf.id} publicFigure={pf} />;
const renderAssociatedPublicFigures = compose( const renderAssociatedPublicFigures = compose(
map(PublicFigureAvatarMapper), map(PublicFigureAvatarMapper),
take(5), take(5),
prop('remarquablePublicFigures'), prop('remarquablePublicFigures'),
); );
const HomeSubject = ({ subject }) => ( const HomeSubject = ({ subject }) => (
<li> <li>
<tr> <tr>
<td style={{ width: '50%', border: 'none', textTransform: 'uppercase' }}> <td style={{ width: '50%', border: 'none', textTransform: 'uppercase' }}>
<h2 className="subjects-title"> <h2 className="subjects-title">
<Link to={paths.getFor.subject(subject)}> <Link to={paths.getFor.subject(subject)}>
{subject.title} {subject.title}
</Link> </Link>
</h2> </h2>
<h6 className="count"> <h6 className="count">
{`${subject.remarquablePublicFigures.length} personnalité(s)`} {`${subject.remarquablePublicFigures.length} personnalité(s)`}
</h6> </h6>
</td> </td>
<td style={{ width: '50%', textAlign: 'center', verticalAlign: 'middle' }}> <td style={{ width: '50%', textAlign: 'center', verticalAlign: 'middle' }}>
{renderAssociatedPublicFigures(subject)} {renderAssociatedPublicFigures(subject)}
</td> </td>
<td className="seemore"> <td className="seemore">
<div> <div>
<Link to={paths.getFor.subject(subject)}>Voir plus de personnalités</Link> <Link to={paths.getFor.subject(subject)}>Voir plus de personnalités</Link>
</div> </div>
</td> </td>
</tr> </tr>
</li> </li>
); );
HomeSubject.propTypes = { HomeSubject.propTypes = {
subject: PropTypes.shape({ subject: PropTypes.shape({
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
remarquablePublicFigures: PropTypes.arrayOf(PropTypes.object).isRequired, remarquablePublicFigures: PropTypes.arrayOf(PropTypes.object).isRequired,
}).isRequired, }).isRequired,
}; };
export default (HomeSubject); export default (HomeSubject);

View file

@ -3,11 +3,11 @@ import { getHomeSubjectsWithRelations } from 'store/selectors';
import { onHottestSubjectsAccess } from 'store/actions/entities'; import { onHottestSubjectsAccess } from 'store/actions/entities';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
subjects: getHomeSubjectsWithRelations(state), subjects: getHomeSubjectsWithRelations(state),
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
onAccess: () => dispatch(onHottestSubjectsAccess()), onAccess: () => dispatch(onHottestSubjectsAccess()),
}); });
export default connect(mapStateToProps, mapDispatchToProps); export default connect(mapStateToProps, mapDispatchToProps);

View file

@ -4,26 +4,26 @@ import connector from './connectors';
class HomeSubjects extends Component { class HomeSubjects extends Component {
static propTypes = { static propTypes = {
subjects: PropTypes.arrayOf(PropTypes.object).isRequired, subjects: PropTypes.arrayOf(PropTypes.object).isRequired,
onAccess: PropTypes.func.isRequired, onAccess: PropTypes.func.isRequired,
} }
componentWillMount() { componentWillMount() {
this.props.onAccess(); this.props.onAccess();
} }
render() { render() {
if (!this.props.subjects) return <span>loading subjects ...</span>; if (!this.props.subjects) return <span>loading subjects ...</span>;
return ( return (
<div> {/* TODO Bootstrap */} <div> {/* TODO Bootstrap */}
{this.props.subjects.map( {this.props.subjects.map(
s => <HomeSubject key={s.id} subject={s} /> s => <HomeSubject key={s.id} subject={s} />
)} )}
</div> </div>
); );
} }
} }

View file

@ -1,37 +1,37 @@
import React from 'react'; import React from 'react';
import cssModules from 'react-css-modules'; import cssModules from 'react-css-modules';
import HomeSubjects from './HomeSubjects';
import LastStatements from 'components/LastStatements'; import LastStatements from 'components/LastStatements';
import HomeSubjects from './HomeSubjects';
import bgSrc from './images/intro-bg.jpg'; import bgSrc from './images/intro-bg.jpg';
import styles from './Home.css'; import styles from './Home.css';
const Home = () => ( const Home = () => (
<div className="container-fluid" styleName="container"> <div className="container-fluid" styleName="container">
<div className="row" styleName="background-title" style={{ backgroundImage: `url(${bgSrc})` }}> <div className="row" styleName="background-title" style={{ backgroundImage: `url(${bgSrc})` }}>
<div styleName="title-mask"> <div styleName="title-mask">
<h5 >Bienvenue sur Débats.co</h5> <h5 >Bienvenue sur Débats.co</h5>
<div styleName="introduction"> <div styleName="introduction">
<span>Débats est un projet francophone et participatif, ayant pour objectif </span> <span>Débats est un projet francophone et participatif, ayant pour objectif </span>
<span>doffrir une synthèse ouverte, impartiale et vérifiable, des sujets clivants de notre société.</span> <span>doffrir une synthèse ouverte, impartiale et vérifiable, des sujets clivants de notre société.</span>
</div>
</div>
</div> </div>
<div className="col-md-1"></div> </div>
<div className="subjects-index col-md-7 subjects-home">
<h1>Sujets d'actualité</h1>
<table className="table">
<tbody>
<ul id="subject">
<HomeSubjects />
</ul>
</tbody>
</table>
</div>
<div className="col-md-3 col-centered" style={{ textAlign: 'right' }} >
<LastStatements />
</div>
<div className="col-md-1"></div>
</div> </div>
<div className="col-md-1" />
<div className="subjects-index col-md-7 subjects-home">
<h1>Sujets d'actualité</h1>
<table className="table">
<tbody>
<ul id="subject">
<HomeSubjects />
</ul>
</tbody>
</table>
</div>
<div className="col-md-3 col-centered" style={{ textAlign: 'right' }} >
<LastStatements />
</div>
<div className="col-md-1" />
</div>
); );
export default cssModules(Home, styles); export default cssModules(Home, styles);

View file

@ -1,55 +1,47 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import paths from '../../../constants/paths'; import cssModules from 'react-css-modules';
import { Link } from 'react-router'; import { Link } from 'react-router';
import paths from 'constants/paths';
import PublicFigureAvatar from 'components/PublicFigureAvatar'; import PublicFigureAvatar from 'components/PublicFigureAvatar';
import AssociatedSubjects from 'components/AssociatedSubjects'; import AssociatedSubjects from 'components/AssociatedSubjects';
import PublicFigureStyle from './PublicFigure.css'; import PublicFigureStyle from './PublicFigure.css';
import cssModules from 'react-css-modules';
const PublicFigureInList = ({ publicFigure }) => ( const PublicFigureInList = ({ publicFigure }) => (
<table className="table"> <table className="table">
<tbody> <tbody>
<ul id="subject"> <ul id="subject">
<li> <li>
<tr> <tr>
<td> <td>
<PublicFigureAvatar publicFigure={publicFigure} /> <PublicFigureAvatar publicFigure={publicFigure} />
</td> </td>
<td style= {{ width: '33%' }}> <td style={{ width: '33%' }}>
<h2 className="figure-title" style={{ color: '#333333 !important' }}> <h2 className="figure-title" style={{ color: '#333333 !important' }}>
<Link to={paths.getFor.subject(publicFigure)}> <Link to={paths.getFor.subject(publicFigure)}>
{publicFigure.name} {publicFigure.name}
</Link> </Link>
</h2> </h2>
<AssociatedSubjects publicFigure={publicFigure} /> <AssociatedSubjects publicFigure={publicFigure} />
</td> </td>
<td styleName="presentationWrapper"> <td styleName="presentationWrapper">
<p className="figure-presentation-text" styleName="presentation"> <p className="figure-presentation-text" styleName="presentation">
{publicFigure.presentation} {publicFigure.presentation}
</p> </p>
</td> </td>
</tr> </tr>
</li> </li>
</ul> </ul>
</tbody> </tbody>
</table> </table>
); );
PublicFigureInList.propTypes = { PublicFigureInList.propTypes = {
publicFigure: PropTypes.object.isRequired, publicFigure: PropTypes.object.isRequired,
}; };
export default cssModules(PublicFigureInList, PublicFigureStyle); export default cssModules(PublicFigureInList, PublicFigureStyle);
/*
<script type="text/javascript">
$(document).ready(function() {
$(".figure-presentation-text").shorten();
});
</script>
*/

View file

@ -3,11 +3,11 @@ import { getPublicFiguresWithRelations } from 'store/selectors';
import { onPublicFiguresListAccess } from 'store/actions/entities'; import { onPublicFiguresListAccess } from 'store/actions/entities';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
publicFigures: getPublicFiguresWithRelations(state), publicFigures: getPublicFiguresWithRelations(state),
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
onAccess: () => dispatch(onPublicFiguresListAccess()), onAccess: () => dispatch(onPublicFiguresListAccess()),
}); });
export default connect(mapStateToProps, mapDispatchToProps); export default connect(mapStateToProps, mapDispatchToProps);

View file

@ -1,37 +1,38 @@
import React, { PropTypes, Component } from 'react'; import React, { PropTypes, Component } from 'react';
import LastStatements from 'components/LastStatements';
import PublicFigureInList from './PublicFigureInList'; import PublicFigureInList from './PublicFigureInList';
import connect from './connector'; import connect from './connector';
import LastStatements from 'components/LastStatements';
class PublicFigures extends Component { class PublicFigures extends Component {
static propTypes = { static propTypes = {
publicFigures: PropTypes.arrayOf(PropTypes.object).isRequired, publicFigures: PropTypes.arrayOf(PropTypes.object).isRequired,
onAccess: PropTypes.func.isRequired, onAccess: PropTypes.func.isRequired,
} }
componentWillMount() { componentWillMount() {
this.props.onAccess(); this.props.onAccess();
} }
render() { render() {
if (!this.props.publicFigures) return <span>loading public figures ...</span>; if (!this.props.publicFigures) return <span>loading public figures ...</span>;
const renderChilds = () => this.props.publicFigures.map( const renderChilds = () => this.props.publicFigures.map(
pf => <PublicFigureInList key={pf.id} publicFigure={pf} /> pf => <PublicFigureInList key={pf.id} publicFigure={pf} />
); );
return ( return (
<div> <div>
<div className="col-md-9"> <div className="col-md-9">
{renderChilds()} {renderChilds()}
</div> </div>
<div className="col-md-3"> <div className="col-md-3">
<LastStatements /> <LastStatements />
</div> </div>
</div> </div>
); );
} }
} }
export default connect(PublicFigures); export default connect(PublicFigures);

View file

@ -4,14 +4,14 @@ import { Router } from 'react-router';
import routes from '../routes'; import routes from '../routes';
const Root = ({ store, history }) => ( const Root = ({ store, history }) => (
<Provider store={store}> <Provider store={store}>
<Router history={history} routes={routes} /> <Router history={history} routes={routes} />
</Provider> </Provider>
); );
Root.propTypes = { Root.propTypes = {
store: PropTypes.object.isRequired, store: PropTypes.object.isRequired,
history: PropTypes.object.isRequired, history: PropTypes.object.isRequired,
}; };
export default Root; export default Root;

View file

@ -1,81 +0,0 @@
import React from 'react';
import { Route, IndexRoute } from 'react-router';
// Main Page
import Main from 'Main/index';
export default (
<Route path="/" component={Main}>
<IndexRoute
getComponent={(nextState, done) => {
require.ensure([], require => {
done(null, require('pages/Home').default);
}, 'home');
}}
/>
<Route
path="contact"
name="contact"
getComponent={(nextState, done) => {
require.ensure([], require => {
done(null, require('pages/Contact').default);
}, 'contact');
}}
/>
<Route
path="about"
name="about"
getComponent={(nextState, done) => {
require.ensure([], require => {
done(null, require('pages/About').default);
}, 'about');
}}
/>
<Route
path="guide"
name="guide"
getComponent={(nextState, done) => {
require.ensure([], require => {
done(null, require('pages/Guide').default);
}, 'guide');
}}
/>
<Route path="s" name="subjects">
<IndexRoute
name="subjects.index"
getComponent={(nextState, done) => {
require.ensure([], require => {
done(null, require('pages/Subjects').default);
}, 'subjects.index');
}}
/>
<Route
name="subjects.page"
path=":subjectSlug"
getComponent={(nextState, done) => {
require.ensure([], require => {
done(null, require('pages/Subject').default);
}, 'subjects.page');
}}
/>
</Route>
<Route path="p" name="publicFigures">
<IndexRoute
name="publicFigures.index"
getComponent={(nextState, done) => {
require.ensure([], require => {
done(null, require('pages/PublicFigures').default);
}, 'publicFigures.index');
}}
/>
<Route
name="publicFigures.page"
path=":publicFigureSlug"
getComponent={(nextState, done) => {
require.ensure([], require => {
done(null, require('pages/publicFigure').default);
}, 'publicFigures.page');
}}
/>
</Route>
</Route>
);

82
src/routes/index.jsx Normal file
View file

@ -0,0 +1,82 @@
import React from 'react';
import { Route, IndexRoute } from 'react-router';
// Main Page
import Main from 'Main/index';
export default (
<Route path="/" component={Main}>
<IndexRoute
getComponent={(nextState, done) => {
require.ensure([], (require) => {
done(null, require('pages/Home').default);
}, 'home');
}}
/>
<Route
path="contact"
name="contact"
getComponent={(nextState, done) => {
require.ensure([], (require) => {
done(null, require('pages/Contact').default);
}, 'contact');
}}
/>
<Route
path="about"
name="about"
getComponent={(nextState, done) => {
require.ensure([], (require) => {
done(null, require('pages/About').default);
}, 'about');
}}
/>
<Route
path="guide"
name="guide"
getComponent={(nextState, done) => {
require.ensure([], (require) => {
done(null, require('pages/Guide').default);
}, 'guide');
}}
/>
<Route path="s" name="subjects">
<IndexRoute
name="subjects.index"
getComponent={(nextState, done) => {
require.ensure([], (require) => {
done(null, require('pages/Subjects').default);
}, 'subjects.index');
}}
/>
<Route
name="subjects.page"
path=":subjectSlug"
getComponent={(nextState, done) => {
require.ensure([], (require) => {
done(null, require('pages/Subject').default);
}, 'subjects.page');
}}
/>
</Route>
<Route path="p" name="publicFigures">
<IndexRoute
name="publicFigures.index"
getComponent={(nextState, done) => {
require.ensure([], (require) => {
done(null, require('pages/PublicFigures').default);
}, 'publicFigures.index');
}}
/>
<Route
name="publicFigures.page"
path=":publicFigureSlug"
getComponent={(nextState, done) => {
require.ensure([], (require) => {
done(null, require('pages/publicFigure').default);
}, 'publicFigures.page');
}}
/>
</Route>
</Route>
);

View file

@ -1,27 +1,27 @@
import types from '../actions_types'; import types from '../actions_types';
export const onAddStatementPublicFigureSelection = (id) => ({ export const onAddStatementPublicFigureSelection = id => ({
type: types.ADD_STATEMENT_PUBLIC_FIGURE_SELECTION, type: types.ADD_STATEMENT_PUBLIC_FIGURE_SELECTION,
id id,
}); });
export const onAddStatementSubjectSelection = (id) => ({ export const onAddStatementSubjectSelection = id => ({
type: types.ADD_STATEMENT_SUBJECT_SELECTION, type: types.ADD_STATEMENT_SUBJECT_SELECTION,
id id,
}); });
export const onAddStatementPositionSelection = (id) => ({ export const onAddStatementPositionSelection = id => ({
type: types.ADD_STATEMENT_POSITION_SELECTION, type: types.ADD_STATEMENT_POSITION_SELECTION,
id id,
}); });
export const onAddStatementUpdateEvidenceUrl = (url) => ({ export const onAddStatementUpdateEvidenceUrl = url => ({
type: types.ADD_STATEMENT_UPDATE_EVIDENCE_URL, type: types.ADD_STATEMENT_UPDATE_EVIDENCE_URL,
url url,
}); });
export const onAddStatementUpdateEvidenceFile = (file) => ({ export const onAddStatementUpdateEvidenceFile = file => ({
type: types.ADD_STATEMENT_UPDATE_EVIDENCE_FILE, type: types.ADD_STATEMENT_UPDATE_EVIDENCE_FILE,
file file,
}); });
export const onAddStatementValidate = () => ({ export const onAddStatementValidate = () => ({
type: types.ADD_STATEMENT_VALIDATE, type: types.ADD_STATEMENT_VALIDATE,
}); });

View file

@ -1,22 +1,22 @@
import actionsTypes from '../actions_types'; import actionsTypes from '../actions_types';
export const onLastStatementsAccess = () => ({ export const onLastStatementsAccess = () => ({
type: actionsTypes.ENTITY_ACCESS, type: actionsTypes.ENTITY_ACCESS,
accessType: 'list', accessType: 'list',
entityType: 'statements', entityType: 'statements',
listType: 'latest', listType: 'latest',
}); });
export const onHottestSubjectsAccess = () => ({ export const onHottestSubjectsAccess = () => ({
type: actionsTypes.ENTITY_ACCESS, type: actionsTypes.ENTITY_ACCESS,
accessType: 'list', accessType: 'list',
entityType: 'subjects', entityType: 'subjects',
listType: 'hottest', listType: 'hottest',
}); });
export const onPublicFiguresListAccess = () => ({ export const onPublicFiguresListAccess = () => ({
type: actionsTypes.ENTITY_ACCESS, type: actionsTypes.ENTITY_ACCESS,
accessType: 'list', accessType: 'list',
entityType: 'publicFigures', entityType: 'publicFigures',
listType: 'all', listType: 'all',
}); });

View file

@ -1,11 +1,11 @@
export default { export default {
ENTITY_ACCESS: 'ENTITY_ACCESS', ENTITY_ACCESS: 'ENTITY_ACCESS',
ENTITY_READ: 'ENTITY_READ', ENTITY_READ: 'ENTITY_READ',
ADD_STATEMENT_NEXT_STEP: 'ADD_STATEMENT_NEXT_STEP', ADD_STATEMENT_NEXT_STEP: 'ADD_STATEMENT_NEXT_STEP',
ADD_STATEMENT_PUBLIC_FIGURE_SELECTION: 'ADD_STATEMENT_PUBLIC_FIGURE_SELECTION', ADD_STATEMENT_PUBLIC_FIGURE_SELECTION: 'ADD_STATEMENT_PUBLIC_FIGURE_SELECTION',
ADD_STATEMENT_SUBJECT_SELECTION: 'ADD_STATEMENT_SUBJECT_SELECTION', ADD_STATEMENT_SUBJECT_SELECTION: 'ADD_STATEMENT_SUBJECT_SELECTION',
ADD_STATEMENT_POSITION_SELECTION: 'ADD_STATEMENT_POSITION_SELECTION', ADD_STATEMENT_POSITION_SELECTION: 'ADD_STATEMENT_POSITION_SELECTION',
ADD_STATEMENT_UPDATE_EVIDENCE_URL: 'ADD_STATEMENT_UPDATE_EVIDENCE_URL', ADD_STATEMENT_UPDATE_EVIDENCE_URL: 'ADD_STATEMENT_UPDATE_EVIDENCE_URL',
ADD_STATEMENT_UPDATE_EVIDENCE_FILE: 'ADD_STATEMENT_UPDATE_EVIDENCE_FILE', ADD_STATEMENT_UPDATE_EVIDENCE_FILE: 'ADD_STATEMENT_UPDATE_EVIDENCE_FILE',
ADD_STATEMENT_VALIDATE: 'ADD_STATEMENT_PUBLIC_FIGURE_SELECTION', ADD_STATEMENT_VALIDATE: 'ADD_STATEMENT_VALIDATE',
}; };

View file

@ -1,16 +1,15 @@
import { createStore, applyMiddleware, combineReducers, compose } from 'redux'; import { createStore, applyMiddleware, combineReducers, compose } from 'redux';
import createSagaMiddleware from 'redux-saga'; import createSagaMiddleware from 'redux-saga';
import { routerReducer, routerMiddleware } from 'react-router-redux'; import { routerReducer, routerMiddleware, syncHistoryWithStore } from 'react-router-redux';
import { map } from 'ramda'; import { map } from 'ramda';
import { isClientSide } from 'helpers/env'; import { isClientSide } from 'helpers/env';
import rootSaga from './sagas';
import { entitiesReducer, addStatementReducer } from './reducers';
import { useRouterHistory } from 'react-router'; import { useRouterHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import createHashHistory from 'history/lib/createHashHistory'; import createHashHistory from 'history/lib/createHashHistory';
import createMemoryHistory from 'history/lib/createMemoryHistory'; import createMemoryHistory from 'history/lib/createMemoryHistory';
import rootSaga from './sagas';
import { entitiesReducer, addStatementReducer } from './reducers';
// Build history // Build history
const createHistory = isClientSide() ? createHashHistory : createMemoryHistory; const createHistory = isClientSide() ? createHashHistory : createMemoryHistory;
// const browserHistory = useScroll(useRouterHistory(createHistory))(); // const browserHistory = useScroll(useRouterHistory(createHistory))();
@ -21,31 +20,31 @@ const initialState = isClientSide() ? window.__INITIAL_STATE__ : {};
// REDUCERS // REDUCERS
const reducer = combineReducers({ const reducer = combineReducers({
routing: routerReducer, routing: routerReducer,
entities: entitiesReducer, entities: entitiesReducer,
addStatement: addStatementReducer, addStatement: addStatementReducer,
}); });
// MIDDLEWARE // MIDDLEWARE
const sagaMiddleware = createSagaMiddleware(); const sagaMiddleware = createSagaMiddleware();
const middlewares = [routerMiddleware(browserHistory), sagaMiddleware]; const middlewares = [routerMiddleware(browserHistory), sagaMiddleware];
if (isClientSide() && process.env.NODE_ENV !== 'production') { if (isClientSide() && process.env.NODE_ENV !== 'production') {
middlewares.push(require('redux-logger')({ middlewares.push(require('redux-logger')({
stateTransformer: map(state => ((state && state.toJS) ? state.toJS() : state)), stateTransformer: map(state => ((state && state.toJS) ? state.toJS() : state)),
timestamp: true, timestamp: true,
duration: true, duration: true,
collapsed: true, collapsed: true,
predicate: (getState, action) => action.type !== 'APP_LOG', predicate: (getState, action) => action.type !== 'APP_LOG',
})); }));
} }
// STORE // STORE
const store = createStore( const store = createStore(
reducer, reducer,
initialState, initialState,
compose( compose(
applyMiddleware(...middlewares), applyMiddleware(...middlewares),
window.devToolsExtension ? window.devToolsExtension() : f => f, window.devToolsExtension ? window.devToolsExtension() : f => f,
) )
); );
const history = syncHistoryWithStore(browserHistory, store); const history = syncHistoryWithStore(browserHistory, store);

View file

@ -4,42 +4,43 @@ import {
import actionsTypes from '../actions_types'; import actionsTypes from '../actions_types';
const initialState = { const initialState = {
publicFigureId: null, publicFigureId: null,
subjectId: null, subjectId: null,
positionId: null, positionId: null,
statementDate: null, statementDate: null,
evidenceUrl: null, evidenceUrl: null,
evidenceFile: null, evidenceFile: null,
quote: null, quote: null,
note: null, note: null,
tags: [], tags: [],
}; };
const isPublicFigureChosen = compose(not, isNil, prop('publicFigureId')); const isPublicFigureChosen = compose(not, isNil, prop('publicFigureId'));
const isSubjectChosen = compose(not, isNil, prop('publicFigureId')); const isSubjectChosen = compose(not, isNil, prop('publicFigureId'));
const isPositionChosen = compose(not, isNil, prop('publicFigureId')); const isPositionChosen = compose(not, isNil, prop('publicFigureId'));
const isStatementComplete = allPass([ const isStatementComplete = allPass([
compose(not, isNil, prop('statementDate')), compose(not, isNil, prop('statementDate')),
compose(not, isNil, prop('quote')), compose(not, isNil, prop('quote')),
compose(not, isNil, prop('statementDate')), compose(not, isNil, prop('statementDate')),
either( either(
compose(not, isNil, prop('evidenceUrl')), compose(not, isNil, prop('evidenceUrl')),
compose(not, isNil, prop('evidenceFile')), compose(not, isNil, prop('evidenceFile')),
) ),
]); ]);
/* eslint-disable no-unused-vars */
const isPublicFigureMissing = complement(isPublicFigureChosen); const isPublicFigureMissing = complement(isPublicFigureChosen);
const isSubjectMissing = complement(isSubjectChosen); const isSubjectMissing = complement(isSubjectChosen);
const isPositionMissing = complement(isPositionChosen); const isPositionMissing = complement(isPositionChosen);
const isStatementIncomplete = complement(isStatementComplete); const isStatementIncomplete = complement(isStatementComplete);
export const addStatementReducer = (state = initialState, action) => { export const addStatementReducer = (state = initialState, action) => {
switch (action.type) { switch (action.type) {
case actionsTypes.ADD_STATEMENT_PUBLIC_FIGURE_SELECTION: return assoc('publicFigureId', action.id, state); case actionsTypes.ADD_STATEMENT_PUBLIC_FIGURE_SELECTION: return assoc('publicFigureId', action.id, state);
case actionsTypes.ADD_STATEMENT_SUBJECT_SELECTION: return assoc('subjectId', action.id, state); case actionsTypes.ADD_STATEMENT_SUBJECT_SELECTION: return assoc('subjectId', action.id, state);
case actionsTypes.ADD_STATEMENT_POSITION_SELECTION: return assoc('positionId', action.id, state); case actionsTypes.ADD_STATEMENT_POSITION_SELECTION: return assoc('positionId', action.id, state);
case actionsTypes.ADD_STATEMENT_UPDATE_EVIDENCE_URL: return assoc('evidenceUrl', action.url, state); case actionsTypes.ADD_STATEMENT_UPDATE_EVIDENCE_URL: return assoc('evidenceUrl', action.url, state);
case actionsTypes.ADD_STATEMENT_UPDATE_EVIDENCE_FILE: return assoc('evidenceFile', action.file, state); case actionsTypes.ADD_STATEMENT_UPDATE_EVIDENCE_FILE: return assoc('evidenceFile', action.file, state);
default: return state; default: return state;
} }
}; };

View file

@ -1,35 +1,36 @@
import actionsType from '../actions_types';
import { map, compose, prop, merge, assoc, not, isNil, pipe, when } from 'ramda'; import { map, compose, prop, merge, assoc, not, isNil, pipe, when } from 'ramda';
const initialState = {};
import { indexAndGroup } from 'api/jsonApiParser'; import { indexAndGroup } from 'api/jsonApiParser';
import actionsType from '../actions_types';
const initialState = {};
const isNotNil = compose(not, isNil); const isNotNil = compose(not, isNil);
export const indexAndGroupMainData = compose( export const indexAndGroupMainData = compose(
indexAndGroup, indexAndGroup,
map(assoc('fetched', true)), map(assoc('fetched', true)),
prop('data') prop('data')
); );
const indexAndGroupIncludedData = pipe( const indexAndGroupIncludedData = pipe(
prop('included'), prop('included'),
when( when(
isNotNil, isNotNil,
compose( compose(
indexAndGroup, indexAndGroup,
map(assoc('fetched', true)), map(assoc('fetched', true)),
) )
), ),
); );
const saveData = data => compose( const saveData = data => compose(
merge(indexAndGroupIncludedData(data)), merge(indexAndGroupIncludedData(data)),
indexAndGroupMainData, indexAndGroupMainData,
)(data); )(data);
export const entitiesReducer = (state = initialState, action) => { export const entitiesReducer = (state = initialState, action) => {
switch (action.type) { switch (action.type) {
case actionsType.ENTITY_READ: return merge(state, saveData(action.data)); case actionsType.ENTITY_READ: return merge(state, saveData(action.data));
default: return state; default: return state;
} }
}; };

View file

@ -1,59 +1,59 @@
import { identity } from 'ramda'; import { identity } from 'ramda';
import actionsTypes from '../actions_types';
import { takeEvery } from 'redux-saga'; import { takeEvery } from 'redux-saga';
import { call, put } from 'redux-saga/effects'; import { call, put } from 'redux-saga/effects';
import { getSubjects, getStatements, getPositions } from 'api/debats'; import { getSubjects, getStatements, getPositions } from 'api/debats';
import actionsTypes from '../actions_types';
const getApiCallFor = (entityType, entityRequest) => { const getApiCallFor = (entityType, entityRequest) => {
switch (entityType) { switch (entityType) {
case 'statements': return getStatements; case 'statements': return getStatements;
case 'subjects': case 'subjects':
switch (entityRequest) { switch (entityRequest) {
case 'hottest': return getSubjects; case 'hottest': return getSubjects;
default: return identity;
}
case 'publicFigures':
switch (entityRequest) {
case 'hottest': return getSubjects;
case 'all': return getSubjects;
default: return identity;
}
default: return identity; default: return identity;
} }
case 'publicFigures':
switch (entityRequest) {
case 'hottest': return getSubjects;
case 'all': return getSubjects;
default: return identity;
}
default: return identity;
}
}; };
function* fetchEntityIfNeeded(action) { function* fetchEntityIfNeeded(action) {
// Test Do we have to call API // Test Do we have to call API
const apiCall = getApiCallFor(action.entityType, ( const apiCall = getApiCallFor(action.entityType, (
action.accessType === 'list' ? action.listType : action.accessedId action.accessType === 'list' ? action.listType : action.accessedId
)); ));
// Call API // Call API
const response = yield call(apiCall); const response = yield call(apiCall);
// Error actions // Error actions
// Success actions // Success actions
yield put({ type: actionsTypes.ENTITY_READ, data: response.data }); yield put({ type: actionsTypes.ENTITY_READ, data: response.data });
} }
export function* watchEntityAccess() { export function* watchEntityAccess() {
yield* takeEvery(actionsTypes.ENTITY_ACCESS, fetchEntityIfNeeded); yield* takeEvery(actionsTypes.ENTITY_ACCESS, fetchEntityIfNeeded);
} }
function* fetchPositionsOfSubject(action) { function* fetchPositionsOfSubject(action) {
if (!!action.id) { if (action.id) {
// Call API // Call API
const response = yield call(getPositions, action.id); const response = yield call(getPositions, action.id);
// Error actions // Error actions
// Success actions // Success actions
yield put({ type: actionsTypes.ENTITY_READ, data: response.data }); yield put({ type: actionsTypes.ENTITY_READ, data: response.data });
} }
} }
export function* watchSubjectSelection() { export function* watchSubjectSelection() {
yield* takeEvery(actionsTypes.ADD_STATEMENT_SUBJECT_SELECTION, fetchPositionsOfSubject); yield* takeEvery(actionsTypes.ADD_STATEMENT_SUBJECT_SELECTION, fetchPositionsOfSubject);
} }

View file

@ -2,8 +2,8 @@ import { watchEntityAccess, watchSubjectSelection } from './apiSaga';
export default function* rootSaga() { export default function* rootSaga() {
yield [ yield [
watchEntityAccess(), watchEntityAccess(),
watchSubjectSelection() watchSubjectSelection(),
]; ];
} }

View file

@ -2,8 +2,6 @@ import { createSelector } from 'reselect';
import { prop, propEq, find, pipe, dissoc, when, not, isNil, compose } from 'ramda'; import { prop, propEq, find, pipe, dissoc, when, not, isNil, compose } from 'ramda';
import { getPublicFigures, getSubjects, getPositions, enrichWithRelationships } from './entities'; import { getPublicFigures, getSubjects, getPositions, enrichWithRelationships } from './entities';
import { withConsole } from 'helpers/debug';
const isNotNil = compose(not, isNil); const isNotNil = compose(not, isNil);
const getAddState = state => state.addStatement; const getAddState = state => state.addStatement;
@ -11,43 +9,43 @@ const getAddState = state => state.addStatement;
const injectPositions = enrichWithRelationships('positions', 'positions'); const injectPositions = enrichWithRelationships('positions', 'positions');
export const getAddStatementPublicFigure = createSelector( export const getAddStatementPublicFigure = createSelector(
getAddState, getAddState,
getPublicFigures, getPublicFigures,
(addState, publicFigures) => find( (addState, publicFigures) => find(
propEq('id', prop('publicFigureId')(addState)) propEq('id', prop('publicFigureId')(addState))
)(publicFigures) )(publicFigures)
); );
export const getAddStatementSubject = createSelector( export const getAddStatementSubject = createSelector(
getAddState, getAddState,
getSubjects, getSubjects,
getPositions, getPositions,
(addState, allSubjects, allPositions) => pipe( (addState, allSubjects, allPositions) => pipe(
find(propEq('id', prop('subjectId')(addState))), find(propEq('id', prop('subjectId')(addState))),
when( when(
isNotNil, isNotNil,
pipe( pipe(
injectPositions(allPositions), injectPositions(allPositions),
dissoc('relationthips') dissoc('relationthips')
) )
), ),
)(allSubjects) )(allSubjects)
); );
export const getAddStatementPosition = createSelector( export const getAddStatementPosition = createSelector(
getAddState, getAddState,
getPositions, getPositions,
(addState, publicFigures) => find( (addState, publicFigures) => find(
propEq('id', prop('positionId')(addState)) propEq('id', prop('positionId')(addState))
)(publicFigures) )(publicFigures)
); );
export const getAddStatementEvidenceUrl = createSelector( export const getAddStatementEvidenceUrl = createSelector(
getAddState, getAddState,
prop('evidenceUrl') prop('evidenceUrl')
); );
export const getAddStatementEvidenceFile = createSelector( export const getAddStatementEvidenceFile = createSelector(
getAddState, getAddState,
prop('evidenceFile') prop('evidenceFile')
); );

View file

@ -1,18 +1,18 @@
import { curry, assoc, compose, when, take, path, is, map, isNil, always, values, propEq, ifElse, pipe, find } from 'ramda'; import { curry, assoc, compose, when, take, path, is, map, isNil, always, values, propEq, ifElse, pipe, find } from 'ramda';
export const getSubjects = state => values(state.entities.subjects); export const getSubjects = state => values(state.entities.subjects);
export const getPositions = state => values(state.entities.positions); export const getPositions = state => values(state.entities.positions);
export const getStatements = state => values(state.entities.statements); export const getStatements = state => values(state.entities.statements);
export const getPublicFigures = state => values(state.entities['public-figures']); export const getPublicFigures = state => values(state.entities['public-figures']);
import { withConsole, warn } from 'helpers/debug';
const getEntityByRef = curry( const getEntityByRef = curry(
(sourceEntities, entityReference) => ifElse( (sourceEntities, entityReference) => ifElse(
isNil, isNil,
always(entityReference), // No source entities yet, return reference object always(entityReference), // No source entities yet, return reference object
pipe( pipe(
find(propEq('id', entityReference.id)), find(propEq('id', entityReference.id)),
when(isNil, always(entityReference)), // Entity not fetched yet, return reference object when(isNil, always(entityReference)), // Entity not fetched yet, return reference object
), ),
)(sourceEntities) )(sourceEntities)
); );
@ -23,14 +23,14 @@ const getEntityByRef = curry(
* const injectPublicFigure = enrichWithRelationship('publicFigure', 'public-figure'); * const injectPublicFigure = enrichWithRelationship('publicFigure', 'public-figure');
*/ */
export const enrichWithRelationship = curry( export const enrichWithRelationship = curry(
(propertyName, relationshipName, fromCollection, entity) => assoc( (propertyName, relationshipName, fromCollection, entity) => assoc(
propertyName, propertyName,
compose( compose(
getEntityByRef(fromCollection), getEntityByRef(fromCollection),
when(is(Array), take(1)), when(is(Array), take(1)),
path(['relationships', relationshipName, 'data']), path(['relationships', relationshipName, 'data']),
)(entity), )(entity),
entity entity
) )
); );
@ -41,13 +41,13 @@ export const enrichWithRelationship = curry(
* = enrichWithRelationships('remarquablePublicFigures', 'remarquable-public-figures'); * = enrichWithRelationships('remarquablePublicFigures', 'remarquable-public-figures');
*/ */
export const enrichWithRelationships = curry( export const enrichWithRelationships = curry(
(propertyName, relationshipName, fromCollection, entity) => assoc( (propertyName, relationshipName, fromCollection, entity) => assoc(
propertyName, propertyName,
compose( compose(
map(getEntityByRef(fromCollection)), map(getEntityByRef(fromCollection)),
path(['relationships', relationshipName, 'data']) path(['relationships', relationshipName, 'data'])
)(entity), )(entity),
entity entity
) )
); );

View file

@ -1,4 +1,6 @@
/* eslint-disable import/export */ // since positions.js is an empty file
export * from './positions'; export * from './positions';
export * from './publicFigures'; export * from './publicFigures';
export * from './statements'; export * from './statements';
export * from './subjects'; export * from './subjects';

View file

@ -1,4 +1,6 @@
import { values, pipe, compose, map, dissoc, prop } from 'ramda'; /* eslint-disable no-unused-vars */
import { values, pipe, compose, map, dissoc } from 'ramda';
import { whenNotNil } from 'helpers/ramda-ext'; import { whenNotNil } from 'helpers/ramda-ext';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { enrichWithRelationships, getSubjects, getPublicFigures } from './entities'; import { enrichWithRelationships, getSubjects, getPublicFigures } from './entities';
@ -6,14 +8,14 @@ import { enrichWithRelationships, getSubjects, getPublicFigures } from './entiti
const injectSubjects = enrichWithRelationships('subjects', 'subjects'); const injectSubjects = enrichWithRelationships('subjects', 'subjects');
export const getPublicFiguresWithRelations = createSelector( export const getPublicFiguresWithRelations = createSelector(
getPublicFigures, getPublicFigures,
getSubjects, getSubjects,
(publicFigures, allSubjects) => whenNotNil( (publicFigures, allSubjects) => whenNotNil(
compose( compose(
values, values,
map(pipe( map(pipe(
// injectSubjects(allSubjects), // injectSubjects(allSubjects),
dissoc('relationthips'), dissoc('relationthips'),
)) ))
) )
)(publicFigures) )(publicFigures)

View file

@ -2,7 +2,7 @@ import { values, pipe, map, compose, dissoc } from 'ramda';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { whenNotNil } from 'helpers/ramda-ext'; import { whenNotNil } from 'helpers/ramda-ext';
import { import {
enrichWithRelationship, getPublicFigures, getPositions, getSubjects, getStatements enrichWithRelationship, getPublicFigures, getPositions, getSubjects, getStatements,
} from './entities'; } from './entities';
const injectPublicFigure = enrichWithRelationship('publicFigure', 'public-figure'); const injectPublicFigure = enrichWithRelationship('publicFigure', 'public-figure');
@ -10,18 +10,18 @@ const injectPosition = enrichWithRelationship('position', 'position');
const injectSubject = enrichWithRelationship('subject', 'subject'); const injectSubject = enrichWithRelationship('subject', 'subject');
export const getLatestStatements = createSelector( export const getLatestStatements = createSelector(
getStatements, getStatements,
getPublicFigures, getPublicFigures,
getPositions, getPositions,
getSubjects, getSubjects,
(allStatements, allPublicFigures, allPositions, allSubjects) => whenNotNil( (allStatements, allPublicFigures, allPositions, allSubjects) => whenNotNil(
compose( compose(
values, values,
map(pipe( map(pipe(
injectPublicFigure(allPublicFigures), injectPublicFigure(allPublicFigures),
injectSubject(allSubjects), injectSubject(allSubjects),
injectPosition(allPositions), injectPosition(allPositions),
dissoc('relationthips'), dissoc('relationthips'),
)) ))
) )
)(allStatements) )(allStatements)

View file

@ -1,9 +1,8 @@
import { values, pipe, compose, map, dissoc, propEq } from 'ramda'; import { find, values, pipe, compose, map, dissoc, propEq } from 'ramda';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { whenNotNil } from 'helpers/ramda-ext'; import { whenNotNil } from 'helpers/ramda-ext';
import { enrichWithRelationships, getPublicFigures, getPositions, getSubjects } from './entities';
import { withConsole } from 'helpers/debug'; import { enrichWithRelationships, getPublicFigures, getPositions, getSubjects } from './entities';
export const getHomeSubjects = state => values(getSubjects(state)); export const getHomeSubjects = state => values(getSubjects(state));
@ -13,29 +12,29 @@ const injectRemarquablePublicFigures
const injectPositions = enrichWithRelationships('positions', 'positions'); const injectPositions = enrichWithRelationships('positions', 'positions');
export const getHomeSubjectsWithRelations = createSelector( export const getHomeSubjectsWithRelations = createSelector(
getHomeSubjects, getHomeSubjects,
getPublicFigures, getPublicFigures,
getPositions, getPositions,
(homeSubjects, allPublicFigures, allPositions) => whenNotNil( (homeSubjects, allPublicFigures, allPositions) => whenNotNil(
compose( compose(
values, values,
map(pipe( map(pipe(
injectRemarquablePublicFigures(allPublicFigures), injectRemarquablePublicFigures(allPublicFigures),
injectPositions(allPositions), injectPositions(allPositions),
dissoc('relationships'), dissoc('relationships'),
)) ))
) )
)(homeSubjects) )(homeSubjects)
); );
export const getSubject = (state, props) => createSelector( export const getSubject = (state, props) => createSelector(
getSubjects, getSubjects,
getPositions, getPositions,
getPublicFigures, getPublicFigures,
(allSubjects, allPositions, allPublicFigures) => pipe( (allSubjects, allPositions, allPublicFigures) => pipe(
find(propEq('id', props.subjectId)), find(propEq('id', props.subjectId)),
injectRemarquablePublicFigures(allPublicFigures), injectRemarquablePublicFigures(allPublicFigures),
injectPositions(allPositions), injectPositions(allPositions),
dissoc('relationthips'), dissoc('relationthips'),
)(allSubjects) )(allSubjects)
); );

View file

@ -1,3 +1,3 @@
import { urlRegex } from './generic'; import { urlRegex } from './generic';
export const isValidEvidenceUrl = (tested) => urlRegex.test(tested); export const isValidEvidenceUrl = tested => urlRegex.test(tested);

View file

@ -4,11 +4,11 @@ const SRC_FOLDER = 'src';
const APP_ROOT = path.normalize(path.join(__dirname, '..')); const APP_ROOT = path.normalize(path.join(__dirname, '..'));
const constants = Object.freeze({ const constants = Object.freeze({
APP_NAME: 'debats', APP_NAME: 'debats',
APP_ROOT, APP_ROOT,
APP_PATH: path.join(APP_ROOT, SRC_FOLDER), APP_PATH: path.join(APP_ROOT, SRC_FOLDER),
STATIC_PATH: path.join(APP_ROOT, SRC_FOLDER, 'static'), STATIC_PATH: path.join(APP_ROOT, SRC_FOLDER, 'static'),
SRC_FOLDER, SRC_FOLDER,
}); });
module.exports = constants; module.exports = constants;

View file

@ -1,54 +1,55 @@
const CONSTANTS = require('../webpack/constants'); const CONSTANTS = require('../webpack/constants');
const APP_PATH = CONSTANTS.APP_PATH; const APP_PATH = CONSTANTS.APP_PATH;
const loaders = [ const loaders = [
{ {
test: /\.jsx?$/, test: /\.jsx?$/,
loader: 'babel-loader', loader: 'babel-loader',
exclude: /node_modules/, exclude: /node_modules/,
}, },
{ {
test: /src.+\.css$/, test: /src.+\.css$/,
loader: 'style-loader' loader: 'style-loader'
+ '!css-loader' + '!css-loader'
+ '?modules&localIdentName=[name]__[local]___[hash:base64:5]' + '?modules&localIdentName=[name]__[local]___[hash:base64:5]'
+ '&importLoaders=1' + '&importLoaders=1'
+ '!postcss-loader', + '!postcss-loader',
}, },
{ {
test: /node_modules.*\.css$/, test: /node_modules.*\.css$/,
loader: 'style-loader' loader: 'style-loader'
+ '!css-loader' + '!css-loader'
+ '!postcss-loader', + '!postcss-loader',
}, },
{ {
test: /\.(png|svg|gif|jpeg|jpg)$/, test: /\.(png|svg|gif|jpeg|jpg)$/,
include: APP_PATH, include: APP_PATH,
loader: 'file?name=images/[name].[ext]', loader: 'file?name=images/[name].[ext]',
}, },
{ {
test: /\.(eot|woff|ttf)$/, test: /\.(eot|woff|ttf)$/,
include: APP_PATH, include: APP_PATH,
loader: 'file-loader?name=fonts/[name].[ext]', loader: 'file-loader?name=fonts/[name].[ext]',
}, },
{ {
test: /\.(svg|woff2?)$/, test: /\.(svg|woff2?)$/,
exclude: APP_PATH, exclude: APP_PATH,
loader: 'url?limit=10000', loader: 'url?limit=10000',
}, },
{ {
test: /\.(ttf|eot)$/, test: /\.(ttf|eot)$/,
exclude: APP_PATH, exclude: APP_PATH,
loader: 'file', loader: 'file',
}, },
{ {
test: /\.json$/, test: /\.json$/,
loader: 'json', loader: 'json',
}, },
{ {
test: /\.md$/, test: /\.md$/,
loader: 'raw', loader: 'raw',
}, },
]; ];
module.exports = loaders; module.exports = loaders;

View file

@ -12,35 +12,36 @@ const postcssHide = require('postcss-hide');
const CONSTANTS = require('./constants'); const CONSTANTS = require('./constants');
const makeMap = aPath => { const makeMap = (aPath) => {
try { try {
return webpackPostcssTools.makeVarMap(aPath); return webpackPostcssTools.makeVarMap(aPath);
} catch (e) { } catch (e) {
// console.log(`${aPath} not found.`); // console.log(`${aPath} not found.`);
process.exit(1); process.exit(1);
} }
return null; return null;
}; };
const map = makeMap('./src/styles/_constants.css'); const map = makeMap('./src/styles/_constants.css');
// eslint-disable-next-line no-unused-vars
module.exports = webpack => [ module.exports = webpack => [
postcssMixins({ postcssMixins({
mixinsDir: path.join(CONSTANTS.APP_PATH, 'style', 'mixins'), mixinsDir: path.join(CONSTANTS.APP_PATH, 'style', 'mixins'),
}), }),
atImport(), atImport(),
postcssShortPosition(), postcssShortPosition(),
postcssAssets(), postcssAssets(),
postcssColorFunction(), postcssColorFunction(),
postcssNext({ postcssNext({
features: { features: {
customProperties: { customProperties: {
variables: map.vars, variables: map.vars,
warnings: false, warnings: false,
}, },
}, },
}), }),
lost(), lost(),
postcssInlineSVG(), postcssInlineSVG(),
postcssHide(), postcssHide(),
]; ];

View file

@ -1,12 +1,12 @@
const path = require('path'); const path = require('path');
const CONSTANTS = require('./constants'); const CONSTANTS = require('./constants');
const APP_ROOT = CONSTANTS.APP_ROOT; const APP_ROOT = CONSTANTS.APP_ROOT;
module.exports = { module.exports = {
alias: { alias: {
react: path.resolve(APP_ROOT, 'node_modules/react'), react: path.resolve(APP_ROOT, 'node_modules/react'),
}, },
modulesDirectories: ['src', 'web_modules', 'node_modules'], modulesDirectories: ['src', 'web_modules', 'node_modules'],
extensions: ['', '.js', '.jsx'], extensions: ['', '.js', '.jsx'],
}; };

View file

@ -5,6 +5,7 @@ const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
const CONSTANTS = require('./constants'); const CONSTANTS = require('./constants');
const APP_ROOT = CONSTANTS.APP_ROOT; const APP_ROOT = CONSTANTS.APP_ROOT;
const SRC_FOLDER = CONSTANTS.SRC_FOLDER; const SRC_FOLDER = CONSTANTS.SRC_FOLDER;

View file

@ -5,6 +5,7 @@ const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
const CONSTANTS = require('./constants'); const CONSTANTS = require('./constants');
const APP_ROOT = CONSTANTS.APP_ROOT; const APP_ROOT = CONSTANTS.APP_ROOT;
const APP_PATH = CONSTANTS.APP_PATH; const APP_PATH = CONSTANTS.APP_PATH;
const SRC_FOLDER = CONSTANTS.SRC_FOLDER; const SRC_FOLDER = CONSTANTS.SRC_FOLDER;
@ -21,12 +22,12 @@ config.plugins.push(new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"product
config.plugins.push(new webpack.optimize.DedupePlugin()); config.plugins.push(new webpack.optimize.DedupePlugin());
config.plugins.push(new webpack.optimize.OccurenceOrderPlugin(true)); // Assign the module and chunk ids by occurrence count config.plugins.push(new webpack.optimize.OccurenceOrderPlugin(true)); // Assign the module and chunk ids by occurrence count
config.plugins.push(new webpack.optimize.UglifyJsPlugin({ config.plugins.push(new webpack.optimize.UglifyJsPlugin({
compressor: { screw_ie8: false, keep_fnames: true, warnings: true }, compressor: { screw_ie8: false, keep_fnames: true, warnings: true },
mangle: { mangle: {
except: ['$super', '$', 'exports', 'require'], except: ['$super', '$', 'exports', 'require'],
screw_ie8: false, screw_ie8: false,
keep_fnames: true, keep_fnames: true,
}, },
})); }));
// config.plugins.push(new CompressionPlugin({threshold: 10240})); // config.plugins.push(new CompressionPlugin({threshold: 10240}));
config.plugins.push(new CopyWebpackPlugin(copyConfig)); config.plugins.push(new CopyWebpackPlugin(copyConfig));
@ -34,7 +35,7 @@ config.devtool = 'cheap-module-source-map';
config.cache = false; config.cache = false;
config.debug = false; config.debug = false;
config.entry = `${APP_PATH}/index.js`; config.entry = `${APP_PATH}/index.js`;
config.output.path = outputPath;; config.output.path = outputPath;
module.exports = config; module.exports = config;

View file

@ -2,20 +2,21 @@ const config = require('./webpack.base.config');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const CONSTANTS = require('./constants'); const CONSTANTS = require('./constants');
const APP_PATH = CONSTANTS.APP_PATH; const APP_PATH = CONSTANTS.APP_PATH;
const APP_NAME = CONSTANTS.APP_NAME; const APP_NAME = CONSTANTS.APP_NAME;
const SRC_FOLDER = CONSTANTS.SRC_FOLDER; const SRC_FOLDER = CONSTANTS.SRC_FOLDER;
config.plugins.push(new HtmlWebpackPlugin({ config.plugins.push(new HtmlWebpackPlugin({
template: `${SRC_FOLDER}/index.html`, template: `${SRC_FOLDER}/index.html`,
inject: true, inject: true,
filename: 'index.html', filename: 'index.html',
})); }));
config.entry = `${APP_PATH}/index.js`; config.entry = `${APP_PATH}/index.jsx`;
config.output = { config.output = {
filename: `${APP_NAME}.[name].[hash].js`, filename: `${APP_NAME}.[name].[hash].js`,
}; };
module.exports = config; module.exports = config;

View file

@ -5,6 +5,7 @@ const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
const CONSTANTS = require('./constants'); const CONSTANTS = require('./constants');
const APP_ROOT = CONSTANTS.APP_ROOT; const APP_ROOT = CONSTANTS.APP_ROOT;
const SRC_FOLDER = CONSTANTS.SRC_FOLDER; const SRC_FOLDER = CONSTANTS.SRC_FOLDER;
@ -33,30 +34,30 @@ var devServerAPIUrl;
var devServerRewrite; var devServerRewrite;
if (args.proxy) { if (args.proxy) {
if (args.mockAPI) { if (args.mockAPI) {
devServerAPIUrl = 'http://localhost:3030'; devServerAPIUrl = 'http://localhost:3030';
devServerRewrite = function rewrite(req) { devServerRewrite = function rewrite(req) {
req.url = req.url // eslint-disable-line no-param-reassign req.url = req.url // eslint-disable-line no-param-reassign
.replace(/\/api\//, '/fake-api/') // Local mocks as API .replace(/\/api\//, '/fake-api/') // Local mocks as API
.split('?')[0]; // strip query string if exists .split('?')[0]; // strip query string if exists
if (req.method === 'POST') req.method = 'GET'; // eslint-disable-line no-param-reassign if (req.method === 'POST') req.method = 'GET'; // eslint-disable-line no-param-reassign
}; };
} }
if (args.prodAPI) devServerAPIUrl = 'http://api.débats.co'; if (args.prodAPI) devServerAPIUrl = 'http://api.débats.co';
} }
config.devServer = { config.devServer = {
quiet: false, quiet: false,
stats: { colors: true }, stats: { colors: true },
outputPath, outputPath,
proxy: { proxy: {
'/api/*': { '/api/*': {
target: devServerAPIUrl, target: devServerAPIUrl,
rewrite: devServerRewrite, rewrite: devServerRewrite,
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
},
}, },
},
}; };
module.exports = config; module.exports = config;