Merge pull request #88 from netlify/class-properties-initializers

Class properties initializers
This commit is contained in:
Andrey Okonetchnikov 2016-10-03 16:42:25 +02:00 committed by GitHub
commit 2022e203bf
26 changed files with 243 additions and 364 deletions

View File

@ -3,7 +3,10 @@ env:
es6: true es6: true
parser: babel-eslint parser: babel-eslint
plugins: [ "react" ] plugins: [
"react",
"class-property"
]
rules: rules:
# Possible Errors # Possible Errors
@ -97,6 +100,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

View File

@ -42,6 +42,7 @@
"babel-runtime": "^6.5.0", "babel-runtime": "^6.5.0",
"css-loader": "^0.23.1", "css-loader": "^0.23.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 {

View File

@ -5,11 +5,8 @@ import PreviewPane from './PreviewPane';
import styles from './EntryEditor.css'; import styles from './EntryEditor.css';
export default class EntryEditor extends React.Component { export default class EntryEditor extends React.Component {
constructor(props) {
super(props); state = {};
this.state = {};
this.handleResize = this.handleResize.bind(this);
}
componentDidMount() { componentDidMount() {
this.calculateHeight(); this.calculateHeight();
@ -20,9 +17,9 @@ export default class EntryEditor extends React.Component {
window.removeEventListener('resize', this.handleResize); window.removeEventListener('resize', this.handleResize);
} }
handleResize() { handleResize = () => {
this.calculateHeight(); this.calculateHeight();
} };
calculateHeight() { calculateHeight() {
const height = window.innerHeight - 54; const height = window.innerHeight - 54;

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) => {
@ -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

@ -6,6 +6,12 @@ import { resolveWidget } from './Widgets';
import styles from './PreviewPane.css'; import styles from './PreviewPane.css';
class Preview extends React.Component { class Preview extends React.Component {
static propTypes = {
collection: ImmutablePropTypes.map.isRequired,
entry: ImmutablePropTypes.map.isRequired,
getMedia: PropTypes.func.isRequired,
};
previewFor(field) { previewFor(field) {
const { entry, getMedia } = this.props; const { entry, getMedia } = this.props;
const widget = resolveWidget(field.get('widget')); const widget = resolveWidget(field.get('widget'));
@ -26,24 +32,12 @@ class Preview extends React.Component {
} }
} }
Preview.propTypes = {
collection: ImmutablePropTypes.map.isRequired,
entry: ImmutablePropTypes.map.isRequired,
getMedia: PropTypes.func.isRequired,
};
export default class PreviewPane extends React.Component { export default class PreviewPane extends React.Component {
constructor(props) {
super(props);
this.handleIframeRef = this.handleIframeRef.bind(this);
this.widgetFor = this.widgetFor.bind(this);
}
componentDidUpdate() { componentDidUpdate() {
this.renderPreview(); this.renderPreview();
} }
widgetFor(name) { widgetFor = name => {
const { collection, entry, getMedia } = this.props; const { collection, entry, getMedia } = this.props;
const field = collection.get('fields').find((field) => field.get('name') === name); const field = collection.get('fields').find((field) => field.get('name') === name);
const widget = resolveWidget(field.get('widget')); const widget = resolveWidget(field.get('widget'));
@ -52,7 +46,7 @@ export default class PreviewPane extends React.Component {
value: entry.getIn(['data', field.get('name')]), value: entry.getIn(['data', field.get('name')]),
getMedia: getMedia, getMedia: getMedia,
}); });
} };
renderPreview() { renderPreview() {
const props = Object.assign({}, this.props, { widgetFor: this.widgetFor }); const props = Object.assign({}, this.props, { widgetFor: this.widgetFor });
@ -61,7 +55,7 @@ export default class PreviewPane extends React.Component {
render(React.createElement(component, props), this.previewEl); render(React.createElement(component, props), this.previewEl);
} }
handleIframeRef(ref) { handleIframeRef = ref => {
if (ref) { if (ref) {
registry.getPreviewStyles().forEach((style) => { registry.getPreviewStyles().forEach((style) => {
const linkEl = document.createElement('link'); const linkEl = document.createElement('link');
@ -73,7 +67,7 @@ export default class PreviewPane extends React.Component {
ref.contentDocument.body.appendChild(this.previewEl); ref.contentDocument.body.appendChild(this.previewEl);
this.renderPreview(); this.renderPreview();
} }
} };
render() { render() {
const { collection } = this.props; const { collection } = this.props;

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) {
@ -48,7 +44,7 @@ export default class Loader extends React.Component {
</ReactCSSTransitionGroup> </ReactCSSTransitionGroup>
</div>; </div>;
} }
} };
render() { render() {
const { active, style, className = '' } = this.props; const { active, style, className = '' } = this.props;

View File

@ -3,15 +3,11 @@ 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); state = {
this.state = {
shown: false shown: false
}; };
this.autoHideTimeout = this.autoHideTimeout.bind(this);
}
componentWillMount() { componentWillMount() {
if (this.props.show) { if (this.props.show) {
this.autoHideTimeout(); this.autoHideTimeout();
@ -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

@ -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) {
@ -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,7 +57,7 @@ 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();

View File

@ -7,24 +7,31 @@ 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 {
constructor(props, context) { static propTypes = {
super(props, context); editor: PropTypes.object.isRequired,
this.useVisualEditor = this.useVisualEditor.bind(this); onChange: PropTypes.func.isRequired,
this.useRawEditor = this.useRawEditor.bind(this); onAddMedia: PropTypes.func.isRequired,
} getMedia: PropTypes.func.isRequired,
switchVisualMode: PropTypes.func.isRequired,
value: PropTypes.node,
};
static contextTypes = {
plugins: PropTypes.object,
};
componentWillMount() { componentWillMount() {
this.useRawEditor(); this.useRawEditor();
processEditorPlugins(registry.getEditorComponents()); processEditorPlugins(registry.getEditorComponents());
} }
useVisualEditor() { useVisualEditor = () => {
this.props.switchVisualMode(true); this.props.switchVisualMode(true);
} };
useRawEditor() { useRawEditor = () => {
this.props.switchVisualMode(false); this.props.switchVisualMode(false);
} };
renderEditor() { renderEditor() {
const { editor, onChange, onAddMedia, getMedia, value } = this.props; const { editor, onChange, onAddMedia, getMedia, value } = this.props;
@ -66,19 +73,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

@ -73,7 +73,6 @@ const SCHEMA = {
}; };
class RawEditor extends React.Component { class RawEditor extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -82,10 +81,6 @@ class RawEditor extends React.Component {
this.state = { this.state = {
state: content state: content
}; };
this.handleChange = this.handleChange.bind(this);
this.handleDocumentChange = this.handleDocumentChange.bind(this);
} }
/** /**
@ -94,14 +89,14 @@ class RawEditor extends React.Component {
* It also have an onDocumentChange, that get's dispached only when the actual * It also have an onDocumentChange, that get's dispached only when the actual
* content changes * content changes
*/ */
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 (

View File

@ -12,16 +12,6 @@ export default class BlockTypesMenu extends Component {
expanded: false, expanded: false,
menu: null menu: null
}; };
this.updateMenuPosition = this.updateMenuPosition.bind(this);
this.toggleMenu = this.toggleMenu.bind(this);
this.handleOpen = this.handleOpen.bind(this);
this.handleBlockTypeClick = this.handleBlockTypeClick.bind(this);
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);
} }
/** /**
@ -41,7 +31,7 @@ export default class BlockTypesMenu extends Component {
this.updateMenuPosition(); this.updateMenuPosition();
} }
updateMenuPosition() { updateMenuPosition = () => {
const { menu } = this.state; const { menu } = this.state;
const { position } = this.props; const { position } = this.props;
if (!menu) return; if (!menu) return;
@ -50,29 +40,29 @@ export default class BlockTypesMenu extends Component {
menu.style.top = `${position.top}px`; menu.style.top = `${position.top}px`;
menu.style.left = `${position.left - menu.offsetWidth * 2}px`; menu.style.left = `${position.left - menu.offsetWidth * 2}px`;
} };
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); data[field.name] = window.prompt(field.label);
}); });
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();
@ -92,21 +82,21 @@ export default 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;
@ -133,9 +123,9 @@ export default class BlockTypesMenu extends Component {
/** /**
* When the portal opens, cache the menu element. * When the portal opens, cache the menu element.
*/ */
handleOpen(portal) { handleOpen = portal => {
this.setState({ menu: portal.firstChild }); this.setState({ menu: portal.firstChild });
} };
render() { render() {
const { isOpen } = this.props; const { isOpen } = this.props;

View File

@ -4,24 +4,12 @@ import { Icon } from '../../../UI';
import styles from './StylesMenu.css'; import styles from './StylesMenu.css';
export default class StylesMenu extends Component { export default class StylesMenu extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
menu: null menu: null
}; };
this.hasMark = this.hasMark.bind(this);
this.hasBlock = this.hasBlock.bind(this);
this.renderMarkButton = this.renderMarkButton.bind(this);
this.renderBlockButton = this.renderBlockButton.bind(this);
this.renderLinkButton = this.renderLinkButton.bind(this);
this.updateMenuPosition = this.updateMenuPosition.bind(this);
this.handleMarkClick = this.handleMarkClick.bind(this);
this.handleInlineClick = this.handleInlineClick.bind(this);
this.handleBlockClick = this.handleBlockClick.bind(this);
this.handleOpen = this.handleOpen.bind(this);
} }
/** /**
@ -35,7 +23,7 @@ export default class StylesMenu extends Component {
this.updateMenuPosition(); this.updateMenuPosition();
} }
updateMenuPosition() { updateMenuPosition = () => {
const { menu } = this.state; const { menu } = this.state;
const { position } = this.props; const { position } = this.props;
if (!menu) return; if (!menu) return;
@ -43,30 +31,32 @@ export default class StylesMenu extends Component {
menu.style.opacity = 1; menu.style.opacity = 1;
menu.style.top = `${position.top - menu.offsetHeight}px`; menu.style.top = `${position.top - menu.offsetHeight}px`;
menu.style.left = `${position.left - menu.offsetWidth / 2 + position.width / 2}px`; menu.style.left = `${position.left - menu.offsetWidth / 2 + position.width / 2}px`;
} };
/** /**
* 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 (
@ -74,14 +64,14 @@ export default 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 (
@ -89,16 +79,16 @@ export default 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);
@ -107,14 +97,14 @@ export default class StylesMenu extends Component {
<Icon type={icon}/> <Icon type={icon}/>
</span> </span>
); );
} };
/** /**
* When the portal opens, cache the menu element. * When the portal opens, cache the menu element.
*/ */
handleOpen(portal) { handleOpen = portal => {
this.setState({ menu: portal.firstChild }); this.setState({ menu: portal.firstChild });
} };
render() { render() {
const { isOpen } = this.props; const { isOpen } = this.props;
@ -133,7 +123,6 @@ export default class StylesMenu extends Component {
</Portal> </Portal>
); );
} }
} }
StylesMenu.propTypes = { StylesMenu.propTypes = {

View File

@ -17,7 +17,6 @@ class VisualEditor extends React.Component {
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);
@ -51,24 +50,13 @@ class VisualEditor extends React.Component {
state: Raw.deserialize(rawJson, { terse: true }) state: Raw.deserialize(rawJson, { terse: true })
}; };
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.calculateHoverMenuPosition = _.throttle(this.calculateHoverMenuPosition.bind(this), 30); this.calculateHoverMenuPosition = _.throttle(this.calculateHoverMenuPosition.bind(this), 30);
this.calculateBlockMenuPosition = _.throttle(this.calculateBlockMenuPosition.bind(this), 100); this.calculateBlockMenuPosition = _.throttle(this.calculateBlockMenuPosition.bind(this), 100);
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.
@ -76,20 +64,20 @@ class VisualEditor extends React.Component {
* It also have an onDocumentChange, that get's dispached only when the actual * It also have an onDocumentChange, that get's dispached 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.calculateHoverMenuPosition(); this.calculateHoverMenuPosition();
this.setState({ state }, this.calculateBlockMenuPosition); this.setState({ state }, this.calculateBlockMenuPosition);
} }
} };
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));
} };
calculateHoverMenuPosition() { calculateHoverMenuPosition() {
const rect = position(); const rect = position();
@ -120,7 +108,7 @@ class VisualEditor extends React.Component {
/** /**
* 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
@ -129,9 +117,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;
@ -175,7 +163,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.
@ -184,7 +172,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') {
@ -210,10 +198,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
@ -225,9 +212,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
@ -243,9 +230,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);
@ -262,9 +249,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();
@ -278,10 +265,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;
@ -292,9 +278,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');
@ -308,7 +294,7 @@ class VisualEditor extends React.Component {
onClickImage={this.handleImageClick} onClickImage={this.handleImageClick}
/> />
); );
} };
renderStylesMenu() { renderStylesMenu() {
const { state } = this.state; const { state } = this.state;

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

@ -21,7 +21,7 @@ class App extends React.Component {
state = { state = {
navDrawerIsVisible: true 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;

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) {
@ -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';
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;
@ -38,12 +43,6 @@ export default function CollectionPageHOC(CollectionPage) {
} }
} }
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);