From bfea94123cfa17ee29850a3b3323148b12211637 Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Tue, 17 Apr 2018 14:22:30 +0200 Subject: [PATCH 001/105] Render messages on mobile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Doesn't look like much yet, but this packs a punch—we now render some first messages on mobile! :tada: --- mobile/components/Message/index.js | 41 +++++++++++++++++++ mobile/components/Messages/index.js | 10 +++-- mobile/components/ThreadItem/Facepile.js | 9 +++- .../mentions-decorator/index.native.js | 16 ++++---- .../draft-js/message/renderer.native.js | 40 ++++++++++++++++++ .../clients/draft-js/message/renderer.web.js | 12 ++---- shared/clients/draft-js/message/types.js | 9 ++++ 7 files changed, 117 insertions(+), 20 deletions(-) create mode 100644 mobile/components/Message/index.js create mode 100644 shared/clients/draft-js/message/renderer.native.js create mode 100644 shared/clients/draft-js/message/types.js diff --git a/mobile/components/Message/index.js b/mobile/components/Message/index.js new file mode 100644 index 0000000000..c7e34570ed --- /dev/null +++ b/mobile/components/Message/index.js @@ -0,0 +1,41 @@ +// @flow +import React from 'react'; +import redraft from 'redraft'; +import Text from '../Text'; +import { messageRenderer } from '../../../shared/clients/draft-js/message/renderer.native'; +import type { MessageInfoType } from '../../../shared/graphql/fragments/message/messageInfo'; + +type Props = { + message: MessageInfoType, + type: string, +}; + +const Message = ({ message, type }: Props) => { + switch (type) { + case 'text': + return {message.content.body}; + // case 'media': { + // // don't apply imgix url params to optimistic image messages + // const src = props.id + // ? message.body + // : `${message.body}?max-w=${window.innerWidth * 0.6}`; + // if (typeof data.id === 'number' && data.id < 0) { + // return null; + // } + // return ; + // } + // case 'emoji': + // return {message.content.body}; + case 'draftjs': { + return ( + + {redraft(JSON.parse(message.content.body), messageRenderer)} + + ); + } + default: + return null; + } +}; + +export default Message; diff --git a/mobile/components/Messages/index.js b/mobile/components/Messages/index.js index 1cd1bc2cbd..33435a0d52 100644 --- a/mobile/components/Messages/index.js +++ b/mobile/components/Messages/index.js @@ -3,6 +3,7 @@ import React from 'react'; import { View } from 'react-native'; import ViewNetworkHandler from '../ViewNetworkHandler'; import Text from '../Text'; +import Message from '../Message'; import { sortAndGroupMessages } from '../../../shared/clients/group-messages'; import type { ThreadMessageConnectionType } from '../../../shared/graphql/fragments/thread/threadMessageConnection.js'; @@ -79,9 +80,12 @@ class Messages extends React.Component { {group.map(message => { return ( - - {message.content.body} - + // TODO(@mxstbr): Figure out message types + ); // return ( // { if (!participant) { return null; } - return ; + return ( + + ); }); }; diff --git a/shared/clients/draft-js/mentions-decorator/index.native.js b/shared/clients/draft-js/mentions-decorator/index.native.js index 27c4cea68e..be0f500c33 100644 --- a/shared/clients/draft-js/mentions-decorator/index.native.js +++ b/shared/clients/draft-js/mentions-decorator/index.native.js @@ -1,21 +1,23 @@ // @flow import React from 'react'; -import { withNavigation, type NavigationProps } from 'react-navigation'; +// TODO(@mxstbr): ReactNative throws an error if we try and import react-navigation here +// not sure why, but we need it to open user profiles on mobile when clicking on mentions +// import { withNavigation, type NavigationProps } from 'react-navigation'; import createMentionsDecorator, { type MentionComponentPropsType, } from './core'; import Anchor from '../../../../mobile/components/Anchor'; type Props = { - ...NavigationProps, - ...MentionComponentPropsType, + // ...NavigationProps, + ...$Exact, }; class Mention extends React.Component { openUser = () => { - this.props.navigation.navigate('Profile', { - username: this.props.username, - }); + // this.props.navigation.navigate('Profile', { + // username: this.props.username, + // }); }; render() { @@ -23,4 +25,4 @@ class Mention extends React.Component { } } -export default createMentionsDecorator(withNavigation(Mention)); +export default createMentionsDecorator(Mention); diff --git a/shared/clients/draft-js/message/renderer.native.js b/shared/clients/draft-js/message/renderer.native.js new file mode 100644 index 0000000000..398cb4d691 --- /dev/null +++ b/shared/clients/draft-js/message/renderer.native.js @@ -0,0 +1,40 @@ +// @flow +import React from 'react'; +import mentionsDecorator from '../mentions-decorator/index.native'; +// import linksDecorator from '../links-decorator/index.native'; +import Text from '../../../../mobile/components/Text'; +import Codeblock from '../../../../mobile/components/Codeblock'; +import type { Node } from 'react'; +import type { KeyObj, KeysObj } from './types'; + +const messageRenderer = { + inline: { + BOLD: (children: Array, { key }: KeyObj) => ( + + {children} + + ), + ITALIC: (children: Array, { key }: KeyObj) => ( + + {children} + + ), + CODE: (children: string, { key }: KeyObj) => ( + {children} + ), + }, + blocks: { + unstyled: (children: Array, { keys }: KeysObj) => + children.map((child, index) => ( + + {child} + + )), + 'code-block': (children: string, { keys }: KeysObj) => ( + {children} + ), + }, + decorators: [mentionsDecorator], +}; + +export { messageRenderer }; diff --git a/shared/clients/draft-js/message/renderer.web.js b/shared/clients/draft-js/message/renderer.web.js index ddadfa7366..7954c2e41d 100644 --- a/shared/clients/draft-js/message/renderer.web.js +++ b/shared/clients/draft-js/message/renderer.web.js @@ -4,10 +4,7 @@ import mentionsDecorator from '../mentions-decorator/index.web'; import linksDecorator from '../links-decorator/index.web'; import { Line, Paragraph } from 'src/components/message/style'; import type { Node } from 'react'; - -type KeyObj = { - key: string, -}; +import type { KeyObj, KeysObj } from './types'; const messageRenderer = { inline: { @@ -24,14 +21,11 @@ const messageRenderer = { ), }, blocks: { - unstyled: (children: Array, { keys }: { keys: Array }) => + unstyled: (children: Array, { keys }: KeysObj) => children.map((child, index) => ( {child} )), - 'code-block': ( - children: Array, - { keys }: { keys: Array } - ) => ( + 'code-block': (children: Array, { keys }: KeysObj) => ( {children.map((child, i) => [child,
])}
diff --git a/shared/clients/draft-js/message/types.js b/shared/clients/draft-js/message/types.js new file mode 100644 index 0000000000..b99fe68192 --- /dev/null +++ b/shared/clients/draft-js/message/types.js @@ -0,0 +1,9 @@ +// @flow + +export type KeyObj = { + key: string, +}; + +export type KeysObj = { + keys: string[], +}; From 59fb1e6252d799fc1ff5bffef301505985011cc8 Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Tue, 17 Apr 2018 14:55:36 +0200 Subject: [PATCH 002/105] Send messages from mobile --- mobile/components/ChatInput/index.js | 45 +++++++++++++++++++ mobile/views/Thread/index.js | 33 +++++++++++++- .../graphql/mutations/message/sendMessage.js | 2 +- 3 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 mobile/components/ChatInput/index.js diff --git a/mobile/components/ChatInput/index.js b/mobile/components/ChatInput/index.js new file mode 100644 index 0000000000..881a004496 --- /dev/null +++ b/mobile/components/ChatInput/index.js @@ -0,0 +1,45 @@ +// @flow +import React from 'react'; +import { TextInput, View, Button } from 'react-native'; + +type Props = { + onSubmit: (text: string) => void, +}; + +type State = { + value: string, +}; + +class ChatInput extends React.Component { + state = { + value: '', + }; + + onChangeText = (value: string) => { + this.setState({ value }); + }; + + submit = () => { + this.props.onSubmit(this.state.value); + this.onChangeText(''); + }; + + render() { + return ( + + + + + + + + + )} + {showLinkPreview && + linkPreview && + linkPreview.loading && ( + + )} + {showLinkPreview && + linkPreview && + linkPreview.data && ( + + )} + + ); + } else { + return ( +
+ + { + this.editor = editor; + if (editorRef) editorRef(editor); + }} + readOnly={readOnly} + placeholder={!readOnly && placeholder} + spellCheck={true} + autoCapitalize="sentences" + autoComplete="on" + autoCorrect="on" + stripPastedStyles={true} + decorators={[mentionsDecorator]} + customStyleMap={customStyleMap} + {...rest} + /> + + {showLinkPreview && + linkPreview && + linkPreview.loading && ( + + )} + {showLinkPreview && + linkPreview && + linkPreview.data && ( + + )} + {!readOnly && ( + + + Add + + + )} +
+ ); + } + } +} + +export default Editor; diff --git a/src/components/rich-text-editor/index.js b/src/components/rich-text-editor/index.js index e9fb98ae28..56dfe6e16c 100644 --- a/src/components/rich-text-editor/index.js +++ b/src/components/rich-text-editor/index.js @@ -1,356 +1,12 @@ // @flow import React from 'react'; -import DraftEditor, { composeDecorators } from 'draft-js-plugins-editor'; -import createImagePlugin from 'draft-js-image-plugin'; -import createFocusPlugin from 'draft-js-focus-plugin'; -import createBlockDndPlugin from 'draft-js-drag-n-drop-plugin'; -import createMarkdownPlugin from 'draft-js-markdown-plugin'; -import createEmbedPlugin from 'draft-js-embed-plugin'; -import createLinkifyPlugin from 'draft-js-linkify-plugin'; -import Prism from 'prismjs'; -import 'prismjs/components/prism-java'; -import 'prismjs/components/prism-scala'; -import 'prismjs/components/prism-go'; -import 'prismjs/components/prism-sql'; -import 'prismjs/components/prism-bash'; -import 'prismjs/components/prism-c'; -import 'prismjs/components/prism-cpp'; -import 'prismjs/components/prism-kotlin'; -import 'prismjs/components/prism-perl'; -import 'prismjs/components/prism-ruby'; -import 'prismjs/components/prism-swift'; -import createPrismPlugin from 'draft-js-prism-plugin'; -import createCodeEditorPlugin from 'draft-js-code-editor-plugin'; -import OutsideClickHandler from '../outsideClickHandler'; -import Icon from '../icons'; -import { IconButton } from '../buttons'; -import mentionsDecorator from 'shared/clients/draft-js/mentions-decorator/index.web.js'; -import { renderLanguageSelect } from './LanguageSelect'; +import Loadable from 'react-loadable'; +import { Loading } from '../loading'; -import Image from './Image'; -import Embed, { addEmbed, parseEmbedUrl } from './Embed'; -import MediaInput from '../mediaInput'; -import SideToolbar from './toolbar'; -import { - Wrapper, - MediaRow, - ComposerBase, - Expander, - Action, - EmbedUI, - customStyleMap, -} from './style'; -import { LinkPreview, LinkPreviewLoading } from '../linkPreview'; +/* prettier-ignore */ +const RichTextEditor = Loadable({ + loader: () => import('./container'/* webpackChunkName: "RichTextEditor" */), + loading: ({ isLoading }) => isLoading && , +}); -type Props = { - state: Object, - onChange: Function, - showLinkPreview?: boolean, - linkPreview?: Object, - focus?: boolean, - readOnly?: boolean, - editorRef?: any => void, - placeholder?: string, - className?: string, - style?: Object, - version?: 2, -}; - -type State = { - plugins: Array, - addEmbed: (Object, string) => mixed, - addImage: (Object, string, ?Object) => mixed, - inserting: boolean, - embedding: boolean, - embedUrl: string, -}; - -class Editor extends React.Component { - editor: any; - - constructor(props: Props) { - super(props); - - const pluginState = this.getPluginState(props); - - this.state = { - ...pluginState, - inserting: false, - embedding: false, - embedUrl: '', - }; - } - - componentDidUpdate(prev: Props) { - if (prev.readOnly !== this.props.readOnly) { - this.setState({ - ...this.getPluginState(this.props), - }); - } - } - - getPluginState = (props: Props) => { - const focusPlugin = createFocusPlugin(); - const dndPlugin = createBlockDndPlugin(); - const linkifyPlugin = createLinkifyPlugin({ - target: '_blank', - }); - const embedPlugin = createEmbedPlugin({ - EmbedComponent: Embed, - }); - const prismPlugin = createPrismPlugin({ - prism: Prism, - }); - const codePlugin = createCodeEditorPlugin(); - - const decorator = composeDecorators( - focusPlugin.decorator, - dndPlugin.decorator - ); - - const imagePlugin = createImagePlugin({ - decorator, - imageComponent: Image, - }); - - return { - plugins: [ - imagePlugin, - prismPlugin, - embedPlugin, - createMarkdownPlugin({ - renderLanguageSelect: props.readOnly - ? () => null - : renderLanguageSelect, - }), - codePlugin, - linkifyPlugin, - dndPlugin, - focusPlugin, - ], - addImage: imagePlugin.addImage, - addEmbed: addEmbed, - }; - }; - - changeEmbedUrl = (evt: SyntheticInputEvent) => { - this.setState({ - embedUrl: evt.target.value, - }); - }; - - addEmbed = (evt: ?SyntheticUIEvent<>) => { - evt && evt.preventDefault(); - - const { state, onChange } = this.props; - onChange(this.state.addEmbed(state, parseEmbedUrl(this.state.embedUrl))); - this.closeToolbar(); - }; - - addImages = (files: FileList) => { - const { addImage } = this.state; - const { state, onChange } = this.props; - // Add images to editorState - // eslint-disable-next-line - for (var i = 0, file; (file = files[i]); i++) { - onChange(addImage(state, window.URL.createObjectURL(file), { file })); - } - }; - - addImage = (e: SyntheticInputEvent) => { - this.addImages(e.target.files); - this.closeToolbar(); - }; - - handleDroppedFiles = (_: any, files: FileList) => { - this.addImages(files); - }; - - toggleToolbarDisplayState = () => { - const { inserting } = this.state; - - this.setState({ - inserting: !inserting, - embedding: false, - }); - }; - - closeToolbar = () => { - this.setState({ - embedUrl: '', - embedding: false, - inserting: false, - }); - }; - - toggleEmbedInputState = () => { - const { embedding } = this.state; - - this.setState({ - embedding: !embedding, - inserting: false, - }); - }; - - render() { - const { - state, - onChange, - className, - style, - editorRef, - showLinkPreview, - linkPreview, - focus, - version, - placeholder, - readOnly, - ...rest - } = this.props; - const { embedding, inserting } = this.state; - - if (version === 2) { - return ( - - { - this.editor = editor; - if (editorRef) editorRef(editor); - }} - readOnly={readOnly} - placeholder={!readOnly && placeholder} - spellCheck={true} - autoCapitalize="sentences" - autoComplete="on" - autoCorrect="on" - stripPastedStyles={true} - decorators={[mentionsDecorator]} - customStyleMap={customStyleMap} - {...rest} - /> - {!readOnly && ( - - - - - - - - - - - - - - - - - )} - {showLinkPreview && - linkPreview && - linkPreview.loading && ( - - )} - {showLinkPreview && - linkPreview && - linkPreview.data && ( - - )} - - ); - } else { - return ( -
- - { - this.editor = editor; - if (editorRef) editorRef(editor); - }} - readOnly={readOnly} - placeholder={!readOnly && placeholder} - spellCheck={true} - autoCapitalize="sentences" - autoComplete="on" - autoCorrect="on" - stripPastedStyles={true} - decorators={[mentionsDecorator]} - customStyleMap={customStyleMap} - {...rest} - /> - - {showLinkPreview && - linkPreview && - linkPreview.loading && ( - - )} - {showLinkPreview && - linkPreview && - linkPreview.data && ( - - )} - {!readOnly && ( - - - Add - - - )} -
- ); - } - } -} - -export default Editor; +export default RichTextEditor; From 9c444935fc59bec89d1a9477cccd2b2db84150d2 Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Sat, 21 Apr 2018 18:16:51 +0200 Subject: [PATCH 032/105] Fix thread loading indicator --- src/views/thread/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/thread/index.js b/src/views/thread/index.js index 9bd8196ca8..207c707841 100644 --- a/src/views/thread/index.js +++ b/src/views/thread/index.js @@ -6,7 +6,7 @@ import LoadingThread from './components/loading'; /* prettier-ignore */ const Thread = Loadable({ loader: () => import('./container'/* webpackChunkName: "Thread" */), - loading: ({ isLoading }) => isLoading && , + loading: ({ isLoading }) => isLoading && , }); export default Thread; From fdf6613d4ae743f614916e62667eb8ceda3e7c80 Mon Sep 17 00:00:00 2001 From: gdad-s-river Date: Fri, 20 Apr 2018 21:07:37 +0530 Subject: [PATCH 033/105] fix for #2917 + https://github.com/withspectrum/spectrum/issues/2917#issuecomment-382850059 1. Typo correction (Uppercase to Lowercase) of package name in package.json 2. Wrapped Flyout and Thread Settings Icon Button with React Popper to handle cutting out of dropdown due to scrollable container; removed unnecessary styles to complement this change. 3. Wrapped Flyout with OutsideClickHandler to handle click outside of it (and handle its closing), instead of setting a transparent div over the whole page to handle closing of the dropdown. --- package.json | 4 +- src/components/flyout/index.js | 2 +- src/views/thread/components/actionBar.js | 286 +++++++++++++---------- src/views/thread/style.js | 11 +- 4 files changed, 171 insertions(+), 132 deletions(-) diff --git a/package.json b/package.json index 80d80321eb..8f4555b0a6 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "Spectrum", + "name": "spectrum", "version": "2.1.6", "license": "BSD-3-Clause", "devDependencies": { @@ -261,4 +261,4 @@ ] }, "pre-commit": "lint:staged" -} +} \ No newline at end of file diff --git a/src/components/flyout/index.js b/src/components/flyout/index.js index f04c3f6386..25c530bf1d 100644 --- a/src/components/flyout/index.js +++ b/src/components/flyout/index.js @@ -14,7 +14,7 @@ const StyledFlyout = styled(FlexRow)` top: 36px; z-index: ${zIndex.flyout}; color: ${({ theme }) => theme.text.default}; - transition: ${Transition.dropdown.off}; + ${props => props.style}; `; const StyledRow = styled(FlexCol)` diff --git a/src/views/thread/components/actionBar.js b/src/views/thread/components/actionBar.js index 3949da36ef..2ffceee7f0 100644 --- a/src/views/thread/components/actionBar.js +++ b/src/views/thread/components/actionBar.js @@ -2,6 +2,7 @@ import * as React from 'react'; import { connect } from 'react-redux'; import Clipboard from 'react-clipboard.js'; +import { Manager, Reference, Popper } from 'react-popper'; import { addToastWithTimeout } from '../../../actions/toasts'; import { openModal } from '../../../actions/modals'; import Icon from '../../../components/icons'; @@ -11,6 +12,8 @@ import Flyout from '../../../components/flyout'; import { track } from '../../../helpers/events'; import type { GetThreadType } from 'shared/graphql/queries/thread/getThread'; import toggleThreadNotificationsMutation from 'shared/graphql/mutations/thread/toggleThreadNotifications'; +import OutsideClickHandler from '../../../components/outsideClickHandler'; + import { FollowButton, ShareButtons, @@ -42,11 +45,19 @@ type Props = { type State = { notificationStateLoading: boolean, flyoutOpen: boolean, + isSettingsBtnHovering: boolean, }; class ActionBar extends React.Component { state = { notificationStateLoading: false, flyoutOpen: false, + isSettingsBtnHovering: false, + }; + + toggleHover = () => { + this.setState(({ isSettingsBtnHovering }) => ({ + isSettingsBtnHovering: !isSettingsBtnHovering, + })); }; toggleFlyout = val => { @@ -216,7 +227,11 @@ class ActionBar extends React.Component { isLockingThread, isPinningThread, } = this.props; - const { notificationStateLoading, flyoutOpen } = this.state; + const { + notificationStateLoading, + flyoutOpen, + isSettingsBtnHovering, + } = this.state; const isPinned = thread.community.pinnedThreadId === thread.id; const shouldRenderActionsDropdown = this.shouldRenderActionsDropdown(); @@ -350,129 +365,162 @@ class ActionBar extends React.Component {
{shouldRenderActionsDropdown && ( - - - - - - {thread.receiveNotifications ? 'Subscribed' : 'Notify me'} - - - - {shouldRenderEditThreadAction && ( - - - - - - )} - - {shouldRenderPinThreadAction && ( - - - - - - )} - - {shouldRenderMoveThreadAction && ( - - - Move thread - - - )} - - {shouldRenderLockThreadAction && ( - - - - - - )} - - {shouldRenderDeleteThreadAction && ( - - + + + {({ ref }) => { + return ( + + ); + }} + + {(isSettingsBtnHovering || flyoutOpen) && ( + + - - - + {({ style, ref, placement }) => { + return ( + { + flyoutOpen && this.toggleFlyout(); + this.toggleHover(); + }} + > + + + {thread.receiveNotifications + ? 'Subscribed' + : 'Notify me'} + + + + {shouldRenderEditThreadAction && ( + + + + + + )} + + {shouldRenderPinThreadAction && ( + + + + + + )} + + {shouldRenderMoveThreadAction && ( + + + Move thread + + + )} + + {shouldRenderLockThreadAction && ( + + + + + + )} + + {shouldRenderDeleteThreadAction && ( + + + + + + )} + + ); + }} + + )} - + )}
- {flyoutOpen && ( -
- setTimeout(() => { - this.toggleFlyout(false); - }) - } - /> - )} ); } diff --git a/src/views/thread/style.js b/src/views/thread/style.js index f0232b7fac..2a2121ebfb 100644 --- a/src/views/thread/style.js +++ b/src/views/thread/style.js @@ -184,17 +184,8 @@ export const DropWrap = styled(FlexCol)` } .flyout { - display: none; position: absolute; - top: 100%; - right: 0; - transition: ${Transition.hover.off}; - } - - &:hover .flyout, - &.open > .flyout { - display: inline-block; - transition: ${Transition.hover.on}; + right: auto; } `; From 5cb6483cdba2eaf57e334d5328e4dafb4749a375 Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Sun, 22 Apr 2018 11:46:50 +0200 Subject: [PATCH 034/105] Add commons chunk for frequently used modules This deduplicates frequently used modules --- config-overrides.js | 12 ++ package.json | 4 +- yarn.lock | 307 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 303 insertions(+), 20 deletions(-) diff --git a/config-overrides.js b/config-overrides.js index 2d0fe39e08..37377c90de 100644 --- a/config-overrides.js +++ b/config-overrides.js @@ -16,6 +16,7 @@ const WriteFilePlugin = require('write-file-webpack-plugin'); const { ReactLoadablePlugin } = require('react-loadable/webpack'); const OfflinePlugin = require('offline-plugin'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); +const BundleBuddyWebpackPlugin = require('bundle-buddy-webpack-plugin'); // Recursively walk a folder and get all file paths function walkFolder(currentDirPath, callback) { @@ -147,6 +148,9 @@ module.exports = function override(config, env) { }) ); } + if (process.env.BUNDLE_BUDDY === 'true') { + config.plugins.push(new BundleBuddyWebpackPlugin()); + } if (process.env.NODE_ENV === 'development') { config.plugins.push( WriteFilePlugin({ @@ -155,5 +159,13 @@ module.exports = function override(config, env) { }) ); } + config.plugins.push( + new webpack.optimize.CommonsChunkPlugin({ + minChunks: 3, + name: 'main', + async: 'commons', + children: true, + }) + ); return rewireStyledComponents(config, env, { ssr: true }); }; diff --git a/package.json b/package.json index 0615ef7ef2..6c25e2a574 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "babel-plugin-transform-object-rest-spread": "^6.23.0", "babel-preset-env": "^1.5.2", "backpack-core": "^0.4.1", + "bundle-buddy-webpack-plugin": "^0.3.0", "cheerio": "^1.0.0-rc.2", "cross-env": "^5.0.5", "cypress": "^2.1.0", @@ -253,7 +254,8 @@ "db:reset": "yarn run db:drop && yarn run db:migrate && yarn run db:seed", "rethinkdb:migrate": "rethinkdb-migrate -f ./api/migrations/config.js", "eject": "react-scripts eject", - "lint:staged": "lint-staged" + "lint:staged": "lint-staged", + "webpack-defaults": "webpack-defaults" }, "lint-staged": { "*.js": [ diff --git a/yarn.lock b/yarn.lock index 6746b0f0f0..bad93f9fa7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -572,12 +572,21 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" -ansi-styles@^3.1.0, ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.0.0, ansi-styles@^3.1.0, ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" dependencies: color-convert "^1.9.0" +ansy@^1.0.0: + version "1.0.13" + resolved "https://registry.yarnpkg.com/ansy/-/ansy-1.0.13.tgz#972cbd54c525112f36814fdefe26269ef993810f" + dependencies: + ansi-styles "^3.0.0" + custom-return "^1.0.0" + supports-color "^3.1.2" + ul "^5.2.1" + anymatch@^1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" @@ -875,6 +884,24 @@ asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" +asciify-pixel-matrix@^1.0.8: + version "1.0.12" + resolved "https://registry.yarnpkg.com/asciify-pixel-matrix/-/asciify-pixel-matrix-1.0.12.tgz#8a7d36cf37757861af0c8c218044f7ecab6ad278" + dependencies: + asciify-pixel "^1.0.0" + ul "^5.2.1" + +asciify-pixel@^1.0.0: + version "1.2.12" + resolved "https://registry.yarnpkg.com/asciify-pixel/-/asciify-pixel-1.2.12.tgz#08adbc5bd09a48da4bada726ccb6d46ade9ae8c9" + dependencies: + couleurs "^6.0.0" + deffy "^2.2.1" + pixel-bg "^1.0.0" + pixel-class "^1.0.0" + pixel-white-bg "^1.0.0" + ul "^5.2.1" + asn1.js@^4.0.0: version "4.10.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" @@ -2216,6 +2243,27 @@ bull@3.3.10: semver "^5.4.1" uuid "^3.1.0" +bundle-buddy-webpack-plugin@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/bundle-buddy-webpack-plugin/-/bundle-buddy-webpack-plugin-0.3.0.tgz#31c596950817eca9221ed6420ac4567c9c0392f3" + dependencies: + asciify-pixel-matrix "^1.0.8" + bundle-buddy "^0.1.2" + chalk "^2.0.1" + webpack-defaults "^1.5.0" + +bundle-buddy@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/bundle-buddy/-/bundle-buddy-0.1.2.tgz#211b74ee609eb0ddb65ade42fde36f13bfdcdb1e" + dependencies: + chalk "^2.0.1" + globby "^6.1.0" + http-server "^0.10.0" + meow "^3.7.0" + openport "^0.0.4" + opn "^5.1.0" + source-map "^0.5.6" + busboy@^0.2.14: version "0.2.14" resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" @@ -2237,6 +2285,18 @@ bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" +cac@^3.0.3: + version "3.0.4" + resolved "https://registry.yarnpkg.com/cac/-/cac-3.0.4.tgz#6d24ceec372efe5c9b798808bc7f49b47242a4ef" + dependencies: + camelcase-keys "^3.0.0" + chalk "^1.1.3" + indent-string "^3.0.0" + minimist "^1.2.0" + read-pkg-up "^1.0.1" + suffix "^0.1.0" + text-table "^0.2.0" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -2285,6 +2345,13 @@ camelcase-keys@^2.0.0: camelcase "^2.0.0" map-obj "^1.0.0" +camelcase-keys@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-3.0.0.tgz#fc0c6c360363f7377e3793b9a16bccf1070c1ca4" + dependencies: + camelcase "^3.0.0" + map-obj "^1.0.0" + camelcase@4.1.0, camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" @@ -2645,7 +2712,7 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^1.3.0, color-convert@^1.9.0: +color-convert@^1.0.0, color-convert@^1.3.0, color-convert@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" dependencies: @@ -2685,14 +2752,14 @@ colors@0.6.x, colors@0.x.x, colors@~0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc" +colors@1.0.3, colors@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" + colors@^1.1.2: version "1.2.1" resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794" -colors@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" - colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" @@ -2881,6 +2948,10 @@ cors@^2.8.3: object-assign "^4" vary "^1" +corser@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87" + cosmiconfig@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-1.1.0.tgz#0dea0f9804efdfb929fbb1b188e25553ea053d37" @@ -2906,6 +2977,25 @@ cosmiconfig@^2.1.0, cosmiconfig@^2.1.1: parse-json "^2.2.0" require-from-string "^1.1.0" +couleurs@^6.0.0: + version "6.0.9" + resolved "https://registry.yarnpkg.com/couleurs/-/couleurs-6.0.9.tgz#b2b2a3ee37dae51875c9efd243ec7e7894afbc9e" + dependencies: + ansy "^1.0.0" + color-convert "^1.0.0" + iterate-object "^1.3.1" + typpy "^2.3.1" + +cp-file@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/cp-file/-/cp-file-4.2.0.tgz#715361663b71ede0b6dddbc3c80e2ba02e725ec3" + dependencies: + graceful-fs "^4.1.2" + make-dir "^1.0.0" + nested-error-stacks "^2.0.0" + pify "^2.3.0" + safe-buffer "^5.0.1" + crc@3.4.4: version "3.4.4" resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b" @@ -2987,6 +3077,13 @@ cross-spawn@5.1.0, cross-spawn@^5.0.1, cross-spawn@^5.1.0: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" + dependencies: + lru-cache "^4.0.1" + which "^1.2.9" + crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" @@ -3153,6 +3250,12 @@ curry2@^1.0.0: dependencies: fast-bind "^1.0.0" +custom-return@^1.0.0: + version "1.0.10" + resolved "https://registry.yarnpkg.com/custom-return/-/custom-return-1.0.10.tgz#ba875b2a97c9fba1fc12729ce1c21eeaa5de6a0c" + dependencies: + noop6 "^1.0.0" + cycle@1.0.x: version "1.0.3" resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" @@ -3394,6 +3497,12 @@ default-require-extensions@^1.0.0: dependencies: strip-bom "^2.0.0" +deffy@^2.2.1, deffy@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/deffy/-/deffy-2.2.2.tgz#088f40913cb47078653fa6f697c206e03471d523" + dependencies: + typpy "^2.0.0" + define-properties@^1.1.1, define-properties@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" @@ -3865,6 +3974,15 @@ ecdsa-sig-formatter@1.0.9: base64url "^2.0.0" safe-buffer "^5.0.1" +ecstatic@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ecstatic/-/ecstatic-2.2.1.tgz#b5087fad439dd9dd49d31e18131454817fe87769" + dependencies: + he "^1.1.1" + mime "^1.2.11" + minimist "^1.1.0" + url-join "^2.0.2" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -4377,6 +4495,10 @@ eventemitter3@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" +eventemitter3@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.0.1.tgz#4ce66c3fc5b5a6b9f2245e359e1938f1ab10f960" + events@^1.0.0, events@^1.1.0, events@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -4853,7 +4975,7 @@ flow-typed@^2.1.5: which "^1.3.0" yargs "^4.2.0" -follow-redirects@^1.2.5: +follow-redirects@^1.0.0, follow-redirects@^1.2.5: version "1.4.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.4.1.tgz#d8120f4518190f55aac65bb6fc7b85fcd666d6aa" dependencies: @@ -5039,6 +5161,12 @@ function-bind@^1.0.2, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" +function.name@^1.0.3: + version "1.0.10" + resolved "https://registry.yarnpkg.com/function.name/-/function.name-1.0.10.tgz#ed00c828d98ff9a80186926fd439bdd7c16d4f0d" + dependencies: + noop6 "^1.0.1" + functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" @@ -5510,7 +5638,7 @@ hawk@~6.0.2: hoek "4.x.x" sntp "2.x.x" -he@1.1.x: +he@1.1.x, he@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" @@ -5741,6 +5869,27 @@ http-proxy@^1.16.2: eventemitter3 "1.x.x" requires-port "1.x.x" +http-proxy@^1.8.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" + dependencies: + eventemitter3 "^3.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +http-server@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/http-server/-/http-server-0.10.0.tgz#b2a446b16a9db87ed3c622ba9beb1b085b1234a7" + dependencies: + colors "1.0.3" + corser "~2.0.0" + ecstatic "^2.0.0" + http-proxy "^1.8.1" + opener "~1.4.0" + optimist "0.6.x" + portfinder "^1.0.13" + union "~0.4.3" + http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -6456,6 +6605,10 @@ iterall@^1.1.3, iterall@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" +iterate-object@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/iterate-object/-/iterate-object-1.3.2.tgz#24ec15affa5d0039e8839695a21c2cae1f45b66b" + jest-changed-files@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-22.4.3.tgz#8882181e022c38bd46a2e4d18d44d19d90a90fb2" @@ -6745,7 +6898,7 @@ js-tokens@^3.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" -js-yaml@^3.4.3, js-yaml@^3.7.0, js-yaml@^3.9.1: +js-yaml@^3.4.3, js-yaml@^3.7.0, js-yaml@^3.8.2, js-yaml@^3.9.1: version "3.11.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" dependencies: @@ -7693,6 +7846,24 @@ moment@2.x.x, "moment@>= 2.9.0", moment@^2.11.2, moment@^2.15.2, moment@^2.18.1, version "2.22.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.0.tgz#7921ade01017dd45186e7fee5f424f0b8663a730" +mrm-core@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mrm-core/-/mrm-core-1.1.0.tgz#97df4be3bc208d623519d6a18f18407ef79aa226" + dependencies: + babel-code-frame "^6.22.0" + chalk "^1.1.3" + cp-file "^4.1.1" + js-yaml "^3.8.2" + lodash "^4.17.4" + mkdirp "^0.5.1" + prop-ini "^0.0.2" + readme-badger "^0.1.2" + split-lines "^1.1.0" + strip-bom "^3.0.0" + strip-json-comments "^2.0.1" + webpack-merge "^4.0.0" + yarn-install "^0.2.1" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -7761,6 +7932,12 @@ neo-async@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.0.tgz#76b1c823130cca26acfbaccc8fbaf0a2fa33b18f" +nested-error-stacks@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.0.0.tgz#98b2ffaefb4610fa3936f1e71435d30700de2840" + dependencies: + inherits "~2.0.1" + next-tick@1: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" @@ -7870,6 +8047,10 @@ nodemon@^1.11.0: undefsafe "^2.0.2" update-notifier "^2.3.0" +noop6@^1.0.0, noop6@^1.0.1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/noop6/-/noop6-1.0.7.tgz#96767bf2058ba59ca8cb91559347ddc80239fa8e" + nopt@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" @@ -8077,10 +8258,14 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -opener@^1.4.3: +opener@^1.4.3, opener@~1.4.0: version "1.4.3" resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8" +openport@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/openport/-/openport-0.0.4.tgz#1d6715d8a8789695f985fa84f68dd4cd1ba426cb" + opn@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.1.0.tgz#72ce2306a17dbea58ff1041853352b4a8fc77519" @@ -8121,7 +8306,7 @@ optimist@0.6.0: minimist "~0.0.1" wordwrap "~0.0.2" -optimist@^0.6.1, optimist@~0.6.0: +optimist@0.6.x, optimist@^0.6.1, optimist@~0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" dependencies: @@ -8473,7 +8658,7 @@ performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" -pify@^2.0.0: +pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -8495,6 +8680,24 @@ pinpoint@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pinpoint/-/pinpoint-1.1.0.tgz#0cf7757a6977f1bf7f6a32207b709e377388e874" +pixel-bg@^1.0.0: + version "1.0.8" + resolved "https://registry.yarnpkg.com/pixel-bg/-/pixel-bg-1.0.8.tgz#24292649c50566558fbf030890f6919e14366c58" + dependencies: + pixel-class "^1.0.0" + +pixel-class@^1.0.0: + version "1.0.7" + resolved "https://registry.yarnpkg.com/pixel-class/-/pixel-class-1.0.7.tgz#904a858f1d4a0cc032d461a1e47e7bc0ec7def23" + dependencies: + deffy "^2.2.1" + +pixel-white-bg@^1.0.0: + version "1.0.7" + resolved "https://registry.yarnpkg.com/pixel-white-bg/-/pixel-white-bg-1.0.7.tgz#0cfbc5b385e4cd8e5da33662f896bc9958399587" + dependencies: + pixel-bg "^1.0.0" + pkg-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" @@ -8535,7 +8738,7 @@ popper.js@^1.14.1: version "1.14.3" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" -portfinder@^1.0.9: +portfinder@^1.0.13, portfinder@^1.0.9: version "1.0.13" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" dependencies: @@ -8948,6 +9151,12 @@ prompt@0.2.14: utile "0.2.x" winston "0.8.x" +prop-ini@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/prop-ini/-/prop-ini-0.0.2.tgz#6733a7cb5242acab2be42e607583d8124b172a5b" + dependencies: + extend "^3.0.0" + prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0: version "15.6.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" @@ -9025,6 +9234,10 @@ qs@6.5.1, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" +qs@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-2.3.3.tgz#e9e85adbe75da0bbe4c8e0476a086290f863b404" + qs@~6.0.4: version "6.0.4" resolved "https://registry.yarnpkg.com/qs/-/qs-6.0.4.tgz#51019d84720c939b82737e84556a782338ecea7b" @@ -9514,6 +9727,10 @@ readline-sync@^1.4.7: version "1.4.9" resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.9.tgz#3eda8e65f23cd2a17e61301b1f0003396af5ecda" +readme-badger@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/readme-badger/-/readme-badger-0.1.2.tgz#81b138df9723c733df6a27c7bd9caebd383e08a5" + realpath-native@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.0.tgz#7885721a83b43bd5327609f0ddecb2482305fdf0" @@ -9802,7 +10019,7 @@ require-uncached@^1.0.3: caller-path "^0.1.0" resolve-from "^1.0.0" -requires-port@1.0.x, requires-port@1.x.x, requires-port@~1.0.0: +requires-port@1.0.x, requires-port@1.x.x, requires-port@^1.0.0, requires-port@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -10429,6 +10646,10 @@ spdy@^3.4.1: select-hose "^2.0.0" spdy-transport "^2.0.18" +split-lines@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/split-lines/-/split-lines-1.1.0.tgz#3abba8f598614142f9db8d27ab6ab875662a1e09" + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -10625,14 +10846,14 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" +strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + strip-json-comments@~0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-0.1.3.tgz#164c64e370a8a3cc00c9e01b539e569823f0ee54" -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - stripe@^4.15.0: version "4.25.0" resolved "https://registry.yarnpkg.com/stripe/-/stripe-4.25.0.tgz#16af99c255e4fe22adbaf629f392af0715370760" @@ -10701,6 +10922,10 @@ subscriptions-transport-ws@0.9.x: symbol-observable "^1.0.4" ws "^3.0.0" +suffix@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/suffix/-/suffix-0.1.1.tgz#cc58231646a0ef1102f79478ef3a9248fd9c842f" + supports-color@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.1.0.tgz#058a021d1b619f7ddf3980d712ea3590ce7de3d5" @@ -10886,7 +11111,7 @@ test-exclude@^4.2.1: read-pkg-up "^1.0.1" require-main-filename "^1.0.1" -text-table@0.2.0, text-table@~0.2.0: +text-table@0.2.0, text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -11081,6 +11306,12 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" +typpy@^2.0.0, typpy@^2.3.1, typpy@^2.3.4: + version "2.3.10" + resolved "https://registry.yarnpkg.com/typpy/-/typpy-2.3.10.tgz#63a39e4171cbbb4cdefb590009228a3de9a22b2f" + dependencies: + function.name "^1.0.3" + ua-parser-js@^0.7.9: version "0.7.17" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" @@ -11138,6 +11369,13 @@ uid2@0.0.x: version "0.0.3" resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" +ul@^5.2.1: + version "5.2.13" + resolved "https://registry.yarnpkg.com/ul/-/ul-5.2.13.tgz#9ff0504ea35ca1f74c0bf59e6480def009bad7b5" + dependencies: + deffy "^2.2.2" + typpy "^2.3.4" + ultron@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" @@ -11161,6 +11399,12 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^0.4.3" +union@~0.4.3: + version "0.4.6" + resolved "https://registry.yarnpkg.com/union/-/union-0.4.6.tgz#198fbdaeba254e788b0efcb630bc11f24a2959e0" + dependencies: + qs "~2.3.3" + uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" @@ -11277,6 +11521,10 @@ urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" +url-join@^2.0.2: + version "2.0.5" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.5.tgz#5af22f18c052a000a48d7b82c5e9c2e2feeda728" + url-loader@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.6.2.tgz#a007a7109620e9d988d14bce677a1decb9a993f7" @@ -11536,6 +11784,13 @@ webpack-bundle-analyzer@^2.9.1: opener "^1.4.3" ws "^4.0.0" +webpack-defaults@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/webpack-defaults/-/webpack-defaults-1.6.0.tgz#0eb33b36860e3bafbf035f78ca06139f658b3dda" + dependencies: + chalk "^1.1.3" + mrm-core "^1.1.0" + webpack-dev-middleware@^1.11.0: version "1.12.2" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz#f8fc1120ce3b4fc5680ceecb43d777966b21105e" @@ -11585,6 +11840,12 @@ webpack-manifest-plugin@1.3.2: fs-extra "^0.30.0" lodash ">=3.5 <5" +webpack-merge@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.1.2.tgz#5d372dddd3e1e5f8874f5bf5a8e929db09feb216" + dependencies: + lodash "^4.17.5" + webpack-node-externals@^1.6.0: version "1.7.2" resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz#6e1ee79ac67c070402ba700ef033a9b8d52ac4e3" @@ -12009,6 +12270,14 @@ yargs@~3.10.0: decamelize "^1.0.0" window-size "0.1.0" +yarn-install@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/yarn-install/-/yarn-install-0.2.1.tgz#43841c12d7099a481f89fbfa6ca49d3bf92d15e3" + dependencies: + cac "^3.0.3" + chalk "^1.1.3" + cross-spawn "^4.0.2" + yauzl@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" From a3d298bf939e0d258eb02cf07bd7b00b98c09375 Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Sun, 22 Apr 2018 12:03:17 +0200 Subject: [PATCH 035/105] Remove Buffer usage from frontend This method wasn't used anywhere, and yet caused the Node `buffer` polyfill to be loaded in the browser. This saves ~6.5kB gzip. --- src/helpers/utils.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/helpers/utils.js b/src/helpers/utils.js index b9efcfadc7..cfd072d512 100644 --- a/src/helpers/utils.js +++ b/src/helpers/utils.js @@ -66,13 +66,6 @@ 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) - * - * Stolen from http://stackoverflow.com/a/38237610/2115623 - */ -export const encode = (string: string) => - Buffer.from(string).toString('base64'); /* Best guess at if user is on a mobile device. Used in the modal components From eae3f5b5ca39db6a722a2a8615de43f29302e1ca Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Sat, 7 Apr 2018 20:08:00 +0530 Subject: [PATCH 036/105] Spinner changes button sizes #1919 --- src/components/buttons/style.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/buttons/style.js b/src/components/buttons/style.js index 71675906ae..4f6503d4e0 100644 --- a/src/components/buttons/style.js +++ b/src/components/buttons/style.js @@ -52,10 +52,13 @@ export const Label = styled.span` `; export const StyledSolidButton = styled.button` - ${baseButton} background-color: ${props => - props.disabled - ? props.theme.bg.inactive - : eval(`props.theme.${props.color ? props.color : `brand.alt`}`)}; + ${baseButton} + + ${props => props.disabled && 'span + span {margin: 0;}'} + background-color: ${props => + props.disabled + ? props.theme.bg.inactive + : eval(`props.theme.${props.color ? props.color : `brand.alt`}`)}; background-image: ${props => props.disabled || props.gradientTheme === 'none' ? 'none' From f7e8564409354f957ef339495a55275625ed8e89 Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Sat, 7 Apr 2018 20:23:37 +0530 Subject: [PATCH 037/105] Spinner changes button sizes #1919 --- src/components/buttons/index.js | 11 +++++++++-- src/components/globals/index.js | 9 +++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/buttons/index.js b/src/components/buttons/index.js index e382a112b4..edd654503d 100644 --- a/src/components/buttons/index.js +++ b/src/components/buttons/index.js @@ -45,7 +45,11 @@ export const Button = (props: ButtonProps) => ( {props.icon ? ( props.loading ? ( - + ) : ( @@ -53,7 +57,10 @@ export const Button = (props: ButtonProps) => ( ) : ( '' )} - {props.loading && !props.icon && } + {props.loading && + !props.icon && ( + + )} diff --git a/src/components/globals/index.js b/src/components/globals/index.js index aa18ccad49..6c63108a9e 100644 --- a/src/components/globals/index.js +++ b/src/components/globals/index.js @@ -96,10 +96,11 @@ const spin = keyframes` `; export const Spinner = styled.span` - width: ${props => (props.size ? `${props.size}px` : '32px')}; - height: ${props => (props.size ? `${props.size}px` : '32px')}; - - &:before { + ${props => + !props.isBtnLoading && + ` + width: ${props.size ? `${props.size}px` : '32px'}; + height: ${props.size ? `${props.size}px` : '32px'};`} &:before { content: ''; box-sizing: border-box; display: inline-block; From 88461d4869ce49045c0877cc11e44b8509f6fcdc Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Sat, 7 Apr 2018 21:33:57 +0530 Subject: [PATCH 038/105] fixed for buttons which are not disabling also, earlier fix was only taking disabled buttons like publish,save, now buttons liek create community which are not disabled are also fixed --- src/components/buttons/index.js | 7 ++++++- src/components/buttons/style.js | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/buttons/index.js b/src/components/buttons/index.js index edd654503d..e72d703950 100644 --- a/src/components/buttons/index.js +++ b/src/components/buttons/index.js @@ -41,7 +41,12 @@ type IconProps = { }; export const Button = (props: ButtonProps) => ( - + {props.icon ? ( props.loading ? ( diff --git a/src/components/buttons/style.js b/src/components/buttons/style.js index 4f6503d4e0..61f4441d36 100644 --- a/src/components/buttons/style.js +++ b/src/components/buttons/style.js @@ -54,7 +54,7 @@ export const Label = styled.span` export const StyledSolidButton = styled.button` ${baseButton} - ${props => props.disabled && 'span + span {margin: 0;}'} + ${props => props.isBtnLoading && 'span + span {margin: 0;}'} background-color: ${props => props.disabled ? props.theme.bg.inactive From 9c1e15e7a9a52d230db70f945a4dfaf77cb22e9b Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Thu, 12 Apr 2018 00:11:41 +0530 Subject: [PATCH 039/105] initial clear text fromlocalStorage changes --- src/components/chatInput/index.js | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index 828e2fa529..b647a0249e 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -64,21 +64,42 @@ type Props = { }; const LS_KEY = 'last-chat-input-content'; +const LS_KEY_EXPIRE = 'last-chat-input-content-expire'; let storedContent; // We persist the body and title to localStorage // so in case the app crashes users don't loose content if (localStorage) { try { - storedContent = toState(JSON.parse(localStorage.getItem(LS_KEY) || '')); + const expireTime = localStorage.getItem(LS_KEY_EXPIRE); + const currTime = new Date().getTime(); + /////if current time is greater than valid till of text then please expire text back to '' + if (currTime > expireTime) { + localStorage.removeItem(LS_KEY); + localStorage.removeItem(LS_KEY_EXPIRE); + } else { + storedContent = toState(JSON.parse(localStorage.getItem(LS_KEY) || '')); + } } catch (err) { localStorage.removeItem(LS_KEY); + localStorage.removeItem(LS_KEY_EXPIRE); } } -const forcePersist = content => +const forcePersist = content => { localStorage && localStorage.setItem(LS_KEY, JSON.stringify(toJSON(content))); + localStorage && + localStorage.setItem( + LS_KEY_EXPIRE, + new Date().getTime() + 60 * 60 * 24 * 1000 + ); +}; const persistContent = debounce(content => { localStorage && localStorage.setItem(LS_KEY, JSON.stringify(toJSON(content))); + localStorage && + localStorage.setItem( + LS_KEY_EXPIRE, + new Date().getTime() + 60 * 60 * 24 * 1000 + ); }, 500); class ChatInput extends React.Component { @@ -262,6 +283,7 @@ class ChatInput extends React.Component { }) .then(() => { localStorage.removeItem(LS_KEY); + localStorage.removeItem(LS_KEY_EXPIRE); return track(`${threadType} message`, 'text message created', null); }) .catch(err => { @@ -289,6 +311,7 @@ class ChatInput extends React.Component { } localStorage.removeItem(LS_KEY); + localStorage.removeItem(LS_KEY_EXPIRE); return track(`${threadType} message`, 'text message created', null); }) .catch(err => { From 4be765c9e1284f1d632050289b6c2b7e01c94695 Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Thu, 12 Apr 2018 00:30:46 +0530 Subject: [PATCH 040/105] removed files of another PR --- src/components/buttons/index.js | 18 +++--------------- src/components/buttons/style.js | 2 +- src/components/globals/index.js | 9 ++++----- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/components/buttons/index.js b/src/components/buttons/index.js index e72d703950..e382a112b4 100644 --- a/src/components/buttons/index.js +++ b/src/components/buttons/index.js @@ -41,20 +41,11 @@ type IconProps = { }; export const Button = (props: ButtonProps) => ( - + {props.icon ? ( props.loading ? ( - + ) : ( @@ -62,10 +53,7 @@ export const Button = (props: ButtonProps) => ( ) : ( '' )} - {props.loading && - !props.icon && ( - - )} + {props.loading && !props.icon && } diff --git a/src/components/buttons/style.js b/src/components/buttons/style.js index 61f4441d36..4f6503d4e0 100644 --- a/src/components/buttons/style.js +++ b/src/components/buttons/style.js @@ -54,7 +54,7 @@ export const Label = styled.span` export const StyledSolidButton = styled.button` ${baseButton} - ${props => props.isBtnLoading && 'span + span {margin: 0;}'} + ${props => props.disabled && 'span + span {margin: 0;}'} background-color: ${props => props.disabled ? props.theme.bg.inactive diff --git a/src/components/globals/index.js b/src/components/globals/index.js index 6c63108a9e..aa18ccad49 100644 --- a/src/components/globals/index.js +++ b/src/components/globals/index.js @@ -96,11 +96,10 @@ const spin = keyframes` `; export const Spinner = styled.span` - ${props => - !props.isBtnLoading && - ` - width: ${props.size ? `${props.size}px` : '32px'}; - height: ${props.size ? `${props.size}px` : '32px'};`} &:before { + width: ${props => (props.size ? `${props.size}px` : '32px')}; + height: ${props => (props.size ? `${props.size}px` : '32px')}; + + &:before { content: ''; box-sizing: border-box; display: inline-block; From 038309543953f0491d9a04a607ae4cfde13e6ffb Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Thu, 12 Apr 2018 00:34:28 +0530 Subject: [PATCH 041/105] removed files of another PR, missed style.js --- src/components/buttons/style.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/components/buttons/style.js b/src/components/buttons/style.js index 4f6503d4e0..71675906ae 100644 --- a/src/components/buttons/style.js +++ b/src/components/buttons/style.js @@ -52,13 +52,10 @@ export const Label = styled.span` `; export const StyledSolidButton = styled.button` - ${baseButton} - - ${props => props.disabled && 'span + span {margin: 0;}'} - background-color: ${props => - props.disabled - ? props.theme.bg.inactive - : eval(`props.theme.${props.color ? props.color : `brand.alt`}`)}; + ${baseButton} background-color: ${props => + props.disabled + ? props.theme.bg.inactive + : eval(`props.theme.${props.color ? props.color : `brand.alt`}`)}; background-image: ${props => props.disabled || props.gradientTheme === 'none' ? 'none' From f134906a131324e674ea150174c442f8adb2c3be Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Sun, 15 Apr 2018 22:26:18 +0530 Subject: [PATCH 042/105] made changes for all --- src/components/chatInput/index.js | 14 +-- src/components/composer/index.js | 95 +++++++++++-------- .../threadComposer/components/composer.js | 21 +++- src/views/thread/index.js | 2 + 4 files changed, 81 insertions(+), 51 deletions(-) diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index b647a0249e..c925742f8c 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -65,6 +65,8 @@ type Props = { const LS_KEY = 'last-chat-input-content'; const LS_KEY_EXPIRE = 'last-chat-input-content-expire'; + +const ONE_DAY = () => new Date().getTime() + 60 * 60 * 24 * 1000; let storedContent; // We persist the body and title to localStorage // so in case the app crashes users don't loose content @@ -87,19 +89,11 @@ if (localStorage) { const forcePersist = content => { localStorage && localStorage.setItem(LS_KEY, JSON.stringify(toJSON(content))); - localStorage && - localStorage.setItem( - LS_KEY_EXPIRE, - new Date().getTime() + 60 * 60 * 24 * 1000 - ); + localStorage && localStorage.setItem(LS_KEY_EXPIRE, ONE_DAY()); }; const persistContent = debounce(content => { localStorage && localStorage.setItem(LS_KEY, JSON.stringify(toJSON(content))); - localStorage && - localStorage.setItem( - LS_KEY_EXPIRE, - new Date().getTime() + 60 * 60 * 24 * 1000 - ); + localStorage && localStorage.setItem(LS_KEY_EXPIRE, ONE_DAY()); }, 500); class ChatInput extends React.Component { diff --git a/src/components/composer/index.js b/src/components/composer/index.js index 6b037334f6..08f053fb1f 100644 --- a/src/components/composer/index.js +++ b/src/components/composer/index.js @@ -82,6 +82,10 @@ type Props = { const LS_BODY_KEY = 'last-thread-composer-body'; const LS_TITLE_KEY = 'last-thread-composer-title'; +const LS_COMPOSER_EXPIRE = 'last-thread-composer-expire'; + +const ONE_DAY = () => new Date().getTime() + 60 * 60 * 24 * 1000; + // We persist the body and title to localStorage // so in case the app crashes users don't loose content class ComposerWithData extends Component { @@ -90,19 +94,7 @@ class ComposerWithData extends Component { constructor(props) { super(props); - let storedBody; - let storedTitle; - if (localStorage) { - try { - storedBody = toState( - JSON.parse(localStorage.getItem(LS_BODY_KEY) || '') - ); - storedTitle = localStorage.getItem(LS_TITLE_KEY); - } catch (err) { - localStorage.removeItem(LS_BODY_KEY); - localStorage.removeItem(LS_TITLE_KEY); - } - } + let { storedBody, storedTitle } = this.getTitleAndBody(); this.state = { title: storedTitle || '', @@ -129,6 +121,39 @@ class ComposerWithData extends Component { ); } + removeStorage = () => { + localStorage.removeItem(LS_BODY_KEY); + localStorage.removeItem(LS_TITLE_KEY); + localStorage.removeItem(LS_COMPOSER_EXPIRE); + }; + + getTitleAndBody = () => { + let storedBody; + let storedTitle; + + if (localStorage) { + try { + const expireTime = localStorage.getItem(LS_COMPOSER_EXPIRE); + const currTime = new Date().getTime(); + /////if current time is greater than valid till of text then please expire title/body back to '' + if (currTime > expireTime) { + this.removeStorage(); + } else { + storedBody = toState( + JSON.parse(localStorage.getItem(LS_BODY_KEY) || '') + ); + storedTitle = localStorage.getItem(LS_TITLE_KEY); + } + } catch (err) { + this.removeStorage(); + } + } + return { + storedBody, + storedTitle, + }; + }; + handleIncomingProps = props => { const { user } = props.data; // if the user doesn't exist, bust outta here @@ -199,20 +224,7 @@ class ComposerWithData extends Component { }; componentWillMount() { - let storedBody; - let storedTitle; - if (localStorage) { - try { - storedBody = toState( - JSON.parse(localStorage.getItem(LS_BODY_KEY) || '') - ); - storedTitle = localStorage.getItem(LS_TITLE_KEY); - } catch (err) { - localStorage.removeItem(LS_BODY_KEY); - localStorage.removeItem(LS_TITLE_KEY); - } - } - + let { storedBody, storedTitle } = this.getTitleAndBody(); this.setState({ title: this.state.title || storedTitle || '', body: this.state.body || storedBody || '', @@ -317,8 +329,7 @@ class ComposerWithData extends Component { clearEditorStateAfterPublish = () => { try { - localStorage.removeItem(LS_BODY_KEY); - localStorage.removeItem(LS_TITLE_KEY); + this.removeStorage(); } catch (err) { console.error(err); } @@ -328,30 +339,36 @@ class ComposerWithData extends Component { this.activateLastThread(); }; + handleTitleBodyChange = titleOrBody => { + if ((titleOrBody = 'body')) { + localStorage.setItem( + LS_BODY_KEY, + JSON.stringify(toJSON(this.state.body)) + ); + } else { + localStorage.setItem(LS_TITLE_KEY, this.state.title); + } + localStorage.setItem(LS_COMPOSER_EXPIRE, ONE_DAY()); + }; + persistBodyToLocalStorageWithDebounce = body => { if (!localStorage) return; - return localStorage.setItem( - LS_BODY_KEY, - JSON.stringify(toJSON(this.state.body)) - ); + this.handleTitleBodyChange('body'); }; persistTitleToLocalStorageWithDebounce = title => { if (!localStorage) return; - return localStorage.setItem(LS_TITLE_KEY, this.state.title); + this.handleTitleBodyChange('title'); }; persistTitleToLocalStorage = title => { if (!localStorage) return; - return localStorage.setItem(LS_TITLE_KEY, this.state.title); + this.handleTitleBodyChange('title'); }; persistBodyToLocalStorage = body => { if (!localStorage) return; - return localStorage.setItem( - LS_BODY_KEY, - JSON.stringify(toJSON(this.state.body)) - ); + this.handleTitleBodyChange('body'); }; setActiveCommunity = e => { diff --git a/src/components/threadComposer/components/composer.js b/src/components/threadComposer/components/composer.js index 7ffde98ad4..f42915fe54 100644 --- a/src/components/threadComposer/components/composer.js +++ b/src/components/threadComposer/components/composer.js @@ -74,17 +74,31 @@ type State = { const LS_BODY_KEY = 'last-thread-composer-body'; const LS_TITLE_KEY = 'last-thread-composer-title'; +const LS_COMPOSER_EXPIRE = 'last-thread-composer-expire'; + +const ONE_DAY = () => new Date().getTime() + 60 * 60 * 24 * 1000; + let storedBody; let storedTitle; // We persist the body and title to localStorage // so in case the app crashes users don't loose content if (localStorage) { try { - storedBody = toState(JSON.parse(localStorage.getItem(LS_BODY_KEY) || '')); - storedTitle = localStorage.getItem(LS_TITLE_KEY); + const expireTime = localStorage.getItem(LS_COMPOSER_EXPIRE); + const currTime = new Date().getTime(); + /////if current time is greater than valid till of text then please expire title/body back to '' + if (currTime > expireTime) { + localStorage.removeItem(LS_BODY_KEY); + localStorage.removeItem(LS_TITLE_KEY); + localStorage.removeItem(LS_COMPOSER_EXPIRE); + } else { + storedBody = toState(JSON.parse(localStorage.getItem(LS_BODY_KEY) || '')); + storedTitle = localStorage.getItem(LS_TITLE_KEY); + } } catch (err) { localStorage.removeItem(LS_BODY_KEY); localStorage.removeItem(LS_TITLE_KEY); + localStorage.removeItem(LS_COMPOSER_EXPIRE); } } @@ -92,12 +106,14 @@ const persistTitle = localStorage && debounce((title: string) => { localStorage.setItem(LS_TITLE_KEY, title); + localStorage.setItem(LS_COMPOSER_EXPIRE, ONE_DAY()); }, 500); const persistBody = localStorage && debounce(body => { localStorage.setItem(LS_BODY_KEY, JSON.stringify(toJSON(body))); + localStorage.setItem(LS_COMPOSER_EXPIRE, ONE_DAY()); }, 500); class ThreadComposerWithData extends React.Component { @@ -553,6 +569,7 @@ class ThreadComposerWithData extends React.Component { track('thread', 'published', null); localStorage.removeItem(LS_TITLE_KEY); localStorage.removeItem(LS_BODY_KEY); + localStorage.removeItem(LS_COMPOSER_EXPIRE); // stop the loading spinner on the publish button this.setState({ diff --git a/src/views/thread/index.js b/src/views/thread/index.js index 04c0c92b54..ad3d427e86 100644 --- a/src/views/thread/index.js +++ b/src/views/thread/index.js @@ -238,6 +238,7 @@ class ThreadContainer extends React.Component { if (isBlockedInChannelOrCommunity) return null; const LS_KEY = 'last-chat-input-content'; + const LS_KEY_EXPIRE = 'last-chat-input-content-expire'; let storedContent; // We persist the body and title to localStorage // so in case the app crashes users don't loose content @@ -246,6 +247,7 @@ class ThreadContainer extends React.Component { storedContent = toState(JSON.parse(localStorage.getItem(LS_KEY) || '')); } catch (err) { localStorage.removeItem(LS_KEY); + localStorage.removeItem(LS_KEY_EXPIRE); } } From 632d82ba688b0e9b72ceacc584aac1c9d2d56d42 Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Sun, 15 Apr 2018 22:36:48 +0530 Subject: [PATCH 043/105] made REMOVE_STORAGE --- .../threadComposer/components/composer.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/threadComposer/components/composer.js b/src/components/threadComposer/components/composer.js index f42915fe54..254c3d7cc5 100644 --- a/src/components/threadComposer/components/composer.js +++ b/src/components/threadComposer/components/composer.js @@ -78,6 +78,12 @@ const LS_COMPOSER_EXPIRE = 'last-thread-composer-expire'; const ONE_DAY = () => new Date().getTime() + 60 * 60 * 24 * 1000; +const REMOVE_STORAGE = () => { + localStorage.removeItem(LS_BODY_KEY); + localStorage.removeItem(LS_TITLE_KEY); + localStorage.removeItem(LS_COMPOSER_EXPIRE); +}; + let storedBody; let storedTitle; // We persist the body and title to localStorage @@ -88,17 +94,13 @@ if (localStorage) { const currTime = new Date().getTime(); /////if current time is greater than valid till of text then please expire title/body back to '' if (currTime > expireTime) { - localStorage.removeItem(LS_BODY_KEY); - localStorage.removeItem(LS_TITLE_KEY); - localStorage.removeItem(LS_COMPOSER_EXPIRE); + REMOVE_STORAGE(); } else { storedBody = toState(JSON.parse(localStorage.getItem(LS_BODY_KEY) || '')); storedTitle = localStorage.getItem(LS_TITLE_KEY); } } catch (err) { - localStorage.removeItem(LS_BODY_KEY); - localStorage.removeItem(LS_TITLE_KEY); - localStorage.removeItem(LS_COMPOSER_EXPIRE); + REMOVE_STORAGE(); } } @@ -567,9 +569,7 @@ class ThreadComposerWithData extends React.Component { const id = data.publishThread.id; track('thread', 'published', null); - localStorage.removeItem(LS_TITLE_KEY); - localStorage.removeItem(LS_BODY_KEY); - localStorage.removeItem(LS_COMPOSER_EXPIRE); + REMOVE_STORAGE(); // stop the loading spinner on the publish button this.setState({ From 8bc3c3cf8f6f17ada651c927a53e6e097553b7ff Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Mon, 16 Apr 2018 01:57:57 +0530 Subject: [PATCH 044/105] seperated DM and other chats --- src/components/chatInput/index.js | 113 ++++++++++++++++++++++++++---- 1 file changed, 101 insertions(+), 12 deletions(-) diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index c925742f8c..3f26d27acf 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -65,15 +65,19 @@ type Props = { const LS_KEY = 'last-chat-input-content'; const LS_KEY_EXPIRE = 'last-chat-input-content-expire'; +const LS_DM_KEY = 'last-chat-input-content-dm'; +const LS_DM_KEY_EXPIRE = 'last-chat-input-content-dm-expire'; const ONE_DAY = () => new Date().getTime() + 60 * 60 * 24 * 1000; let storedContent; +let storedContentDM; // We persist the body and title to localStorage // so in case the app crashes users don't loose content +const currTime = new Date().getTime(); if (localStorage) { try { const expireTime = localStorage.getItem(LS_KEY_EXPIRE); - const currTime = new Date().getTime(); + /////if current time is greater than valid till of text then please expire text back to '' if (currTime > expireTime) { localStorage.removeItem(LS_KEY); @@ -85,15 +89,50 @@ if (localStorage) { localStorage.removeItem(LS_KEY); localStorage.removeItem(LS_KEY_EXPIRE); } + + try { + const expireTimeDM = localStorage.getItem(LS_KEY_EXPIRE); + + /////if current time is greater than valid till of text then please expire text back to '' + if (currTime > expireTimeDM) { + localStorage.removeItem(LS_DM_KEY); + localStorage.removeItem(LS_DM_KEY_EXPIRE); + } else { + storedContentDM = toState( + JSON.parse(localStorage.getItem(LS_DM_KEY) || '') + ); + } + } catch (err) { + localStorage.removeItem(LS_DM_KEY); + localStorage.removeItem(LS_DM_KEY_EXPIRE); + } } -const forcePersist = content => { - localStorage && localStorage.setItem(LS_KEY, JSON.stringify(toJSON(content))); - localStorage && localStorage.setItem(LS_KEY_EXPIRE, ONE_DAY()); +const returnText = (type = '') => { + if (type === 'directMessageThread') { + return storedContentDM; + } else { + return storedContent; + } +}; + +const setText = (content, threadType = '') => { + if (threadType === 'directMessageThread') { + localStorage && + localStorage.setItem(LS_DM_KEY, JSON.stringify(toJSON(content))); + localStorage && localStorage.setItem(LS_DM_KEY_EXPIRE, ONE_DAY()); + } else { + localStorage && + localStorage.setItem(LS_KEY, JSON.stringify(toJSON(content))); + localStorage && localStorage.setItem(LS_KEY_EXPIRE, ONE_DAY()); + } +}; + +const forcePersist = (content, threadType = '') => { + setText(content, threadType); }; -const persistContent = debounce(content => { - localStorage && localStorage.setItem(LS_KEY, JSON.stringify(toJSON(content))); - localStorage && localStorage.setItem(LS_KEY_EXPIRE, ONE_DAY()); +const persistContent = debounce((content, threadType = '') => { + setText(content, threadType); }, 500); class ChatInput extends React.Component { @@ -102,9 +141,13 @@ class ChatInput extends React.Component { photoSizeError: '', code: false, isSendingMediaMessage: false, +<<<<<<< HEAD mediaPreview: '', mediaPreviewFile: null, markdownHint: false, +======= + text: '', +>>>>>>> seperated DM and other chats }; editor: any; @@ -153,12 +196,21 @@ class ChatInput extends React.Component { }; onChange = (state, ...rest) => { +<<<<<<< HEAD const { onChange } = this.props; this.toggleMarkdownHint(state); persistContent(state); onChange(state, ...rest); }; +======= + const { onChange, threadType } = this.props; + console.log( + '=== state index.js [171]/Users/zend/Documents/gitwork/spectrum/src/components/chatInput/index.js ===', + state + ); + persistContent(state, threadType); +>>>>>>> seperated DM and other chats toggleMarkdownHint = state => { // eslint-disable-next-line @@ -181,6 +233,30 @@ class ChatInput extends React.Component { }, 0); }; +<<<<<<< HEAD +======= + toggleCodeMessage = (keepCurrentText?: boolean = true) => { + const { onChange, state, threadType } = this.props; + + const { code } = this.state; + this.setState( + { + code: !code, + }, + () => { + onChange( + changeCurrentBlockType( + state, + code ? 'unstyled' : 'code-block', + keepCurrentText ? toPlainText(state(threadType)) : '' + ) + ); + setTimeout(() => this.triggerFocus()); + } + ); + }; + +>>>>>>> seperated DM and other chats submit = e => { if (e) e.preventDefault(); @@ -243,10 +319,14 @@ class ChatInput extends React.Component { } // If the input is empty don't do anything +<<<<<<< HEAD if (!state.getCurrentContent().hasText()) return 'handled'; +======= + if (toPlainText(state(threadType)).trim() === '') return 'handled'; +>>>>>>> seperated DM and other chats // do one last persist before sending - forcePersist(state); + forcePersist(state, threadType); // user is creating a new directMessageThread, break the chain // and initiate a new group creation with the message being sent @@ -270,14 +350,18 @@ class ChatInput extends React.Component { messageType: !isAndroid() ? 'draftjs' : 'text', threadType, content: { +<<<<<<< HEAD body: !isAndroid() ? JSON.stringify(toJSON(state)) : toPlainText(state), +======= + body: JSON.stringify(toJSON(state(threadType))), +>>>>>>> seperated DM and other chats }, }) .then(() => { - localStorage.removeItem(LS_KEY); - localStorage.removeItem(LS_KEY_EXPIRE); + localStorage.removeItem(LS_DM_KEY); + localStorage.removeItem(LS_DM_KEY_EXPIRE); return track(`${threadType} message`, 'text message created', null); }) .catch(err => { @@ -289,9 +373,13 @@ class ChatInput extends React.Component { messageType: !isAndroid() ? 'draftjs' : 'text', threadType, content: { +<<<<<<< HEAD body: !isAndroid() ? JSON.stringify(toJSON(state)) : toPlainText(state), +======= + body: JSON.stringify(toJSON(state(threadType))), +>>>>>>> seperated DM and other chats }, }) .then(() => { @@ -541,7 +629,6 @@ class ChatInput extends React.Component { mediaPreview, markdownHint, } = this.state; - const networkDisabled = !networkOnline || (websocketConnection !== 'connected' && @@ -625,7 +712,9 @@ export default compose( sendDirectMessage, // $FlowIssue connect(map), - withState('state', 'changeState', () => storedContent || fromPlainText('')), + withState('state', 'changeState', props => { + return returnText(props.threadType) || fromPlainText(''); + }), withHandlers({ onChange: ({ changeState }) => state => changeState(state), clear: ({ changeState }) => () => changeState(fromPlainText('')), From 743326ad4f68173160ebccfdadba5a81749e14c5 Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Mon, 16 Apr 2018 02:06:52 +0530 Subject: [PATCH 045/105] seperated DM and other chats- fixed code --- src/components/chatInput/index.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index 3f26d27acf..f87a8ca882 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -205,10 +205,7 @@ class ChatInput extends React.Component { }; ======= const { onChange, threadType } = this.props; - console.log( - '=== state index.js [171]/Users/zend/Documents/gitwork/spectrum/src/components/chatInput/index.js ===', - state - ); + persistContent(state, threadType); >>>>>>> seperated DM and other chats @@ -248,7 +245,7 @@ class ChatInput extends React.Component { changeCurrentBlockType( state, code ? 'unstyled' : 'code-block', - keepCurrentText ? toPlainText(state(threadType)) : '' + keepCurrentText ? toPlainText(state) : '' ) ); setTimeout(() => this.triggerFocus()); @@ -319,12 +316,16 @@ class ChatInput extends React.Component { } // If the input is empty don't do anything +<<<<<<< HEAD <<<<<<< HEAD if (!state.getCurrentContent().hasText()) return 'handled'; ======= if (toPlainText(state(threadType)).trim() === '') return 'handled'; >>>>>>> seperated DM and other chats +======= + if (toPlainText(state).trim() === '') return 'handled'; +>>>>>>> seperated DM and other chats- fixed code // do one last persist before sending forcePersist(state, threadType); @@ -350,6 +351,7 @@ class ChatInput extends React.Component { messageType: !isAndroid() ? 'draftjs' : 'text', threadType, content: { +<<<<<<< HEAD <<<<<<< HEAD body: !isAndroid() ? JSON.stringify(toJSON(state)) @@ -357,6 +359,9 @@ class ChatInput extends React.Component { ======= body: JSON.stringify(toJSON(state(threadType))), >>>>>>> seperated DM and other chats +======= + body: JSON.stringify(toJSON(state)), +>>>>>>> seperated DM and other chats- fixed code }, }) .then(() => { @@ -373,6 +378,7 @@ class ChatInput extends React.Component { messageType: !isAndroid() ? 'draftjs' : 'text', threadType, content: { +<<<<<<< HEAD <<<<<<< HEAD body: !isAndroid() ? JSON.stringify(toJSON(state)) @@ -380,6 +386,9 @@ class ChatInput extends React.Component { ======= body: JSON.stringify(toJSON(state(threadType))), >>>>>>> seperated DM and other chats +======= + body: JSON.stringify(toJSON(state)), +>>>>>>> seperated DM and other chats- fixed code }, }) .then(() => { From d3f51e9d911d2025988895a1fe7f971b4c4dd432 Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Mon, 16 Apr 2018 02:19:12 +0530 Subject: [PATCH 046/105] seperated DM and other chats- removed junk code --- src/components/chatInput/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index f87a8ca882..5b7eb35467 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -233,7 +233,7 @@ class ChatInput extends React.Component { <<<<<<< HEAD ======= toggleCodeMessage = (keepCurrentText?: boolean = true) => { - const { onChange, state, threadType } = this.props; + const { onChange, state } = this.props; const { code } = this.state; this.setState( From e94f5e061642d5abd02aae01b18d4c3565c5d5c2 Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Mon, 23 Apr 2018 02:51:40 +0530 Subject: [PATCH 047/105] added comment --- cypress/integration/thread_spec.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cypress/integration/thread_spec.js b/cypress/integration/thread_spec.js index 38e6d68694..ed087607e4 100644 --- a/cypress/integration/thread_spec.js +++ b/cypress/integration/thread_spec.js @@ -84,4 +84,21 @@ describe('/new/thread', () => { cy.contains(title); cy.contains(body); }); + + it('should allow to continue composing thread incase of crash or reload', () => { + const title = 'Persist Title'; + const body = 'with some persisting content'; + cy.get('[data-cy="rich-text-editor"]').should('be.visible'); + cy.get('[data-cy="composer-community-selector"]').should('be.visible'); + cy.get('[data-cy="composer-channel-selector"]').should('be.visible'); + // Type title and body + cy.get('[data-cy="composer-title-input"]').type(title); + cy.get('[contenteditable="true"]').type(body); + /////need time as our localstorage is not set + cy.wait(1000); + cy.reload(); + + cy.get('[data-cy="composer-title-input"]').contains(title); + cy.get('[contenteditable="true"]').contains(body); + }); }); From ec27c0934fe0e81fbaad47b4225ae1f95717ad1e Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Mon, 23 Apr 2018 03:20:15 +0530 Subject: [PATCH 048/105] updated chatinput with live copy --- src/components/chatInput/index.js | 63 +------------------------------ 1 file changed, 2 insertions(+), 61 deletions(-) diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index 5b7eb35467..964f3edea3 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -141,13 +141,9 @@ class ChatInput extends React.Component { photoSizeError: '', code: false, isSendingMediaMessage: false, -<<<<<<< HEAD mediaPreview: '', mediaPreviewFile: null, markdownHint: false, -======= - text: '', ->>>>>>> seperated DM and other chats }; editor: any; @@ -196,18 +192,12 @@ class ChatInput extends React.Component { }; onChange = (state, ...rest) => { -<<<<<<< HEAD - const { onChange } = this.props; + const { onChange, threadType } = this.props; this.toggleMarkdownHint(state); - persistContent(state); + persistContent(state), threadType; onChange(state, ...rest); }; -======= - const { onChange, threadType } = this.props; - - persistContent(state, threadType); ->>>>>>> seperated DM and other chats toggleMarkdownHint = state => { // eslint-disable-next-line @@ -230,30 +220,6 @@ class ChatInput extends React.Component { }, 0); }; -<<<<<<< HEAD -======= - toggleCodeMessage = (keepCurrentText?: boolean = true) => { - const { onChange, state } = this.props; - - const { code } = this.state; - this.setState( - { - code: !code, - }, - () => { - onChange( - changeCurrentBlockType( - state, - code ? 'unstyled' : 'code-block', - keepCurrentText ? toPlainText(state) : '' - ) - ); - setTimeout(() => this.triggerFocus()); - } - ); - }; - ->>>>>>> seperated DM and other chats submit = e => { if (e) e.preventDefault(); @@ -316,16 +282,7 @@ class ChatInput extends React.Component { } // If the input is empty don't do anything -<<<<<<< HEAD -<<<<<<< HEAD if (!state.getCurrentContent().hasText()) return 'handled'; - -======= - if (toPlainText(state(threadType)).trim() === '') return 'handled'; ->>>>>>> seperated DM and other chats -======= - if (toPlainText(state).trim() === '') return 'handled'; ->>>>>>> seperated DM and other chats- fixed code // do one last persist before sending forcePersist(state, threadType); @@ -351,17 +308,9 @@ class ChatInput extends React.Component { messageType: !isAndroid() ? 'draftjs' : 'text', threadType, content: { -<<<<<<< HEAD -<<<<<<< HEAD body: !isAndroid() ? JSON.stringify(toJSON(state)) : toPlainText(state), -======= - body: JSON.stringify(toJSON(state(threadType))), ->>>>>>> seperated DM and other chats -======= - body: JSON.stringify(toJSON(state)), ->>>>>>> seperated DM and other chats- fixed code }, }) .then(() => { @@ -378,17 +327,9 @@ class ChatInput extends React.Component { messageType: !isAndroid() ? 'draftjs' : 'text', threadType, content: { -<<<<<<< HEAD -<<<<<<< HEAD body: !isAndroid() ? JSON.stringify(toJSON(state)) : toPlainText(state), -======= - body: JSON.stringify(toJSON(state(threadType))), ->>>>>>> seperated DM and other chats -======= - body: JSON.stringify(toJSON(state)), ->>>>>>> seperated DM and other chats- fixed code }, }) .then(() => { From 176be1c49703c304d8e7dcba8d14e81d2a49785f Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Mon, 23 Apr 2018 03:21:27 +0530 Subject: [PATCH 049/105] updated chatinput with live copy --- src/components/chatInput/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index 964f3edea3..146fc73187 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -195,7 +195,7 @@ class ChatInput extends React.Component { const { onChange, threadType } = this.props; this.toggleMarkdownHint(state); - persistContent(state), threadType; + persistContent(state, threadType); onChange(state, ...rest); }; From 2ff57e150fda5a8746212624c10b87f7276fe831 Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Mon, 23 Apr 2018 03:27:35 +0530 Subject: [PATCH 050/105] fixed merging bugs --- src/components/chatInput/index.js | 2 +- src/components/composer/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index 146fc73187..17bef543e4 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -91,7 +91,7 @@ if (localStorage) { } try { - const expireTimeDM = localStorage.getItem(LS_KEY_EXPIRE); + const expireTimeDM = localStorage.getItem(LS_DM_KEY_EXPIRE); /////if current time is greater than valid till of text then please expire text back to '' if (currTime > expireTimeDM) { diff --git a/src/components/composer/index.js b/src/components/composer/index.js index 08f053fb1f..1df6598e9d 100644 --- a/src/components/composer/index.js +++ b/src/components/composer/index.js @@ -340,7 +340,7 @@ class ComposerWithData extends Component { }; handleTitleBodyChange = titleOrBody => { - if ((titleOrBody = 'body')) { + if (titleOrBody === 'body') { localStorage.setItem( LS_BODY_KEY, JSON.stringify(toJSON(this.state.body)) From ea83a9f2a08720861296fb7ea49fb0d627e400b0 Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Mon, 23 Apr 2018 03:35:47 +0530 Subject: [PATCH 051/105] fixed merging bugs --- cypress/integration/messages_spec.js | 25 +++++++++++++++++++ cypress/integration/thread/chat_input_spec.js | 12 +++++++++ 2 files changed, 37 insertions(+) create mode 100644 cypress/integration/messages_spec.js diff --git a/cypress/integration/messages_spec.js b/cypress/integration/messages_spec.js new file mode 100644 index 0000000000..5cc7e4b99a --- /dev/null +++ b/cypress/integration/messages_spec.js @@ -0,0 +1,25 @@ +import data from '../../shared/testing/data'; + +const thread = data.threads[0]; +const community = data.communities.find( + community => community.id === thread.communityId +); +const author = data.users.find(user => user.id === thread.creatorId); + +describe('/messages/new', () => { + beforeEach(() => { + cy.auth(author.id); + cy.visit('/messages/new'); + }); + + it('should allow to continue composing message incase of crash or reload', () => { + const newMessage = 'Persist New Message'; + cy.get('[contenteditable="true"]').type(newMessage); + cy.get('[contenteditable="true"]').contains(newMessage); + + cy.wait(2000); + // Reload page(incase page closed or crashed ,reload should have same effect) + cy.reload(); + cy.get('[contenteditable="true"]').contains(newMessage); + }); +}); diff --git a/cypress/integration/thread/chat_input_spec.js b/cypress/integration/thread/chat_input_spec.js index 3d96106012..f5f8944d83 100644 --- a/cypress/integration/thread/chat_input_spec.js +++ b/cypress/integration/thread/chat_input_spec.js @@ -83,6 +83,18 @@ describe('chat input', () => { cy.get('[contenteditable="true"]').type(''); cy.contains(newMessage); }); + + it('should allow chat input to be maintained', () => { + const newMessage = 'Persist New Message'; + cy.get('[data-cy="thread-view"]').should('be.visible'); + cy.get('[contenteditable="true"]').type(newMessage); + cy.get('[contenteditable="true"]').contains(newMessage); + cy.get('[data-cy="message-group"]').should('be.visible'); + cy.wait(1000); + // Reload page(incase page closed or crashed ,reload should have same effect) + cy.reload(); + cy.get('[contenteditable="true"]').contains(newMessage); + }); }); // NOTE(@mxstbr): This fails in CI, but not locally for some reason From 413c72d1c7f8914721444cf9a6401fd2fcc59d8a Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Mon, 23 Apr 2018 12:56:22 +0200 Subject: [PATCH 052/105] Show quoted message in chat input --- shared/graphql/queries/message/getMessage.js | 31 +++++++++++++++ src/components/chatInput/index.js | 12 +++++- src/components/chatInput/input.js | 3 ++ src/components/message/view.js | 40 ++++++++++++-------- 4 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 shared/graphql/queries/message/getMessage.js diff --git a/shared/graphql/queries/message/getMessage.js b/shared/graphql/queries/message/getMessage.js new file mode 100644 index 0000000000..5375bb0500 --- /dev/null +++ b/shared/graphql/queries/message/getMessage.js @@ -0,0 +1,31 @@ +// @flow +import { graphql } from 'react-apollo'; +import gql from 'graphql-tag'; +import messageInfoFragment from '../../fragments/message/messageInfo'; +import type { MessageInfoType } from '../../fragments/message/messageInfo'; + +export type GetMessageType = { + ...$Exact, +}; + +export const getMessageByIdQuery = gql` + query getMessageById($id: ID!) { + message(id: $id) { + ...messageInfo + } + } + ${messageInfoFragment} +`; + +const getMessageByIdOptions = { + options: ({ id }) => ({ + variables: { + id, + }, + }), +}; + +export const getMessageById = graphql( + getMessageByIdQuery, + getMessageByIdOptions +); diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index 4574ed8daa..8950433edb 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -30,7 +30,15 @@ import { import Input from './input'; import sendMessage from 'shared/graphql/mutations/message/sendMessage'; import sendDirectMessage from 'shared/graphql/mutations/message/sendDirectMessage'; +import { getMessageById } from 'shared/graphql/queries/message/getMessage'; import MediaUploader from './components/mediaUploader'; +import { QuotedMessage as QuotedMessageComponent } from '../message/view'; + +const QuotedMessage = getMessageById(props => { + if (props.data && props.data.message) + return ; + return null; +}); type State = { isFocused: boolean, @@ -585,7 +593,9 @@ class ChatInput extends React.Component { editorKey="chat-input" decorators={[mentionsDecorator, linksDecorator]} networkDisabled={networkDisabled} - /> + > + {quotedMessage && } + void, networkDisabled: boolean, mediaPreview?: string, + children?: React$Node, onRemoveMedia: Object => void, }; @@ -88,6 +89,7 @@ class Input extends React.Component { networkDisabled, mediaPreview, onRemoveMedia, + children, ...rest } = this.props; const { plugins } = this.state; @@ -100,6 +102,7 @@ class Input extends React.Component { diff --git a/src/views/newCommunity/style.js b/src/views/newCommunity/style.js index 8d2121609f..635b396c2e 100644 --- a/src/views/newCommunity/style.js +++ b/src/views/newCommunity/style.js @@ -52,7 +52,9 @@ export const Divider = styled.div` margin-bottom: 24px; `; -export const ContentContainer = styled.div`padding: 0 24px 24px;`; +export const ContentContainer = styled.div` + padding: 0 24px 24px; +`; export const FormContainer = styled.div``; diff --git a/src/views/notifications/style.js b/src/views/notifications/style.js index d5cc81b69d..fc8f562ad2 100644 --- a/src/views/notifications/style.js +++ b/src/views/notifications/style.js @@ -14,7 +14,9 @@ import { HorizontalRule } from '../../components/globals'; import Card from '../../components/card'; import { IconButton } from '../../components/buttons'; -export const HzRule = styled(HorizontalRule)`margin: 0;`; +export const HzRule = styled(HorizontalRule)` + margin: 0; +`; export const NotificationCard = styled(Card)` padding: 16px; @@ -151,9 +153,13 @@ export const ActorPhotosContainer = styled(FlexRow)` max-width: 100%; `; -export const ActorPhotoItem = styled.div`margin-right: 4px;`; +export const ActorPhotoItem = styled.div` + margin-right: 4px; +`; -export const ActorPhoto = styled.img`width: 100%;`; +export const ActorPhoto = styled.img` + width: 100%; +`; export const ContextRow = styled(FlexRow)` align-items: center; From 2bf893b43796203b4885b81683a1f88253161241 Mon Sep 17 00:00:00 2001 From: ryota-murakami Date: Fri, 27 Apr 2018 02:19:45 +0900 Subject: [PATCH 075/105] chore(src): replace tab-charactor to whitespace --- src/components/globals/index.js | 16 ++++++++-------- src/components/linkPreview/style.js | 12 ++++++------ src/components/loginButtonSet/style.js | 26 +++++++++++++------------- src/components/reaction/style.js | 6 +++--- src/components/scrollRow/style.js | 18 +++++++++--------- src/components/toasts/style.js | 16 ++++++++-------- src/components/upsell/style.js | 26 +++++++++++++------------- src/views/pages/style.js | 2 +- 8 files changed, 61 insertions(+), 61 deletions(-) diff --git a/src/components/globals/index.js b/src/components/globals/index.js index aa18ccad49..c1704df941 100644 --- a/src/components/globals/index.js +++ b/src/components/globals/index.js @@ -343,8 +343,8 @@ const returnTooltip = props => { bottom: 100%; right: 0; transform: translateX(-100%); - border-bottom-width: 0; - border-top-color: ${ + border-bottom-width: 0; + border-top-color: ${ props.onboarding ? props.theme.brand.alt : props.theme.bg.reverse }; } @@ -359,8 +359,8 @@ const returnTooltip = props => { bottom: 100%; left: 0; transform: translateX(100%); - border-bottom-width: 0; - border-top-color: ${ + border-bottom-width: 0; + border-top-color: ${ props.onboarding ? props.theme.brand.alt : props.theme.bg.reverse }; } @@ -409,8 +409,8 @@ const returnTooltip = props => { top: 100%; right: 0; transform: translateX(-100%); - border-top-width: 0; - border-bottom-color: ${ + border-top-width: 0; + border-bottom-color: ${ props.onboarding ? props.theme.brand.alt : props.theme.bg.reverse }; } @@ -425,8 +425,8 @@ const returnTooltip = props => { top: 100%; left: 0; transform: translateX(100%); - border-top-width: 0; - border-bottom-color: ${ + border-top-width: 0; + border-bottom-color: ${ props.onboarding ? props.theme.brand.alt : props.theme.bg.reverse }; } diff --git a/src/components/linkPreview/style.js b/src/components/linkPreview/style.js index 9c891b7b02..9d4bc1ae6d 100644 --- a/src/components/linkPreview/style.js +++ b/src/components/linkPreview/style.js @@ -99,12 +99,12 @@ export const LinkPreviewSkeleton = styled.div` `; const placeHolderShimmer = keyframes` - 0%{ - background-position: -600px 0 - } - 100%{ - background-position: 600px 0 - } + 0%{ + background-position: -600px 0 + } + 100%{ + background-position: 600px 0 + } `; export const AnimatedBackground = styled.div` diff --git a/src/components/loginButtonSet/style.js b/src/components/loginButtonSet/style.js index a968d6ead5..663fcbbaa3 100644 --- a/src/components/loginButtonSet/style.js +++ b/src/components/loginButtonSet/style.js @@ -38,19 +38,19 @@ export const SigninButton = styled.div` ${props => props.showAfter && ` - &:after { - content: 'Previously signed in with'; - position: absolute; - top: -32px; - font-size: 14px; - font-weight: 600; - left: 50%; - transform: translateX(-50%); - width: 100%; - text-align: center; - color: ${props.theme.text.alt}; - } - `} svg { + &:after { + content: 'Previously signed in with'; + position: absolute; + top: -32px; + font-size: 14px; + font-weight: 600; + left: 50%; + transform: translateX(-50%); + width: 100%; + text-align: center; + color: ${props.theme.text.alt}; + } + `} svg { fill: currentColor !important; } `; diff --git a/src/components/reaction/style.js b/src/components/reaction/style.js index 3ad46f1b0e..4a43427e04 100644 --- a/src/components/reaction/style.js +++ b/src/components/reaction/style.js @@ -19,14 +19,14 @@ export const ReactionWrapper = styled.b` ? `background-color: ${ props.active ? props.theme.warn.default : props.theme.text.alt }; - background-image: ${ + background-image: ${ props.active ? Gradient(props.theme.warn.alt, props.theme.warn.default) : 'none' } - ` + ` : `background-color: ${props.theme.bg.border}; - background-image: none;`}; + background-image: none;`}; padding: ${props => (props.hasCount ? '0 10px 0 6px' : '0')}; display: flex; diff --git a/src/components/scrollRow/style.js b/src/components/scrollRow/style.js index f5020ab004..c746e9a428 100644 --- a/src/components/scrollRow/style.js +++ b/src/components/scrollRow/style.js @@ -2,14 +2,14 @@ import styled from 'styled-components'; import { FlexRow } from '../globals'; export const ScrollableFlexRow = styled(FlexRow)` - overflow-x: scroll; - flex-wrap: nowrap; - background: transparent; - cursor: pointer; - cursor: hand; - cursor: grab; + overflow-x: scroll; + flex-wrap: nowrap; + background: transparent; + cursor: pointer; + cursor: hand; + cursor: grab; - &:active { - cursor: grabbing; - } + &:active { + cursor: grabbing; + } `; diff --git a/src/components/toasts/style.js b/src/components/toasts/style.js index 34624bfc27..aad2c88322 100644 --- a/src/components/toasts/style.js +++ b/src/components/toasts/style.js @@ -18,18 +18,18 @@ export const Container = styled.div` `; const toastFade = keyframes` - 0% { - opacity: 0; + 0% { + opacity: 0; top: 8px; - } - 5% { - opacity: 1; + } + 5% { + opacity: 1; top: 0; - } + } 95% { - opacity: 1; + opacity: 1; top: 0; - } + } 100% { opacity: 0; top: -4px; diff --git a/src/components/upsell/style.js b/src/components/upsell/style.js index 3dcacbec04..f694225f74 100644 --- a/src/components/upsell/style.js +++ b/src/components/upsell/style.js @@ -304,19 +304,19 @@ export const SigninButton = styled.a` ${props => props.after && ` - &:after { - content: 'Previously signed in with'; - position: absolute; - top: -32px; - font-size: 14px; - font-weight: 600; - left: 50%; - transform: translateX(-50%); - width: 100%; - text-align: center; - color: ${props.theme.text.alt}; - } - `} span { + &:after { + content: 'Previously signed in with'; + position: absolute; + top: -32px; + font-size: 14px; + font-weight: 600; + left: 50%; + transform: translateX(-50%); + width: 100%; + text-align: center; + color: ${props.theme.text.alt}; + } + `} span { display: inline-block; flex: 0 0 auto; margin-top: -1px; diff --git a/src/views/pages/style.js b/src/views/pages/style.js index caa32be586..5236eaff94 100644 --- a/src/views/pages/style.js +++ b/src/views/pages/style.js @@ -189,7 +189,7 @@ export const SignInButton = styled.a` ${props => props.after && ` - margin: 24px 0; + margin: 24px 0; &:after { content: 'Previously signed in with'; From 71a90804a3226b001c9e600b9b607798be5216ca Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Thu, 26 Apr 2018 19:23:44 +0200 Subject: [PATCH 076/105] Fix replies in DMs --- .../fragments/message/directMessageInfo.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/shared/graphql/fragments/message/directMessageInfo.js b/shared/graphql/fragments/message/directMessageInfo.js index e62161d3f2..8ca1b5cb45 100644 --- a/shared/graphql/fragments/message/directMessageInfo.js +++ b/shared/graphql/fragments/message/directMessageInfo.js @@ -12,6 +12,19 @@ export type DirectMessageInfoType = { ...$Exact, }, }, + parent: { + id: string, + timestamp: Date, + messageType: string, + author: { + user: { + ...$Exact, + }, + }, + content: { + body: string, + }, + }, reactions: { count: number, hasReacted: boolean, @@ -31,6 +44,19 @@ export default gql` ...userInfo } } + parent { + id + timestamp + messageType + author { + user { + ...userInfo + } + } + content { + body + } + } reactions { count hasReacted From 8c853762315741b4d77073d0c3bb31e791c2929f Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Thu, 26 Apr 2018 19:29:59 +0200 Subject: [PATCH 077/105] Fix breaking error --- src/views/directMessages/components/messages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/directMessages/components/messages.js b/src/views/directMessages/components/messages.js index 36a06281ce..00ff07f175 100644 --- a/src/views/directMessages/components/messages.js +++ b/src/views/directMessages/components/messages.js @@ -63,7 +63,7 @@ class MessagesWithData extends React.Component { // force scroll to bottom when a message is sent in the same thread if (prev.data.messages !== data.messages && contextualScrollToBottom) { // mark this thread as unread when new messages come in and i'm viewing it - setLastSeen(data.directMessageThread.id); + if (data.directMessageThread) setLastSeen(data.directMessageThread.id); contextualScrollToBottom(); } } From e0b5ab580099146e5ee00eb924dacaa3e162b778 Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Thu, 26 Apr 2018 19:32:10 +0200 Subject: [PATCH 078/105] Fix inline code in quoted messages --- src/components/message/style.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/message/style.js b/src/components/message/style.js index 2526b1fccd..6b4808e510 100644 --- a/src/components/message/style.js +++ b/src/components/message/style.js @@ -353,6 +353,10 @@ export const QuotedParagraph = styled(Paragraph)` border-left: 4px solid ${props => props.theme.bg.border}; margin: 4px 0; color: ${props => props.theme.text.alt}; + + code { + color: ${props => props.theme.text.alt}; + } `; export const QuoteWrapper = styled.div` From e3b430ea4da8d600ab29e1bd9e7b18d74c9c5942 Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Thu, 26 Apr 2018 19:45:07 +0200 Subject: [PATCH 079/105] Send mention notification to quoted message author --- athena/queues/new-message-in-thread/index.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/athena/queues/new-message-in-thread/index.js b/athena/queues/new-message-in-thread/index.js index 88ed9cc676..de06696931 100644 --- a/athena/queues/new-message-in-thread/index.js +++ b/athena/queues/new-message-in-thread/index.js @@ -19,8 +19,11 @@ import { import { getThreadNotificationUsers } from '../../models/usersThreads'; import { getUserPermissionsInChannel } from '../../models/usersChannels'; import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; +import { getUserById } from '../../models/user'; +import { getMessageById } from '../../models/message'; import { sendMentionNotificationQueue } from 'shared/bull/queues'; import type { MessageNotificationJobData, Job } from 'shared/bull/types'; +import type { DBMessage } from 'shared/types'; export default async (job: Job) => { const { message: incomingMessage } = job.data; @@ -97,7 +100,20 @@ export default async (job: Job) => { : incomingMessage.content.body; // get mentions in the message - const mentions = getMentions(body); + let mentions = getMentions(body); + // If the message quoted another message, send a mention notification to the author + // of the quoted message + if (typeof incomingMessage.parentId === 'string') { + // $FlowIssue + const parent = await getMessageById(incomingMessage.parentId); + // eslint-disable-next-line + (parent: DBMessage); + if (parent) { + const parentAuthor = await getUserById(parent.senderId); + if (parentAuthor && parentAuthor.username) + mentions.push(parentAuthor.username); + } + } if (mentions && mentions.length > 0) { mentions.forEach(username => { sendMentionNotificationQueue.add({ From 4259668974e1a7a26d3420d0b25a23a11e28ea74 Mon Sep 17 00:00:00 2001 From: ryota-murakami Date: Fri, 27 Apr 2018 03:03:09 +0900 Subject: [PATCH 080/105] fix(src): define github theme --- src/components/loginButtonSet/style.js | 6 ++++-- src/components/theme/index.js | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/loginButtonSet/style.js b/src/components/loginButtonSet/style.js index a968d6ead5..d86ea8dc50 100644 --- a/src/components/loginButtonSet/style.js +++ b/src/components/loginButtonSet/style.js @@ -101,8 +101,10 @@ export const GoogleButton = styled(SigninButton)` `; export const GithubButton = styled(SigninButton)` - background: ${props => (props.preferred ? props.theme.text.default : 'none')}; - color: ${props => (props.preferred ? '#fff' : props.theme.text.default)}; + background: ${props => + props.preferred ? props.theme.social.github.default : 'none'}; + color: ${props => + props.preferred ? '#fff' : props.theme.social.github.default}; &:after { color: ${props => props.theme.text.default}; diff --git a/src/components/theme/index.js b/src/components/theme/index.js index d70016a77d..e100ef6db8 100644 --- a/src/components/theme/index.js +++ b/src/components/theme/index.js @@ -37,8 +37,8 @@ export const theme = { alt: '#ea4335', }, github: { - default: '#1475DA', - alt: '#1475DA', + default: '#16171A', + alt: '#16171A', }, ph: { default: '#D85537', From 28f45144cd41019034542f91b5e24a18d1103b98 Mon Sep 17 00:00:00 2001 From: Cut Javascript Date: Fri, 27 Apr 2018 01:11:15 +0530 Subject: [PATCH 081/105] fixed bug where dm msg was not persisting when navigating back from another link in same tab --- src/components/chatInput/index.js | 57 +++++++++++++++---------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index 17bef543e4..1f4e6e7d14 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -69,46 +69,47 @@ const LS_DM_KEY = 'last-chat-input-content-dm'; const LS_DM_KEY_EXPIRE = 'last-chat-input-content-dm-expire'; const ONE_DAY = () => new Date().getTime() + 60 * 60 * 24 * 1000; -let storedContent; -let storedContentDM; + // We persist the body and title to localStorage // so in case the app crashes users don't loose content -const currTime = new Date().getTime(); -if (localStorage) { - try { - const expireTime = localStorage.getItem(LS_KEY_EXPIRE); +const returnText = (type = '') => { + let storedContent; + let storedContentDM; + const currTime = new Date().getTime(); + if (localStorage) { + try { + const expireTime = localStorage.getItem(LS_KEY_EXPIRE); - /////if current time is greater than valid till of text then please expire text back to '' - if (currTime > expireTime) { + /////if current time is greater than valid till of text then please expire text back to '' + if (currTime > expireTime) { + localStorage.removeItem(LS_KEY); + localStorage.removeItem(LS_KEY_EXPIRE); + } else { + storedContent = toState(JSON.parse(localStorage.getItem(LS_KEY) || '')); + } + } catch (err) { localStorage.removeItem(LS_KEY); localStorage.removeItem(LS_KEY_EXPIRE); - } else { - storedContent = toState(JSON.parse(localStorage.getItem(LS_KEY) || '')); } - } catch (err) { - localStorage.removeItem(LS_KEY); - localStorage.removeItem(LS_KEY_EXPIRE); - } - try { - const expireTimeDM = localStorage.getItem(LS_DM_KEY_EXPIRE); + try { + const expireTimeDM = localStorage.getItem(LS_DM_KEY_EXPIRE); - /////if current time is greater than valid till of text then please expire text back to '' - if (currTime > expireTimeDM) { + /////if current time is greater than valid till of text then please expire text back to '' + if (currTime > expireTimeDM) { + localStorage.removeItem(LS_DM_KEY); + localStorage.removeItem(LS_DM_KEY_EXPIRE); + } else { + storedContentDM = toState( + JSON.parse(localStorage.getItem(LS_DM_KEY) || '') + ); + } + } catch (err) { localStorage.removeItem(LS_DM_KEY); localStorage.removeItem(LS_DM_KEY_EXPIRE); - } else { - storedContentDM = toState( - JSON.parse(localStorage.getItem(LS_DM_KEY) || '') - ); } - } catch (err) { - localStorage.removeItem(LS_DM_KEY); - localStorage.removeItem(LS_DM_KEY_EXPIRE); } -} -const returnText = (type = '') => { if (type === 'directMessageThread') { return storedContentDM; } else { @@ -156,7 +157,6 @@ class ChatInput extends React.Component { shouldComponentUpdate(next, nextState) { const curr = this.props; const currState = this.state; - // User changed if (curr.currentUser !== next.currentUser) return true; @@ -193,7 +193,6 @@ class ChatInput extends React.Component { onChange = (state, ...rest) => { const { onChange, threadType } = this.props; - this.toggleMarkdownHint(state); persistContent(state, threadType); onChange(state, ...rest); From 15ece2ea19d98cb4142b9284e297c53d9acb0e64 Mon Sep 17 00:00:00 2001 From: Keraito Date: Thu, 26 Apr 2018 23:37:11 +0200 Subject: [PATCH 082/105] Change regex to include the / before a mention --- shared/regexps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/regexps.js b/shared/regexps.js index ca3c6eabcb..77f17d1d50 100644 --- a/shared/regexps.js +++ b/shared/regexps.js @@ -1,5 +1,5 @@ // @flow -module.exports.MENTIONS = /(? Date: Thu, 26 Apr 2018 23:50:32 +0200 Subject: [PATCH 083/105] Filter out mention starting with a / --- shared/get-mentions.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/get-mentions.js b/shared/get-mentions.js index 1ba26d8f20..5b913be720 100644 --- a/shared/get-mentions.js +++ b/shared/get-mentions.js @@ -2,7 +2,8 @@ import { MENTIONS } from './regexps'; export const getMentions = (text: string): Array => { - const mentions = text.match(MENTIONS) || []; + const mentions = + text.match(MENTIONS).filter(mention => !mention.startsWith('/')) || []; if (!mentions || mentions.length === 0) return []; // " @Mxstbr" => "@Mxstbr" const trimmed = mentions.map( From 5f41243436f2dd44aa6c76cba32c9667952ada70 Mon Sep 17 00:00:00 2001 From: Keraito Date: Thu, 26 Apr 2018 23:52:49 +0200 Subject: [PATCH 084/105] Update the mention decorator with preceeding / filter --- shared/clients/draft-js/mentions-decorator/core.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shared/clients/draft-js/mentions-decorator/core.js b/shared/clients/draft-js/mentions-decorator/core.js index 98aa3c28a5..ea430b3d6e 100644 --- a/shared/clients/draft-js/mentions-decorator/core.js +++ b/shared/clients/draft-js/mentions-decorator/core.js @@ -20,7 +20,9 @@ const createMentionsDecorator = ( // -> "@brian_lovin, what's up with @mxstbr?" const text = contentBlock.getText(); // -> ["@brian_lovin", " @mxstbr"]; - const matches = text.match(MENTIONS); + const matches = text + .match(MENTIONS) + .filter(mention => !mention.startsWith('/')); if (!matches || matches.length === 0) return; matches.forEach(match => { From d72550126371c111a2f2945180f984b43171580f Mon Sep 17 00:00:00 2001 From: Keraito Date: Fri, 27 Apr 2018 00:13:37 +0200 Subject: [PATCH 085/105] Conform to filter to flow --- shared/get-mentions.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shared/get-mentions.js b/shared/get-mentions.js index 5b913be720..977448142f 100644 --- a/shared/get-mentions.js +++ b/shared/get-mentions.js @@ -2,8 +2,10 @@ import { MENTIONS } from './regexps'; export const getMentions = (text: string): Array => { - const mentions = - text.match(MENTIONS).filter(mention => !mention.startsWith('/')) || []; + const matchedMentions = text.match(MENTIONS); + const mentions = matchedMentions + ? matchedMentions.filter(mention => !mention.startsWith('/')) + : []; if (!mentions || mentions.length === 0) return []; // " @Mxstbr" => "@Mxstbr" const trimmed = mentions.map( From 2cec429cdba2212d15728ec61fc9ba4a51a7fb62 Mon Sep 17 00:00:00 2001 From: Alejandro Nanez Date: Thu, 26 Apr 2018 17:51:00 -0500 Subject: [PATCH 086/105] mentions and links are no longer clickable in code blocks --- shared/clients/draft-js/links-decorator/core.js | 9 ++++++++- shared/clients/draft-js/mentions-decorator/core.js | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/shared/clients/draft-js/links-decorator/core.js b/shared/clients/draft-js/links-decorator/core.js index 10778c5e3a..5e1253b2b2 100644 --- a/shared/clients/draft-js/links-decorator/core.js +++ b/shared/clients/draft-js/links-decorator/core.js @@ -19,7 +19,14 @@ let i = 0; const createLinksDecorator = ( Component: ComponentType ) => ({ - strategy: linkStrategy, + strategy: ( + contentBlock: ContentBlock, + callback: (...args?: Array) => any + ) => { + if (contentBlock.type === 'code-block') return; + + linkStrategy(contentBlock, callback); + }, component: ({ decoratedText, children }: DecoratorComponentProps) => ( ) => any ) => { + // This prevents the search for mentions when we're inside of a code-block + if (contentBlock.type === 'code-block') return; + // -> "@brian_lovin, what's up with @mxstbr?" const text = contentBlock.getText(); // -> ["@brian_lovin", " @mxstbr"]; From a8687eaac6bb6588d16d55e953a6a8682f3e3812 Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Fri, 27 Apr 2018 11:20:50 +0200 Subject: [PATCH 087/105] Fix peril --- api/file-without-flow.js | 1 - 1 file changed, 1 deletion(-) delete mode 100644 api/file-without-flow.js diff --git a/api/file-without-flow.js b/api/file-without-flow.js deleted file mode 100644 index 3ccd4bd052..0000000000 --- a/api/file-without-flow.js +++ /dev/null @@ -1 +0,0 @@ -console.log('This is a file without flow'); From f7bf1e3370574bdf46b352812b74400479f99376 Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Fri, 27 Apr 2018 16:23:25 +0200 Subject: [PATCH 088/105] Revert "Fix peril" This reverts commit a8687eaac6bb6588d16d55e953a6a8682f3e3812. --- api/file-without-flow.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 api/file-without-flow.js diff --git a/api/file-without-flow.js b/api/file-without-flow.js new file mode 100644 index 0000000000..3ccd4bd052 --- /dev/null +++ b/api/file-without-flow.js @@ -0,0 +1 @@ +console.log('This is a file without flow'); From a91d5f938c7b597df8ea88617a608e2a4b794496 Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Fri, 27 Apr 2018 16:25:24 +0200 Subject: [PATCH 089/105] Revert "Revert "Fix peril"" This reverts commit f7bf1e3370574bdf46b352812b74400479f99376. --- api/file-without-flow.js | 1 - 1 file changed, 1 deletion(-) delete mode 100644 api/file-without-flow.js diff --git a/api/file-without-flow.js b/api/file-without-flow.js deleted file mode 100644 index 3ccd4bd052..0000000000 --- a/api/file-without-flow.js +++ /dev/null @@ -1 +0,0 @@ -console.log('This is a file without flow'); From bca2b611a23f17a02222346b3d41060bc63116e5 Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Fri, 27 Apr 2018 08:12:05 -0700 Subject: [PATCH 090/105] Deduplicate mention notifications in replies --- athena/queues/new-message-in-thread/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/athena/queues/new-message-in-thread/index.js b/athena/queues/new-message-in-thread/index.js index de06696931..73e59a5da7 100644 --- a/athena/queues/new-message-in-thread/index.js +++ b/athena/queues/new-message-in-thread/index.js @@ -110,8 +110,13 @@ export default async (job: Job) => { (parent: DBMessage); if (parent) { const parentAuthor = await getUserById(parent.senderId); - if (parentAuthor && parentAuthor.username) + if ( + parentAuthor && + parentAuthor.username && + mentions.indexOf(parentAuthor.username) < 0 + ) { mentions.push(parentAuthor.username); + } } } if (mentions && mentions.length > 0) { From 7278a51a702e367c1ee0ee8491f45c555263444a Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Fri, 27 Apr 2018 08:17:38 -0700 Subject: [PATCH 091/105] Fix code blocks color in replies --- src/components/message/style.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/message/style.js b/src/components/message/style.js index 6b4808e510..e25dee27a2 100644 --- a/src/components/message/style.js +++ b/src/components/message/style.js @@ -354,7 +354,8 @@ export const QuotedParagraph = styled(Paragraph)` margin: 4px 0; color: ${props => props.theme.text.alt}; - code { + code, + pre { color: ${props => props.theme.text.alt}; } `; From 34ba660d855042dd0c4a0315ee51fde366e2b2c1 Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Fri, 27 Apr 2018 17:38:08 +0200 Subject: [PATCH 092/105] Fix DOM nesting --- src/components/message/style.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/message/style.js b/src/components/message/style.js index e25dee27a2..06a0295d05 100644 --- a/src/components/message/style.js +++ b/src/components/message/style.js @@ -348,7 +348,7 @@ export const Paragraph = styled.p` } `; -export const QuotedParagraph = styled(Paragraph)` +export const QuotedParagraph = Paragraph.withComponent('div').extend` padding-left: 12px; border-left: 4px solid ${props => props.theme.bg.border}; margin: 4px 0; From b08174c78d62d9b1229938f5250a2203e25a8f95 Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Fri, 27 Apr 2018 09:56:45 -0700 Subject: [PATCH 093/105] Fix mention bug --- shared/clients/draft-js/mentions-decorator/core.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/shared/clients/draft-js/mentions-decorator/core.js b/shared/clients/draft-js/mentions-decorator/core.js index 1b32d08c2b..3d4daea013 100644 --- a/shared/clients/draft-js/mentions-decorator/core.js +++ b/shared/clients/draft-js/mentions-decorator/core.js @@ -23,9 +23,12 @@ const createMentionsDecorator = ( // -> "@brian_lovin, what's up with @mxstbr?" const text = contentBlock.getText(); // -> ["@brian_lovin", " @mxstbr"]; - const matches = text - .match(MENTIONS) - .filter(mention => !mention.startsWith('/')); + let matches = text.match(MENTIONS); + + if (!matches || matches.length === 0) return; + + matches = matches.filter(mention => !mention.startsWith('/')); + if (!matches || matches.length === 0) return; matches.forEach(match => { From 1792d0c0335f6e86bcbb1d7f96e5997271ba48ec Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Fri, 27 Apr 2018 11:03:35 -0700 Subject: [PATCH 094/105] Fix case of trying to reply to deleted message --- src/components/chatInput/index.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index 9094f10c77..12d0cee3ae 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -38,11 +38,26 @@ import { getMessageById } from 'shared/graphql/queries/message/getMessage'; import MediaUploader from './components/mediaUploader'; import { QuotedMessage as QuotedMessageComponent } from '../message/view'; -const QuotedMessage = getMessageById(props => { - if (props.data && props.data.message) - return ; - return null; -}); +const QuotedMessage = compose(connect())( + getMessageById(props => { + if (props.data && props.data.message) { + return ; + } + + // if the query is done loading and no message was returned, clear the input + if (props.data && props.data.networkStatus === 7 && !props.data.message) { + props.dispatch( + addToastWithTimeout( + 'error', + 'The message you are replying to was deleted or could not be fetched.' + ) + ); + props.dispatch(replyToMessage(null)); + } + + return null; + }) +); type State = { isFocused: boolean, From a605a04c9e1a3943db496d311c5a4c1877197a13 Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Fri, 27 Apr 2018 11:13:27 -0700 Subject: [PATCH 095/105] Remove unnecessary compose --- src/components/chatInput/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index 12d0cee3ae..5a08b7fa1b 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -38,7 +38,7 @@ import { getMessageById } from 'shared/graphql/queries/message/getMessage'; import MediaUploader from './components/mediaUploader'; import { QuotedMessage as QuotedMessageComponent } from '../message/view'; -const QuotedMessage = compose(connect())( +const QuotedMessage = connect()( getMessageById(props => { if (props.data && props.data.message) { return ; From c3545b42c68bab571db56d922d89e373ae4354b3 Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Fri, 27 Apr 2018 11:30:05 -0700 Subject: [PATCH 096/105] Fix failing tests --- src/components/chatInput/style.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/chatInput/style.js b/src/components/chatInput/style.js index abf481ce9d..1f7bbd6116 100644 --- a/src/components/chatInput/style.js +++ b/src/components/chatInput/style.js @@ -157,6 +157,7 @@ export const SendButton = styled(IconButton)` background-color: transparent; transition: ${Transition.hover.off}; align-self: flex-end; + z-index: ${zIndex.chatInput}; `; export const MediaInput = styled.input` From 8bafd0a1aa22546b4f674541f0b496552fb652d2 Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Fri, 27 Apr 2018 12:33:07 -0700 Subject: [PATCH 097/105] Fix flow issues --- src/components/chatInput/index.js | 15 +++++++++------ src/components/composer/index.js | 9 ++++++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index a4e8967eb6..8927f75867 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -95,20 +95,23 @@ const LS_KEY_EXPIRE = 'last-chat-input-content-expire'; const LS_DM_KEY = 'last-chat-input-content-dm'; const LS_DM_KEY_EXPIRE = 'last-chat-input-content-dm-expire'; -const ONE_DAY = () => new Date().getTime() + 60 * 60 * 24 * 1000; +const ONE_DAY = (): string => { + const time = new Date().getTime() + 60 * 60 * 24 * 1000; + return time.toString(); +}; // We persist the body and title to localStorage // so in case the app crashes users don't loose content const returnText = (type = '') => { let storedContent; let storedContentDM; - const currTime = new Date().getTime(); + const currTime = new Date().getTime().toString(); if (localStorage) { try { const expireTime = localStorage.getItem(LS_KEY_EXPIRE); - /////if current time is greater than valid till of text then please expire text back to '' - if (currTime > expireTime) { + // if current time is greater than valid till of text then please expire text back to '' + if (expireTime && currTime > expireTime) { localStorage.removeItem(LS_KEY); localStorage.removeItem(LS_KEY_EXPIRE); } else { @@ -122,8 +125,8 @@ const returnText = (type = '') => { try { const expireTimeDM = localStorage.getItem(LS_DM_KEY_EXPIRE); - /////if current time is greater than valid till of text then please expire text back to '' - if (currTime > expireTimeDM) { + // if current time is greater than valid till of text then please expire text back to '' + if (expireTimeDM && currTime > expireTimeDM) { localStorage.removeItem(LS_DM_KEY); localStorage.removeItem(LS_DM_KEY_EXPIRE); } else { diff --git a/src/components/composer/index.js b/src/components/composer/index.js index 1df6598e9d..753756db84 100644 --- a/src/components/composer/index.js +++ b/src/components/composer/index.js @@ -84,7 +84,10 @@ const LS_BODY_KEY = 'last-thread-composer-body'; const LS_TITLE_KEY = 'last-thread-composer-title'; const LS_COMPOSER_EXPIRE = 'last-thread-composer-expire'; -const ONE_DAY = () => new Date().getTime() + 60 * 60 * 24 * 1000; +const ONE_DAY = (): string => { + const time = new Date().getTime() + 60 * 60 * 24 * 1000; + return time.toString(); +}; // We persist the body and title to localStorage // so in case the app crashes users don't loose content @@ -134,9 +137,9 @@ class ComposerWithData extends Component { if (localStorage) { try { const expireTime = localStorage.getItem(LS_COMPOSER_EXPIRE); - const currTime = new Date().getTime(); + const currTime = new Date().getTime().toString(); /////if current time is greater than valid till of text then please expire title/body back to '' - if (currTime > expireTime) { + if (expireTime && currTime > expireTime) { this.removeStorage(); } else { storedBody = toState( From 4d599fb7b38690870a67b1442931abe2488d8dda Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Fri, 27 Apr 2018 14:05:33 -0700 Subject: [PATCH 098/105] Dont change package name --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 26c838ed42..ac34428420 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "spectrum", + "name": "Spectrum", "version": "2.1.6", "license": "BSD-3-Clause", "devDependencies": { From 48f645f7e874a68c3879b62245ae5097d786e742 Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Fri, 27 Apr 2018 14:05:43 -0700 Subject: [PATCH 099/105] Fix console warning for hide prop --- src/views/thread/components/actionBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/thread/components/actionBar.js b/src/views/thread/components/actionBar.js index 2ffceee7f0..d6cafef076 100644 --- a/src/views/thread/components/actionBar.js +++ b/src/views/thread/components/actionBar.js @@ -389,7 +389,7 @@ class ActionBar extends React.Component { Date: Fri, 27 Apr 2018 14:16:38 -0700 Subject: [PATCH 100/105] Fix flyout behavior to fix tests --- src/views/thread/components/actionBar.js | 4 ---- src/views/thread/style.js | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/views/thread/components/actionBar.js b/src/views/thread/components/actionBar.js index d6cafef076..b4e65e8d55 100644 --- a/src/views/thread/components/actionBar.js +++ b/src/views/thread/components/actionBar.js @@ -403,10 +403,6 @@ class ActionBar extends React.Component { data-cy="thread-actions-dropdown" innerRef={ref} style={style} - onClick={() => { - flyoutOpen && this.toggleFlyout(); - this.toggleHover(); - }} > Date: Fri, 27 Apr 2018 16:12:12 -0700 Subject: [PATCH 101/105] Fix markdown helper collapsing in dms --- src/components/chatInput/style.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/chatInput/style.js b/src/components/chatInput/style.js index 1f7bbd6116..c8a19fab7a 100644 --- a/src/components/chatInput/style.js +++ b/src/components/chatInput/style.js @@ -275,6 +275,7 @@ export const Preformatted = styled.code` export const MarkdownHint = styled.div` display: flex; + flex: 0 0 auto; justify-content: flex-end; margin-right: 12px; font-size: 11px; From 371c303efbcaceb31b4408aed504a52440934a17 Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Fri, 27 Apr 2018 16:12:21 -0700 Subject: [PATCH 102/105] Fix dm optimistic update --- shared/graphql/mutations/message/sendDirectMessage.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/shared/graphql/mutations/message/sendDirectMessage.js b/shared/graphql/mutations/message/sendDirectMessage.js index 4f488b209c..36c5f9697b 100644 --- a/shared/graphql/mutations/message/sendDirectMessage.js +++ b/shared/graphql/mutations/message/sendDirectMessage.js @@ -50,6 +50,13 @@ const sendDirectMessageOptions = { roles: [], __typename: 'ThreadParticipant', }, + parent: message.parentId + ? { + __typename: 'Message', + id: message.parentId, + // TODO(@mxstbr): Get the rest of information + } + : null, content: { ...message.content, __typename: 'MessageContent', From 4e5aa69d45ab8f52031b7bdffaf89dba9f917464 Mon Sep 17 00:00:00 2001 From: Comus Leong Date: Sat, 28 Apr 2018 16:10:45 +0800 Subject: [PATCH 103/105] fix Raven.requestHandler --- shared/raven/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/raven/index.js b/shared/raven/index.js index 94d9ec4bd7..7553b9124f 100644 --- a/shared/raven/index.js +++ b/shared/raven/index.js @@ -20,7 +20,7 @@ if ( captureException: noop, setUserContext: noop, config: () => ({ install: noop }), - requestHandler: (req, res, next) => next(), + requestHandler: () => (req, res, next) => next(), parsers: { parseRequest: noop, }, From 57ea238886642ae27dd6bc375097b48e1d4e2106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20=C3=91=C3=A1=C3=B1ez=20Ortiz?= Date: Sat, 28 Apr 2018 20:34:54 -0500 Subject: [PATCH 104/105] Fix typo --- docs/workers/intro.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/workers/intro.md b/docs/workers/intro.md index 877eeec7b2..ec445e1080 100644 --- a/docs/workers/intro.md +++ b/docs/workers/intro.md @@ -9,7 +9,7 @@ Our asynchronos background job processing is powered by a series of worker serve - [Hermes](hermes/intro.md): sends emails - [Mercury](mercury/intro.md): processes reputation events - [Pluto](pluto/intro.md): processes payments events -- [Vulan](vulcan/intro.md): indexes content for search +- [Vulcan](vulcan/intro.md): indexes content for search Each one of these can be run and developed independently with matching `npm run dev:x` and `npm run build:x` commands. (where `x` is the name of the server) @@ -28,4 +28,4 @@ As you can see we follow a loose naming scheme based on ancient Greek, Roman, an Many of our workers run off of our [Redis queue](background-jobs.md) to handle asynchronous events. -[Learn more about background jobs](background-jobs.md) \ No newline at end of file +[Learn more about background jobs](background-jobs.md) From 760517ec4bbbdc4b9d171ebaf32d7eef157646f3 Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Mon, 30 Apr 2018 08:29:56 -0700 Subject: [PATCH 105/105] Version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 656e024de0..1c0e786b7b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Spectrum", - "version": "2.2.7", + "version": "2.2.8", "license": "BSD-3-Clause", "devDependencies": { "babel-cli": "^6.24.1",