Merge branch 'master' into markitup-react

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

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 {
@ -43,41 +43,41 @@ export default class AppHeader extends React.Component {
return (
<AppBar
fixed
theme={styles}
fixed
theme={styles}
>
<IconButton
icon="menu"
inverse
onClick={toggleNavDrawer}
icon="menu"
inverse
onClick={toggleNavDrawer}
/>
<IndexLink to="/">
Dashboard
</IndexLink>
<FindBar
commands={commands}
defaultCommands={defaultCommands}
runCommand={runCommand}
commands={commands}
defaultCommands={defaultCommands}
runCommand={runCommand}
/>
<Button
className={styles.createBtn}
icon='add'
floating
accent
onClick={this.handleCreateButtonClick}
className={styles.createBtn}
icon='add'
floating
accent
onClick={this.handleCreateButtonClick}
>
<Menu
active={createMenuActive}
position="topRight"
onHide={this.handleCreateMenuHide}
active={createMenuActive}
position="topRight"
onHide={this.handleCreateMenuHide}
>
{
collections.valueSeq().map(collection =>
<MenuItem
key={collection.get('name')}
value={collection.get('name')}
onClick={this.handleCreatePostClick.bind(this, collection.get('name'))}
caption={pluralize(collection.get('label'), 1)}
key={collection.get('name')}
value={collection.get('name')}
onClick={this.handleCreatePostClick.bind(this, collection.get('name'))}
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: '750px', columns: 3, gutter: 15 },
{ mq: '1005px', columns: 4, gutter: 15 },
{ mq: '1260px', columns: 5, gutter: 15 },
{ mq: '1515px', columns: 6, gutter: 15 },
{ mq: '1770px', columns: 7, gutter: 15 },
{ mq: '1515px', columns: 5, gutter: 15 },
{ mq: '1770px', columns: 6, gutter: 15 },
]
};

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) => {
@ -309,11 +307,11 @@ class FindBar extends Component {
}
return (
<div
className={this.state.highlightedIndex === index ? styles.highlightedCommand : styles.command}
key={command.token.trim().replace(/[^a-z0-9]+/gi, '-')}
onMouseDown={() => this.setIgnoreBlur(true)}
onMouseEnter={() => this.highlightCommandFromMouse(index)}
onClick={() => this.selectCommandFromMouse(command)}
className={this.state.highlightedIndex === index ? styles.highlightedCommand : styles.command}
key={command.token.trim().replace(/[^a-z0-9]+/gi, '-')}
onMouseDown={() => this.setIgnoreBlur(true)}
onMouseEnter={() => this.highlightCommandFromMouse(index)}
onClick={() => this.selectCommandFromMouse(command)}
>
{children}
</div>
@ -347,15 +345,15 @@ class FindBar extends Component {
<label className={styles.inputArea}>
{scope}
<input
className={styles.inputField}
ref={(c) => this._input = c}
onFocus={this.handleInputFocus}
onBlur={this.handleInputBlur}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
onClick={this.handleInputClick}
placeholder={this.state.placeholder}
value={this.state.value}
className={styles.inputField}
ref={(c) => this._input = c}
onFocus={this.handleInputFocus}
onBlur={this.handleInputBlur}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
onClick={this.handleInputClick}
placeholder={this.state.placeholder}
value={this.state.value}
/>
</label>
{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;

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) {
@ -40,15 +36,15 @@ export default class Loader extends React.Component {
this.setAnimation();
return <div className={styles.text}>
<ReactCSSTransitionGroup
transitionName={styles}
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
transitionName={styles}
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
>
<div key={currentItem} className={styles.animateItem}>{children[currentItem]}</div>
</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

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

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) {
@ -60,10 +53,10 @@ class UnpublishedListing extends React.Component {
<DragSource key={slug} slug={slug} collection={collection} ownStatus={ownStatus}>
<div className={styles.drag}>
<Card className={styles.card}>
<h2><Link to={link}>{entry.getIn(['data', 'title'])}</Link> <small>by {author}</small></h2>
<p>Last updated: {timeStamp} by {entry.getIn(['metaData', 'user'])}</p>
<span className={styles.cardHeading}><Link to={link}>{entry.getIn(['data', 'title'])}</Link> <small>by {author}</small></span>
<p className={styles.cardText}>Last updated: {timeStamp} by {entry.getIn(['metaData', 'user'])}</p>
{(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>
</div>
@ -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,25 +57,25 @@ export default class ImageControl extends React.Component {
return truncateMiddle(this.props.value, MAX_DISPLAY_LENGTH);
}
}
};
render() {
const imageName = this.renderImageName();
return (
<div
onDragEnter={this.handleDragEnter}
onDragOver={this.handleDragOver}
onDrop={this.handleChange}
onDragEnter={this.handleDragEnter}
onDragOver={this.handleDragOver}
onDrop={this.handleChange}
>
<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'}
</span>
<input
type="file"
accept="image/*"
onChange={this.handleChange}
style={styles.input}
ref={this.handleFileInputRef}
type="file"
accept="image/*"
onChange={this.handleChange}
style={styles.input}
ref={this.handleFileInputRef}
/>
</div>
);

View File

@ -7,6 +7,14 @@ import { connect } from 'react-redux';
import { switchVisualMode } from '../../actions/editor';
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() {
this.useRawEditor();
@ -15,11 +23,11 @@ class MarkdownControl extends React.Component {
useVisualEditor = () => {
this.props.switchVisualMode(true);
}
};
useRawEditor = () => {
this.props.switchVisualMode(false);
}
};
render() {
const { editor, onChange, onAddMedia, getMedia, value } = this.props;
@ -28,11 +36,11 @@ class MarkdownControl extends React.Component {
<div className='cms-editor-visual'>
{null && <button onClick={this.useRawEditor}>Switch to Raw Editor</button>}
<VisualEditor
onChange={onChange}
onAddMedia={onAddMedia}
getMedia={getMedia}
registeredComponents={editor.get('registeredComponents')}
value={value}
onChange={onChange}
onAddMedia={onAddMedia}
getMedia={getMedia}
registeredComponents={editor.get('registeredComponents')}
value={value}
/>
</div>
);
@ -41,10 +49,10 @@ class MarkdownControl extends React.Component {
<div className='cms-editor-raw'>
{null && <button onClick={this.useVisualEditor}>Switch to Visual Editor</button>}
<RawEditor
onChange={onChange}
onAddMedia={onAddMedia}
getMedia={getMedia}
value={value}
onChange={onChange}
onAddMedia={onAddMedia}
getMedia={getMedia}
value={value}
/>
</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(
state => ({ editor: state.editor }),
{ 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) {
super(props);
@ -104,12 +111,12 @@ class RawEditor extends React.Component {
*/
handleChange = state => {
this.setState({ state });
}
};
handleDocumentChange = (document, state) => {
const content = Plain.serialize(state, { terse: true });
this.props.onChange(content);
}
};
render() {
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';
class BlockTypesMenu extends Component {
constructor(props) {
super(props);
this.state = {
expanded: false
};
static propTypes = {
plugins: PropTypes.array.isRequired,
onClickBlock: PropTypes.func.isRequired,
onClickPlugin: PropTypes.func.isRequired,
onClickImage: PropTypes.func.isRequired
};
this.toggleMenu = this.toggleMenu.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);
}
state = {
expanded: false
};
componentWillUpdate() {
if (this.state.expanded) {
@ -27,27 +23,27 @@ class BlockTypesMenu extends Component {
}
}
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); // eslint-disable-line
});
this.props.onClickPlugin(plugin.id, data);
}
};
handleFileUploadClick() {
handleFileUploadClick = () => {
this._fileInput.click();
}
};
handleFileUploadChange(e) {
handleFileUploadChange = e => {
e.stopPropagation();
e.preventDefault();
@ -67,21 +63,21 @@ 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;
@ -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);

View File

@ -5,43 +5,39 @@ import styles from './StylesMenu.css';
class StylesMenu extends Component {
constructor() {
super();
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.handleMarkClick = this.handleMarkClick.bind(this);
this.handleInlineClick = this.handleInlineClick.bind(this);
this.handleBlockClick = this.handleBlockClick.bind(this);
}
static 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
};
/**
* 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) {
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 (
@ -49,14 +45,14 @@ 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 (
@ -64,16 +60,16 @@ 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);
@ -82,7 +78,7 @@ class StylesMenu extends Component {
<Icon type={icon}/>
</span>
);
}
};
render() {
return (
@ -98,16 +94,6 @@ class StylesMenu extends Component {
</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);

View File

@ -13,11 +13,18 @@ import BlockTypesMenu from './BlockTypesMenu';
/**
* 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) {
super(props);
this.getMedia = this.getMedia.bind(this);
const MarkdownSyntax = getSyntaxes(this.getMedia).markdown;
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);
}
};
/**
* Slate keeps track of selections, scroll position etc.
* 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
*/
handleChange(state) {
handleChange = state => {
if (this.blockEdit) {
this.blockEdit = false;
} else {
this.setState({ state });
}
}
};
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));
}
};
/**
* Toggle marks / blocks when button is clicked
*/
handleMarkStyleClick(type) {
handleMarkStyleClick = type => {
let { state } = this.state;
state = state
@ -96,9 +91,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;
@ -142,7 +137,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.
@ -151,7 +146,7 @@ class VisualEditor extends React.Component {
* @param {Event} e
*/
handleInlineClick(type, isActive) {
handleInlineClick = (type, isActive) => {
let { state } = this.state;
if (type === 'link') {
@ -177,9 +172,9 @@ class VisualEditor extends React.Component {
}
}
this.setState({ state });
}
};
handleBlockTypeClick(type) {
handleBlockTypeClick = type => {
let { state } = this.state;
state = state
@ -191,9 +186,9 @@ class VisualEditor extends React.Component {
.apply();
this.setState({ state }, this.focusAndAddParagraph);
}
};
handlePluginClick(type, data) {
handlePluginClick = (type, data) => {
let { state } = this.state;
state = state
@ -209,9 +204,9 @@ class VisualEditor extends React.Component {
.apply();
this.setState({ state });
}
};
handleImageClick(mediaProxy) {
handleImageClick = mediaProxy => {
let { state } = this.state;
this.props.onAddMedia(mediaProxy);
@ -221,9 +216,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();
@ -237,9 +232,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;
@ -250,9 +245,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');
@ -265,7 +260,7 @@ class VisualEditor extends React.Component {
onClickImage={this.handleImageClick}
/>
);
}
};
renderStylesMenu() {
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 {
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}/>;