From 084a933dbdd54fcfdc2186249bf5ef563e57b648 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 5 Jul 2017 10:24:55 +0100 Subject: [PATCH] Implement MessageComposerStore to persist composer state across room switching This behaviour was present in the old composer but implemented using local storage. This is unecessary as we don't really care about our drafts across clients, the important thing is that our draft is kept when switching rooms. As a bonus, ifnore vertical arrow key presses when a modifier key is pressed so that the room switching keys (alt + up/down arrow) don't also cause history browsing (or autocomplete browsing). --- .../views/rooms/MessageComposerInput.js | 19 +++++ src/stores/MessageComposerStore.js | 73 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/stores/MessageComposerStore.js diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 818c108211..01e836765a 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -43,6 +43,8 @@ import Markdown from '../../../Markdown'; import ComposerHistoryManager from '../../../ComposerHistoryManager'; import {onSendMessageFailed} from './MessageComposerInputOld'; +import MessageComposerStore from '../../../stores/MessageComposerStore'; + const TYPING_USER_TIMEOUT = 10000, TYPING_SERVER_TIMEOUT = 30000; const ZWS_CODE = 8203; @@ -170,6 +172,11 @@ export default class MessageComposerInput extends React.Component { componentDidMount() { this.dispatcherRef = dis.register(this.onAction); this.historyManager = new ComposerHistoryManager(this.props.room.roomId); + + // Reinstate the editor state for this room + this.setState({ + editorState: MessageComposerStore.getEditorState(this.props.room.roomId), + }); } componentWillUnmount() { @@ -336,6 +343,14 @@ export default class MessageComposerInput extends React.Component { this.onFinishedTyping(); } + // Record the editor state for this room so that it can be retrieved after + // switching to another room and back + dis.dispatch({ + action: 'editor_state', + room_id: this.props.room.roomId, + editor_state: state.editorState, + }); + if (!state.hasOwnProperty('originalEditorState')) { state.originalEditorState = null; } @@ -632,6 +647,10 @@ export default class MessageComposerInput extends React.Component { }; onVerticalArrow = (e, up) => { + if (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) { + return; + } + // Select history only if we are not currently auto-completing if (this.autocomplete.state.completionList.length === 0) { // Don't go back in history if we're in the middle of a multi-line message diff --git a/src/stores/MessageComposerStore.js b/src/stores/MessageComposerStore.js new file mode 100644 index 0000000000..1e83105300 --- /dev/null +++ b/src/stores/MessageComposerStore.js @@ -0,0 +1,73 @@ +/* +Copyright 2017 Vector Creations Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +import dis from '../dispatcher'; +import {Store} from 'flux/utils'; + +const INITIAL_STATE = { + editorStateMap: {}, +}; + +/** + * A class for storing application state to do with the message composer. This is a simple + * flux store that listens for actions and updates its state accordingly, informing any + * listeners (views) of state changes. + */ +class MessageComposerStore extends Store { + constructor() { + super(dis); + + // Initialise state + this._state = INITIAL_STATE; + } + + _setState(newState) { + this._state = Object.assign(this._state, newState); + this.__emitChange(); + } + + __onDispatch(payload) { + switch (payload.action) { + case 'editor_state': + this._editorState(payload); + break; + case 'on_logged_out': + this.reset(); + break; + } + } + + _editorState(payload) { + const editorStateMap = this._state.editorStateMap; + editorStateMap[payload.room_id] = payload.editor_state; + this._setState({ + editorStateMap: editorStateMap, + }); + } + + getEditorState(roomId) { + return this._state.editorStateMap[roomId]; + } + + reset() { + this._state = Object.assign({}, INITIAL_STATE); + } +} + +let singletonMessageComposerStore = null; +if (!singletonMessageComposerStore) { + singletonMessageComposerStore = new MessageComposerStore(); +} +module.exports = singletonMessageComposerStore;