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
parser: babel-eslint
plugins: [ "react" ]
plugins: [
"react",
"class-property"
]
rules:
# Possible Errors
@ -97,6 +100,8 @@ rules:
react/self-closing-comp: 1
react/sort-comp: 1
class-property/class-property-semicolon: 2
# Global scoped method and vars
globals:
netlify: true

View File

@ -42,6 +42,7 @@
"babel-runtime": "^6.5.0",
"css-loader": "^0.23.1",
"eslint": "^3.5.0",
"eslint-plugin-class-property": "^1.0.1",
"eslint-plugin-react": "^5.1.1",
"expect": "^1.20.2",
"exports-loader": "^0.6.3",

View File

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

View File

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

View File

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

View File

@ -10,26 +10,26 @@ export default class AppHeader extends React.Component {
state = {
createMenuActive: false
}
};
handleCreatePostClick = collectionName => {
const { onCreateEntryClick } = this.props;
if (onCreateEntryClick) {
onCreateEntryClick(collectionName);
}
}
};
handleCreateButtonClick = () => {
this.setState({
createMenuActive: true
});
}
};
handleCreateMenuHide = () => {
this.setState({
createMenuActive: false
});
}
};
render() {
const {

View File

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

View File

@ -8,8 +8,18 @@ export const SEARCH = 'SEARCH';
const PLACEHOLDER = 'Search or enter a command';
class FindBar extends Component {
constructor(props) {
super(props);
static 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,
};
constructor() {
super();
this._compiledCommands = [];
this._searchCommand = {
search: true,
@ -26,18 +36,6 @@ class FindBar extends Component {
};
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() {
@ -58,7 +56,7 @@ class FindBar extends Component {
}
// Generates a regexp and splits a token and param details for a command
compileCommand(command) {
compileCommand = command => {
let regexp = '';
let param = null;
@ -79,11 +77,11 @@ class FindBar extends Component {
token,
param
});
}
};
// Check if the entered string matches any command.
// 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;
let match;
let command = this._compiledCommands.find(command => {
@ -133,20 +131,20 @@ class FindBar extends Component {
}
this.props.runCommand(command.type, payload);
}
}
};
maybeRemoveActiveScope() {
maybeRemoveActiveScope = () => {
if (this.state.value.length === 0 && this.state.activeScope) {
this.setState({
activeScope: null,
placeholder: PLACEHOLDER
});
}
}
};
getSuggestions() {
getSuggestions = () => {
return this._getSuggestions(this.state.value, this.state.activeScope, this._compiledCommands, this.props.defaultCommands);
}
};
// Memoized version
_getSuggestions(value, scope, commands, defaultCommands) {
@ -173,7 +171,7 @@ class FindBar extends Component {
return returnResults;
}
handleKeyDown(event) {
handleKeyDown = event => {
let highlightedIndex, index;
switch (event.key) {
case 'ArrowDown':
@ -240,37 +238,37 @@ class FindBar extends Component {
isOpen: true
});
}
}
};
handleChange(event) {
handleChange = event => {
this.setState({
value: event.target.value,
});
}
};
handleInputBlur() {
handleInputBlur = () => {
if (this._ignoreBlur) return;
this.setState({
isOpen: false,
highlightedIndex: 0
});
}
};
handleInputFocus() {
handleInputFocus = () => {
if (this._ignoreBlur) return;
this.setState({ isOpen: true });
}
};
handleInputClick() {
handleInputClick = () => {
if (this.state.isOpen === false)
this.setState({ isOpen: true });
}
};
highlightCommandFromMouse(index) {
highlightCommandFromMouse = index => {
this.setState({ highlightedIndex: index });
}
};
selectCommandFromMouse(command) {
selectCommandFromMouse = command => {
const newState = {
isOpen: false,
highlightedIndex: 0
@ -283,11 +281,11 @@ class FindBar extends Component {
this._input.focus();
this.setIgnoreBlur(false);
});
}
};
setIgnoreBlur(ignore) {
setIgnoreBlur = ignore => {
this._ignoreBlur = ignore;
}
};
renderMenu() {
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;

View File

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

View File

@ -3,14 +3,10 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import styles from './Loader.css';
export default class Loader extends React.Component {
constructor(props) {
super(props);
this.state = {
currentItem: 0,
};
this.setAnimation = this.setAnimation.bind(this);
this.renderChild = this.renderChild.bind(this);
}
state = {
currentItem: 0,
};
componentWillUnmount() {
if (this.interval) {
@ -18,7 +14,7 @@ export default class Loader extends React.Component {
}
}
setAnimation() {
setAnimation = () => {
if (this.interval) return;
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;
this.setState({ currentItem: nextItem });
}, 5000);
}
};
renderChild() {
renderChild = () => {
const { children } = this.props;
const { currentItem } = this.state;
if (!children) {
@ -48,7 +44,7 @@ export default class Loader extends React.Component {
</ReactCSSTransitionGroup>
</div>;
}
}
};
render() {
const { active, style, className = '' } = this.props;

View File

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

View File

@ -8,28 +8,21 @@ import { status, statusDescriptions } from '../constants/publishModes';
import styles from './UnpublishedListing.css';
class UnpublishedListing extends React.Component {
constructor(props) {
super(props);
this.renderColumns = this.renderColumns.bind(this);
this.handleChangeStatus = this.handleChangeStatus.bind(this);
this.requestPublish = this.requestPublish.bind(this);
}
handleChangeStatus(newStatus, dragProps) {
handleChangeStatus = (newStatus, dragProps) => {
const slug = dragProps.slug;
const collection = dragProps.collection;
const oldStatus = dragProps.ownStatus;
this.props.handleChangeStatus(collection, slug, oldStatus, newStatus);
}
};
requestPublish(collection, slug, ownStatus) {
requestPublish = (collection, slug, ownStatus) => {
if (ownStatus !== status.last()) return;
if (window.confirm('Are you sure you want to publish this entry?')) {
this.props.handlePublish(collection, slug, ownStatus);
}
}
};
renderColumns(entries, column) {
renderColumns = (entries, column) => {
if (!entries) return;
if (!column) {
@ -74,7 +67,13 @@ class UnpublishedListing extends React.Component {
)}
</div>;
}
}
};
static propTypes = {
entries: ImmutablePropTypes.orderedMap,
handleChangeStatus: PropTypes.func.isRequired,
handlePublish: PropTypes.func.isRequired,
};
render() {
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);

View File

@ -2,14 +2,9 @@ import React, { PropTypes } from 'react';
import DateTime from 'react-datetime';
export default class DateTimeControl extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(datetime) {
handleChange = datetime => {
this.props.onChange(datetime);
}
};
render() {
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;
export default class ImageControl extends React.Component {
constructor(props) {
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) {
handleFileInputRef = el => {
this._fileInput = el;
}
};
handleClick(e) {
handleClick = e => {
this._fileInput.click();
}
};
handleDragEnter(e) {
handleDragEnter = e => {
e.stopPropagation();
e.preventDefault();
}
};
handleDragOver(e) {
handleDragOver = e => {
e.stopPropagation();
e.preventDefault();
}
};
handleChange(e) {
handleChange = e => {
e.stopPropagation();
e.preventDefault();
@ -58,9 +47,9 @@ export default class ImageControl extends React.Component {
this.props.onChange(null);
}
}
};
renderImageName() {
renderImageName = () => {
if (!this.props.value) return null;
if (this.value instanceof MediaProxy) {
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);
}
}
};
render() {
const imageName = this.renderImageName();

View File

@ -7,24 +7,31 @@ import { connect } from 'react-redux';
import { switchVisualMode } from '../../actions/editor';
class MarkdownControl extends React.Component {
constructor(props, context) {
super(props, context);
this.useVisualEditor = this.useVisualEditor.bind(this);
this.useRawEditor = this.useRawEditor.bind(this);
}
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,
};
static contextTypes = {
plugins: PropTypes.object,
};
componentWillMount() {
this.useRawEditor();
processEditorPlugins(registry.getEditorComponents());
}
useVisualEditor() {
useVisualEditor = () => {
this.props.switchVisualMode(true);
}
};
useRawEditor() {
useRawEditor = () => {
this.props.switchVisualMode(false);
}
};
renderEditor() {
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(
state => ({ editor: state.editor }),
{ switchVisualMode }

View File

@ -73,7 +73,6 @@ const SCHEMA = {
};
class RawEditor extends React.Component {
constructor(props) {
super(props);
@ -82,10 +81,6 @@ class RawEditor extends React.Component {
this.state = {
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
* content changes
*/
handleChange(state) {
handleChange = state => {
this.setState({ state });
}
};
handleDocumentChange(document, state) {
handleDocumentChange = (document, state) => {
const content = Plain.serialize(state, { terse: true });
this.props.onChange(content);
}
};
render() {
return (

View File

@ -12,16 +12,6 @@ export default class BlockTypesMenu extends Component {
expanded: false,
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();
}
updateMenuPosition() {
updateMenuPosition = () => {
const { menu } = this.state;
const { position } = this.props;
if (!menu) return;
@ -50,29 +40,29 @@ export default class BlockTypesMenu extends Component {
menu.style.top = `${position.top}px`;
menu.style.left = `${position.left - menu.offsetWidth * 2}px`;
}
};
toggleMenu() {
toggleMenu = () => {
this.setState({ expanded: !this.state.expanded });
}
};
handleBlockTypeClick(e, type) {
handleBlockTypeClick = (e, type) => {
this.props.onClickBlock(type);
}
};
handlePluginClick(e, plugin) {
handlePluginClick = (e, plugin) => {
const data = {};
plugin.fields.forEach(field => {
data[field.name] = window.prompt(field.label);
});
this.props.onClickPlugin(plugin.id, data);
}
};
handleFileUploadClick() {
handleFileUploadClick = () => {
this._fileInput.click();
}
};
handleFileUploadChange(e) {
handleFileUploadChange = e => {
e.stopPropagation();
e.preventDefault();
@ -92,21 +82,21 @@ export default class BlockTypesMenu extends Component {
this.props.onClickImage(mediaProxy);
}
}
};
renderBlockTypeButton(type, icon) {
renderBlockTypeButton = (type, icon) => {
const onClick = e => this.handleBlockTypeClick(e, type);
return (
<Icon key={type} type={icon} onClick={onClick} className={styles.icon} />
);
}
};
renderPluginButton(plugin) {
renderPluginButton = plugin => {
const onClick = e => this.handlePluginClick(e, plugin);
return (
<Icon key={plugin.id} type={plugin.icon} onClick={onClick} className={styles.icon} />
);
}
};
renderMenu() {
const { plugins } = this.props;
@ -133,9 +123,9 @@ export default class BlockTypesMenu extends Component {
/**
* When the portal opens, cache the menu element.
*/
handleOpen(portal) {
handleOpen = portal => {
this.setState({ menu: portal.firstChild });
}
};
render() {
const { isOpen } = this.props;

View File

@ -4,24 +4,12 @@ import { Icon } from '../../../UI';
import styles from './StylesMenu.css';
export default class StylesMenu extends Component {
constructor(props) {
super(props);
this.state = {
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();
}
updateMenuPosition() {
updateMenuPosition = () => {
const { menu } = this.state;
const { position } = this.props;
if (!menu) return;
@ -43,30 +31,32 @@ export default class StylesMenu extends Component {
menu.style.opacity = 1;
menu.style.top = `${position.top - menu.offsetHeight}px`;
menu.style.left = `${position.left - menu.offsetWidth / 2 + position.width / 2}px`;
}
};
/**
* Used to set toolbar buttons to active state
*/
hasMark(type) {
hasMark = type => {
const { marks } = this.props;
return marks.some(mark => mark.type == type);
}
hasBlock(type) {
};
hasBlock = type => {
const { blocks } = this.props;
return blocks.some(node => node.type == type);
}
};
hasLinks(type) {
const { inlines } = this.props;
return inlines.some(inline => inline.type == 'link');
}
handleMarkClick(e, type) {
handleMarkClick = (e, type) => {
e.preventDefault();
this.props.onClickMark(type);
}
};
renderMarkButton(type, icon) {
renderMarkButton = (type, icon) => {
const isActive = this.hasMark(type);
const onMouseDown = e => this.handleMarkClick(e, type);
return (
@ -74,14 +64,14 @@ export default class StylesMenu extends Component {
<Icon type={icon}/>
</span>
);
}
};
handleInlineClick(e, type, isActive) {
handleInlineClick = (e, type, isActive) => {
e.preventDefault();
this.props.onClickInline(type, isActive);
}
};
renderLinkButton() {
renderLinkButton = () => {
const isActive = this.hasLinks();
const onMouseDown = e => this.handleInlineClick(e, 'link', isActive);
return (
@ -89,16 +79,16 @@ export default class StylesMenu extends Component {
<Icon type="link"/>
</span>
);
}
};
handleBlockClick(e, type) {
handleBlockClick = (e, type) => {
e.preventDefault();
const isActive = this.hasBlock(type);
const isList = this.hasBlock('list-item');
this.props.onClickBlock(type, isActive, isList);
}
};
renderBlockButton(type, icon, checkType) {
renderBlockButton = (type, icon, checkType) => {
checkType = checkType || type;
const isActive = this.hasBlock(checkType);
const onMouseDown = e => this.handleBlockClick(e, type);
@ -107,14 +97,14 @@ export default class StylesMenu extends Component {
<Icon type={icon}/>
</span>
);
}
};
/**
* When the portal opens, cache the menu element.
*/
handleOpen(portal) {
handleOpen = portal => {
this.setState({ menu: portal.firstChild });
}
};
render() {
const { isOpen } = this.props;
@ -133,7 +123,6 @@ export default class StylesMenu extends Component {
</Portal>
);
}
}
StylesMenu.propTypes = {

View File

@ -17,7 +17,6 @@ class VisualEditor extends React.Component {
constructor(props) {
super(props);
this.getMedia = this.getMedia.bind(this);
const MarkdownSyntax = getSyntaxes(this.getMedia).markdown;
this.markdown = new MarkupIt(MarkdownSyntax);
@ -51,24 +50,13 @@ class VisualEditor extends React.Component {
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.calculateBlockMenuPosition = _.throttle(this.calculateBlockMenuPosition.bind(this), 100);
this.renderBlockTypesMenu = this.renderBlockTypesMenu.bind(this);
}
getMedia(src) {
getMedia = src => {
return this.props.getMedia(src);
}
};
/**
* 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
* content changes
*/
handleChange(state) {
handleChange = state => {
if (this.blockEdit) {
this.blockEdit = false;
} else {
this.calculateHoverMenuPosition();
this.setState({ state }, this.calculateBlockMenuPosition);
}
}
};
handleDocumentChange(document, state) {
handleDocumentChange = (document, state) => {
const rawJson = Raw.serialize(state, { terse: true });
const content = SlateUtils.decode(rawJson);
this.props.onChange(this.markdown.toText(content));
}
};
calculateHoverMenuPosition() {
const rect = position();
@ -120,7 +108,7 @@ class VisualEditor extends React.Component {
/**
* Toggle marks / blocks when button is clicked
*/
handleMarkStyleClick(type) {
handleMarkStyleClick = type => {
let { state } = this.state;
state = state
@ -129,9 +117,9 @@ class VisualEditor extends React.Component {
.apply();
this.setState({ state });
}
};
handleBlockStyleClick(type, isActive, isList) {
handleBlockStyleClick = (type, isActive, isList) => {
let { state } = this.state;
let transform = state.transform();
const { document } = state;
@ -175,7 +163,7 @@ class VisualEditor extends React.Component {
state = transform.apply();
this.setState({ state });
}
};
/**
* 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
*/
handleInlineClick(type, isActive) {
handleInlineClick = (type, isActive) => {
let { state } = this.state;
if (type === 'link') {
@ -210,10 +198,9 @@ class VisualEditor extends React.Component {
}
}
this.setState({ state });
}
};
handleBlockTypeClick(type) {
handleBlockTypeClick = type => {
let { state } = this.state;
state = state
@ -225,9 +212,9 @@ class VisualEditor extends React.Component {
.apply();
this.setState({ state }, this.focusAndAddParagraph);
}
};
handlePluginClick(type, data) {
handlePluginClick = (type, data) => {
let { state } = this.state;
state = state
@ -243,9 +230,9 @@ class VisualEditor extends React.Component {
.apply();
this.setState({ state });
}
};
handleImageClick(mediaProxy) {
handleImageClick = mediaProxy => {
let { state } = this.state;
this.props.onAddMedia(mediaProxy);
@ -262,9 +249,9 @@ class VisualEditor extends React.Component {
.apply();
this.setState({ state });
}
};
focusAndAddParagraph() {
focusAndAddParagraph = () => {
const { state } = this.state;
const blocks = state.document.getBlocks();
const last = blocks.last();
@ -278,10 +265,9 @@ class VisualEditor extends React.Component {
snapshot: false
});
this.setState({ state:normalized });
}
};
handleKeyDown(evt) {
handleKeyDown = evt => {
if (evt.shiftKey && evt.key === 'Enter') {
this.blockEdit = true;
let { state } = this.state;
@ -292,9 +278,9 @@ class VisualEditor extends React.Component {
this.setState({ state });
}
}
};
renderBlockTypesMenu() {
renderBlockTypesMenu = () => {
const currentBlock = this.state.state.blocks.get(0);
const isOpen = (this.props.value !== undefined && currentBlock.isEmpty && currentBlock.type !== 'horizontal-rule');
@ -308,7 +294,7 @@ class VisualEditor extends React.Component {
onClickImage={this.handleImageClick}
/>
);
}
};
renderStylesMenu() {
const { state } = this.state;

View File

@ -18,6 +18,14 @@ const EditorComponent = Record({
class Plugin extends Component {
static propTypes = {
children: PropTypes.element.isRequired
};
static childContextTypes = {
plugins: PropTypes.object
};
getChildContext() {
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) {
const configObj = new EditorComponent({
id: config.id || config.label.replace(/[^A-Z0-9]+/ig, '_'),

View File

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

View File

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

View File

@ -21,7 +21,7 @@ class App extends React.Component {
state = {
navDrawerIsVisible: true
}
};
componentDidMount() {
this.props.dispatch(loadConfig());
@ -100,7 +100,7 @@ class App extends React.Component {
this.setState({
navDrawerIsVisible: !this.state.navDrawerIsVisible
});
}
};
render() {
const { navDrawerIsVisible } = this.state;

View File

@ -9,6 +9,13 @@ import styles from './CollectionPage.css';
import CollectionPageHOC from './editorialWorkflow/CollectionPageHOC';
class DashboardPage extends React.Component {
static propTypes = {
collection: ImmutablePropTypes.map.isRequired,
collections: ImmutablePropTypes.orderedMap.isRequired,
dispatch: PropTypes.func.isRequired,
entries: ImmutablePropTypes.list,
};
componentDidMount() {
const { collection, dispatch } = this.props;
if (collection) {
@ -39,12 +46,6 @@ class DashboardPage extends React.Component {
</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,

View File

@ -15,11 +15,22 @@ import EntryEditor from '../components/EntryEditor';
import EntryPageHOC from './editorialWorkflow/EntryPageHOC';
class EntryPage extends React.Component {
constructor(props) {
super(props);
this.createDraft = this.createDraft.bind(this);
this.handlePersistEntry = this.handlePersistEntry.bind(this);
}
static 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,
};
componentDidMount() {
if (!this.props.newEntry) {
@ -45,13 +56,13 @@ class EntryPage extends React.Component {
this.props.discardDraft();
}
createDraft(entry) {
createDraft = entry => {
if (entry) this.props.createDraftFromEntry(entry);
}
};
handlePersistEntry() {
handlePersistEntry = () => {
this.props.persistEntry(this.props.collection, this.props.entryDraft);
}
};
render() {
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) {
const { collections, entryDraft } = state;
const collection = collections.get(ownProps.params.name);

View File

@ -10,6 +10,11 @@ import styles from '../CollectionPage.css';
export default function CollectionPageHOC(CollectionPage) {
class CollectionPageHOC extends CollectionPage {
static propTypes = {
dispatch: PropTypes.func.isRequired,
isEditorialWorkflow: PropTypes.bool.isRequired,
unpublishedEntries: ImmutablePropTypes.map,
};
componentDidMount() {
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) {
const publish_mode = state.config.get('publish_mode');
const isEditorialWorkflow = (publish_mode === EDITORIAL_WORKFLOW);