Merge branch 'master' into markitup-react

This commit is contained in:
Andrey Okonetchnikov 2016-10-03 16:57:48 +02:00
commit ca34def49e
30 changed files with 392 additions and 466 deletions

View File

@ -4,7 +4,10 @@ env:
jest: true jest: true
parser: babel-eslint parser: babel-eslint
plugins: [ "react" ] plugins: [
"react",
"class-property"
]
rules: rules:
# Possible Errors # Possible Errors
@ -100,6 +103,8 @@ rules:
react/self-closing-comp: 1 react/self-closing-comp: 1
react/sort-comp: 1 react/sort-comp: 1
class-property/class-property-semicolon: 2
# Global scoped method and vars # Global scoped method and vars
globals: globals:
netlify: true netlify: true

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) 2016 Netlify <team@netlify.com>
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -3,6 +3,9 @@
A CMS for static site generators. Give non-technical users a simple way to edit A CMS for static site generators. Give non-technical users a simple way to edit
and add content to any site built with a static site generator. and add content to any site built with a static site generator.
Netlify CMS is released under the [MIT License](LICENSE).
Please make sure you understand its [implications and guarantees](https://writing.kemitchell.com/2016/09/21/MIT-License-Line-by-Line.html).
## How it works ## How it works
Netlify CMS is a single-page app that you pull into the `/admin` part of your site. Netlify CMS is a single-page app that you pull into the `/admin` part of your site.

View File

@ -44,6 +44,7 @@
"css-loader": "^0.23.1", "css-loader": "^0.23.1",
"enzyme": "^2.4.1", "enzyme": "^2.4.1",
"eslint": "^3.5.0", "eslint": "^3.5.0",
"eslint-plugin-class-property": "^1.0.1",
"eslint-plugin-react": "^5.1.1", "eslint-plugin-react": "^5.1.1",
"expect": "^1.20.2", "expect": "^1.20.2",
"exports-loader": "^0.6.3", "exports-loader": "^0.6.3",

View File

@ -6,13 +6,9 @@ export default class AuthenticationPage extends React.Component {
onLogin: React.PropTypes.func.isRequired onLogin: React.PropTypes.func.isRequired
}; };
constructor(props) { state = {};
super(props);
this.state = {};
this.handleLogin = this.handleLogin.bind(this);
}
handleLogin(e) { handleLogin = e => {
e.preventDefault(); e.preventDefault();
let auth; let auth;
if (document.location.host.split(':')[0] === 'localhost') { if (document.location.host.split(':')[0] === 'localhost') {
@ -28,7 +24,7 @@ export default class AuthenticationPage extends React.Component {
} }
this.props.onLogin(data); this.props.onLogin(data);
}); });
} };
render() { render() {
const { loginError } = this.state; const { loginError } = this.state;

View File

@ -5,13 +5,9 @@ export default class AuthenticationPage extends React.Component {
onLogin: React.PropTypes.func.isRequired onLogin: React.PropTypes.func.isRequired
}; };
constructor(props) { state = {};
super(props);
this.state = {};
this.handleLogin = this.handleLogin.bind(this);
}
handleLogin(e) { handleLogin = e => {
e.preventDefault(); e.preventDefault();
const { email, password } = this.state; const { email, password } = this.state;
this.setState({ authenticating: true }); this.setState({ authenticating: true });
@ -33,7 +29,7 @@ export default class AuthenticationPage extends React.Component {
this.setState({ loginError: data.msg }); this.setState({ loginError: data.msg });
}); });
}); });
} };
handleChange(key) { handleChange(key) {
return (e) => { return (e) => {

View File

@ -5,21 +5,16 @@ export default class AuthenticationPage extends React.Component {
onLogin: React.PropTypes.func.isRequired onLogin: React.PropTypes.func.isRequired
}; };
constructor(props) { state = { email: '' };
super(props);
this.state = { email: '' };
this.handleLogin = this.handleLogin.bind(this);
this.handleEmailChange = this.handleEmailChange.bind(this);
}
handleLogin(e) { handleLogin = e => {
e.preventDefault(); e.preventDefault();
this.props.onLogin(this.state); this.props.onLogin(this.state);
} };
handleEmailChange(e) { handleEmailChange = e => {
this.setState({ email: e.target.value }); this.setState({ email: e.target.value });
} };
render() { render() {
return <form onSubmit={this.handleLogin}> return <form onSubmit={this.handleLogin}>

View File

@ -10,26 +10,26 @@ export default class AppHeader extends React.Component {
state = { state = {
createMenuActive: false createMenuActive: false
} };
handleCreatePostClick = collectionName => { handleCreatePostClick = collectionName => {
const { onCreateEntryClick } = this.props; const { onCreateEntryClick } = this.props;
if (onCreateEntryClick) { if (onCreateEntryClick) {
onCreateEntryClick(collectionName); onCreateEntryClick(collectionName);
} }
} };
handleCreateButtonClick = () => { handleCreateButtonClick = () => {
this.setState({ this.setState({
createMenuActive: true createMenuActive: true
}); });
} };
handleCreateMenuHide = () => { handleCreateMenuHide = () => {
this.setState({ this.setState({
createMenuActive: false createMenuActive: false
}); });
} };
render() { render() {
const { const {
@ -43,41 +43,41 @@ export default class AppHeader extends React.Component {
return ( return (
<AppBar <AppBar
fixed fixed
theme={styles} theme={styles}
> >
<IconButton <IconButton
icon="menu" icon="menu"
inverse inverse
onClick={toggleNavDrawer} onClick={toggleNavDrawer}
/> />
<IndexLink to="/"> <IndexLink to="/">
Dashboard Dashboard
</IndexLink> </IndexLink>
<FindBar <FindBar
commands={commands} commands={commands}
defaultCommands={defaultCommands} defaultCommands={defaultCommands}
runCommand={runCommand} runCommand={runCommand}
/> />
<Button <Button
className={styles.createBtn} className={styles.createBtn}
icon='add' icon='add'
floating floating
accent accent
onClick={this.handleCreateButtonClick} onClick={this.handleCreateButtonClick}
> >
<Menu <Menu
active={createMenuActive} active={createMenuActive}
position="topRight" position="topRight"
onHide={this.handleCreateMenuHide} onHide={this.handleCreateMenuHide}
> >
{ {
collections.valueSeq().map(collection => collections.valueSeq().map(collection =>
<MenuItem <MenuItem
key={collection.get('name')} key={collection.get('name')}
value={collection.get('name')} value={collection.get('name')}
onClick={this.handleCreatePostClick.bind(this, collection.get('name'))} onClick={this.handleCreatePostClick.bind(this, collection.get('name'))}
caption={pluralize(collection.get('label'), 1)} caption={pluralize(collection.get('label'), 1)}
/> />
) )
} }

View File

@ -17,9 +17,8 @@ export default class EntryListing extends React.Component {
{ mq: '495px', columns: 2, gutter: 15 }, { mq: '495px', columns: 2, gutter: 15 },
{ mq: '750px', columns: 3, gutter: 15 }, { mq: '750px', columns: 3, gutter: 15 },
{ mq: '1005px', columns: 4, gutter: 15 }, { mq: '1005px', columns: 4, gutter: 15 },
{ mq: '1260px', columns: 5, gutter: 15 }, { mq: '1515px', columns: 5, gutter: 15 },
{ mq: '1515px', columns: 6, gutter: 15 }, { mq: '1770px', columns: 6, gutter: 15 },
{ mq: '1770px', columns: 7, gutter: 15 },
] ]
}; };

View File

@ -8,8 +8,18 @@ export const SEARCH = 'SEARCH';
const PLACEHOLDER = 'Search or enter a command'; const PLACEHOLDER = 'Search or enter a command';
class FindBar extends Component { class FindBar extends Component {
constructor(props) { static propTypes = {
super(props); commands: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
pattern: PropTypes.string.isRequired
})).isRequired,
defaultCommands: PropTypes.arrayOf(PropTypes.string),
runCommand: PropTypes.func.isRequired,
};
constructor() {
super();
this._compiledCommands = []; this._compiledCommands = [];
this._searchCommand = { this._searchCommand = {
search: true, search: true,
@ -26,18 +36,6 @@ class FindBar extends Component {
}; };
this._getSuggestions = _.memoize(this._getSuggestions, (value, activeScope) => value + activeScope); this._getSuggestions = _.memoize(this._getSuggestions, (value, activeScope) => value + activeScope);
this.compileCommand = this.compileCommand.bind(this);
this.matchCommand = this.matchCommand.bind(this);
this.maybeRemoveActiveScope = this.maybeRemoveActiveScope.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleInputBlur = this.handleInputBlur.bind(this);
this.handleInputFocus = this.handleInputFocus.bind(this);
this.handleInputClick = this.handleInputClick.bind(this);
this.getSuggestions = this.getSuggestions.bind(this);
this.highlightCommandFromMouse = this.highlightCommandFromMouse.bind(this);
this.selectCommandFromMouse = this.selectCommandFromMouse.bind(this);
this.setIgnoreBlur = this.setIgnoreBlur.bind(this);
} }
componentWillMount() { componentWillMount() {
@ -58,7 +56,7 @@ class FindBar extends Component {
} }
// Generates a regexp and splits a token and param details for a command // Generates a regexp and splits a token and param details for a command
compileCommand(command) { compileCommand = command => {
let regexp = ''; let regexp = '';
let param = null; let param = null;
@ -79,11 +77,11 @@ class FindBar extends Component {
token, token,
param param
}); });
} };
// Check if the entered string matches any command. // Check if the entered string matches any command.
// adds a scope (so user can type param value) and dispatches action for fully matched commands // adds a scope (so user can type param value) and dispatches action for fully matched commands
matchCommand() { matchCommand = () => {
const string = this.state.activeScope ? this.state.activeScope + this.state.value : this.state.value; const string = this.state.activeScope ? this.state.activeScope + this.state.value : this.state.value;
let match; let match;
let command = this._compiledCommands.find(command => { let command = this._compiledCommands.find(command => {
@ -133,20 +131,20 @@ class FindBar extends Component {
} }
this.props.runCommand(command.type, payload); this.props.runCommand(command.type, payload);
} }
} };
maybeRemoveActiveScope() { maybeRemoveActiveScope = () => {
if (this.state.value.length === 0 && this.state.activeScope) { if (this.state.value.length === 0 && this.state.activeScope) {
this.setState({ this.setState({
activeScope: null, activeScope: null,
placeholder: PLACEHOLDER placeholder: PLACEHOLDER
}); });
} }
} };
getSuggestions() { getSuggestions = () => {
return this._getSuggestions(this.state.value, this.state.activeScope, this._compiledCommands, this.props.defaultCommands); return this._getSuggestions(this.state.value, this.state.activeScope, this._compiledCommands, this.props.defaultCommands);
} };
// Memoized version // Memoized version
_getSuggestions(value, scope, commands, defaultCommands) { _getSuggestions(value, scope, commands, defaultCommands) {
@ -173,7 +171,7 @@ class FindBar extends Component {
return returnResults; return returnResults;
} }
handleKeyDown(event) { handleKeyDown = event => {
let highlightedIndex, index; let highlightedIndex, index;
switch (event.key) { switch (event.key) {
case 'ArrowDown': case 'ArrowDown':
@ -240,37 +238,37 @@ class FindBar extends Component {
isOpen: true isOpen: true
}); });
} }
} };
handleChange(event) { handleChange = event => {
this.setState({ this.setState({
value: event.target.value, value: event.target.value,
}); });
} };
handleInputBlur() { handleInputBlur = () => {
if (this._ignoreBlur) return; if (this._ignoreBlur) return;
this.setState({ this.setState({
isOpen: false, isOpen: false,
highlightedIndex: 0 highlightedIndex: 0
}); });
} };
handleInputFocus() { handleInputFocus = () => {
if (this._ignoreBlur) return; if (this._ignoreBlur) return;
this.setState({ isOpen: true }); this.setState({ isOpen: true });
} };
handleInputClick() { handleInputClick = () => {
if (this.state.isOpen === false) if (this.state.isOpen === false)
this.setState({ isOpen: true }); this.setState({ isOpen: true });
} };
highlightCommandFromMouse(index) { highlightCommandFromMouse = index => {
this.setState({ highlightedIndex: index }); this.setState({ highlightedIndex: index });
} };
selectCommandFromMouse(command) { selectCommandFromMouse = command => {
const newState = { const newState = {
isOpen: false, isOpen: false,
highlightedIndex: 0 highlightedIndex: 0
@ -283,11 +281,11 @@ class FindBar extends Component {
this._input.focus(); this._input.focus();
this.setIgnoreBlur(false); this.setIgnoreBlur(false);
}); });
} };
setIgnoreBlur(ignore) { setIgnoreBlur = ignore => {
this._ignoreBlur = ignore; this._ignoreBlur = ignore;
} };
renderMenu() { renderMenu() {
const commands = this.getSuggestions().map((command, index) => { const commands = this.getSuggestions().map((command, index) => {
@ -309,11 +307,11 @@ class FindBar extends Component {
} }
return ( return (
<div <div
className={this.state.highlightedIndex === index ? styles.highlightedCommand : styles.command} className={this.state.highlightedIndex === index ? styles.highlightedCommand : styles.command}
key={command.token.trim().replace(/[^a-z0-9]+/gi, '-')} key={command.token.trim().replace(/[^a-z0-9]+/gi, '-')}
onMouseDown={() => this.setIgnoreBlur(true)} onMouseDown={() => this.setIgnoreBlur(true)}
onMouseEnter={() => this.highlightCommandFromMouse(index)} onMouseEnter={() => this.highlightCommandFromMouse(index)}
onClick={() => this.selectCommandFromMouse(command)} onClick={() => this.selectCommandFromMouse(command)}
> >
{children} {children}
</div> </div>
@ -347,15 +345,15 @@ class FindBar extends Component {
<label className={styles.inputArea}> <label className={styles.inputArea}>
{scope} {scope}
<input <input
className={styles.inputField} className={styles.inputField}
ref={(c) => this._input = c} ref={(c) => this._input = c}
onFocus={this.handleInputFocus} onFocus={this.handleInputFocus}
onBlur={this.handleInputBlur} onBlur={this.handleInputBlur}
onChange={this.handleChange} onChange={this.handleChange}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
onClick={this.handleInputClick} onClick={this.handleInputClick}
placeholder={this.state.placeholder} placeholder={this.state.placeholder}
value={this.state.value} value={this.state.value}
/> />
</label> </label>
{menu} {menu}
@ -364,14 +362,4 @@ class FindBar extends Component {
} }
} }
FindBar.propTypes = {
commands: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
pattern: PropTypes.string.isRequired
})).isRequired,
defaultCommands: PropTypes.arrayOf(PropTypes.string),
runCommand: PropTypes.func.isRequired,
};
export default FindBar; export default FindBar;

View File

@ -3,14 +3,10 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import styles from './Loader.css'; import styles from './Loader.css';
export default class Loader extends React.Component { export default class Loader extends React.Component {
constructor(props) {
super(props); state = {
this.state = { currentItem: 0,
currentItem: 0, };
};
this.setAnimation = this.setAnimation.bind(this);
this.renderChild = this.renderChild.bind(this);
}
componentWillUnmount() { componentWillUnmount() {
if (this.interval) { if (this.interval) {
@ -18,7 +14,7 @@ export default class Loader extends React.Component {
} }
} }
setAnimation() { setAnimation = () => {
if (this.interval) return; if (this.interval) return;
const { children } = this.props; const { children } = this.props;
@ -27,9 +23,9 @@ export default class Loader extends React.Component {
const nextItem = (this.state.currentItem === children.length - 1) ? 0 : this.state.currentItem + 1; const nextItem = (this.state.currentItem === children.length - 1) ? 0 : this.state.currentItem + 1;
this.setState({ currentItem: nextItem }); this.setState({ currentItem: nextItem });
}, 5000); }, 5000);
} };
renderChild() { renderChild = () => {
const { children } = this.props; const { children } = this.props;
const { currentItem } = this.state; const { currentItem } = this.state;
if (!children) { if (!children) {
@ -40,15 +36,15 @@ export default class Loader extends React.Component {
this.setAnimation(); this.setAnimation();
return <div className={styles.text}> return <div className={styles.text}>
<ReactCSSTransitionGroup <ReactCSSTransitionGroup
transitionName={styles} transitionName={styles}
transitionEnterTimeout={500} transitionEnterTimeout={500}
transitionLeaveTimeout={500} transitionLeaveTimeout={500}
> >
<div key={currentItem} className={styles.animateItem}>{children[currentItem]}</div> <div key={currentItem} className={styles.animateItem}>{children[currentItem]}</div>
</ReactCSSTransitionGroup> </ReactCSSTransitionGroup>
</div>; </div>;
} }
} };
render() { render() {
const { active, style, className = '' } = this.props; const { active, style, className = '' } = this.props;

View File

@ -3,14 +3,10 @@ import { Icon } from '../index';
import styles from './Toast.css'; import styles from './Toast.css';
export default class Toast extends React.Component { export default class Toast extends React.Component {
constructor(props) {
super(props);
this.state = {
shown: false
};
this.autoHideTimeout = this.autoHideTimeout.bind(this); state = {
} shown: false
};
componentWillMount() { componentWillMount() {
if (this.props.show) { if (this.props.show) {
@ -32,12 +28,12 @@ export default class Toast extends React.Component {
} }
} }
autoHideTimeout() { autoHideTimeout = () => {
clearTimeout(this.timeOut); clearTimeout(this.timeOut);
this.timeOut = setTimeout(() => { this.timeOut = setTimeout(() => {
this.setState({ shown: false }); this.setState({ shown: false });
}, 4000); }, 4000);
} };
render() { render() {
const { style, type, className, children } = this.props; const { style, type, className, children } = this.props;

View File

@ -24,25 +24,26 @@
.card { .card {
width: 100% !important; width: 100% !important;
margin: 7px 0; margin: 7px 0 0 10px;
padding: 7px 0;
}
& h2 { .cardHeading {
font-size: 17px; font-size: 17px;
& small { & small {
font-weight: normal; font-weight: normal;
}
} }
}
& p { .cardText {
color: #555; color: #555;
font-size: 12px; font-size: 12px;
margin-top: 5px; margin-top: 5px;
} }
& button { .button {
margin: 10px 10px 0 0; margin: 10px 10px 0 0;
float: right; float: right;
}
} }

View File

@ -8,28 +8,21 @@ import { status, statusDescriptions } from '../constants/publishModes';
import styles from './UnpublishedListing.css'; import styles from './UnpublishedListing.css';
class UnpublishedListing extends React.Component { class UnpublishedListing extends React.Component {
constructor(props) { handleChangeStatus = (newStatus, dragProps) => {
super(props);
this.renderColumns = this.renderColumns.bind(this);
this.handleChangeStatus = this.handleChangeStatus.bind(this);
this.requestPublish = this.requestPublish.bind(this);
}
handleChangeStatus(newStatus, dragProps) {
const slug = dragProps.slug; const slug = dragProps.slug;
const collection = dragProps.collection; const collection = dragProps.collection;
const oldStatus = dragProps.ownStatus; const oldStatus = dragProps.ownStatus;
this.props.handleChangeStatus(collection, slug, oldStatus, newStatus); this.props.handleChangeStatus(collection, slug, oldStatus, newStatus);
} };
requestPublish(collection, slug, ownStatus) { requestPublish = (collection, slug, ownStatus) => {
if (ownStatus !== status.last()) return; if (ownStatus !== status.last()) return;
if (window.confirm('Are you sure you want to publish this entry?')) { if (window.confirm('Are you sure you want to publish this entry?')) {
this.props.handlePublish(collection, slug, ownStatus); this.props.handlePublish(collection, slug, ownStatus);
} }
} };
renderColumns(entries, column) { renderColumns = (entries, column) => {
if (!entries) return; if (!entries) return;
if (!column) { if (!column) {
@ -60,10 +53,10 @@ class UnpublishedListing extends React.Component {
<DragSource key={slug} slug={slug} collection={collection} ownStatus={ownStatus}> <DragSource key={slug} slug={slug} collection={collection} ownStatus={ownStatus}>
<div className={styles.drag}> <div className={styles.drag}>
<Card className={styles.card}> <Card className={styles.card}>
<h2><Link to={link}>{entry.getIn(['data', 'title'])}</Link> <small>by {author}</small></h2> <span className={styles.cardHeading}><Link to={link}>{entry.getIn(['data', 'title'])}</Link> <small>by {author}</small></span>
<p>Last updated: {timeStamp} by {entry.getIn(['metaData', 'user'])}</p> <p className={styles.cardText}>Last updated: {timeStamp} by {entry.getIn(['metaData', 'user'])}</p>
{(ownStatus === status.last()) && {(ownStatus === status.last()) &&
<button onClick={this.requestPublish.bind(this, collection, slug, status)}>Publish now</button> <button className={styles.button} onClick={this.requestPublish.bind(this, collection, slug, status)}>Publish now</button>
} }
</Card> </Card>
</div> </div>
@ -74,7 +67,13 @@ class UnpublishedListing extends React.Component {
)} )}
</div>; </div>;
} }
} };
static propTypes = {
entries: ImmutablePropTypes.orderedMap,
handleChangeStatus: PropTypes.func.isRequired,
handlePublish: PropTypes.func.isRequired,
};
render() { render() {
const columns = this.renderColumns(this.props.entries); const columns = this.renderColumns(this.props.entries);
@ -89,10 +88,4 @@ class UnpublishedListing extends React.Component {
} }
} }
UnpublishedListing.propTypes = {
entries: ImmutablePropTypes.orderedMap,
handleChangeStatus: PropTypes.func.isRequired,
handlePublish: PropTypes.func.isRequired,
};
export default HTML5DragDrop(UnpublishedListing); export default HTML5DragDrop(UnpublishedListing);

View File

@ -2,14 +2,9 @@ import React, { PropTypes } from 'react';
import DateTime from 'react-datetime'; import DateTime from 'react-datetime';
export default class DateTimeControl extends React.Component { export default class DateTimeControl extends React.Component {
constructor(props) { handleChange = datetime => {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(datetime) {
this.props.onChange(datetime); this.props.onChange(datetime);
} };
render() { render() {
return <DateTime value={this.props.value || new Date()} onChange={this.handleChange}/>; return <DateTime value={this.props.value || new Date()} onChange={this.handleChange}/>;

View File

@ -5,36 +5,25 @@ import MediaProxy from '../../valueObjects/MediaProxy';
const MAX_DISPLAY_LENGTH = 50; const MAX_DISPLAY_LENGTH = 50;
export default class ImageControl extends React.Component { export default class ImageControl extends React.Component {
constructor(props) { handleFileInputRef = el => {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleFileInputRef = this.handleFileInputRef.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleDragEnter = this.handleDragEnter.bind(this);
this.handleDragOver = this.handleDragOver.bind(this);
this.renderImageName = this.renderImageName.bind(this);
}
handleFileInputRef(el) {
this._fileInput = el; this._fileInput = el;
} };
handleClick(e) { handleClick = e => {
this._fileInput.click(); this._fileInput.click();
} };
handleDragEnter(e) { handleDragEnter = e => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
} };
handleDragOver(e) { handleDragOver = e => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
} };
handleChange(e) { handleChange = e => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@ -58,9 +47,9 @@ export default class ImageControl extends React.Component {
this.props.onChange(null); this.props.onChange(null);
} }
} };
renderImageName() { renderImageName = () => {
if (!this.props.value) return null; if (!this.props.value) return null;
if (this.value instanceof MediaProxy) { if (this.value instanceof MediaProxy) {
return truncateMiddle(this.props.value.path, MAX_DISPLAY_LENGTH); return truncateMiddle(this.props.value.path, MAX_DISPLAY_LENGTH);
@ -68,25 +57,25 @@ export default class ImageControl extends React.Component {
return truncateMiddle(this.props.value, MAX_DISPLAY_LENGTH); return truncateMiddle(this.props.value, MAX_DISPLAY_LENGTH);
} }
} };
render() { render() {
const imageName = this.renderImageName(); const imageName = this.renderImageName();
return ( return (
<div <div
onDragEnter={this.handleDragEnter} onDragEnter={this.handleDragEnter}
onDragOver={this.handleDragOver} onDragOver={this.handleDragOver}
onDrop={this.handleChange} onDrop={this.handleChange}
> >
<span style={styles.imageUpload} onClick={this.handleClick}> <span style={styles.imageUpload} onClick={this.handleClick}>
{imageName ? imageName : 'Tip: Click here to upload an image from your file browser, or drag an image directly into this box from your desktop'} {imageName ? imageName : 'Tip: Click here to upload an image from your file browser, or drag an image directly into this box from your desktop'}
</span> </span>
<input <input
type="file" type="file"
accept="image/*" accept="image/*"
onChange={this.handleChange} onChange={this.handleChange}
style={styles.input} style={styles.input}
ref={this.handleFileInputRef} ref={this.handleFileInputRef}
/> />
</div> </div>
); );

View File

@ -7,6 +7,14 @@ import { connect } from 'react-redux';
import { switchVisualMode } from '../../actions/editor'; import { switchVisualMode } from '../../actions/editor';
class MarkdownControl extends React.Component { class MarkdownControl extends React.Component {
static propTypes = {
editor: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
onAddMedia: PropTypes.func.isRequired,
getMedia: PropTypes.func.isRequired,
switchVisualMode: PropTypes.func.isRequired,
value: PropTypes.node,
};
componentWillMount() { componentWillMount() {
this.useRawEditor(); this.useRawEditor();
@ -15,11 +23,11 @@ class MarkdownControl extends React.Component {
useVisualEditor = () => { useVisualEditor = () => {
this.props.switchVisualMode(true); this.props.switchVisualMode(true);
} };
useRawEditor = () => { useRawEditor = () => {
this.props.switchVisualMode(false); this.props.switchVisualMode(false);
} };
render() { render() {
const { editor, onChange, onAddMedia, getMedia, value } = this.props; const { editor, onChange, onAddMedia, getMedia, value } = this.props;
@ -28,11 +36,11 @@ class MarkdownControl extends React.Component {
<div className='cms-editor-visual'> <div className='cms-editor-visual'>
{null && <button onClick={this.useRawEditor}>Switch to Raw Editor</button>} {null && <button onClick={this.useRawEditor}>Switch to Raw Editor</button>}
<VisualEditor <VisualEditor
onChange={onChange} onChange={onChange}
onAddMedia={onAddMedia} onAddMedia={onAddMedia}
getMedia={getMedia} getMedia={getMedia}
registeredComponents={editor.get('registeredComponents')} registeredComponents={editor.get('registeredComponents')}
value={value} value={value}
/> />
</div> </div>
); );
@ -41,10 +49,10 @@ class MarkdownControl extends React.Component {
<div className='cms-editor-raw'> <div className='cms-editor-raw'>
{null && <button onClick={this.useVisualEditor}>Switch to Visual Editor</button>} {null && <button onClick={this.useVisualEditor}>Switch to Visual Editor</button>}
<RawEditor <RawEditor
onChange={onChange} onChange={onChange}
onAddMedia={onAddMedia} onAddMedia={onAddMedia}
getMedia={getMedia} getMedia={getMedia}
value={value} value={value}
/> />
</div> </div>
); );
@ -52,19 +60,6 @@ class MarkdownControl extends React.Component {
} }
} }
MarkdownControl.propTypes = {
editor: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
onAddMedia: PropTypes.func.isRequired,
getMedia: PropTypes.func.isRequired,
switchVisualMode: PropTypes.func.isRequired,
value: PropTypes.node,
};
MarkdownControl.contextTypes = {
plugins: PropTypes.object,
};
export default connect( export default connect(
state => ({ editor: state.editor }), state => ({ editor: state.editor }),
{ switchVisualMode } { switchVisualMode }

View File

@ -72,7 +72,14 @@ const SCHEMA = {
} }
}; };
class RawEditor extends React.Component { export default class RawEditor extends React.Component {
static propTypes = {
onAddMedia: PropTypes.func.isRequired,
getMedia: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
value: PropTypes.string,
};
constructor(props) { constructor(props) {
super(props); super(props);
@ -104,12 +111,12 @@ class RawEditor extends React.Component {
*/ */
handleChange = state => { handleChange = state => {
this.setState({ state }); this.setState({ state });
} };
handleDocumentChange = (document, state) => { handleDocumentChange = (document, state) => {
const content = Plain.serialize(state, { terse: true }); const content = Plain.serialize(state, { terse: true });
this.props.onChange(content); this.props.onChange(content);
} };
render() { render() {
return ( return (
@ -125,12 +132,3 @@ class RawEditor extends React.Component {
); );
} }
} }
export default RawEditor;
RawEditor.propTypes = {
onAddMedia: PropTypes.func.isRequired,
getMedia: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
value: PropTypes.string,
};

View File

@ -5,21 +5,17 @@ import MediaProxy from '../../../../valueObjects/MediaProxy';
import styles from './BlockTypesMenu.css'; import styles from './BlockTypesMenu.css';
class BlockTypesMenu extends Component { class BlockTypesMenu extends Component {
constructor(props) {
super(props);
this.state = { static propTypes = {
expanded: false plugins: PropTypes.array.isRequired,
}; onClickBlock: PropTypes.func.isRequired,
onClickPlugin: PropTypes.func.isRequired,
onClickImage: PropTypes.func.isRequired
};
this.toggleMenu = this.toggleMenu.bind(this); state = {
this.handleBlockTypeClick = this.handleBlockTypeClick.bind(this); expanded: false
this.handlePluginClick = this.handlePluginClick.bind(this); };
this.handleFileUploadClick = this.handleFileUploadClick.bind(this);
this.handleFileUploadChange = this.handleFileUploadChange.bind(this);
this.renderBlockTypeButton = this.renderBlockTypeButton.bind(this);
this.renderPluginButton = this.renderPluginButton.bind(this);
}
componentWillUpdate() { componentWillUpdate() {
if (this.state.expanded) { if (this.state.expanded) {
@ -27,27 +23,27 @@ class BlockTypesMenu extends Component {
} }
} }
toggleMenu() { toggleMenu = () => {
this.setState({ expanded: !this.state.expanded }); this.setState({ expanded: !this.state.expanded });
} };
handleBlockTypeClick(e, type) { handleBlockTypeClick = (e, type) => {
this.props.onClickBlock(type); this.props.onClickBlock(type);
} };
handlePluginClick(e, plugin) { handlePluginClick = (e, plugin) => {
const data = {}; const data = {};
plugin.fields.forEach(field => { plugin.fields.forEach(field => {
data[field.name] = window.prompt(field.label); // eslint-disable-line data[field.name] = window.prompt(field.label); // eslint-disable-line
}); });
this.props.onClickPlugin(plugin.id, data); this.props.onClickPlugin(plugin.id, data);
} };
handleFileUploadClick() { handleFileUploadClick = () => {
this._fileInput.click(); this._fileInput.click();
} };
handleFileUploadChange(e) { handleFileUploadChange = e => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@ -67,21 +63,21 @@ class BlockTypesMenu extends Component {
this.props.onClickImage(mediaProxy); this.props.onClickImage(mediaProxy);
} }
} };
renderBlockTypeButton(type, icon) { renderBlockTypeButton = (type, icon) => {
const onClick = e => this.handleBlockTypeClick(e, type); const onClick = e => this.handleBlockTypeClick(e, type);
return ( return (
<Icon key={type} type={icon} onClick={onClick} className={styles.icon}/> <Icon key={type} type={icon} onClick={onClick} className={styles.icon}/>
); );
} };
renderPluginButton(plugin) { renderPluginButton = plugin => {
const onClick = e => this.handlePluginClick(e, plugin); const onClick = e => this.handlePluginClick(e, plugin);
return ( return (
<Icon key={plugin.id} type={plugin.icon} onClick={onClick} className={styles.icon}/> <Icon key={plugin.id} type={plugin.icon} onClick={onClick} className={styles.icon}/>
); );
} };
renderMenu() { renderMenu() {
const { plugins } = this.props; const { plugins } = this.props;
@ -117,11 +113,4 @@ class BlockTypesMenu extends Component {
} }
} }
BlockTypesMenu.propTypes = {
plugins: PropTypes.array.isRequired,
onClickBlock: PropTypes.func.isRequired,
onClickPlugin: PropTypes.func.isRequired,
onClickImage: PropTypes.func.isRequired
};
export default withPortalAtCursorPosition(BlockTypesMenu); export default withPortalAtCursorPosition(BlockTypesMenu);

View File

@ -5,43 +5,39 @@ import styles from './StylesMenu.css';
class StylesMenu extends Component { class StylesMenu extends Component {
constructor() { static propTypes = {
super(); marks: PropTypes.object.isRequired,
blocks: PropTypes.object.isRequired,
this.hasMark = this.hasMark.bind(this); inlines: PropTypes.object.isRequired,
this.hasBlock = this.hasBlock.bind(this); onClickBlock: PropTypes.func.isRequired,
this.renderMarkButton = this.renderMarkButton.bind(this); onClickMark: PropTypes.func.isRequired,
this.renderBlockButton = this.renderBlockButton.bind(this); onClickInline: PropTypes.func.isRequired
this.renderLinkButton = this.renderLinkButton.bind(this); };
this.handleMarkClick = this.handleMarkClick.bind(this);
this.handleInlineClick = this.handleInlineClick.bind(this);
this.handleBlockClick = this.handleBlockClick.bind(this);
}
/** /**
* Used to set toolbar buttons to active state * Used to set toolbar buttons to active state
*/ */
hasMark(type) { hasMark = type => {
const { marks } = this.props; const { marks } = this.props;
return marks.some(mark => mark.type == type); return marks.some(mark => mark.type == type);
} };
hasBlock(type) { hasBlock = type => {
const { blocks } = this.props; const { blocks } = this.props;
return blocks.some(node => node.type == type); return blocks.some(node => node.type == type);
} };
hasLinks(type) { hasLinks = type => {
const { inlines } = this.props; const { inlines } = this.props;
return inlines.some(inline => inline.type == 'link'); return inlines.some(inline => inline.type == 'link');
} };
handleMarkClick(e, type) { handleMarkClick = (e, type) => {
e.preventDefault(); e.preventDefault();
this.props.onClickMark(type); this.props.onClickMark(type);
} };
renderMarkButton(type, icon) { renderMarkButton = (type, icon) => {
const isActive = this.hasMark(type); const isActive = this.hasMark(type);
const onMouseDown = e => this.handleMarkClick(e, type); const onMouseDown = e => this.handleMarkClick(e, type);
return ( return (
@ -49,14 +45,14 @@ class StylesMenu extends Component {
<Icon type={icon}/> <Icon type={icon}/>
</span> </span>
); );
} };
handleInlineClick(e, type, isActive) { handleInlineClick = (e, type, isActive) => {
e.preventDefault(); e.preventDefault();
this.props.onClickInline(type, isActive); this.props.onClickInline(type, isActive);
} };
renderLinkButton() { renderLinkButton = () => {
const isActive = this.hasLinks(); const isActive = this.hasLinks();
const onMouseDown = e => this.handleInlineClick(e, 'link', isActive); const onMouseDown = e => this.handleInlineClick(e, 'link', isActive);
return ( return (
@ -64,16 +60,16 @@ class StylesMenu extends Component {
<Icon type="link"/> <Icon type="link"/>
</span> </span>
); );
} };
handleBlockClick(e, type) { handleBlockClick = (e, type) => {
e.preventDefault(); e.preventDefault();
const isActive = this.hasBlock(type); const isActive = this.hasBlock(type);
const isList = this.hasBlock('list-item'); const isList = this.hasBlock('list-item');
this.props.onClickBlock(type, isActive, isList); this.props.onClickBlock(type, isActive, isList);
} };
renderBlockButton(type, icon, checkType) { renderBlockButton = (type, icon, checkType) => {
checkType = checkType || type; checkType = checkType || type;
const isActive = this.hasBlock(checkType); const isActive = this.hasBlock(checkType);
const onMouseDown = e => this.handleBlockClick(e, type); const onMouseDown = e => this.handleBlockClick(e, type);
@ -82,7 +78,7 @@ class StylesMenu extends Component {
<Icon type={icon}/> <Icon type={icon}/>
</span> </span>
); );
} };
render() { render() {
return ( return (
@ -98,16 +94,6 @@ class StylesMenu extends Component {
</div> </div>
); );
} }
} }
StylesMenu.propTypes = {
marks: PropTypes.object.isRequired,
blocks: PropTypes.object.isRequired,
inlines: PropTypes.object.isRequired,
onClickBlock: PropTypes.func.isRequired,
onClickMark: PropTypes.func.isRequired,
onClickInline: PropTypes.func.isRequired
};
export default withPortalAtCursorPosition(StylesMenu); export default withPortalAtCursorPosition(StylesMenu);

View File

@ -13,11 +13,18 @@ import BlockTypesMenu from './BlockTypesMenu';
/** /**
* Slate Render Configuration * Slate Render Configuration
*/ */
class VisualEditor extends React.Component { export default class VisualEditor extends React.Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
onAddMedia: PropTypes.func.isRequired,
getMedia: PropTypes.func.isRequired,
value: PropTypes.string,
};
constructor(props) { constructor(props) {
super(props); super(props);
this.getMedia = this.getMedia.bind(this);
const MarkdownSyntax = getSyntaxes(this.getMedia).markdown; const MarkdownSyntax = getSyntaxes(this.getMedia).markdown;
this.markdown = new MarkupIt(MarkdownSyntax); this.markdown = new MarkupIt(MarkdownSyntax);
@ -46,48 +53,36 @@ class VisualEditor extends React.Component {
} }
}) })
]; ];
this.handleChange = this.handleChange.bind(this);
this.handleDocumentChange = this.handleDocumentChange.bind(this);
this.handleMarkStyleClick = this.handleMarkStyleClick.bind(this);
this.handleBlockStyleClick = this.handleBlockStyleClick.bind(this);
this.handleInlineClick = this.handleInlineClick.bind(this);
this.handleBlockTypeClick = this.handleBlockTypeClick.bind(this);
this.handlePluginClick = this.handlePluginClick.bind(this);
this.handleImageClick = this.handleImageClick.bind(this);
this.focusAndAddParagraph = this.focusAndAddParagraph.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.renderBlockTypesMenu = this.renderBlockTypesMenu.bind(this);
} }
getMedia(src) { getMedia = src => {
return this.props.getMedia(src); return this.props.getMedia(src);
} };
/** /**
* Slate keeps track of selections, scroll position etc. * Slate keeps track of selections, scroll position etc.
* So, onChange gets dispatched on every interaction (click, arrows, everything...) * So, onChange gets dispatched on every interaction (click, arrows, everything...)
* It also have an onDocumentChange, that get's dispached only when the actual * It also have an onDocumentChange, that get's dispatched only when the actual
* content changes * content changes
*/ */
handleChange(state) { handleChange = state => {
if (this.blockEdit) { if (this.blockEdit) {
this.blockEdit = false; this.blockEdit = false;
} else { } else {
this.setState({ state }); this.setState({ state });
} }
} };
handleDocumentChange(document, state) { handleDocumentChange = (document, state) => {
const rawJson = Raw.serialize(state, { terse: true }); const rawJson = Raw.serialize(state, { terse: true });
const content = SlateUtils.decode(rawJson); const content = SlateUtils.decode(rawJson);
this.props.onChange(this.markdown.toText(content)); this.props.onChange(this.markdown.toText(content));
} };
/** /**
* Toggle marks / blocks when button is clicked * Toggle marks / blocks when button is clicked
*/ */
handleMarkStyleClick(type) { handleMarkStyleClick = type => {
let { state } = this.state; let { state } = this.state;
state = state state = state
@ -96,9 +91,9 @@ class VisualEditor extends React.Component {
.apply(); .apply();
this.setState({ state }); this.setState({ state });
} };
handleBlockStyleClick(type, isActive, isList) { handleBlockStyleClick = (type, isActive, isList) => {
let { state } = this.state; let { state } = this.state;
let transform = state.transform(); let transform = state.transform();
const { document } = state; const { document } = state;
@ -142,7 +137,7 @@ class VisualEditor extends React.Component {
state = transform.apply(); state = transform.apply();
this.setState({ state }); this.setState({ state });
} };
/** /**
* When clicking a link, if the selection has a link in it, remove the link. * When clicking a link, if the selection has a link in it, remove the link.
@ -151,7 +146,7 @@ class VisualEditor extends React.Component {
* @param {Event} e * @param {Event} e
*/ */
handleInlineClick(type, isActive) { handleInlineClick = (type, isActive) => {
let { state } = this.state; let { state } = this.state;
if (type === 'link') { if (type === 'link') {
@ -177,9 +172,9 @@ class VisualEditor extends React.Component {
} }
} }
this.setState({ state }); this.setState({ state });
} };
handleBlockTypeClick(type) { handleBlockTypeClick = type => {
let { state } = this.state; let { state } = this.state;
state = state state = state
@ -191,9 +186,9 @@ class VisualEditor extends React.Component {
.apply(); .apply();
this.setState({ state }, this.focusAndAddParagraph); this.setState({ state }, this.focusAndAddParagraph);
} };
handlePluginClick(type, data) { handlePluginClick = (type, data) => {
let { state } = this.state; let { state } = this.state;
state = state state = state
@ -209,9 +204,9 @@ class VisualEditor extends React.Component {
.apply(); .apply();
this.setState({ state }); this.setState({ state });
} };
handleImageClick(mediaProxy) { handleImageClick = mediaProxy => {
let { state } = this.state; let { state } = this.state;
this.props.onAddMedia(mediaProxy); this.props.onAddMedia(mediaProxy);
@ -221,9 +216,9 @@ class VisualEditor extends React.Component {
.apply(); .apply();
this.setState({ state }); this.setState({ state });
} };
focusAndAddParagraph() { focusAndAddParagraph = () => {
const { state } = this.state; const { state } = this.state;
const blocks = state.document.getBlocks(); const blocks = state.document.getBlocks();
const last = blocks.last(); const last = blocks.last();
@ -237,9 +232,9 @@ class VisualEditor extends React.Component {
snapshot: false snapshot: false
}); });
this.setState({ state: normalized }); this.setState({ state: normalized });
} };
handleKeyDown(evt) { handleKeyDown = evt => {
if (evt.shiftKey && evt.key === 'Enter') { if (evt.shiftKey && evt.key === 'Enter') {
this.blockEdit = true; this.blockEdit = true;
let { state } = this.state; let { state } = this.state;
@ -250,9 +245,9 @@ class VisualEditor extends React.Component {
this.setState({ state }); this.setState({ state });
} }
} };
renderBlockTypesMenu() { renderBlockTypesMenu = () => {
const currentBlock = this.state.state.blocks.get(0); const currentBlock = this.state.state.blocks.get(0);
const isOpen = (this.props.value !== undefined && currentBlock.isEmpty && currentBlock.type !== 'horizontal-rule'); const isOpen = (this.props.value !== undefined && currentBlock.isEmpty && currentBlock.type !== 'horizontal-rule');
@ -265,7 +260,7 @@ class VisualEditor extends React.Component {
onClickImage={this.handleImageClick} onClickImage={this.handleImageClick}
/> />
); );
} };
renderStylesMenu() { renderStylesMenu() {
const { state } = this.state; const { state } = this.state;
@ -302,12 +297,3 @@ class VisualEditor extends React.Component {
); );
} }
} }
export default VisualEditor;
VisualEditor.propTypes = {
onChange: PropTypes.func.isRequired,
onAddMedia: PropTypes.func.isRequired,
getMedia: PropTypes.func.isRequired,
value: PropTypes.string,
};

View File

@ -18,6 +18,14 @@ const EditorComponent = Record({
class Plugin extends Component { class Plugin extends Component {
static propTypes = {
children: PropTypes.element.isRequired
};
static childContextTypes = {
plugins: PropTypes.object
};
getChildContext() { getChildContext() {
return { plugins: plugins }; return { plugins: plugins };
} }
@ -27,13 +35,6 @@ class Plugin extends Component {
} }
} }
Plugin.propTypes = {
children: PropTypes.element.isRequired
};
Plugin.childContextTypes = {
plugins: PropTypes.object
};
export function newEditorPlugin(config) { export function newEditorPlugin(config) {
const configObj = new EditorComponent({ const configObj = new EditorComponent({
id: config.id || config.label.replace(/[^A-Z0-9]+/ig, '_'), id: config.id || config.label.replace(/[^A-Z0-9]+/ig, '_'),

View File

@ -1,14 +1,9 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
export default class StringControl extends React.Component { export default class StringControl extends React.Component {
constructor(props) { handleChange = e => {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(e.target.value); this.props.onChange(e.target.value);
} };
render() { render() {
return <input type="text" value={this.props.value || ''} onChange={this.handleChange}/>; return <input type="text" value={this.props.value || ''} onChange={this.handleChange}/>;

View File

@ -1,20 +1,14 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
export default class StringControl extends React.Component { export default class StringControl extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleRef = this.handleRef.bind(this);
}
componentDidMount() { componentDidMount() {
this.updateHeight(); this.updateHeight();
} }
handleChange(e) { handleChange = e => {
this.props.onChange(e.target.value); this.props.onChange(e.target.value);
this.updateHeight(); this.updateHeight();
} };
updateHeight() { updateHeight() {
if (this.element.scrollHeight > this.element.clientHeight) { if (this.element.scrollHeight > this.element.clientHeight) {
@ -22,9 +16,9 @@ export default class StringControl extends React.Component {
} }
} }
handleRef(ref) { handleRef = ref => {
this.element = ref; this.element = ref;
} };
render() { render() {
return <textarea ref={this.handleRef} value={this.props.value || ''} onChange={this.handleChange}/>; return <textarea ref={this.handleRef} value={this.props.value || ''} onChange={this.handleChange}/>;

View File

@ -4,7 +4,17 @@
.nav { .nav {
display: block; display: block;
padding: 1rem; padding: 1rem;
& .heading {
border: none;
}
} }
.main { .main {
padding-top: 54px; padding-top: 54px;
} }
.navDrawer {
max-width: 240px !important;
& .drawerContent {
max-width: 240px !important;
}
}

View File

@ -20,8 +20,8 @@ import styles from './App.css';
class App extends React.Component { class App extends React.Component {
state = { state = {
navDrawerIsVisible: false navDrawerIsVisible: true
} };
componentDidMount() { componentDidMount() {
this.props.dispatch(loadConfig()); this.props.dispatch(loadConfig());
@ -100,7 +100,7 @@ class App extends React.Component {
this.setState({ this.setState({
navDrawerIsVisible: !this.state.navDrawerIsVisible navDrawerIsVisible: !this.state.navDrawerIsVisible
}); });
} };
render() { render() {
const { navDrawerIsVisible } = this.state; const { navDrawerIsVisible } = this.state;
@ -135,18 +135,19 @@ class App extends React.Component {
return ( return (
<Layout theme={styles}> <Layout theme={styles}>
<NavDrawer <NavDrawer
active={navDrawerIsVisible} active={navDrawerIsVisible}
scrollY scrollY
permanentAt="md" permanentAt={navDrawerIsVisible ? 'lg' : null}
theme={styles}
> >
<nav className={styles.nav}> <nav className={styles.nav}>
<h1>Collections</h1> <h1 className={styles.heading}>Collections</h1>
<Navigation type='vertical'> <Navigation type='vertical'>
{ {
collections.valueSeq().map(collection => collections.valueSeq().map(collection =>
<Link <Link
key={collection.get('name')} key={collection.get('name')}
onClick={navigateToCollection.bind(this, collection.get('name'))} onClick={navigateToCollection.bind(this, collection.get('name'))}
> >
{collection.get('label')} {collection.get('label')}
</Link> </Link>
@ -157,14 +158,14 @@ class App extends React.Component {
</NavDrawer> </NavDrawer>
<Panel scrollY> <Panel scrollY>
<AppHeader <AppHeader
collections={collections} collections={collections}
commands={commands} commands={commands}
defaultCommands={defaultCommands} defaultCommands={defaultCommands}
runCommand={runCommand} runCommand={runCommand}
onCreateEntryClick={createNewEntryInCollection} onCreateEntryClick={createNewEntryInCollection}
toggleNavDrawer={this.toggleNavDrawer} toggleNavDrawer={this.toggleNavDrawer}
/> />
<div className={`${styles.alignable} ${styles.main}`}> <div className={styles.main}>
{children} {children}
</div> </div>
</Panel> </Panel>

View File

@ -1,39 +1,39 @@
.alignable { .root {
margin: 0px auto; margin: auto;
} }
@media (max-width: 749px) and (min-width: 495px) { @media (max-width: 749px) and (min-width: 495px) {
.alignable { .root {
width: 495px; width: 495px;
margin: auto;
} }
} }
@media (max-width: 1004px) and (min-width: 750px) { @media (max-width: 1004px) and (min-width: 750px) {
.alignable { .root {
width: 750px; width: 750px;
margin: auto;
} }
} }
@media (max-width: 1259px) and (min-width: 1005px) { @media (max-width: 1514px) and (min-width: 1005px) {
.alignable { .root {
width: 1005px; width: 1005px;
margin: auto;
} }
} }
@media (max-width: 1514px) and (min-width: 1260px) {
.alignable {
width: 1260px;
}
}
@media (max-width: 1769px) and (min-width: 1515px) { @media (max-width: 1769px) and (min-width: 1515px) {
.alignable { .root {
width: 1515px; width: 1515px;
margin: auto;
} }
} }
@media (min-width: 1770px) { @media (min-width: 1770px) {
.alignable { .root {
width: 1770px; width: 1770px;
margin: auto;
} }
} }

View File

@ -9,6 +9,13 @@ import styles from './CollectionPage.css';
import CollectionPageHOC from './editorialWorkflow/CollectionPageHOC'; import CollectionPageHOC from './editorialWorkflow/CollectionPageHOC';
class DashboardPage extends React.Component { class DashboardPage extends React.Component {
static propTypes = {
collection: ImmutablePropTypes.map.isRequired,
collections: ImmutablePropTypes.orderedMap.isRequired,
dispatch: PropTypes.func.isRequired,
entries: ImmutablePropTypes.list,
};
componentDidMount() { componentDidMount() {
const { collection, dispatch } = this.props; const { collection, dispatch } = this.props;
if (collection) { if (collection) {
@ -30,7 +37,7 @@ class DashboardPage extends React.Component {
} }
return <div className={styles.alignable}> return <div className={styles.root}>
{entries ? {entries ?
<EntryListing collection={collection} entries={entries}/> <EntryListing collection={collection} entries={entries}/>
: :
@ -39,12 +46,6 @@ class DashboardPage extends React.Component {
</div>; </div>;
} }
} }
DashboardPage.propTypes = {
collection: ImmutablePropTypes.map.isRequired,
collections: ImmutablePropTypes.orderedMap.isRequired,
dispatch: PropTypes.func.isRequired,
entries: ImmutablePropTypes.list,
};
/* /*
* Instead of checking the publish mode everywhere to dispatch & render the additional editorial workflow stuff, * Instead of checking the publish mode everywhere to dispatch & render the additional editorial workflow stuff,

View File

@ -15,11 +15,22 @@ import EntryEditor from '../components/EntryEditor/EntryEditor';
import EntryPageHOC from './editorialWorkflow/EntryPageHOC'; import EntryPageHOC from './editorialWorkflow/EntryPageHOC';
class EntryPage extends React.Component { class EntryPage extends React.Component {
constructor(props) { static propTypes = {
super(props); addMedia: PropTypes.func.isRequired,
this.createDraft = this.createDraft.bind(this); boundGetMedia: PropTypes.func.isRequired,
this.handlePersistEntry = this.handlePersistEntry.bind(this); changeDraft: PropTypes.func.isRequired,
} collection: ImmutablePropTypes.map.isRequired,
createDraftFromEntry: PropTypes.func.isRequired,
createEmptyDraft: PropTypes.func.isRequired,
discardDraft: PropTypes.func.isRequired,
entry: ImmutablePropTypes.map,
entryDraft: ImmutablePropTypes.map.isRequired,
loadEntry: PropTypes.func.isRequired,
persistEntry: PropTypes.func.isRequired,
removeMedia: PropTypes.func.isRequired,
slug: PropTypes.string,
newEntry: PropTypes.bool.isRequired,
};
componentDidMount() { componentDidMount() {
if (!this.props.newEntry) { if (!this.props.newEntry) {
@ -45,13 +56,13 @@ class EntryPage extends React.Component {
this.props.discardDraft(); this.props.discardDraft();
} }
createDraft(entry) { createDraft = entry => {
if (entry) this.props.createDraftFromEntry(entry); if (entry) this.props.createDraftFromEntry(entry);
} };
handlePersistEntry() { handlePersistEntry = () => {
this.props.persistEntry(this.props.collection, this.props.entryDraft); this.props.persistEntry(this.props.collection, this.props.entryDraft);
} };
render() { render() {
const { const {
@ -75,23 +86,6 @@ class EntryPage extends React.Component {
} }
} }
EntryPage.propTypes = {
addMedia: PropTypes.func.isRequired,
boundGetMedia: PropTypes.func.isRequired,
changeDraft: PropTypes.func.isRequired,
collection: ImmutablePropTypes.map.isRequired,
createDraftFromEntry: PropTypes.func.isRequired,
createEmptyDraft: PropTypes.func.isRequired,
discardDraft: PropTypes.func.isRequired,
entry: ImmutablePropTypes.map,
entryDraft: ImmutablePropTypes.map.isRequired,
loadEntry: PropTypes.func.isRequired,
persistEntry: PropTypes.func.isRequired,
removeMedia: PropTypes.func.isRequired,
slug: PropTypes.string,
newEntry: PropTypes.bool.isRequired,
};
function mapStateToProps(state, ownProps) { function mapStateToProps(state, ownProps) {
const { collections, entryDraft } = state; const { collections, entryDraft } = state;
const collection = collections.get(ownProps.params.name); const collection = collections.get(ownProps.params.name);

View File

@ -10,6 +10,11 @@ import styles from '../CollectionPage.css';
export default function CollectionPageHOC(CollectionPage) { export default function CollectionPageHOC(CollectionPage) {
class CollectionPageHOC extends CollectionPage { class CollectionPageHOC extends CollectionPage {
static propTypes = {
dispatch: PropTypes.func.isRequired,
isEditorialWorkflow: PropTypes.bool.isRequired,
unpublishedEntries: ImmutablePropTypes.map,
};
componentDidMount() { componentDidMount() {
const { dispatch, isEditorialWorkflow } = this.props; const { dispatch, isEditorialWorkflow } = this.props;
@ -24,24 +29,20 @@ export default function CollectionPageHOC(CollectionPage) {
if (!isEditorialWorkflow) return super.render(); if (!isEditorialWorkflow) return super.render();
return ( return (
<div className={styles.alignable}> <div>
<UnpublishedListing <div className={styles.root}>
<UnpublishedListing
entries={unpublishedEntries} entries={unpublishedEntries}
handleChangeStatus={updateUnpublishedEntryStatus} handleChangeStatus={updateUnpublishedEntryStatus}
handlePublish={publishUnpublishedEntry} handlePublish={publishUnpublishedEntry}
/> />
</div>
{super.render()} {super.render()}
</div> </div>
); );
} }
} }
CollectionPageHOC.propTypes = {
dispatch: PropTypes.func.isRequired,
isEditorialWorkflow: PropTypes.bool.isRequired,
unpublishedEntries: ImmutablePropTypes.map,
};
function mapStateToProps(state) { function mapStateToProps(state) {
const publish_mode = state.config.get('publish_mode'); const publish_mode = state.config.get('publish_mode');
const isEditorialWorkflow = (publish_mode === EDITORIAL_WORKFLOW); const isEditorialWorkflow = (publish_mode === EDITORIAL_WORKFLOW);