From 9e4f452c1ca664a7e2d126fffd244baae631d67e Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 24 Jun 2016 12:06:59 -0700 Subject: [PATCH] initial plain text paste support --- lib/components/content.js | 65 +++++++++++++++++++++++++++------------ lib/components/editor.js | 7 +++-- lib/plugins/core.js | 33 ++++++++++++++++++++ 3 files changed, 82 insertions(+), 23 deletions(-) diff --git a/lib/components/content.js b/lib/components/content.js index a4b8cd83f..45dd2f076 100644 --- a/lib/components/content.js +++ b/lib/components/content.js @@ -19,6 +19,7 @@ class Content extends React.Component { onBeforeInput: React.PropTypes.func, onChange: React.PropTypes.func, onKeyDown: React.PropTypes.func, + onPaste: React.PropTypes.func, onSelect: React.PropTypes.func, renderMark: React.PropTypes.func.isRequired, renderNode: React.PropTypes.func.isRequired, @@ -51,13 +52,49 @@ class Content extends React.Component { } /** - * On key down, bubble up. + * On certain events, bubble up. + * + * @param {String} name + * @param {Event} e + */ + + onEvent(name, e) { + this.props[name](e) + } + + /** + * On paste, determine the type and bubble up. * * @param {Event} e */ - onKeyDown(e) { - this.props.onKeyDown(e) + onPaste(e) { + e.preventDefault() + const data = e.clipboardData + const { types } = data + const paste = {} + + // Handle files. + if (data.files.length != 0) { + paste.type = 'files' + paste.files = data.files + } + + // Treat it as rich text if there is HTML content. + else if (types.includes('text/plain') && types.includes('text/html')) { + paste.type = 'html' + paste.text = data.getData('text/plain') + paste.html = data.getData('text/html') + } + + // Treat everything else as plain text. + else { + paste.type = 'text' + paste.text = data.getData('text/plain') + } + + paste.data = data + this.props.onPaste(e, paste) } /** @@ -105,16 +142,6 @@ class Content extends React.Component { this.onChange(state) } - /** - * On before input, add the character to the state. - * - * @param {Event} e - */ - - onBeforeInput(e) { - this.props.onBeforeInput(e) - } - /** * Render the editor content. * @@ -136,14 +163,12 @@ class Content extends React.Component { return (
this.onKeyDown(e)} - onSelect={e => this.onSelect(e)} - onBeforeInput={e => this.onBeforeInput(e)} + contentEditable suppressContentEditableWarning style={style} + onSelect={e => this.onSelect(e)} + onPaste={e => this.onPaste(e)} + onKeyDown={e => this.onEvent('onKeyDown', e)} + onBeforeInput={e => this.onEvent('onBeforeInput', e)} > {children}
diff --git a/lib/components/editor.js b/lib/components/editor.js index ca5faddeb..6dc0cd859 100644 --- a/lib/components/editor.js +++ b/lib/components/editor.js @@ -80,13 +80,13 @@ class Editor extends React.Component { * state if one of them chooses to. * * @param {String} name - * @param {Event} e + * @param {Mixed} ...args */ - onEvent(name, e) { + onEvent(name, ...args) { for (const plugin of this.state.plugins) { if (!plugin[name]) continue - const newState = plugin[name](e, this.props.state, this) + const newState = plugin[name](...args, this.props.state, this) if (!newState) continue this.props.onChange(newState) break @@ -106,6 +106,7 @@ class Editor extends React.Component { onChange={state => this.onChange(state)} renderMark={mark => this.renderMark(mark)} renderNode={node => this.renderNode(node)} + onPaste={(e, paste) => this.onEvent('onPaste', e, paste)} onBeforeInput={e => this.onEvent('onBeforeInput', e)} onKeyDown={e => this.onEvent('onKeyDown', e)} /> diff --git a/lib/plugins/core.js b/lib/plugins/core.js index b8af26bdd..4e7ab22ee 100644 --- a/lib/plugins/core.js +++ b/lib/plugins/core.js @@ -127,6 +127,39 @@ export default { } }, + /** + * The core `onPaste` handler. + * + * @param {Event} e + * @param {Object} paste + * @param {State} state + * @param {Editor} editor + * @return {State or Null} newState + */ + + onPaste(e, paste, state, editor) { + // Don't handle files in core. + if (paste.type == 'files') return + + // If the paste type is html... + if (paste.type == 'html') { + // First, check for a match in the clipboard. + // Otherwise, check if we have a paste deserializer. + } + + // Otherwise, just insert the plain text splitting at characters. + let transform = state.transform() + + paste.text + .split('\n') + .forEach((block, i) => { + if (i > 0) transform = transform.splitBlock() + transform = transform.insertText(block) + }) + + return transform.apply() + }, + /** * Default `node` renderer. *