From 3fecd44129bd57c1b6f9cd6e8dd382175d5fb68d Mon Sep 17 00:00:00 2001 From: Ben Briggs Date: Sat, 6 Apr 2019 13:49:30 -0400 Subject: [PATCH] remove react-create-class --- example/ToolbarButton.js | 56 +++-- example/blockStyles.html | 85 ++++---- example/inlineStyles.html | 75 ++++--- example/link.html | 155 +++++++------- example/mention.html | 253 +++++++++++------------ example/token.html | 78 +++---- example/tray.html | 96 +++++---- package.json | 7 +- src/components/Editor.js | 184 ++++++++++------- src/components/KeyCommandController.js | 275 ++++++++++++++----------- src/components/OverlayWrapper.js | 21 +- src/components/Toolbar.js | 47 ++--- src/plugins/createPlugin.js | 152 +++++++++----- yarn.lock | 48 ++--- 14 files changed, 799 insertions(+), 733 deletions(-) diff --git a/example/ToolbarButton.js b/example/ToolbarButton.js index 7388406..ffa286b 100644 --- a/example/ToolbarButton.js +++ b/example/ToolbarButton.js @@ -1,32 +1,30 @@ -window.ToolbarButton = createReactClass({ - getDefaultProps: function() { - return { - active: false, - label: '', - onClick: function() {} - }; - }, +window.ToolbarButton = function({ active, label, onClick }) { + var toolbarButtonStyle = { + display: 'inline-block', + minWidth: '24px', + padding: '4px', + borderRadius: '3px', + textAlign: 'center', + cursor: 'pointer', + }; - render: function() { - var toolbarButtonStyle = { - display: 'inline-block', - minWidth: '24px', - padding: '4px', - borderRadius: '3px', - textAlign: 'center', - cursor: 'pointer' - }; + if (active) { + toolbarButtonStyle.background = '#000'; + toolbarButtonStyle.color = '#fff'; + } - if (this.props.active) { - toolbarButtonStyle.background = '#000'; - toolbarButtonStyle.color = '#fff'; - } + return React.createElement( + 'div', + { + style: toolbarButtonStyle, + onClick: onClick, + }, + label + ); +}; - return ( - React.createElement('div', { - style: toolbarButtonStyle, - onClick: this.props.onClick - }, this.props.label) - ); - } -}); +ToolbarButton.defaultProps = { + active: false, + label: '', + onClick: function() {}, +}; diff --git a/example/blockStyles.html b/example/blockStyles.html index eb52965..0c63816 100644 --- a/example/blockStyles.html +++ b/example/blockStyles.html @@ -16,7 +16,6 @@
- @@ -27,46 +26,35 @@ + diff --git a/example/inlineStyles.html b/example/inlineStyles.html index 1a7323f..cde310b 100644 --- a/example/inlineStyles.html +++ b/example/inlineStyles.html @@ -16,7 +16,6 @@
- @@ -27,47 +26,33 @@ + diff --git a/example/link.html b/example/link.html index 053a97f..5a4db0d 100644 --- a/example/link.html +++ b/example/link.html @@ -16,7 +16,6 @@
- @@ -27,114 +26,97 @@ + diff --git a/example/mention.html b/example/mention.html index d1f2f34..daaa671 100644 --- a/example/mention.html +++ b/example/mention.html @@ -16,7 +16,6 @@
- @@ -39,43 +38,40 @@ const { Editor, createPlugin, - pluginUtils: {entityStrategy} + pluginUtils: { entityStrategy }, } = window.DraftExtend; - const { - convertToHTML, - convertFromHTML - } = window.DraftConvert; + const { convertToHTML, convertFromHTML } = window.DraftConvert; const ENTITY_TYPE = 'mention'; const users = [ { value: 'user1', - text: 'User 1' + text: 'User 1', }, { value: 'user2', - text: 'User 2' - } + text: 'User 2', + }, ]; const MentionDecorator = { strategy: entityStrategy(ENTITY_TYPE), - component: (props) => { + component: props => { return ( {props.children} ); - } + }, }; const entityToHTML = (entity, originalText) => { @@ -85,60 +81,51 @@ return originalText; }; - const textToEntity = (text) => { + const textToEntity = text => { const results = []; const pattern = /@([\w\d]+)/gi; text.replace(pattern, (match, value, offset) => { - const user = users.find((u) => { + const user = users.find(u => { return u.value === value; }); results.push({ offset, length: match.length, - entity: Entity.create(ENTITY_TYPE, 'IMMUTABLE', {value}), - result: user.text + entity: Entity.create(ENTITY_TYPE, 'IMMUTABLE', { value }), + result: user.text, }); }); return results; }; - const MentionResults = createReactClass({ - propTypes: { - search: PropTypes.string.isRequired, - offset: PropTypes.number.isRequired, - length: PropTypes.number.isRequired, - results: PropTypes.array.isRequired, - onSelect: PropTypes.func.isRequired, - addKeyCommandListener: PropTypes.func.isRequired, - removeKeyCommandListener: PropTypes.func.isRequired - }, + class MentionResults extends React.Component { + constructor(props) { + super(props); - getInitialState() { - return { - selection: 0 + this.state = { + selection: 0, }; - }, + + this.handleKeyCommand = this.handleKeyCommand.bind(this); + this.arrowUp = this.arrowUp.bind(this); + this.arrowDown = this.arrowDown.bind(this); + this.selectItem = this.selectItem.bind(this); + } componentDidMount() { this.props.addKeyCommandListener(this.handleKeyCommand); - }, + } componentWillUnmount() { this.props.removeKeyCommandListener(this.handleKeyCommand); - }, + } handleKeyCommand(editorState, command, keyboardEvent) { - const { - search, - offset, - length, - results, - onSelect - } = this.props; + const { search, offset, length, results, onSelect } = this.props; - const {selection} = this.state; + const { selection } = this.state; const option = selection < results.length ? results[selection] : null; @@ -148,7 +135,7 @@ keyboardEvent.preventDefault(); keyboardEvent.stopPropagation(); if (option !== null) { - onSelect(option, {search, offset, length}); + onSelect(option, { search, offset, length }); return true; } return null; @@ -163,52 +150,45 @@ default: return null; } - }, + } arrowUp() { - const {selection} = this.state; + const { selection } = this.state; if (selection > 0) { this.setState({ - selection: selection - 1 + selection: selection - 1, }); } - }, + } arrowDown() { - const {results} = this.props; - const {selection} = this.state; + const { results } = this.props; + const { selection } = this.state; if (results.length > selection + 1) { this.setState({ - selection: selection + 1 + selection: selection + 1, }); } - }, + } - selectItem({value, text}) { - const { - search, - offset, - length, - onSelect - } = this.props; + selectItem({ value, text }) { + const { search, offset, length, onSelect } = this.props; - onSelect({value, text}, {search, offset, length}); - }, + onSelect({ value, text }, { search, offset, length }); + } renderResults() { - const { - results, - } = this.props; + const { results } = this.props; - const {selection} = this.state; + const { selection } = this.state; const itemStyle = { listStyleType: 'none', padding: '4px', background: 'white', - cursor: 'pointer' + cursor: 'pointer', }; if (results.length === 0) { @@ -217,13 +197,10 @@ style={{ margin: '0', padding: '0', - fontFamily: 'sans-serif' + fontFamily: 'sans-serif', }} > -
  • +
  • No matches found
  • @@ -237,24 +214,21 @@ if (highlighted) { style = Object.assign({}, itemStyle, { background: 'blue', - color: 'white' + color: 'white', }); } const handleClick = () => { this.selectItem(option); - } + }; return ( -
  • +
  • {option.text}
  • ); }); - }, + } render() { return ( @@ -262,56 +236,67 @@ style={{ margin: '0', padding: '0', - fontFamily: 'sans-serif' + fontFamily: 'sans-serif', }} > {this.renderResults()} ); } - }); + } + + MentionResults.propTypes = { + search: PropTypes.string.isRequired, + offset: PropTypes.number.isRequired, + length: PropTypes.number.isRequired, + results: PropTypes.array.isRequired, + onSelect: PropTypes.func.isRequired, + addKeyCommandListener: PropTypes.func.isRequired, + removeKeyCommandListener: PropTypes.func.isRequired, + }; const TYPING_TRIGGER_REGEX = new RegExp('(\\B@[\\w\\s]{1,10})$'); - const MentionOverlay = createReactClass({ - propTypes: { - editorState: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired, - addKeyCommandListener: PropTypes.func.isRequired, - removeKeyCommandListener: PropTypes.func.isRequired - }, + class MentionOverlay extends React.Component { + constructor(props) { + super(props); + + this.selectOption = this.selectOption.bind(this); + } getCurrentSearch() { const selection = this.props.editorState.getSelection(); const contentState = this.props.editorState.getCurrentContent(); - const blockText = contentState.getBlockForKey(selection.getStartKey()).getText(); + const blockText = contentState + .getBlockForKey(selection.getStartKey()) + .getText(); const offset = selection.getStartOffset(); if (!selection.isCollapsed()) { return null; } - const matchArray = blockText.slice(0, offset).match(TYPING_TRIGGER_REGEX); + const matchArray = blockText + .slice(0, offset) + .match(TYPING_TRIGGER_REGEX); if (matchArray === null) { return null; } const beforeCursorText = matchArray[1].slice(1); const pastCursorMatch = blockText.slice(offset).match(/^\w+/); - const pastCursorText = pastCursorMatch !== null ? pastCursorMatch[0] : ''; + const pastCursorText = + pastCursorMatch !== null ? pastCursorMatch[0] : ''; return { search: beforeCursorText + pastCursorText, offset: offset - beforeCursorText.length - 1, - length: 1 + beforeCursorText.length + pastCursorText.length + length: 1 + beforeCursorText.length + pastCursorText.length, }; - }, + } - selectOption(option, {offset, length}) { - const { - editorState, - onChange - } = this.props; + selectOption(option, { offset, length }) { + const { editorState, onChange } = this.props; const blockKey = editorState.getSelection().getStartKey(); @@ -319,7 +304,7 @@ anchorOffset: offset, focusOffset: offset + length, isBackward: false, - hasFocus: true + hasFocus: true, }); const contentState = Modifier.replaceText( @@ -331,42 +316,37 @@ ); onChange( - EditorState.push( - editorState, - contentState, - 'insert-characters' - ) + EditorState.push(editorState, contentState, 'insert-characters') ); - }, + } getResults(search) { const regex = new RegExp(search.trim(), 'i'); - return this.props.users.filter(({value, text}) => { - return (text.match(regex) !== null || value.toString().match(regex) !== null); + return this.props.users.filter(({ value, text }) => { + return ( + text.match(regex) !== null || + value.toString().match(regex) !== null + ); }); - }, + } render() { const { addKeyCommandListener, - removeKeyCommandListener + removeKeyCommandListener, } = this.props; const currentSearch = this.getCurrentSearch(); const shouldBeOpen = currentSearch !== null; if (shouldBeOpen) { - const { - top, - left, - height - } = getVisibleSelectionRect(window); + const { top, left, height } = getVisibleSelectionRect(window); const results = this.getResults(currentSearch.search); - const clickOption = (option) => { + const clickOption = option => { return this.selectOption(option, currentSearch); - } + }; return (
    Mention example @user1. Type @ to add a new mention for User 1 or User 2
    ') - ) + fromHTML( + '
    Mention example @user1. Type @ to add a new mention for User 1 or User 2
    ' + ) + ), }; - }, + + this.onChange = this.onChange.bind(this); + } onChange(editorState) { console.log(toHTML(editorState.getCurrentContent())); - this.setState({editorState}); - }, + this.setState({ editorState }); + } render() { return ( @@ -426,14 +418,17 @@ /> ); } - }); + } - ReactDOM.render( - , - document.getElementById('target') - ); + ReactDOM.render(, document.getElementById('target')); + diff --git a/example/token.html b/example/token.html index d12ae6a..e96a54f 100644 --- a/example/token.html +++ b/example/token.html @@ -16,7 +16,6 @@
    - @@ -27,41 +26,34 @@ + diff --git a/example/tray.html b/example/tray.html index 3970007..f21f418 100644 --- a/example/tray.html +++ b/example/tray.html @@ -16,7 +16,6 @@
    - @@ -27,47 +26,33 @@ + diff --git a/package.json b/package.json index e81fa95..0627b26 100644 --- a/package.json +++ b/package.json @@ -31,10 +31,9 @@ "react-dom": "^15.0.0 || ^16.0.0" }, "dependencies": { - "create-react-class": "^15.6.2", "immutable": "^3.8.1", "invariant": "^2.2.1", - "prop-types": "^15.6.0" + "prop-types": "^15.7.2" }, "devDependencies": { "babel-cli": "^6.10.1", @@ -44,8 +43,8 @@ "babel-preset-env": "^1.6.1", "babel-preset-react": "^6.5.0", "babel-standalone": "^6.7.7", - "draft-convert": "^1.3.1", - "draft-js": "^0.8.1", + "draft-convert": "^2.1.4", + "draft-js": "^0.10.5", "es6-shim": "^0.35.0", "react": "^16.2.0", "react-dom": "^16.2.0", diff --git a/src/components/Editor.js b/src/components/Editor.js index 570f3db..24eb3b9 100644 --- a/src/components/Editor.js +++ b/src/components/Editor.js @@ -1,11 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import { Editor, EditorState, CompositeDecorator, - getDefaultKeyBinding + getDefaultKeyBinding, } from 'draft-js'; import KeyCommandController from './KeyCommandController'; import OverlayWrapper from './OverlayWrapper'; @@ -35,46 +34,31 @@ const propTypes = { renderTray: PropTypes.func, }; -const EditorWrapper = createReactClass({ - propTypes, +class EditorWrapper extends React.Component { + constructor(props) { + super(props); - childContextTypes: { - getEditorState: PropTypes.func, - getReadOnly: PropTypes.func, - setReadOnly: PropTypes.func, - onChange: PropTypes.func, - focus: PropTypes.func, - blur: PropTypes.func - }, + const { baseDecorator } = props; - getDefaultProps() { - return { - className: '', - editorState: EditorState.createEmpty(), - onChange: () => { }, - decorators: [], - baseDecorator: CompositeDecorator, - styleMap: {}, - styleFn: () => { }, - buttons: [], - overlays: [], - blockRendererFn: () => { }, - blockStyleFn: () => { }, - keyBindingFn: () => { }, + const decorator = new baseDecorator(props.decorators); + this.state = { + decorator, readOnly: false, - showButtons: true }; - }, - - getInitialState() { - const { baseDecorator } = this.props; - const decorator = new baseDecorator(this.props.decorators); - return { - decorator, - readOnly: false - }; - }, + this.keyBindingFn = this.keyBindingFn.bind(this); + this.handleReturn = this.handleReturn.bind(this); + this.onEscape = this.onEscape.bind(this); + this.onTab = this.onTab.bind(this); + this.onUpArrow = this.onUpArrow.bind(this); + this.onDownArrow = this.onDownArrow.bind(this); + this.focus = this.focus.bind(this); + this.blur = this.blur.bind(this); + this.getOtherProps = this.getOtherProps.bind(this); + this.getReadOnly = this.getReadOnly.bind(this); + this.setReadOnly = this.setReadOnly.bind(this); + this.getDecoratedState = this.getDecoratedState.bind(this); + } getChildContext() { return { @@ -83,22 +67,28 @@ const EditorWrapper = createReactClass({ setReadOnly: this.setReadOnly, onChange: this.props.onChange, focus: this.focus, - blur: this.blur + blur: this.blur, }; - }, + } componentWillReceiveProps(nextProps) { - if (nextProps.decorators.length === this.state.decorator._decorators.length) { - const allDecoratorsMatch = this.state.decorator._decorators.every((decorator, i) => { - return decorator === nextProps.decorators[i]; - }); + if ( + nextProps.decorators.length === this.state.decorator._decorators.length + ) { + const allDecoratorsMatch = this.state.decorator._decorators.every( + (decorator, i) => { + return decorator === nextProps.decorators[i]; + } + ); if (allDecoratorsMatch) { return; } } - this.setState({ decorator: new nextProps.baseDecorator(nextProps.decorators) }); - }, + this.setState({ + decorator: new nextProps.baseDecorator(nextProps.decorators), + }); + } keyBindingFn(e) { const pluginsCommand = this.props.keyBindingFn(e); @@ -107,41 +97,56 @@ const EditorWrapper = createReactClass({ } return getDefaultKeyBinding(e); - }, + } handleReturn(e, editorState) { - return (this.props.handleReturn && this.props.handleReturn(e, editorState)) || this.props.handleKeyCommand('return', e); - }, + return ( + (this.props.handleReturn && this.props.handleReturn(e, editorState)) || + this.props.handleKeyCommand('return', e) + ); + } onEscape(e) { - return (this.props.onEscape && this.props.onEscape(e)) || this.props.handleKeyCommand('escape', e); - }, + return ( + (this.props.onEscape && this.props.onEscape(e)) || + this.props.handleKeyCommand('escape', e) + ); + } onTab(e) { - return (this.props.onTab && this.props.onTab(e)) || this.props.handleKeyCommand('tab', e); - }, + return ( + (this.props.onTab && this.props.onTab(e)) || + this.props.handleKeyCommand('tab', e) + ); + } onUpArrow(e) { - return (this.props.onUpArrow && this.props.onUpArrow(e)) || this.props.handleKeyCommand('up-arrow', e); - }, + return ( + (this.props.onUpArrow && this.props.onUpArrow(e)) || + this.props.handleKeyCommand('up-arrow', e) + ); + } onDownArrow(e) { - return (this.props.onDownArrow && this.props.onDownArrow(e)) || this.props.handleKeyCommand('down-arrow', e); - }, + return ( + (this.props.onDownArrow && this.props.onDownArrow(e)) || + this.props.handleKeyCommand('down-arrow', e) + ); + } focus() { this.refs.editor.focus(); - }, + } blur() { this.refs.editor.blur(); - }, + } getOtherProps() { const propKeys = Object.keys(this.props); const propTypeKeys = Object.keys(propTypes); - const propsToPass = propKeys.filter((prop) => { + const propsToPass = propKeys.filter(prop => { return propTypeKeys.indexOf(prop) === -1; }); @@ -149,15 +154,15 @@ const EditorWrapper = createReactClass({ acc[prop] = this.props[prop]; return acc; }, {}); - }, + } getReadOnly() { return this.state.readOnly || this.props.readOnly; - }, + } setReadOnly(readOnly) { this.setState({ readOnly }); - }, + } getDecoratedState() { const { editorState } = this.props; @@ -165,12 +170,15 @@ const EditorWrapper = createReactClass({ const currentDecorator = editorState.getDecorator(); - if (currentDecorator && currentDecorator._decorators === decorator._decorators) { + if ( + currentDecorator && + currentDecorator._decorators === decorator._decorators + ) { return editorState; } return EditorState.set(editorState, { decorator }); - }, + } renderTray() { const { renderTray } = this.props; @@ -180,14 +188,14 @@ const EditorWrapper = createReactClass({ } return renderTray(); - }, + } renderPluginButtons() { const { onChange, addKeyCommandListener, removeKeyCommandListener, - showButtons + showButtons, } = this.props; if (showButtons === false) { @@ -209,13 +217,13 @@ const EditorWrapper = createReactClass({ /> ); }); - }, + } renderOverlays() { const { onChange, addKeyCommandListener, - removeKeyCommandListener + removeKeyCommandListener, } = this.props; const decoratedState = this.getDecoratedState(); @@ -233,7 +241,7 @@ const EditorWrapper = createReactClass({ ); }); - }, + } render() { const { @@ -272,19 +280,43 @@ const EditorWrapper = createReactClass({ onUpArrow={this.onUpArrow} onDownArrow={this.onDownArrow} /> -
    - {this.renderTray()} -
    +
    {this.renderTray()}
    {this.renderPluginButtons()}
    -
    - {this.renderOverlays()} -
    +
    {this.renderOverlays()}
    ); } -}); +} + +EditorWrapper.propTypes = propTypes; + +EditorWrapper.defaultProps = { + className: '', + editorState: EditorState.createEmpty(), + onChange: () => {}, + decorators: [], + baseDecorator: CompositeDecorator, + styleMap: {}, + styleFn: () => {}, + buttons: [], + overlays: [], + blockRendererFn: () => {}, + blockStyleFn: () => {}, + keyBindingFn: () => {}, + readOnly: false, + showButtons: true, +}; + +EditorWrapper.childContextTypes = { + getEditorState: PropTypes.func, + getReadOnly: PropTypes.func, + setReadOnly: PropTypes.func, + onChange: PropTypes.func, + focus: PropTypes.func, + blur: PropTypes.func, +}; export default KeyCommandController(EditorWrapper); diff --git a/src/components/KeyCommandController.js b/src/components/KeyCommandController.js index f5e378d..5c98f3a 100644 --- a/src/components/KeyCommandController.js +++ b/src/components/KeyCommandController.js @@ -1,156 +1,179 @@ import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import invariant from 'invariant'; -import {List} from 'immutable'; -import {EditorState} from 'draft-js'; +import { List } from 'immutable'; +import { EditorState } from 'draft-js'; const providedProps = { addKeyCommandListener: PropTypes.func, removeKeyCommandListener: PropTypes.func, - handleKeyCommand: PropTypes.func + handleKeyCommand: PropTypes.func, }; -const KeyCommandController = (Component) => createReactClass({ - displayName: `KeyCommandController(${Component.displayName})`, - - propTypes: { - editorState: PropTypes.object, - onChange: PropTypes.func, - keyCommandListeners: PropTypes.arrayOf(PropTypes.func), - ...providedProps - }, - - getDefaultProps() { - return { - keyCommandListeners: [] - }; - }, - - componentWillMount() { - this.keyCommandOverrides = List(this.props.keyCommandListeners); - this.keyCommandListeners = List(); - }, - - componentDidMount() { - // ensure valid props for deferral - const propNames = Object.keys(providedProps); - const presentProps = propNames.filter((propName) => this.props[propName] !== undefined); - const nonePresent = presentProps.length === 0; - const allPresent = presentProps.length === propNames.length; - - invariant( - nonePresent || allPresent, - `KeyCommandController: A KeyCommandController is receiving only some props (${presentProps.join(', ')}) necessary to defer to a parent key command controller.` - ); - - if (allPresent) { - this.props.keyCommandListeners.forEach((listener) => { - this.props.addKeyCommandListener(listener); - }); +const KeyCommandController = Component => { + class KeyCommand extends React.Component { + constructor(props) { + super(props); + this.addKeyCommandListener = this.addKeyCommandListener.bind(this); + this.removeKeyCommandListener = this.removeKeyCommandListener.bind(this); + this.handleKeyCommand = this.handleKeyCommand.bind(this); + this.focus = this.focus.bind(this); + this.blur = this.blur.bind(this); } - }, - componentWillUnmount() { - if (this.props.removeKeyCommandListener) { - this.props.keyCommandListeners.forEach((listener) => { - this.props.removeKeyCommandListener(listener); - }); + componentWillMount() { + this.keyCommandOverrides = List(this.props.keyCommandListeners); + this.keyCommandListeners = List(); } - }, - addKeyCommandListener(listener) { - const {addKeyCommandListener} = this.props; + componentDidMount() { + // ensure valid props for deferral + const propNames = Object.keys(providedProps); + const presentProps = propNames.filter( + propName => this.props[propName] !== undefined + ); + const nonePresent = presentProps.length === 0; + const allPresent = presentProps.length === propNames.length; + + invariant( + nonePresent || allPresent, + `KeyCommandController: A KeyCommandController is receiving only some props (${presentProps.join( + ', ' + )}) necessary to defer to a parent key command controller.` + ); + + if (allPresent) { + this.props.keyCommandListeners.forEach(listener => { + this.props.addKeyCommandListener(listener); + }); + } + } - if (addKeyCommandListener) { - addKeyCommandListener(listener); - return; + componentWillUnmount() { + if (this.props.removeKeyCommandListener) { + this.props.keyCommandListeners.forEach(listener => { + this.props.removeKeyCommandListener(listener); + }); + } } - this.keyCommandListeners = this.keyCommandListeners.unshift(listener); - }, + addKeyCommandListener(listener) { + const { addKeyCommandListener } = this.props; - removeKeyCommandListener(listener) { - const {removeKeyCommandListener} = this.props; + if (addKeyCommandListener) { + addKeyCommandListener(listener); + return; + } - if (removeKeyCommandListener) { - removeKeyCommandListener(listener); - return; + this.keyCommandListeners = this.keyCommandListeners.unshift(listener); } - this.keyCommandListeners = this.keyCommandListeners.filterNot((l) => l === listener); - }, + removeKeyCommandListener(listener) { + const { removeKeyCommandListener } = this.props; - handleKeyCommand(command, keyboardEvent = null) { - const {editorState, onChange, handleKeyCommand} = this.props; + if (removeKeyCommandListener) { + removeKeyCommandListener(listener); + return; + } - if (handleKeyCommand) { - return handleKeyCommand(command, keyboardEvent); + this.keyCommandListeners = this.keyCommandListeners.filterNot( + l => l === listener + ); } - const result = this.keyCommandListeners.concat(this.keyCommandOverrides).reduce(({state, hasChanged}, listener) => { - if (hasChanged === true) { - return { - state, - hasChanged - }; - } + handleKeyCommand(command, keyboardEvent = null) { + const { editorState, onChange, handleKeyCommand } = this.props; - const listenerResult = listener(state, command, keyboardEvent); - const isEditorState = listenerResult instanceof EditorState; - - if (listenerResult === true || (isEditorState && listenerResult !== state)) { - if (isEditorState) { - onChange(listenerResult); - return { - state: listenerResult, - hasChanged: true - }; - } - return { - state, - hasChanged: true - }; + if (handleKeyCommand) { + return handleKeyCommand(command, keyboardEvent); } - return { - state, - hasChanged - }; - }, {state: editorState, hasChanged: false}); - - return result.hasChanged; - }, - - focus() { - this.refs.editor.focus(); - }, - - blur() { - this.refs.editor.blur(); - }, - - render() { - const { - editorState, - onChange, - keyCommandListeners, // eslint-disable-line no-unused-vars - ...others - } = this.props; - - - return ( - - ); + const result = this.keyCommandListeners + .concat(this.keyCommandOverrides) + .reduce( + ({ state, hasChanged }, listener) => { + if (hasChanged === true) { + return { + state, + hasChanged, + }; + } + + const listenerResult = listener(state, command, keyboardEvent); + const isEditorState = listenerResult instanceof EditorState; + + if ( + listenerResult === true || + (isEditorState && listenerResult !== state) + ) { + if (isEditorState) { + onChange(listenerResult); + return { + state: listenerResult, + hasChanged: true, + }; + } + return { + state, + hasChanged: true, + }; + } + + return { + state, + hasChanged, + }; + }, + { state: editorState, hasChanged: false } + ); + + return result.hasChanged; + } + + focus() { + this.refs.editor.focus(); + } + + blur() { + this.refs.editor.blur(); + } + + render() { + const { + editorState, + onChange, + keyCommandListeners, // eslint-disable-line no-unused-vars + ...others + } = this.props; + + return ( + + ); + } } -}); + + KeyCommand.displayName = `KeyCommandController(${Component.displayName})`; + + KeyCommand.propTypes = { + editorState: PropTypes.object, + onChange: PropTypes.func, + keyCommandListeners: PropTypes.arrayOf(PropTypes.func), + ...providedProps, + }; + + KeyCommand.defaultProps = { + keyCommandListeners: [], + }; + + return KeyCommand; +}; export default KeyCommandController; diff --git a/src/components/OverlayWrapper.js b/src/components/OverlayWrapper.js index 9f1274a..592b506 100644 --- a/src/components/OverlayWrapper.js +++ b/src/components/OverlayWrapper.js @@ -1,33 +1,34 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import createReactClass from 'create-react-class'; -export default createReactClass({ - getInitialState() { +export default class OverlayWrapper extends React.Component { + constructor(props) { + super(props); + const node = document.createElement('div'); document.body.appendChild(node); - return {node}; - }, + this.state = { node }; + } componentDidMount() { this.renderOverlay(); - }, + } componentDidUpdate() { this.renderOverlay(); - }, + } componentWillUnmount() { ReactDOM.unmountComponentAtNode(this.state.node); - }, + } renderOverlay() { const child = React.Children.only(this.props.children); ReactDOM.render(child, this.state.node); - }, + } render() { return null; } -}); +} diff --git a/src/components/Toolbar.js b/src/components/Toolbar.js index 9c8fc4d..04c34ac 100644 --- a/src/components/Toolbar.js +++ b/src/components/Toolbar.js @@ -1,32 +1,24 @@ import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import KeyCommandController from './KeyCommandController'; -const Toolbar = createReactClass({ - propTypes: { - editorState: PropTypes.object, - onChange: PropTypes.func, - buttons: PropTypes.array, - addKeyCommandListener: PropTypes.func.isRequired, - removeKeyCommandListener: PropTypes.func.isRequired - }, +class Toolbar extends React.Component { + constructor(props) { + super(props); - childContextTypes: { - getEditorState: PropTypes.func, - onChange: PropTypes.func - }, + this.getEditorState = this.getEditorState.bind(this); + } getChildContext() { return { getEditorState: this.getEditorState, - onChange: this.props.onChange + onChange: this.props.onChange, }; - }, + } getEditorState() { return this.props.editorState; - }, + } renderButtons() { const { @@ -50,15 +42,24 @@ const Toolbar = createReactClass({ /> ); }); - }, + } render() { - return ( -
      - {this.renderButtons()} -
    - ); + return
      {this.renderButtons()}
    ; } -}); +} + +Toolbar.propTypes = { + editorState: PropTypes.object, + onChange: PropTypes.func, + buttons: PropTypes.array, + addKeyCommandListener: PropTypes.func.isRequired, + removeKeyCommandListener: PropTypes.func.isRequired, +}; + +Toolbar.childContextTypes = { + getEditorState: PropTypes.func, + onChange: PropTypes.func, +}; export default KeyCommandController(Toolbar); diff --git a/src/plugins/createPlugin.js b/src/plugins/createPlugin.js index f8130aa..0dee549 100644 --- a/src/plugins/createPlugin.js +++ b/src/plugins/createPlugin.js @@ -1,7 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; -import {OrderedSet} from 'immutable'; +import { OrderedSet } from 'immutable'; import memoize from '../util/memoize'; import compose from '../util/compose'; import accumulateFunction from '../util/accumulateFunction'; @@ -11,15 +10,17 @@ const emptyFunction = () => {}; const emptyArray = []; const emptyObject = {}; -const defaultMiddlewareFunction = (next) => (...args) => next(...args); +const defaultMiddlewareFunction = next => (...args) => next(...args); defaultMiddlewareFunction.__isMiddleware = true; const memoizedCompose = memoize(compose); const memoizedAccumulateFunction = memoize(accumulateFunction); const memoizedAssign = memoize((...args) => Object.assign({}, ...args)); const memoizedConcat = memoize((a1, a2) => a1.concat(a2)); -const memoizedCoerceArray = memoize((arg) => Array.isArray(arg) ? arg : [arg]); -const memoizedPassEmptyStyles = memoize((func) => (nodeName, node) => func(nodeName, node, OrderedSet())); +const memoizedCoerceArray = memoize(arg => (Array.isArray(arg) ? arg : [arg])); +const memoizedPassEmptyStyles = memoize(func => (nodeName, node) => + func(nodeName, node, OrderedSet()) +); const memoizedMiddlewareAdapter = memoize(middlewareAdapter); const createPlugin = ({ @@ -44,64 +45,58 @@ const createPlugin = ({ styleToHTML = defaultMiddlewareFunction, blockToHTML = defaultMiddlewareFunction, entityToHTML = defaultMiddlewareFunction, -}) => (ToWrap) => { +}) => ToWrap => { decorators = memoizedCoerceArray(decorators); buttons = memoizedCoerceArray(buttons); overlays = memoizedCoerceArray(overlays); if (ToWrap.prototype && ToWrap.prototype.isReactComponent) { // wrapping an Editor component - return createReactClass({ - displayName, - - propTypes: { - styleMap: PropTypes.object, - styleFn: PropTypes.func, - decorators: PropTypes.array, - buttons: PropTypes.array, - overlays: PropTypes.array, - blockRendererFn: PropTypes.func, - blockStyleFn: PropTypes.func, - keyBindingFn: PropTypes.func, - keyCommandListeners: PropTypes.arrayOf(PropTypes.func) - }, - - getDefaultProps() { - return { - styleMap: emptyObject, - styleFn: emptyFunction, - decorators: emptyArray, - buttons: emptyArray, - overlays: emptyArray, - blockRendererFn: emptyFunction, - blockStyleFn: emptyFunction, - keyBindingFn: emptyFunction, - keyCommandListeners: emptyArray - }; - }, + class Plugin extends React.Component { + constructor(props) { + super(props); + + this.focus = this.focus.bind(this); + this.blur = this.blur.bind(this); + } focus() { if (this.refs.child.focus) { this.refs.child.focus(); } - }, + } blur() { if (this.refs.child.blur) { this.refs.child.blur(); } - }, + } render() { const newStyleMap = memoizedAssign(this.props.styleMap, styleMap); - const newStyleFn = memoizedAccumulateFunction(this.props.styleFn, styleFn); + const newStyleFn = memoizedAccumulateFunction( + this.props.styleFn, + styleFn + ); const newDecorators = memoizedConcat(this.props.decorators, decorators); const newButtons = memoizedConcat(this.props.buttons, buttons); const newOverlays = memoizedConcat(this.props.overlays, overlays); - const newBlockRendererFn = memoizedAccumulateFunction(blockRendererFn, this.props.blockRendererFn); - const newBlockStyleFn = memoizedAccumulateFunction(blockStyleFn, this.props.blockStyleFn); - const newKeyBindingFn = memoizedAccumulateFunction(keyBindingFn, this.props.keyBindingFn); - const newKeyCommandListeners = memoizedConcat(this.props.keyCommandListeners, memoizedCoerceArray(keyCommandListener)); + const newBlockRendererFn = memoizedAccumulateFunction( + blockRendererFn, + this.props.blockRendererFn + ); + const newBlockStyleFn = memoizedAccumulateFunction( + blockStyleFn, + this.props.blockStyleFn + ); + const newKeyBindingFn = memoizedAccumulateFunction( + keyBindingFn, + this.props.keyBindingFn + ); + const newKeyCommandListeners = memoizedConcat( + this.props.keyCommandListeners, + memoizedCoerceArray(keyCommandListener) + ); return ( ); } - }); + } + + Plugin.displayName = displayName; + + Plugin.propTypes = { + styleMap: PropTypes.object, + styleFn: PropTypes.func, + decorators: PropTypes.array, + buttons: PropTypes.array, + overlays: PropTypes.array, + blockRendererFn: PropTypes.func, + blockStyleFn: PropTypes.func, + keyBindingFn: PropTypes.func, + keyCommandListeners: PropTypes.arrayOf(PropTypes.func), + }; + + Plugin.defaultProps = { + styleMap: emptyObject, + styleFn: emptyFunction, + decorators: emptyArray, + buttons: emptyArray, + overlays: emptyArray, + blockRendererFn: emptyFunction, + blockStyleFn: emptyFunction, + keyBindingFn: emptyFunction, + keyCommandListeners: emptyArray, + }; + + return Plugin; } else { // wrapping a converter function return (...args) => { - if (args.length === 1 && (typeof args[0] === 'string' || (args[0].hasOwnProperty('_map') && args[0].getBlockMap != null))) { + if ( + args.length === 1 && + (typeof args[0] === 'string' || + (args[0].hasOwnProperty('_map') && args[0].getBlockMap != null)) + ) { // actively converting an HTML string/ContentState, so pass additional options to the next converter function. return ToWrap({ htmlToStyle, @@ -132,7 +159,7 @@ const createPlugin = ({ textToEntity, styleToHTML, blockToHTML, - entityToHTML + entityToHTML, })(...args); } else { // receiving a plugin to accumulate upon for a converter - accumulate @@ -141,25 +168,46 @@ const createPlugin = ({ const oldOptions = args[0]; - const newHTMLToStyle = memoizedCompose(memoizedMiddlewareAdapter(memoizedPassEmptyStyles(htmlToStyle)), memoizedMiddlewareAdapter(oldOptions.htmlToStyle)); + const newHTMLToStyle = memoizedCompose( + memoizedMiddlewareAdapter(memoizedPassEmptyStyles(htmlToStyle)), + memoizedMiddlewareAdapter(oldOptions.htmlToStyle) + ); newHTMLToStyle.__isMiddleware = true; - const newHTMLToBlock = memoizedCompose(memoizedMiddlewareAdapter(htmlToBlock), memoizedMiddlewareAdapter(oldOptions.htmlToBlock)); + const newHTMLToBlock = memoizedCompose( + memoizedMiddlewareAdapter(htmlToBlock), + memoizedMiddlewareAdapter(oldOptions.htmlToBlock) + ); newHTMLToBlock.__isMiddleware = true; - const newHTMLToEntity = memoizedCompose(memoizedMiddlewareAdapter(htmlToEntity), memoizedMiddlewareAdapter(oldOptions.htmlToEntity)); + const newHTMLToEntity = memoizedCompose( + memoizedMiddlewareAdapter(htmlToEntity), + memoizedMiddlewareAdapter(oldOptions.htmlToEntity) + ); newHTMLToEntity.__isMiddleware = true; - const newTextToEntity = memoizedCompose(memoizedMiddlewareAdapter(textToEntity), memoizedMiddlewareAdapter(oldOptions.textToEntity)); + const newTextToEntity = memoizedCompose( + memoizedMiddlewareAdapter(textToEntity), + memoizedMiddlewareAdapter(oldOptions.textToEntity) + ); newTextToEntity.__isMiddleware = true; - const newStyleToHTML = memoizedCompose(memoizedMiddlewareAdapter(styleToHTML), memoizedMiddlewareAdapter(oldOptions.styleToHTML)); + const newStyleToHTML = memoizedCompose( + memoizedMiddlewareAdapter(styleToHTML), + memoizedMiddlewareAdapter(oldOptions.styleToHTML) + ); newStyleToHTML.__isMiddleware = true; - const newBlockToHTML = memoizedCompose(memoizedMiddlewareAdapter(blockToHTML), memoizedMiddlewareAdapter(oldOptions.blockToHTML)); + const newBlockToHTML = memoizedCompose( + memoizedMiddlewareAdapter(blockToHTML), + memoizedMiddlewareAdapter(oldOptions.blockToHTML) + ); newBlockToHTML.__isMiddleware = true; - const newEntityToHTML = memoizedCompose(memoizedMiddlewareAdapter(entityToHTML), memoizedMiddlewareAdapter(oldOptions.entityToHTML)); + const newEntityToHTML = memoizedCompose( + memoizedMiddlewareAdapter(entityToHTML), + memoizedMiddlewareAdapter(oldOptions.entityToHTML) + ); newEntityToHTML.__isMiddleware = true; return createPlugin({ @@ -169,7 +217,7 @@ const createPlugin = ({ textToEntity: newTextToEntity, styleToHTML: newStyleToHTML, blockToHTML: newBlockToHTML, - entityToHTML: newEntityToHTML + entityToHTML: newEntityToHTML, })(ToWrap); } }; diff --git a/yarn.lock b/yarn.lock index 48e6d5a..6009492 100644 --- a/yarn.lock +++ b/yarn.lock @@ -890,14 +890,6 @@ core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" -create-react-class@^15.6.2: - version "15.6.2" - resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.2.tgz#cf1ed15f12aad7f14ef5f2dfe05e6c42f91ef02a" - dependencies: - fbjs "^0.8.9" - loose-envify "^1.3.1" - object-assign "^4.1.1" - cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -959,18 +951,18 @@ domain-browser@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" -draft-convert@^1.3.1: - version "1.4.10" - resolved "https://registry.yarnpkg.com/draft-convert/-/draft-convert-1.4.10.tgz#05007f009b18025451a2ce259aa008f4aa6a1add" +draft-convert@^2.1.4: + version "2.1.4" + resolved "https://npm.hubteam.com/npm-nexus/repository/npm-all/draft-convert/-/draft-convert-2.1.4.tgz#b8817000eaf251e95583c987adda085d67254a0d" dependencies: immutable "~3.7.4" invariant "^2.2.1" -draft-js@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.8.1.tgz#484256414c963dd1f5309700ddada10f2aa1f6ff" +draft-js@^0.10.5: + version "0.10.5" + resolved "https://npm.hubteam.com/npm-nexus/repository/npm-all/draft-js/-/draft-js-0.10.5.tgz#bfa9beb018fe0533dbb08d6675c371a6b08fa742" dependencies: - fbjs "^0.8.3" + fbjs "^0.8.15" immutable "~3.7.4" object-assign "^4.1.0" @@ -1050,7 +1042,7 @@ extsprintf@1.3.0, extsprintf@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" -fbjs@^0.8.16, fbjs@^0.8.3, fbjs@^0.8.9: +fbjs@^0.8.15, fbjs@^0.8.16: version "0.8.16" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" dependencies: @@ -1397,7 +1389,7 @@ isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" -js-tokens@^3.0.0, js-tokens@^3.0.2: +js-tokens@^3.0.0, "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" @@ -1477,12 +1469,18 @@ longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: +loose-envify@^1.0.0: version "1.3.1" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" dependencies: js-tokens "^3.0.0" +loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://npm.hubteam.com/npm-nexus/repository/npm-all/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + memory-fs@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290" @@ -1755,13 +1753,13 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.6.0: - version "15.6.0" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" +prop-types@^15.6.0, prop-types@^15.7.2: + version "15.7.2" + resolved "https://npm.hubteam.com/npm-nexus/repository/npm-all/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" dependencies: - fbjs "^0.8.16" - loose-envify "^1.3.1" + loose-envify "^1.4.0" object-assign "^4.1.1" + react-is "^16.8.1" prr@~0.0.0: version "0.0.0" @@ -1812,6 +1810,10 @@ react-dom@^16.2.0: object-assign "^4.1.1" prop-types "^15.6.0" +react-is@^16.8.1: + version "16.8.6" + resolved "https://npm.hubteam.com/npm-nexus/repository/npm-all/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" + react@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"