From b76b5313381cd8cb62e3309bb9f37b265cb528f4 Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Wed, 19 Jul 2017 11:43:32 -0700 Subject: [PATCH 01/65] WIP --- package.json | 7 ++ src/components/draftjs-editor/index.js | 89 +++++++++++++++++++++++++ src/index.js | 19 ++++-- src/routes.js | 2 + yarn.lock | 92 +++++++++++++++++++++++++- 5 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 src/components/draftjs-editor/index.js diff --git a/package.json b/package.json index 9282658165..a9ff14186a 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,13 @@ "cors": "^2.8.3", "dataloader": "^1.3.0", "debug": "^2.6.8", + "draft-js": "^0.10.1", + "draft-js-drag-n-drop-plugin": "2.0.0-beta2", + "draft-js-focus-plugin": "2.0.0-beta2", + "draft-js-image-plugin": "2.0.0-rc2", + "draft-js-markdown-shortcuts-plugin": "^0.3.0", + "draft-js-plugins-editor": "2.0.0-rc2", + "draftjs-to-markdown": "^0.4.2", "emoji-regex": "^6.1.1", "express": "^4.15.2", "express-session": "^1.15.2", diff --git a/src/components/draftjs-editor/index.js b/src/components/draftjs-editor/index.js new file mode 100644 index 0000000000..ca65c8520c --- /dev/null +++ b/src/components/draftjs-editor/index.js @@ -0,0 +1,89 @@ +import React from 'react'; +import styled from 'styled-components'; +import { EditorState, convertToRaw, convertFromRaw } from 'draft-js'; +import DraftEditor, { composeDecorators } from 'draft-js-plugins-editor'; +import createImagePlugin from 'draft-js-image-plugin'; +import draftToMarkdown from 'draftjs-to-markdown'; +import createFocusPlugin from 'draft-js-focus-plugin'; +import createBlockDndPlugin from 'draft-js-drag-n-drop-plugin'; +import createMarkdownShortcutsPlugin from 'draft-js-markdown-shortcuts-plugin'; + +import MediaInput from '../mediaInput'; +import { ThreadDescription } from '../threadComposer/style'; + +const Wrapper = styled.div` + .DraftEditor-root { + padding: 1em; + border: 1px solid black; + margin: 1em 0; + + ${ThreadDescription}; + } +`; + +const toMarkdown = editorState => + draftToMarkdown(convertToRaw(editorState.getCurrentContent())); + +class Editor extends React.Component { + constructor(props) { + super(props); + + const focusPlugin = createFocusPlugin(); + const dndPlugin = createBlockDndPlugin(); + + const decorator = composeDecorators( + focusPlugin.decorator, + dndPlugin.decorator + ); + + const imagePlugin = createImagePlugin({ decorator }); + + this.state = { + plugins: [ + imagePlugin, + createMarkdownShortcutsPlugin(), + dndPlugin, + focusPlugin, + ], + addImage: imagePlugin.addImage, + editorState: EditorState.createEmpty(), + }; + } + + onChange = editorState => { + this.setState({ + editorState, + }); + }; + + addImage = e => { + const files = e.target.files; + let { editorState, addImage } = this.state; + // Add images to editorState + // eslint-disable-next-line + for (var i = 0, file; (file = files[i]); i++) { + editorState = addImage(editorState, window.URL.createObjectURL(file)); + } + + this.onChange(editorState); + }; + + render() { + return ( + + + + Add + + + ); + } +} + +export { toMarkdown, convertFromRaw }; + +export default Editor; diff --git a/src/index.js b/src/index.js index d8ce6bc145..3e12e4b519 100644 --- a/src/index.js +++ b/src/index.js @@ -16,6 +16,8 @@ import registerServiceWorker from './registerServiceWorker'; import type { ServiceWorkerResult } from './registerServiceWorker'; import { track } from './helpers/events'; +import Editor from './components/draftjs-editor'; + const existingUser = getItemFromStorage('spectrum'); let store; if (existingUser) { @@ -57,11 +59,18 @@ function render() { } } -try { - render(); -} catch (err) { - render(); -} +ReactDOM.render( + + + , + document.querySelector('#root') +); + +// try { +// render(); +// } catch (err) { +// render(); +// } registerServiceWorker().then(({ newContent }: ServiceWorkerResult) => { if (newContent) { diff --git a/src/routes.js b/src/routes.js index b5c8d8cc6c..f253ccc438 100644 --- a/src/routes.js +++ b/src/routes.js @@ -27,6 +27,8 @@ import communitySettings from './views/communitySettings'; import channelSettings from './views/channelSettings'; import NewCommunity from './views/newCommunity'; +import Editor from './components/draftjs-editor'; + const About = () =>

About

diff --git a/yarn.lock b/yarn.lock index 215fa7c064..2be897ecf0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2221,6 +2221,10 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" +decorate-component-with-props@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/decorate-component-with-props/-/decorate-component-with-props-1.0.2.tgz#5764d3cf6a58685a522201bad31bff0cb531e5fe" + deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -2460,6 +2464,80 @@ dotenv@4.0.0, dotenv@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" +draft-js-checkable-list-item@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/draft-js-checkable-list-item/-/draft-js-checkable-list-item-2.0.5.tgz#9c5cec16185d57a36622269ea788009a3aabb4e0" + dependencies: + draft-js-modifiers "^0.1.2" + +draft-js-drag-n-drop-plugin@2.0.0-beta2: + version "2.0.0-rc2" + resolved "https://registry.yarnpkg.com/draft-js-drag-n-drop-plugin/-/draft-js-drag-n-drop-plugin-2.0.0-rc2.tgz#81ffee2323f91daf0ba01312cb4ae3a46b9ba85b" + dependencies: + decorate-component-with-props "^1.0.2" + find-with-regex "^1.0.2" + immutable "~3.7.4" + prop-types "^15.5.8" + union-class-names "^1.0.0" + +draft-js-focus-plugin@2.0.0-beta2: + version "2.0.0-rc2" + resolved "https://registry.yarnpkg.com/draft-js-focus-plugin/-/draft-js-focus-plugin-2.0.0-rc2.tgz#6b65e0c489a0cedc9cfad414b3313d44168375d9" + dependencies: + decorate-component-with-props "^1.0.2" + find-with-regex "^1.0.2" + immutable "~3.7.4" + prop-types "^15.5.8" + union-class-names "^1.0.0" + +draft-js-image-plugin@2.0.0-rc2: + version "2.0.0-rc2" + resolved "https://registry.yarnpkg.com/draft-js-image-plugin/-/draft-js-image-plugin-2.0.0-rc2.tgz#c14d5834419dae3f7bb0ac65f2039cf588ed95ca" + dependencies: + decorate-component-with-props "^1.0.2" + find-with-regex "^1.0.2" + immutable "~3.7.4" + prop-types "^15.5.8" + union-class-names "^1.0.0" + +draft-js-markdown-shortcuts-plugin@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/draft-js-markdown-shortcuts-plugin/-/draft-js-markdown-shortcuts-plugin-0.3.0.tgz#f928cdff941d89a93e5ec2dd8f23df053c8f7ceb" + dependencies: + decorate-component-with-props "^1.0.2" + draft-js "~0.10.1" + draft-js-checkable-list-item "^2.0.5" + immutable "~3.7.4" + +draft-js-modifiers@^0.1.2: + version "0.1.3" + resolved "https://registry.yarnpkg.com/draft-js-modifiers/-/draft-js-modifiers-0.1.3.tgz#7c58d1a43fee59d97f6911b4c64f59283dcdb396" + dependencies: + draft-js "~0.10.0" + immutable "~3.7.4" + +draft-js-plugins-editor@2.0.0-rc2: + version "2.0.0-rc2" + resolved "https://registry.yarnpkg.com/draft-js-plugins-editor/-/draft-js-plugins-editor-2.0.0-rc2.tgz#521eb415882a1f670c03a00581247e752291218c" + dependencies: + decorate-component-with-props "^1.0.2" + find-with-regex "^1.0.2" + immutable "~3.7.4" + prop-types "^15.5.8" + union-class-names "^1.0.0" + +draft-js@^0.10.1, draft-js@~0.10.0, draft-js@~0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.10.1.tgz#6f1219d8095729691429ca6fd7a58d2a8be5cb67" + dependencies: + fbjs "^0.8.7" + immutable "~3.7.4" + object-assign "^4.1.0" + +draftjs-to-markdown@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/draftjs-to-markdown/-/draftjs-to-markdown-0.4.2.tgz#7bc00d72bc03eb42040057e1d4bcb6707a766bba" + duplexer2@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -3067,7 +3145,7 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" -fbjs@^0.8.1, fbjs@^0.8.5, fbjs@^0.8.9: +fbjs@^0.8.1, fbjs@^0.8.5, fbjs@^0.8.7, fbjs@^0.8.9: version "0.8.12" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" dependencies: @@ -3182,6 +3260,10 @@ find-up@^2.1.0: dependencies: locate-path "^2.0.0" +find-with-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/find-with-regex/-/find-with-regex-1.0.2.tgz#d3b36286539f14c527e31f194159c6d251651a45" + findit@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/findit/-/findit-2.0.0.tgz#6509f0126af4c178551cfa99394e032e13a4d56e" @@ -3877,6 +3959,10 @@ immutable@^3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" +immutable@~3.7.4: + version "3.7.6" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -8147,6 +8233,10 @@ underscore@~1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" +union-class-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/union-class-names/-/union-class-names-1.0.0.tgz#9259608adacc39094a2b0cfe16c78e6200617847" + union-value@^0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/union-value/-/union-value-0.2.4.tgz#7375152786679057e7b37aa676e83468fc0274f0" From 272f801892d56d93f3492a8f880a35df43a557a6 Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Wed, 19 Jul 2017 14:14:48 -0700 Subject: [PATCH 02/65] Improve editor --- src/components/draftjs-editor/index.js | 132 +++++++++++++++++++++---- src/index.js | 19 +--- 2 files changed, 116 insertions(+), 35 deletions(-) diff --git a/src/components/draftjs-editor/index.js b/src/components/draftjs-editor/index.js index ca65c8520c..21aae4b65b 100644 --- a/src/components/draftjs-editor/index.js +++ b/src/components/draftjs-editor/index.js @@ -1,30 +1,75 @@ import React from 'react'; import styled from 'styled-components'; -import { EditorState, convertToRaw, convertFromRaw } from 'draft-js'; +import { + EditorState, + convertToRaw, + convertFromRaw, + ContentState, +} from 'draft-js'; import DraftEditor, { composeDecorators } from 'draft-js-plugins-editor'; import createImagePlugin from 'draft-js-image-plugin'; import draftToMarkdown from 'draftjs-to-markdown'; import createFocusPlugin from 'draft-js-focus-plugin'; import createBlockDndPlugin from 'draft-js-drag-n-drop-plugin'; import createMarkdownShortcutsPlugin from 'draft-js-markdown-shortcuts-plugin'; +import 'draft-js/dist/Draft.css'; import MediaInput from '../mediaInput'; +import { LinkPreview, LinkPreviewLoading } from '../linkPreview'; +import { Wrapper, MediaRow } from '../editor/style'; import { ThreadDescription } from '../threadComposer/style'; +import Image, { ImageContainer, ActiveOverlay } from '../editor/Image'; -const Wrapper = styled.div` - .DraftEditor-root { - padding: 1em; - border: 1px solid black; - margin: 1em 0; - - ${ThreadDescription}; - } -`; +const ImageComponent = props => { + const { + block, // eslint-disable-line no-unused-vars + theme, // eslint-disable-line no-unused-vars + blockProps, // eslint-disable-line no-unused-vars + customStyleMap, // eslint-disable-line no-unused-vars + customStyleFn, // eslint-disable-line no-unused-vars + decorator, // eslint-disable-line no-unused-vars + forceSelection, // eslint-disable-line no-unused-vars + offsetKey, // eslint-disable-line no-unused-vars + selection, // eslint-disable-line no-unused-vars + tree, // eslint-disable-line no-unused-vars + contentState, + ...elementProps + } = props; + const active = props.blockProps.isFocused; + const { src } = contentState.getEntity(block.getEntityAt(0)).getData(); + return ( + + + + + ); +}; const toMarkdown = editorState => draftToMarkdown(convertToRaw(editorState.getCurrentContent())); +const toPlainText = editorState => + editorState.getCurrentContent().getPlainText(); + +const fromPlainText = text => + EditorState.createWithContent(ContentState.createFromText(text)); + +type EditorProps = { + markdown?: boolean, + state?: Object, + onChange?: Function, + onEnter?: Function, + placeholder?: string, + singleLine?: boolean, + className?: string, + style?: Object, + images?: boolean, +}; + class Editor extends React.Component { + props: EditorProps; + editor: any; + constructor(props) { super(props); @@ -36,17 +81,20 @@ class Editor extends React.Component { dndPlugin.decorator ); - const imagePlugin = createImagePlugin({ decorator }); + const imagePlugin = createImagePlugin({ + decorator, + imageComponent: ImageComponent, + }); this.state = { plugins: [ - imagePlugin, - createMarkdownShortcutsPlugin(), - dndPlugin, - focusPlugin, + props.image !== false && imagePlugin, + props.markdown !== false && createMarkdownShortcutsPlugin(), + props.image !== false && dndPlugin, + props.image !== false && focusPlugin, ], addImage: imagePlugin.addImage, - editorState: EditorState.createEmpty(), + editorState: props.initialState || EditorState.createEmpty(), }; } @@ -68,22 +116,64 @@ class Editor extends React.Component { this.onChange(editorState); }; + focus = () => { + this.editor.focus(); + }; + render() { + const { + state = this.state.state, + onChange = this.onChange, + onEnter, + className, + style, + images, + showLinkPreview, + linkPreview, + focus, + ...rest + } = this.props; + return ( - + { + this.editor = editor; + if (this.props.editorRef) this.props.editorRef(editor); + }} + {...rest} /> - - Add - + {showLinkPreview && linkPreview && linkPreview.loading + ? + : showLinkPreview && linkPreview && linkPreview.data + ? + : null} + {images !== false && + + + Add + + } ); } } -export { toMarkdown, convertFromRaw }; +export { toMarkdown, convertFromRaw, toPlainText, fromPlainText }; export default Editor; diff --git a/src/index.js b/src/index.js index 3e12e4b519..d8ce6bc145 100644 --- a/src/index.js +++ b/src/index.js @@ -16,8 +16,6 @@ import registerServiceWorker from './registerServiceWorker'; import type { ServiceWorkerResult } from './registerServiceWorker'; import { track } from './helpers/events'; -import Editor from './components/draftjs-editor'; - const existingUser = getItemFromStorage('spectrum'); let store; if (existingUser) { @@ -59,18 +57,11 @@ function render() { } } -ReactDOM.render( - - - , - document.querySelector('#root') -); - -// try { -// render(); -// } catch (err) { -// render(); -// } +try { + render(); +} catch (err) { + render(); +} registerServiceWorker().then(({ newContent }: ServiceWorkerResult) => { if (newContent) { From 6fe3b1c84bf4c490dc9074f836e6dcb8c7d5a24f Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Wed, 19 Jul 2017 14:48:21 -0700 Subject: [PATCH 03/65] Add support for dropping images --- src/components/draftjs-editor/index.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/draftjs-editor/index.js b/src/components/draftjs-editor/index.js index 21aae4b65b..a006cad16a 100644 --- a/src/components/draftjs-editor/index.js +++ b/src/components/draftjs-editor/index.js @@ -104,8 +104,7 @@ class Editor extends React.Component { }); }; - addImage = e => { - const files = e.target.files; + addImages = files => { let { editorState, addImage } = this.state; // Add images to editorState // eslint-disable-next-line @@ -116,6 +115,14 @@ class Editor extends React.Component { this.onChange(editorState); }; + addImage = e => { + this.addImages(e.target.files); + }; + + handleDroppedFiles = (selection, files) => { + this.addImages(files); + }; + focus = () => { this.editor.focus(); }; @@ -145,6 +152,7 @@ class Editor extends React.Component { editorState={this.state.editorState} onChange={this.onChange} plugins={this.state.plugins} + handleDroppedFiles={this.handleDroppedFiles} ref={editor => { this.editor = editor; if (this.props.editorRef) this.props.editorRef(editor); From d2d004f4bd4cd6c3e4a524234dd38a45ccdbe7ed Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Wed, 19 Jul 2017 15:54:23 -0700 Subject: [PATCH 04/65] Fix link previews in thread composer --- src/components/draftjs-editor/index.js | 6 +++--- src/components/threadComposer/index.js | 29 +++++++++++++++----------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/components/draftjs-editor/index.js b/src/components/draftjs-editor/index.js index a006cad16a..a6aa58dc35 100644 --- a/src/components/draftjs-editor/index.js +++ b/src/components/draftjs-editor/index.js @@ -129,7 +129,7 @@ class Editor extends React.Component { render() { const { - state = this.state.state, + state = this.state.editorState, onChange = this.onChange, onEnter, className, @@ -149,8 +149,8 @@ class Editor extends React.Component { focus={focus} > { diff --git a/src/components/threadComposer/index.js b/src/components/threadComposer/index.js index 3ee269518d..4440c62ebe 100644 --- a/src/components/threadComposer/index.js +++ b/src/components/threadComposer/index.js @@ -16,7 +16,10 @@ import { connect } from 'react-redux'; import { track } from '../../helpers/events'; import { openComposer, closeComposer } from '../../actions/composer'; import { addToastWithTimeout } from '../../actions/toasts'; -import Editor, { toPlainText, fromPlainText, toJSON } from '../editor'; +import Editor, { + toPlainText, + fromPlainText /*, toJSON*/, +} from '../draftjs-editor'; import { getComposerCommunitiesAndChannels } from './queries'; import { publishThread } from './mutations'; import { getLinkPreviewFromUrl } from '../../helpers/utils'; @@ -39,6 +42,10 @@ import { Dropdowns, } from './style'; +const toJSON = () => console.log('dummy'); + +const ENDS_IN_WHITESPACE = /(\s|\n)$/; + class ThreadComposerWithData extends Component { // prop types state: { @@ -223,6 +230,7 @@ class ThreadComposerWithData extends Component { }; changeBody = state => { + this.listenForUrl(state); this.setState({ body: state, }); @@ -417,24 +425,21 @@ class ThreadComposerWithData extends Component { }); }; - listenForUrl = (e, data, state) => { - const text = toPlainText(state); + listenForUrl = state => { + const { linkPreview, linkPreviewLength } = this.state; + if (linkPreview !== null) return; + const lastChangeType = state.getLastChangeType(); if ( - e.keyCode !== 8 && - e.keyCode !== 9 && - e.keyCode !== 13 && - e.keyCode !== 32 && - e.keyCode !== 46 + lastChangeType !== 'backspace-character' && + lastChangeType !== 'insert-characters' ) { - // Return if backspace, tab, enter, space or delete was not pressed. return; } - const { linkPreview, linkPreviewLength } = this.state; + const text = toPlainText(state); - // also don't check if we already have a url in the linkPreview state - if (linkPreview !== null) return; + if (!ENDS_IN_WHITESPACE.test(text)) return; const toCheck = text.match(URLS); From 8d365eadaca63b84387fa99d0e100594338125c8 Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Wed, 19 Jul 2017 17:41:13 -0700 Subject: [PATCH 05/65] Fix some stuff --- package.json | 4 ++-- src/components/draftjs-editor/index.js | 3 ++- yarn.lock | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a9ff14186a..0afa6e83bf 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,8 @@ "dataloader": "^1.3.0", "debug": "^2.6.8", "draft-js": "^0.10.1", - "draft-js-drag-n-drop-plugin": "2.0.0-beta2", - "draft-js-focus-plugin": "2.0.0-beta2", + "draft-js-drag-n-drop-plugin": "2.0.0-rc2", + "draft-js-focus-plugin": "2.0.0-rc2", "draft-js-image-plugin": "2.0.0-rc2", "draft-js-markdown-shortcuts-plugin": "^0.3.0", "draft-js-plugins-editor": "2.0.0-rc2", diff --git a/src/components/draftjs-editor/index.js b/src/components/draftjs-editor/index.js index a6aa58dc35..9dbbfcd520 100644 --- a/src/components/draftjs-editor/index.js +++ b/src/components/draftjs-editor/index.js @@ -131,6 +131,7 @@ class Editor extends React.Component { const { state = this.state.editorState, onChange = this.onChange, + markdown, onEnter, className, style, @@ -143,7 +144,7 @@ class Editor extends React.Component { return ( Date: Wed, 19 Jul 2017 18:29:49 -0700 Subject: [PATCH 06/65] Add comment --- src/components/draftjs-editor/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/draftjs-editor/index.js b/src/components/draftjs-editor/index.js index 9dbbfcd520..327d063fe1 100644 --- a/src/components/draftjs-editor/index.js +++ b/src/components/draftjs-editor/index.js @@ -12,6 +12,8 @@ import draftToMarkdown from 'draftjs-to-markdown'; import createFocusPlugin from 'draft-js-focus-plugin'; import createBlockDndPlugin from 'draft-js-drag-n-drop-plugin'; import createMarkdownShortcutsPlugin from 'draft-js-markdown-shortcuts-plugin'; +// NOTE(@mxstbr): This is necessary to make sure the placeholder is aligned +// and stuff like that. import 'draft-js/dist/Draft.css'; import MediaInput from '../mediaInput'; From 948445d1045ed3e914e986e6d111fc61efad53a6 Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Thu, 20 Jul 2017 10:07:44 -0700 Subject: [PATCH 07/65] Trigger new build --- src/components/draftjs-editor/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/draftjs-editor/index.js b/src/components/draftjs-editor/index.js index 327d063fe1..e30d5ad851 100644 --- a/src/components/draftjs-editor/index.js +++ b/src/components/draftjs-editor/index.js @@ -34,7 +34,7 @@ const ImageComponent = props => { offsetKey, // eslint-disable-line no-unused-vars selection, // eslint-disable-line no-unused-vars tree, // eslint-disable-line no-unused-vars - contentState, + contentState, // eslint-disable-line no-unused-vars ...elementProps } = props; const active = props.blockProps.isFocused; From 143bcd91aba19c84040a78a01b54148e54c6a8eb Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Thu, 20 Jul 2017 11:07:36 -0700 Subject: [PATCH 08/65] Add single line mode to draftjs editor --- package.json | 1 + src/components/draftjs-editor/index.js | 9 +++++++++ yarn.lock | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/package.json b/package.json index 0afa6e83bf..434eb21c93 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "draft-js-image-plugin": "2.0.0-rc2", "draft-js-markdown-shortcuts-plugin": "^0.3.0", "draft-js-plugins-editor": "2.0.0-rc2", + "draft-js-single-line-plugin": "^2.0.1", "draftjs-to-markdown": "^0.4.2", "emoji-regex": "^6.1.1", "express": "^4.15.2", diff --git a/src/components/draftjs-editor/index.js b/src/components/draftjs-editor/index.js index e30d5ad851..79090e2ac2 100644 --- a/src/components/draftjs-editor/index.js +++ b/src/components/draftjs-editor/index.js @@ -12,6 +12,7 @@ import draftToMarkdown from 'draftjs-to-markdown'; import createFocusPlugin from 'draft-js-focus-plugin'; import createBlockDndPlugin from 'draft-js-drag-n-drop-plugin'; import createMarkdownShortcutsPlugin from 'draft-js-markdown-shortcuts-plugin'; +import createSingleLinePlugin from 'draft-js-single-line-plugin'; // NOTE(@mxstbr): This is necessary to make sure the placeholder is aligned // and stuff like that. import 'draft-js/dist/Draft.css'; @@ -88,13 +89,17 @@ class Editor extends React.Component { imageComponent: ImageComponent, }); + const singleLine = createSingleLinePlugin(); + this.state = { plugins: [ props.image !== false && imagePlugin, props.markdown !== false && createMarkdownShortcutsPlugin(), props.image !== false && dndPlugin, props.image !== false && focusPlugin, + props.singleLine === true && singleLine, ], + singleLineBlockRenderMap: singleLine.blockRenderMap, addImage: imagePlugin.addImage, editorState: props.initialState || EditorState.createEmpty(), }; @@ -141,6 +146,7 @@ class Editor extends React.Component { showLinkPreview, linkPreview, focus, + singleLine, ...rest } = this.props; @@ -156,6 +162,9 @@ class Editor extends React.Component { onChange={onChange} plugins={this.state.plugins} handleDroppedFiles={this.handleDroppedFiles} + blockRenderMap={ + singleLine === true && this.state.singleLineBlockRenderMap + } ref={editor => { this.editor = editor; if (this.props.editorRef) this.props.editorRef(editor); diff --git a/yarn.lock b/yarn.lock index 0e641e096e..b971a97091 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2526,6 +2526,12 @@ draft-js-plugins-editor@2.0.0-rc2: prop-types "^15.5.8" union-class-names "^1.0.0" +draft-js-single-line-plugin@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/draft-js-single-line-plugin/-/draft-js-single-line-plugin-2.0.1.tgz#3aea1b100b1562d7ef3d5d02e73190da12953857" + dependencies: + immutable "^3.8.1" + draft-js@^0.10.1, draft-js@~0.10.0, draft-js@~0.10.1: version "0.10.1" resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.10.1.tgz#6f1219d8095729691429ca6fd7a58d2a8be5cb67" From 6ed129e898e930fbb6dfcbeee3a9a91a48b28660 Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Thu, 20 Jul 2017 11:08:29 -0700 Subject: [PATCH 09/65] Switch chat input to new editor --- src/components/chatInput/index.js | 14 +++----- src/components/chatInput/style.js | 59 ++++++++++++++++--------------- 2 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index 4450688c47..9c20f8f36a 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -11,7 +11,7 @@ import withHandlers from 'recompose/withHandlers'; // $FlowFixMe import { connect } from 'react-redux'; import { track } from '../../helpers/events'; -import { toPlainText, fromPlainText } from '../../components/editor'; +import { toPlainText, fromPlainText } from '../../components/draftjs-editor'; import { addToastWithTimeout } from '../../actions/toasts'; import { Form, EditorInput, ChatInputWrapper, SendButton } from './style'; import { sendMessageMutation } from '../../api/message'; @@ -31,7 +31,7 @@ class ChatInputWithMutation extends Component { } submit = e => { - e.preventDefault(); + if (e) e.preventDefault(); const { state, @@ -87,12 +87,6 @@ class ChatInputWithMutation extends Component { }); }; - handleEnter = e => { - //=> make the enter key send a message, not create a new line in the next autoexpanding textarea unless shift is pressed. - e.preventDefault(); //=> prevent linebreak - this.submit(e); //=> send the message instead - }; - sendMediaMessage = e => { let reader = new FileReader(); const file = e.target.files[0]; @@ -177,14 +171,14 @@ class ChatInputWithMutation extends Component { focus={isFocused} placeholder="Your message here..." state={state} - onEnter={this.handleEnter} + handleReturn={this.submit} onChange={onChange} markdown={false} onFocus={this.onFocus} onBlur={this.onBlur} singleLine images={false} - editorRef={editor => this.editor = editor} + editorRef={editor => (this.editor = editor)} /> diff --git a/src/components/chatInput/style.js b/src/components/chatInput/style.js index 03efec9eaf..33b7fad391 100644 --- a/src/components/chatInput/style.js +++ b/src/components/chatInput/style.js @@ -1,7 +1,7 @@ import styled from 'styled-components'; import { IconButton } from '../buttons'; import { FlexRow, Transition } from '../globals'; -import Editor from '../../components/editor'; +import Editor from '../../components/draftjs-editor'; export const ChatInputWrapper = styled(FlexRow)` flex: none; @@ -16,20 +16,21 @@ export const ChatInputWrapper = styled(FlexRow)` @media (max-width: 768px) { bottom: ${props => (props.focus ? '0' : 'auto')}; position: ${props => (props.focus ? 'fixed' : 'relative')}; - background-color: ${props => (props.focus ? props.theme.bg.default : 'transparent')}; + background-color: ${props => + props.focus ? props.theme.bg.default : 'transparent'}; z-index: 1001; } `; export const Form = styled.form` - flex: auto; - display: flex; + flex: auto; + display: flex; min-width: 1px; max-width: 100%; - align-items: center; - margin-left: 4px; - border-radius: 24px; - background-color: transparent; + align-items: center; + margin-left: 4px; + border-radius: 24px; + background-color: transparent; position: relative; `; @@ -44,7 +45,8 @@ export const EditorInput = styled(Editor)` padding: 8px 40px 8px 16px; border-radius: 24px; border: 2px solid ${props => props.theme.text.placeholder}; - border-color: ${props => (props.focus ? props.theme.brand.default : props.theme.text.placeholder)}; + border-color: ${props => + props.focus ? props.theme.brand.default : props.theme.text.placeholder}; transition: border 0.3s ease-out; color: ${props => props.theme.text.default}; overflow-y: scroll; @@ -56,7 +58,8 @@ export const EditorInput = styled(Editor)` } &::placeholder { color: ${({ theme }) => theme.text.placeholder} } - &::-webkit-input-placeholder { color: ${({ theme }) => theme.text.placeholder} } + &::-webkit-input-placeholder { color: ${({ theme }) => + theme.text.placeholder} } &:-moz-placeholder { color: ${({ theme }) => theme.text.placeholder} } &:-ms-input-placeholder { color: ${({ theme }) => theme.text.placeholder} } @@ -77,30 +80,30 @@ export const SendButton = styled(IconButton)` `; export const MediaInput = styled.input` - width: 0.1px; - height: 0.1px; - opacity: 0; - overflow: hidden; - position: absolute; - z-index: -1; + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; `; export const MediaLabel = styled.label` - border: none; - outline: 0; - display: inline-block; - background: transparent; - transition: all 0.3s ease-out; - border-radius: 4px; - padding: 4px; - position: relative; - top: 2px; + border: none; + outline: 0; + display: inline-block; + background: transparent; + transition: all 0.3s ease-out; + border-radius: 4px; + padding: 4px; + position: relative; + top: 2px; color: ${({ theme }) => theme.text.alt}; - &:hover { - cursor: pointer; + &:hover { + cursor: pointer; color: ${({ theme }) => theme.brand.default}; - } + } `; export const EmojiToggle = styled(IconButton)` From fd2ff978098d25909fc778b1b0589e88ab8068c3 Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Thu, 20 Jul 2017 12:42:56 -0700 Subject: [PATCH 10/65] Save thread body as draftjs state --- iris/mutations/thread.js | 93 ++++++++++++++------------ iris/types/Thread.js | 1 + src/components/draftjs-editor/index.js | 9 ++- src/components/threadComposer/index.js | 21 +++--- 4 files changed, 66 insertions(+), 58 deletions(-) diff --git a/iris/mutations/thread.js b/iris/mutations/thread.js index fa281b553b..e28c49c917 100644 --- a/iris/mutations/thread.js +++ b/iris/mutations/thread.js @@ -60,12 +60,12 @@ module.exports = { /* If the thread has attachments, we have to iterate through each attachment and JSON.parse() the data payload. This is because we want a generic data shape in the graphQL layer like this: - + { attachmentType: enum String data: String } - + But when we get the data onto the client we JSON.parse the `data` field so that we can have any generic shape for attachments in the future. */ let attachments = []; @@ -114,8 +114,9 @@ module.exports = { }) .then(([newThread, urls]) => { // if no files were uploaded, return the new thread object - if (!urls) return newThread; + return newThread; + // TODO: MAYBE FIXME MOTHERFUCKER // otherwise we need to update the slate object of the thread to replace the image nodes with markdown image text const slateState = JSON.parse(newThread.content.body); let fileIndex = 0; @@ -274,27 +275,31 @@ module.exports = { currentUserCommunityPermissions, ]); }) - .then(([ - thread, - currentUserChannelPermissions, - currentUserCommunityPermissions, - ]) => { - // if the user owns the community or the channel, or they are the original creator, they can delete the thread - if ( - currentUserChannelPermissions.isOwner || - currentUserChannelPermissions.isModerator || - currentUserCommunityPermissions.isOwner || - currentUserCommunityPermissions.isModerator || - thread.creatorId === currentUser.id - ) { - return deleteThread(threadId); - } + .then( + ( + [ + thread, + currentUserChannelPermissions, + currentUserCommunityPermissions, + ] + ) => { + // if the user owns the community or the channel, or they are the original creator, they can delete the thread + if ( + currentUserChannelPermissions.isOwner || + currentUserChannelPermissions.isModerator || + currentUserCommunityPermissions.isOwner || + currentUserCommunityPermissions.isModerator || + thread.creatorId === currentUser.id + ) { + return deleteThread(threadId); + } - // if the user is not a channel or community owner, the thread can't be locked - return new UserError( - "You don't have permission to make changes to this thread." - ); - }); + // if the user is not a channel or community owner, the thread can't be locked + return new UserError( + "You don't have permission to make changes to this thread." + ); + } + ); }, setThreadLock: (_, { threadId, value }, { user }) => { const currentUser = user; @@ -335,26 +340,30 @@ module.exports = { currentUserCommunityPermissions, ]); }) - .then(([ - thread, - currentUserChannelPermissions, - currentUserCommunityPermissions, - ]) => { - // user owns the community or the channel, they can lock the thread - if ( - currentUserChannelPermissions.isOwner || - currentUserChannelPermissions.isModerator || - currentUserCommunityPermissions.isOwner || - currentUserCommunityPermissions.isModerator - ) { - return setThreadLock(threadId, value); - } + .then( + ( + [ + thread, + currentUserChannelPermissions, + currentUserCommunityPermissions, + ] + ) => { + // user owns the community or the channel, they can lock the thread + if ( + currentUserChannelPermissions.isOwner || + currentUserChannelPermissions.isModerator || + currentUserCommunityPermissions.isOwner || + currentUserCommunityPermissions.isModerator + ) { + return setThreadLock(threadId, value); + } - // if the user is not a channel or community owner, the thread can't be locked - return new UserError( - "You don't have permission to make changes to this thread." - ); - }); + // if the user is not a channel or community owner, the thread can't be locked + return new UserError( + "You don't have permission to make changes to this thread." + ); + } + ); }, toggleThreadNotifications: (_, { threadId }, { user }) => { const currentUser = user; diff --git a/iris/types/Thread.js b/iris/types/Thread.js index 585e443c98..5805a28b3f 100644 --- a/iris/types/Thread.js +++ b/iris/types/Thread.js @@ -23,6 +23,7 @@ const Thread = /* GraphQL */ ` enum ThreadType { SLATE + DRAFTJS } type Attachment { diff --git a/src/components/draftjs-editor/index.js b/src/components/draftjs-editor/index.js index 79090e2ac2..52da4a6c01 100644 --- a/src/components/draftjs-editor/index.js +++ b/src/components/draftjs-editor/index.js @@ -8,7 +8,6 @@ import { } from 'draft-js'; import DraftEditor, { composeDecorators } from 'draft-js-plugins-editor'; import createImagePlugin from 'draft-js-image-plugin'; -import draftToMarkdown from 'draftjs-to-markdown'; import createFocusPlugin from 'draft-js-focus-plugin'; import createBlockDndPlugin from 'draft-js-drag-n-drop-plugin'; import createMarkdownShortcutsPlugin from 'draft-js-markdown-shortcuts-plugin'; @@ -48,15 +47,15 @@ const ImageComponent = props => { ); }; -const toMarkdown = editorState => - draftToMarkdown(convertToRaw(editorState.getCurrentContent())); - const toPlainText = editorState => editorState.getCurrentContent().getPlainText(); const fromPlainText = text => EditorState.createWithContent(ContentState.createFromText(text)); +const toJSON = editorState => convertToRaw(editorState.getCurrentContent()); +const fromJSON = json => convertFromRaw(json); + type EditorProps = { markdown?: boolean, state?: Object, @@ -194,6 +193,6 @@ class Editor extends React.Component { } } -export { toMarkdown, convertFromRaw, toPlainText, fromPlainText }; +export { toPlainText, fromPlainText, toJSON, fromJSON }; export default Editor; diff --git a/src/components/threadComposer/index.js b/src/components/threadComposer/index.js index 4440c62ebe..6315835c93 100644 --- a/src/components/threadComposer/index.js +++ b/src/components/threadComposer/index.js @@ -16,10 +16,7 @@ import { connect } from 'react-redux'; import { track } from '../../helpers/events'; import { openComposer, closeComposer } from '../../actions/composer'; import { addToastWithTimeout } from '../../actions/toasts'; -import Editor, { - toPlainText, - fromPlainText /*, toJSON*/, -} from '../draftjs-editor'; +import Editor, { toPlainText, fromPlainText, toJSON } from '../draftjs-editor'; import { getComposerCommunitiesAndChannels } from './queries'; import { publishThread } from './mutations'; import { getLinkPreviewFromUrl } from '../../helpers/utils'; @@ -42,8 +39,6 @@ import { Dropdowns, } from './style'; -const toJSON = () => console.log('dummy'); - const ENDS_IN_WHITESPACE = /(\s|\n)$/; class ThreadComposerWithData extends Component { @@ -358,6 +353,8 @@ class ThreadComposerWithData extends Component { const channelId = activeChannel; const communityId = activeCommunity; + console.log(body); + const content = { title, body: JSON.stringify(toJSON(body)), @@ -375,11 +372,13 @@ class ThreadComposerWithData extends Component { }); } + // TODO(@mxstbr): FIX FILE UPLOADING + const filesToUpload = []; // Get the images - const filesToUpload = body.document.nodes - .filter(node => node.type === 'image') - .map(image => image.getIn(['data', 'file'])) - .toJS(); + // const filesToUpload = body.document.nodes + // .filter(node => node.type === 'image') + // .map(image => image.getIn(['data', 'file'])) + // .toJS(); // this.props.mutate comes from a higher order component defined at the // bottom of this file @@ -389,7 +388,7 @@ class ThreadComposerWithData extends Component { thread: { channelId, communityId, - type: 'SLATE', + type: 'DRAFTJS', content, attachments, filesToUpload, From b6e092ccca8d04b9112e7d11591c2410d422dc12 Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Thu, 20 Jul 2017 12:47:50 -0700 Subject: [PATCH 11/65] Store messages as draft js state --- src/components/chatInput/index.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index 9c20f8f36a..80d8d4bf70 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -11,7 +11,11 @@ import withHandlers from 'recompose/withHandlers'; // $FlowFixMe import { connect } from 'react-redux'; import { track } from '../../helpers/events'; -import { toPlainText, fromPlainText } from '../../components/draftjs-editor'; +import { + toJSON, + fromPlainText, + toPlainText, +} from '../../components/draftjs-editor'; import { addToastWithTimeout } from '../../actions/toasts'; import { Form, EditorInput, ChatInputWrapper, SendButton } from './style'; import { sendMessageMutation } from '../../api/message'; @@ -58,7 +62,7 @@ class ChatInputWithMutation extends Component { // in views/directMessages/containers/newThread.js if (thread === 'newDirectMessageThread') { return createThread({ - messageBody: toPlainText(state), + messageBody: JSON.stringify(toJSON(state)), messageType: 'text', }); } @@ -70,7 +74,7 @@ class ChatInputWithMutation extends Component { messageType: 'text', threadType, content: { - body: toPlainText(state), + body: JSON.stringify(toJSON(state)), }, }) .then(({ data: { addMessage } }) => { From 668dfedcebab0f470b5a155b2d0184b50e81b1f7 Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Thu, 20 Jul 2017 20:36:44 -0700 Subject: [PATCH 12/65] WIP first rendering of DraftJS content --- iris/mutations/message.js | 10 ++---- iris/types/Message.js | 1 + package.json | 1 + src/components/chatInput/index.js | 4 +-- src/components/chatMessages/index.js | 38 +++++++++++++++++++++ src/components/draftjs-editor/index.js | 4 +-- src/views/thread/components/threadDetail.js | 16 ++++++--- yarn.lock | 4 +++ 8 files changed, 62 insertions(+), 16 deletions(-) diff --git a/iris/mutations/message.js b/iris/mutations/message.js index c6c3a92983..20537a1883 100644 --- a/iris/mutations/message.js +++ b/iris/mutations/message.js @@ -3,13 +3,9 @@ import UserError from '../utils/UserError'; const { storeMessage } = require('../models/message'); import type { MessageProps } from '../models/message'; -import { - setDirectMessageThreadLastActive, -} from '../models/directMessageThread'; +import { setDirectMessageThreadLastActive } from '../models/directMessageThread'; import { createParticipantInThread } from '../models/usersThreads'; -import { - setUserLastSeenInDirectMessageThread, -} from '../models/usersDirectMessageThreads'; +import { setUserLastSeenInDirectMessageThread } from '../models/usersDirectMessageThreads'; import { uploadImage } from '../utils/s3'; type AddMessageProps = { @@ -38,7 +34,7 @@ module.exports = { } // all checks passed - if (message.messageType === 'text') { + if (message.messageType === 'text' || message.messageType === 'draftjs') { // send a normal text message return storeMessage(message, currentUser.id); } else if (message.messageType === 'media') { diff --git a/iris/types/Message.js b/iris/types/Message.js index d610633090..b61e21722c 100644 --- a/iris/types/Message.js +++ b/iris/types/Message.js @@ -2,6 +2,7 @@ const Message = /* GraphQL */ ` enum MessageTypes { text media + draftjs } enum ThreadTypes { diff --git a/package.json b/package.json index 434eb21c93..02aaa8253a 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "react-transition-group": "^1.1.2", "react-trend": "^1.2.4", "recompose": "^0.23.1", + "redraft": "^0.8.0", "redux": "^3.6.0", "redux-thunk": "^2.2.0", "rethinkdb-migrate": "^1.1.0", diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index 80d8d4bf70..bce7042b6e 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -63,7 +63,7 @@ class ChatInputWithMutation extends Component { if (thread === 'newDirectMessageThread') { return createThread({ messageBody: JSON.stringify(toJSON(state)), - messageType: 'text', + messageType: 'draftjs', }); } @@ -71,7 +71,7 @@ class ChatInputWithMutation extends Component { // or direct message thread sendMessage({ threadId: thread, - messageType: 'text', + messageType: 'draftjs', threadType, content: { body: JSON.stringify(toJSON(state)), diff --git a/src/components/chatMessages/index.js b/src/components/chatMessages/index.js index 24fc728085..0904fbd36c 100644 --- a/src/components/chatMessages/index.js +++ b/src/components/chatMessages/index.js @@ -4,6 +4,7 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; // $FlowFixMe import { Link } from 'react-router-dom'; +import redraft from 'redraft'; import { openGallery } from '../../actions/gallery'; import { convertTimestampToDate, @@ -12,8 +13,10 @@ import { } from '../../helpers/utils'; import { NullState } from '../upsell'; import { Bubble, EmojiBubble, ImgBubble } from '../bubbles'; +import { TextBubble as RawBubble } from '../bubbles/style'; import Badge from '../badges'; import Reaction from '../reaction'; +import { toState } from '../draftjs-editor'; import { UserAvatar, @@ -182,6 +185,41 @@ class ChatMessages extends Component { />} ); + } else if (message.messageType === 'draftjs') { + // const emojiOnly = onlyContainsEmoji(message.content.body); + // const TextBubble = emojiOnly ? EmojiBubble : Bubble; + return ( + + + {redraft(JSON.parse(message.content.body))} + + {/* + we check if typof equals a string to determine + if the message is coming from the server, or + generated via an optimistic response with apollo + (which has a typeof number) + */} + {/*!emojiOnly &&*/ + typeof message.id === 'string' && + } + + ); } else { return
; } diff --git a/src/components/draftjs-editor/index.js b/src/components/draftjs-editor/index.js index 52da4a6c01..a6cc7c7a88 100644 --- a/src/components/draftjs-editor/index.js +++ b/src/components/draftjs-editor/index.js @@ -54,7 +54,7 @@ const fromPlainText = text => EditorState.createWithContent(ContentState.createFromText(text)); const toJSON = editorState => convertToRaw(editorState.getCurrentContent()); -const fromJSON = json => convertFromRaw(json); +const toState = json => EditorState.createWithContent(convertFromRaw(json)); type EditorProps = { markdown?: boolean, @@ -193,6 +193,6 @@ class Editor extends React.Component { } } -export { toPlainText, fromPlainText, toJSON, fromJSON }; +export { toPlainText, fromPlainText, toJSON, toState }; export default Editor; diff --git a/src/views/thread/components/threadDetail.js b/src/views/thread/components/threadDetail.js index daf4cb0e21..98473191d3 100644 --- a/src/views/thread/components/threadDetail.js +++ b/src/views/thread/components/threadDetail.js @@ -10,6 +10,7 @@ import { connect } from 'react-redux'; import { withRouter } from 'react-router'; // $FlowFixMe import { Link } from 'react-router-dom'; +import redraft from 'redraft'; import { getLinkPreviewFromUrl, timeDifference } from '../../../helpers/utils'; import { URLS } from '../../../helpers/regexps'; import { openModal } from '../../../actions/modals'; @@ -28,7 +29,7 @@ import Editor, { toJSON, toPlainText, toState, -} from '../../../components/editor'; +} from '../../../components/draftjs-editor'; import { LinkPreview } from '../../../components/linkPreview'; import { ThreadTitle, ThreadDescription } from '../style'; // $FlowFixMe @@ -81,11 +82,14 @@ class ThreadDetailPure extends Component { data: JSON.parse(rawLinkPreview.data), }; - const viewBody = + let viewBody = thread.type === 'SLATE' ? toPlainText(toState(JSON.parse(thread.content.body))) : thread.content.body; + if (thread.type === 'DRAFTJS') + viewBody = toState(JSON.parse(thread.content.body)); + const editBody = thread.type === 'SLATE' ? toState(JSON.parse(thread.content.body)) @@ -519,9 +523,11 @@ class ThreadDetailPure extends Component { Edited {timeDifference(Date.now(), editedTimestamp)} }
- - {viewBody} - + {thread.type !== 'DRAFTJS' + ? + {viewBody} + + : }
{linkPreview && diff --git a/yarn.lock b/yarn.lock index b971a97091..68acd2ce54 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6962,6 +6962,10 @@ redis-parser@^2.4.0: version "2.6.0" resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" +redraft@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/redraft/-/redraft-0.8.0.tgz#db2a5c01a8eb6b553f46cc8382c1ed085325e99f" + reduce-css-calc@^1.2.6: version "1.3.0" resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" From 54d49329bdd1b9ea5c1b44da4549425ed01b9732 Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Fri, 21 Jul 2017 10:27:03 -0700 Subject: [PATCH 13/65] Make rendering work --- src/components/bubbles/index.js | 4 ++-- src/components/chatMessages/index.js | 14 ++++++++------ src/components/draftjs-editor/index.js | 1 + src/helpers/utils.js | 4 ++++ 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/components/bubbles/index.js b/src/components/bubbles/index.js index 9b003b20ad..97a67b0c63 100644 --- a/src/components/bubbles/index.js +++ b/src/components/bubbles/index.js @@ -32,10 +32,10 @@ export const Bubble = (props: BubbleProps) => { }; export const EmojiBubble = (props: BubbleProps) => { - const { me, message } = props; + const { me } = props; return ( - {message.body} + {props.children || props.message.body} ); }; diff --git a/src/components/chatMessages/index.js b/src/components/chatMessages/index.js index 0904fbd36c..25059ec985 100644 --- a/src/components/chatMessages/index.js +++ b/src/components/chatMessages/index.js @@ -10,13 +10,14 @@ import { convertTimestampToDate, convertTimestampToTime, onlyContainsEmoji, + draftOnlyContainsEmoji, } from '../../helpers/utils'; import { NullState } from '../upsell'; import { Bubble, EmojiBubble, ImgBubble } from '../bubbles'; import { TextBubble as RawBubble } from '../bubbles/style'; import Badge from '../badges'; import Reaction from '../reaction'; -import { toState } from '../draftjs-editor'; +import { toState, toPlainText } from '../draftjs-editor'; import { UserAvatar, @@ -186,23 +187,24 @@ class ChatMessages extends Component { ); } else if (message.messageType === 'draftjs') { - // const emojiOnly = onlyContainsEmoji(message.content.body); - // const TextBubble = emojiOnly ? EmojiBubble : Bubble; + const body = JSON.parse(message.content.body); + const emojiOnly = draftOnlyContainsEmoji(body); + const TextBubble = emojiOnly ? EmojiBubble : RawBubble; return ( - - {redraft(JSON.parse(message.content.body))} - + {redraft(body)} + {/* we check if typof equals a string to determine if the message is coming from the server, or diff --git a/src/components/draftjs-editor/index.js b/src/components/draftjs-editor/index.js index a6cc7c7a88..31175d71cc 100644 --- a/src/components/draftjs-editor/index.js +++ b/src/components/draftjs-editor/index.js @@ -183,6 +183,7 @@ class Editor extends React.Component { /> : null} {images !== false && + !this.props.readOnly && Add diff --git a/src/helpers/utils.js b/src/helpers/utils.js index 203ac6c812..df075ea707 100644 --- a/src/helpers/utils.js +++ b/src/helpers/utils.js @@ -61,6 +61,10 @@ const regex = new RegExp( ); export const onlyContainsEmoji = (text: string) => regex.test(text); +export const draftOnlyContainsEmoji = (raw: object) => + raw.blocks.length === 1 && + raw.blocks[0].type === 'unstyled' && + onlyContainsEmoji(raw.blocks[0].text); /** * Encode a string to base64 (using the Node built-in Buffer) * From ca5aa61718be27bb2a4d6d17dfe1d2c2135dc509 Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Fri, 21 Jul 2017 15:33:56 -0700 Subject: [PATCH 14/65] Remove Slate editor component --- src/components/draftjs-editor/Image.js | 80 +++++++++ src/components/draftjs-editor/index.js | 33 +--- .../{editor => draftjs-editor}/style.js | 2 +- src/components/editor/Image.js | 50 ------ src/components/editor/image-plugin.js | 117 -------------- src/components/editor/index.js | 153 ------------------ src/components/editor/single-line-plugin.js | 19 --- 7 files changed, 85 insertions(+), 369 deletions(-) create mode 100644 src/components/draftjs-editor/Image.js rename src/components/{editor => draftjs-editor}/style.js (95%) delete mode 100644 src/components/editor/Image.js delete mode 100644 src/components/editor/image-plugin.js delete mode 100644 src/components/editor/index.js delete mode 100644 src/components/editor/single-line-plugin.js diff --git a/src/components/draftjs-editor/Image.js b/src/components/draftjs-editor/Image.js new file mode 100644 index 0000000000..70052cb037 --- /dev/null +++ b/src/components/draftjs-editor/Image.js @@ -0,0 +1,80 @@ +// @flow +import React from 'react'; +import styled, { css, keyframes } from 'styled-components'; + +const blinkBorder = keyframes` + 0% { + border-color: transparent; + } + 49% { + border-color: transparent; + } + 50% { + border-color: black; + } + 100% { + border-color: black; + } +`; + +export const ImageContainer = styled.div` + position: relative; + margin: 12px 0; + ${props => + props.active && + css` + border-radius: 8px; + overflow: hidden; + box-shadow: 0 4px 8px rgba(56, 24, 229, 0.2); + `} transition: all 0.1s ease-in-out; +`; + +export const ActiveOverlay = styled.span` + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + background: rgba(56, 24, 229, 0.1); + border-radius: 8px; + border: 1px solid ${props => props.theme.brand.default}; + opacity: ${props => (props.active ? 1 : 0)}; + transition: all 0.1s ease-in-out; +`; + +const Img = styled.img` + ${props => + props.active && + css` + /* Pretend like there's a cursor next to the image when active */ + box-shadow: inset -1px 0 0 #000; + animation: ${blinkBorder} 1s infinite; + `} max-width: 100%; +`; + +const Image = props => { + const { + block, // eslint-disable-line no-unused-vars + theme, // eslint-disable-line no-unused-vars + blockProps, // eslint-disable-line no-unused-vars + customStyleMap, // eslint-disable-line no-unused-vars + customStyleFn, // eslint-disable-line no-unused-vars + decorator, // eslint-disable-line no-unused-vars + forceSelection, // eslint-disable-line no-unused-vars + offsetKey, // eslint-disable-line no-unused-vars + selection, // eslint-disable-line no-unused-vars + tree, // eslint-disable-line no-unused-vars + contentState, // eslint-disable-line no-unused-vars + ...elementProps + } = props; + const active = props.blockProps.isFocused; + const { src } = contentState.getEntity(block.getEntityAt(0)).getData(); + return ( + + + + + ); +}; + +export default Image; diff --git a/src/components/draftjs-editor/index.js b/src/components/draftjs-editor/index.js index 31175d71cc..3efa6e0a1e 100644 --- a/src/components/draftjs-editor/index.js +++ b/src/components/draftjs-editor/index.js @@ -16,36 +16,11 @@ import createSingleLinePlugin from 'draft-js-single-line-plugin'; // and stuff like that. import 'draft-js/dist/Draft.css'; +import Image from './Image'; +import { Wrapper, MediaRow } from './style'; import MediaInput from '../mediaInput'; -import { LinkPreview, LinkPreviewLoading } from '../linkPreview'; -import { Wrapper, MediaRow } from '../editor/style'; import { ThreadDescription } from '../threadComposer/style'; -import Image, { ImageContainer, ActiveOverlay } from '../editor/Image'; - -const ImageComponent = props => { - const { - block, // eslint-disable-line no-unused-vars - theme, // eslint-disable-line no-unused-vars - blockProps, // eslint-disable-line no-unused-vars - customStyleMap, // eslint-disable-line no-unused-vars - customStyleFn, // eslint-disable-line no-unused-vars - decorator, // eslint-disable-line no-unused-vars - forceSelection, // eslint-disable-line no-unused-vars - offsetKey, // eslint-disable-line no-unused-vars - selection, // eslint-disable-line no-unused-vars - tree, // eslint-disable-line no-unused-vars - contentState, // eslint-disable-line no-unused-vars - ...elementProps - } = props; - const active = props.blockProps.isFocused; - const { src } = contentState.getEntity(block.getEntityAt(0)).getData(); - return ( - - - - - ); -}; +import { LinkPreview, LinkPreviewLoading } from '../linkPreview'; const toPlainText = editorState => editorState.getCurrentContent().getPlainText(); @@ -85,7 +60,7 @@ class Editor extends React.Component { const imagePlugin = createImagePlugin({ decorator, - imageComponent: ImageComponent, + imageComponent: Image, }); const singleLine = createSingleLinePlugin(); diff --git a/src/components/editor/style.js b/src/components/draftjs-editor/style.js similarity index 95% rename from src/components/editor/style.js rename to src/components/draftjs-editor/style.js index ece53f0654..75acdc06a5 100644 --- a/src/components/editor/style.js +++ b/src/components/draftjs-editor/style.js @@ -9,7 +9,7 @@ export const Wrapper = styled.div` export const MediaRow = styled.div` display: flex; - background: #F8FBFE; + background: #f8fbfe; border-top: 2px solid ${props => props.theme.border.default}; padding: 0 16px; margin-left: -24px; diff --git a/src/components/editor/Image.js b/src/components/editor/Image.js deleted file mode 100644 index 0fadb0cf42..0000000000 --- a/src/components/editor/Image.js +++ /dev/null @@ -1,50 +0,0 @@ -import styled, { css, keyframes } from 'styled-components'; - -const blinkBorder = keyframes` - 0% { - border-color: transparent; - } - 49% { - border-color: transparent; - } - 50% { - border-color: black; - } - 100% { - border-color: black; - } -`; - -export const ImageContainer = styled.div` - position: relative; - margin: 12px 0; - ${props => props.active && css` - border-radius: 8px; - overflow: hidden; - box-shadow: 0 4px 8px rgba(56, 24, 229, 0.2); - `} - - transition: all 0.1s ease-in-out; -`; - -export const ActiveOverlay = styled.span` - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - background: rgba(56, 24, 229, 0.1); - border-radius: 8px; - border: 1px solid ${props => props.theme.brand.default}; - opacity: ${props => (props.active ? 1 : 0)}; - transition: all 0.1s ease-in-out; -`; - -export default styled.img` - ${props => props.active && css` - /* Pretend like there's a cursor next to the image when active */ - box-shadow: inset -1px 0 0 #000; - animation: ${blinkBorder} 1s infinite; - `} - max-width: 100%; -`; diff --git a/src/components/editor/image-plugin.js b/src/components/editor/image-plugin.js deleted file mode 100644 index cd5a25c73e..0000000000 --- a/src/components/editor/image-plugin.js +++ /dev/null @@ -1,117 +0,0 @@ -// @flow -import React from 'react'; -import { Block } from 'slate'; -import Image, { ImageContainer, ActiveOverlay } from './Image'; - -const DEFAULT_BLOCK = { - type: 'paragraph', - isVoid: false, - data: {}, -}; - -const onDropNode = (e, data, state) => { - return state - .transform() - .deselect() - .removeNodeByKey(data.node.key) - .select(data.target) - .insertBlock(data.node) - .apply(); -}; - -const insertImage = (state, src, file) => { - return state - .transform() - .insertBlock({ - type: 'image', - isVoid: true, - data: { src, file }, - }) - .apply(); -}; - -const onDropOrPasteFiles = (e, data, state, editor) => { - data.files.forEach(file => { - const reader = new FileReader(); - const [type] = file.type.split('/'); - if (type !== 'image') return; - - reader.addEventListener('load', () => { - state = editor.getState(); - state = insertImage(state, reader.result, file); - editor.onChange(state); - }); - - reader.readAsDataURL(file); - }); -}; - -const ImagePlugin = () => ({ - insertImage: insertImage, - schema: { - nodes: { - image: props => { - const { node, state } = props; - const active = state.isFocused && state.selection.hasEdgeIn(node); - const src = node.data.get('src'); - return ( - - - - - ); - }, - paragraph: props => { - return

{props.children}

; - }, - }, - rules: [ - // Rule to insert a paragraph block if the document is empty. - { - match: node => { - return node.kind === 'document'; - }, - validate: document => { - return document.nodes.size ? null : true; - }, - normalize: (transform, document) => { - const block = Block.create(DEFAULT_BLOCK); - transform.insertNodeByKey(document.key, 0, block); - }, - }, - // Rule to insert a paragraph below a void node (the image) if that node is - // the last one in the document. - { - match: node => { - return node.kind === 'document'; - }, - validate: document => { - const lastNode = document.nodes.last(); - return lastNode && lastNode.isVoid ? true : null; - }, - normalize: (transform, document) => { - const block = Block.create(DEFAULT_BLOCK); - transform.insertNodeByKey(document.key, document.nodes.size, block); - }, - }, - ], - }, - onDrop: (e, data, state, editor) => { - switch (data.type) { - case 'files': - default: - return onDropOrPasteFiles(e, data, state, editor); - case 'node': - return onDropNode(e, data, state); - } - }, - onPaste: (e, data, state, editor) => { - switch (data.type) { - case 'files': - default: - return onDropOrPasteFiles(e, data, state, editor); - } - }, -}); - -export default ImagePlugin; diff --git a/src/components/editor/index.js b/src/components/editor/index.js deleted file mode 100644 index 50b019c17f..0000000000 --- a/src/components/editor/index.js +++ /dev/null @@ -1,153 +0,0 @@ -// @flow -import React, { Component } from 'react'; -//$FlowFixMe -import { Editor as SlateEditor, Raw, Plain } from 'slate'; -//$FlowFixMe -import type { SlatePlugin } from 'slate-mentions/src/types'; -//$FlowFixMe -import MarkdownPlugin from 'slate-markdown'; -import { Wrapper, MediaRow } from './style'; -import SingleLinePlugin from './single-line-plugin'; -import ImagePlugin from './image-plugin'; -import MediaInput from '../mediaInput'; -import { LinkPreview, LinkPreviewLoading } from '../linkPreview'; - -const ENTER = 13; - -const initialState = Plain.deserialize(''); - -type EditorProps = { - markdown?: boolean, - state?: Object, - onChange?: Function, - onEnter?: Function, - placeholder?: string, - singleLine?: boolean, - className?: string, - style?: Object, - images?: boolean, -}; - -class Editor extends Component { - props: EditorProps; - editor: any; - - state: { - state: Object, - plugins: Array, - }; - - constructor(props: EditorProps) { - super(props); - const imagePlugin = ImagePlugin(); - this.insertImage = imagePlugin.insertImage; - this.state = { - state: initialState, - plugins: [ - props.markdown !== false && MarkdownPlugin(), - props.images !== false && imagePlugin, - props.singleLine === true && SingleLinePlugin, - ], - }; - } - - // $FlowFixMe - addImage = e => { - const files = e.target.files; - const onChange = this.props.onChange || this.onChange; - const state = this.props.state || this.state.state; - // files is a FileList, not an array, so it doesn't have .reduce - let filesArray = []; - // eslint-disable-next-line - for (var i = 0, f; (f = files[i]); i++) { - filesArray.push(f); - } - // Add all the files to the state - const newState = filesArray.reduce( - (prevState, file) => - this.insertImage(prevState, window.URL.createObjectURL(file), file), - state - ); - onChange(newState); - }; - - onChange = (state: Object) => { - this.setState({ state }); - }; - - onKeyDown = (e: Object) => { - if (e.which === ENTER) { - this.props.onEnter && this.props.onEnter(e); - } - }; - - focus = () => { - this.editor.focus(); - }; - - render() { - const { - state = this.state.state, - onChange = this.onChange, - onEnter, - className, - style, - images, - showLinkPreview, - linkPreview, - focus, - ...rest - } = this.props; - - return ( - - { - this.editor = editor; - if (this.props.editorRef) this.props.editorRef(editor); - }} - {...rest} - /> - - {showLinkPreview && linkPreview && linkPreview.loading - ? - : showLinkPreview && linkPreview && linkPreview.data - ? - : null} - - {images !== false && - - - Add - - } - - ); - } -} - -const toJSON = (state: Object) => Raw.serialize(state, { terse: true }); -const toState = (json: Object) => Raw.deserialize(json, { terse: true }); - -const toPlainText = (state: Object) => Plain.serialize(state); -const fromPlainText = (string: string) => Plain.deserialize(string); - -export { toJSON, toState, toPlainText, fromPlainText }; - -export default Editor; diff --git a/src/components/editor/single-line-plugin.js b/src/components/editor/single-line-plugin.js deleted file mode 100644 index 60475d682c..0000000000 --- a/src/components/editor/single-line-plugin.js +++ /dev/null @@ -1,19 +0,0 @@ -// @flow -// Taken from https://github.com/ianstormtaylor/slate/issues/419 -// TODO: Make separate package -const SingleLinePlugin = { - schema: { - rules: [ - { - match: node => node.kind === 'document', - validate: node => (node.nodes.size > 1 ? true : null), - normalize: (transform, node, value) => { - const toRemove = node.nodes.slice(1); - toRemove.forEach(child => transform.removeNodeByKey(child.key)); - }, - }, - ], - }, -}; - -export default SingleLinePlugin; From c62c4924810ce9a6c6f8163dd514c105c865224f Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Fri, 21 Jul 2017 16:52:04 -0700 Subject: [PATCH 15/65] WIP: Slate -> Draft --- .../20170721225128-slate-to-draftjs.js | 51 +++++++++++++++++++ package.json | 1 + yarn.lock | 22 ++++++++ 3 files changed, 74 insertions(+) create mode 100644 iris/migrations/20170721225128-slate-to-draftjs.js diff --git a/iris/migrations/20170721225128-slate-to-draftjs.js b/iris/migrations/20170721225128-slate-to-draftjs.js new file mode 100644 index 0000000000..b9bf7d48fb --- /dev/null +++ b/iris/migrations/20170721225128-slate-to-draftjs.js @@ -0,0 +1,51 @@ +'use strict'; +const compose = require('redux/lib/compose').default; +const { stateFromMarkdown } = require('draft-js-import-markdown'); +const { toPlainText, toState } = require('../../shared/slate-utils'); + +const slateToDraft = compose( + JSON.stringify, + console.log.bind(console), + stateFromMarkdown, + console.log.bind(console), + toPlainText, + toState, + JSON.parse +); + +exports.up = function(r, conn) { + return ( + r + .table('threads') + .filter({ type: 'SLATE' }) + .run(conn) + .then(cursor => cursor.toArray()) + // Transform slate state to draftjs state + .then(threads => + threads.map(thread => + Object.assign({}, thread, { + type: 'DRAFTJS', + content: Object.assign({}, thread.content, { + body: slateToDraft(thread.content.body), + }), + }) + ) + ) + .then(() => { + throw new Error('fuck you motherfucker'); + }) + // Store the transformed threads + .then(threads => + Promise.all( + threads.map(thread => + r.table('threads').get(thread.id).update(thread).run(conn) + ) + ) + ) + ); +}; + +exports.down = function(r, conn) { + // Not spending any time undoing this + return Promise.resolve(); +}; diff --git a/package.json b/package.json index 02aaa8253a..e77075ab0b 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "draft-js-drag-n-drop-plugin": "2.0.0-rc2", "draft-js-focus-plugin": "2.0.0-rc2", "draft-js-image-plugin": "2.0.0-rc2", + "draft-js-import-markdown": "^0.2.1", "draft-js-markdown-shortcuts-plugin": "^0.3.0", "draft-js-plugins-editor": "2.0.0-rc2", "draft-js-single-line-plugin": "^2.0.1", diff --git a/yarn.lock b/yarn.lock index 68acd2ce54..3900316036 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2500,6 +2500,20 @@ draft-js-image-plugin@2.0.0-rc2: prop-types "^15.5.8" union-class-names "^1.0.0" +draft-js-import-element@^0.4.0: + version "0.4.3" + resolved "https://registry.yarnpkg.com/draft-js-import-element/-/draft-js-import-element-0.4.3.tgz#785381a2e0323a9836cfa856c442e9013bd482a4" + dependencies: + draft-js-utils "^0.1.5" + synthetic-dom "^0.2.0" + +draft-js-import-markdown@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/draft-js-import-markdown/-/draft-js-import-markdown-0.2.1.tgz#194fccd2df6dd0301a25e80b2c0614de02d09549" + dependencies: + draft-js-import-element "^0.4.0" + synthetic-dom "^0.2.0" + draft-js-markdown-shortcuts-plugin@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/draft-js-markdown-shortcuts-plugin/-/draft-js-markdown-shortcuts-plugin-0.3.0.tgz#f928cdff941d89a93e5ec2dd8f23df053c8f7ceb" @@ -2532,6 +2546,10 @@ draft-js-single-line-plugin@^2.0.1: dependencies: immutable "^3.8.1" +draft-js-utils@^0.1.5: + version "0.1.7" + resolved "https://registry.yarnpkg.com/draft-js-utils/-/draft-js-utils-0.1.7.tgz#e2b6927ca620edf1855a4bfc1cf1d21080a70f16" + draft-js@^0.10.1, draft-js@~0.10.0, draft-js@~0.10.1: version "0.10.1" resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.10.1.tgz#6f1219d8095729691429ca6fd7a58d2a8be5cb67" @@ -7977,6 +7995,10 @@ symbol-tree@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" +synthetic-dom@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/synthetic-dom/-/synthetic-dom-0.2.1.tgz#6c76fcf91ea9a5231318f8f415e27883d534df86" + table@^3.7.8: version "3.8.3" resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" From b4c1bf755239e0415725dc766dfc641ad5c56e28 Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Fri, 21 Jul 2017 17:08:32 -0700 Subject: [PATCH 16/65] Finish slate => draft migration script --- iris/migrations/20170721225128-slate-to-draftjs.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/iris/migrations/20170721225128-slate-to-draftjs.js b/iris/migrations/20170721225128-slate-to-draftjs.js index b9bf7d48fb..37ef5267a3 100644 --- a/iris/migrations/20170721225128-slate-to-draftjs.js +++ b/iris/migrations/20170721225128-slate-to-draftjs.js @@ -1,13 +1,13 @@ 'use strict'; const compose = require('redux/lib/compose').default; +const { convertToRaw } = require('draft-js'); const { stateFromMarkdown } = require('draft-js-import-markdown'); const { toPlainText, toState } = require('../../shared/slate-utils'); const slateToDraft = compose( JSON.stringify, - console.log.bind(console), + convertToRaw, stateFromMarkdown, - console.log.bind(console), toPlainText, toState, JSON.parse @@ -31,9 +31,6 @@ exports.up = function(r, conn) { }) ) ) - .then(() => { - throw new Error('fuck you motherfucker'); - }) // Store the transformed threads .then(threads => Promise.all( From 4268dd42a9f496a90d6c6f03fa2d71257aca83b3 Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Sat, 22 Jul 2017 13:30:49 -0700 Subject: [PATCH 17/65] Update draft-js-import-markdown --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e77075ab0b..7aecb1f35f 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "draft-js-drag-n-drop-plugin": "2.0.0-rc2", "draft-js-focus-plugin": "2.0.0-rc2", "draft-js-image-plugin": "2.0.0-rc2", - "draft-js-import-markdown": "^0.2.1", + "draft-js-import-markdown": "^0.2.3", "draft-js-markdown-shortcuts-plugin": "^0.3.0", "draft-js-plugins-editor": "2.0.0-rc2", "draft-js-single-line-plugin": "^2.0.1", diff --git a/yarn.lock b/yarn.lock index 3900316036..c416672b2b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2507,9 +2507,9 @@ draft-js-import-element@^0.4.0: draft-js-utils "^0.1.5" synthetic-dom "^0.2.0" -draft-js-import-markdown@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/draft-js-import-markdown/-/draft-js-import-markdown-0.2.1.tgz#194fccd2df6dd0301a25e80b2c0614de02d09549" +draft-js-import-markdown@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/draft-js-import-markdown/-/draft-js-import-markdown-0.2.3.tgz#f2db157a5209321f57e77763dc31ffd35f1a8b09" dependencies: draft-js-import-element "^0.4.0" synthetic-dom "^0.2.0" From 03193d436ad3efff89d9aef79946de298aaf18ed Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Sun, 23 Jul 2017 12:25:34 -0700 Subject: [PATCH 18/65] WIP convert plain text threads --- .../20170721225128-slate-to-draftjs.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/iris/migrations/20170721225128-slate-to-draftjs.js b/iris/migrations/20170721225128-slate-to-draftjs.js index 37ef5267a3..0a36622e1e 100644 --- a/iris/migrations/20170721225128-slate-to-draftjs.js +++ b/iris/migrations/20170721225128-slate-to-draftjs.js @@ -4,20 +4,15 @@ const { convertToRaw } = require('draft-js'); const { stateFromMarkdown } = require('draft-js-import-markdown'); const { toPlainText, toState } = require('../../shared/slate-utils'); -const slateToDraft = compose( - JSON.stringify, - convertToRaw, - stateFromMarkdown, - toPlainText, - toState, - JSON.parse -); +const plainToDraft = compose(JSON.stringify, convertToRaw, stateFromMarkdown); + +const slateToDraft = compose(plainToDraft, toPlainText, toState, JSON.parse); exports.up = function(r, conn) { return ( r .table('threads') - .filter({ type: 'SLATE' }) + .filter(thread => thread('type').ne('DRAFTJS')) .run(conn) .then(cursor => cursor.toArray()) // Transform slate state to draftjs state @@ -26,7 +21,10 @@ exports.up = function(r, conn) { Object.assign({}, thread, { type: 'DRAFTJS', content: Object.assign({}, thread.content, { - body: slateToDraft(thread.content.body), + body: + thread.type === 'SLATE' + ? slateToDraft(thread.content.body) + : plainToDraft(thread.content.body), }), }) ) From da2859b5fed8aaadc17956dfce000f6724fcfa32 Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Sun, 23 Jul 2017 12:35:07 -0700 Subject: [PATCH 19/65] Finish migration script from plain text to draftjs --- iris/migrations/20170721225128-slate-to-draftjs.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iris/migrations/20170721225128-slate-to-draftjs.js b/iris/migrations/20170721225128-slate-to-draftjs.js index 0a36622e1e..e664eaed56 100644 --- a/iris/migrations/20170721225128-slate-to-draftjs.js +++ b/iris/migrations/20170721225128-slate-to-draftjs.js @@ -12,7 +12,9 @@ exports.up = function(r, conn) { return ( r .table('threads') - .filter(thread => thread('type').ne('DRAFTJS')) + .filter(thread => + r.not(thread.hasFields('type')).or(thread('type').ne('DRAFTJS')) + ) .run(conn) .then(cursor => cursor.toArray()) // Transform slate state to draftjs state From 1901dc71e98e2df291c24953d00199520bf5a77b Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Tue, 25 Jul 2017 13:22:42 -0700 Subject: [PATCH 20/65] Move migration --- ...128-slate-to-draftjs.js => 20170725170443-slate-to-draftjs.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename iris/migrations/{20170721225128-slate-to-draftjs.js => 20170725170443-slate-to-draftjs.js} (100%) diff --git a/iris/migrations/20170721225128-slate-to-draftjs.js b/iris/migrations/20170725170443-slate-to-draftjs.js similarity index 100% rename from iris/migrations/20170721225128-slate-to-draftjs.js rename to iris/migrations/20170725170443-slate-to-draftjs.js From 3866ec796e68fc1793db2d0e2636903495a2ffae Mon Sep 17 00:00:00 2001 From: Max Stoiber Date: Tue, 25 Jul 2017 13:38:04 -0700 Subject: [PATCH 21/65] Implement editing --- src/views/thread/components/threadDetail.js | 137 +++++++------------- 1 file changed, 45 insertions(+), 92 deletions(-) diff --git a/src/views/thread/components/threadDetail.js b/src/views/thread/components/threadDetail.js index 98473191d3..6572a31339 100644 --- a/src/views/thread/components/threadDetail.js +++ b/src/views/thread/components/threadDetail.js @@ -54,8 +54,7 @@ import { class ThreadDetailPure extends Component { state: { isEditing: boolean, - viewBody: string, - editBody: string, + body: any, title: string, linkPreview: Object, linkPreviewTrueUrl: string, @@ -82,23 +81,9 @@ class ThreadDetailPure extends Component { data: JSON.parse(rawLinkPreview.data), }; - let viewBody = - thread.type === 'SLATE' - ? toPlainText(toState(JSON.parse(thread.content.body))) - : thread.content.body; - - if (thread.type === 'DRAFTJS') - viewBody = toState(JSON.parse(thread.content.body)); - - const editBody = - thread.type === 'SLATE' - ? toState(JSON.parse(thread.content.body)) - : thread.content.body; - this.state = { isEditing: false, - viewBody, - editBody, + body: toState(JSON.parse(thread.content.body)), title: thread.content.title, linkPreview: rawLinkPreview ? cleanLinkPreview.data : null, linkPreviewTrueUrl: @@ -211,7 +196,7 @@ class ThreadDetailPure extends Component { saveEdit = () => { const { dispatch, editThread, thread } = this.props; - const { linkPreview, linkPreviewTrueUrl, title, editBody } = this.state; + const { linkPreview, linkPreviewTrueUrl, title, body } = this.state; const threadId = thread.id; if (!title || title.length === 0) { @@ -237,27 +222,24 @@ class ThreadDetailPure extends Component { }); } - let bodyToSave = editBody; - if (thread.type === 'SLATE') { - bodyToSave = JSON.stringify(toJSON(bodyToSave)); - } - const content = { title, - body: bodyToSave, + body: JSON.stringify(toJSON(body)), }; // Get the images - const filesToUpload = editBody.document.nodes - .filter(node => node.type === 'image') - .map(image => image.getIn(['data', 'file'])) - .toJS(); + const filesToUpload = + [] || + body.document.nodes + .filter(node => node.type === 'image') + .map(image => image.getIn(['data', 'file'])) + .toJS(); const input = { threadId, content, attachments, - filesToUpload, + // filesToUpload, }; editThread(input) @@ -269,13 +251,6 @@ class ThreadDetailPure extends Component { if (editThread && editThread !== null) { this.toggleEdit(); dispatch(addToastWithTimeout('success', 'Thread saved!')); - - this.setState({ - viewBody: - thread.type === 'SLATE' - ? toPlainText(toState(JSON.parse(editThread.content.body))) - : editThread.content.body, - }); } else { dispatch( addToastWithTimeout( @@ -306,7 +281,7 @@ class ThreadDetailPure extends Component { changeBody = state => { this.setState({ - editBody: state, + body: state, }); }; @@ -385,7 +360,7 @@ class ThreadDetailPure extends Component { isEditing, linkPreview, linkPreviewTrueUrl, - viewBody, + body, fetchingLinkPreview, flyoutOpen, isSavingEdit, @@ -471,7 +446,6 @@ class ThreadDetailPure extends Component { /> } {thread.isCreator && - thread.type === 'SLATE' && } - {!isEditing && - - - {thread.content.title} - - {thread.modifiedAt && - - Edited {timeDifference(Date.now(), editedTimestamp)} - } -
- {thread.type !== 'DRAFTJS' - ? - {viewBody} - - : } -
- - {linkPreview && - !fetchingLinkPreview && - } -
} - - {isEditing && - -