diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js
index 698768067a..52fd6d9be4 100644
--- a/src/components/structures/MessagePanel.js
+++ b/src/components/structures/MessagePanel.js
@@ -517,7 +517,8 @@ module.exports = React.createClass({
const DateSeparator = sdk.getComponent('messages.DateSeparator');
const ret = [];
- const isEditing = this.props.editEvent && this.props.editEvent.getId() === mxEv.getId();
+ const isEditing = this.props.editState &&
+ this.props.editState.getEvent().getId() === mxEv.getId();
// is this a continuation of the previous message?
let continuation = false;
@@ -585,7 +586,7 @@ module.exports = React.createClass({
continuation={continuation}
isRedacted={mxEv.isRedacted()}
replacingEventId={mxEv.replacingEventId()}
- isEditing={isEditing}
+ editState={isEditing && this.props.editState}
onHeightChanged={this._onHeightChanged}
readReceipts={readReceipts}
readReceiptMap={this._readReceiptMap}
diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js
index 220c56d754..9c48b8ede1 100644
--- a/src/components/structures/TimelinePanel.js
+++ b/src/components/structures/TimelinePanel.js
@@ -35,6 +35,7 @@ const Modal = require("../../Modal");
const UserActivity = require("../../UserActivity");
import { KeyCode } from '../../Keyboard';
import Timer from '../../utils/Timer';
+import EditorStateTransfer from '../../utils/EditorStateTransfer';
const PAGINATE_SIZE = 20;
const INITIAL_SIZE = 20;
@@ -411,7 +412,8 @@ const TimelinePanel = React.createClass({
this.forceUpdate();
}
if (payload.action === "edit_event") {
- this.setState({editEvent: payload.event}, () => {
+ const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
+ this.setState({editState}, () => {
if (payload.event && this.refs.messagePanel) {
this.refs.messagePanel.scrollToEventIfNeeded(
payload.event.getId(),
@@ -1306,7 +1308,7 @@ const TimelinePanel = React.createClass({
tileShape={this.props.tileShape}
resizeNotifier={this.props.resizeNotifier}
getRelationsForEvent={this.getRelationsForEvent}
- editEvent={this.state.editEvent}
+ editState={this.state.editState}
showReactions={this.props.showReactions}
/>
);
diff --git a/src/components/views/elements/MessageEditor.js b/src/components/views/elements/MessageEditor.js
index ed86bcb0a3..0aff6781ee 100644
--- a/src/components/views/elements/MessageEditor.js
+++ b/src/components/views/elements/MessageEditor.js
@@ -28,13 +28,14 @@ import {parseEvent} from '../../../editor/deserialize';
import Autocomplete from '../rooms/Autocomplete';
import {PartCreator} from '../../../editor/parts';
import {renderModel} from '../../../editor/render';
-import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
+import EditorStateTransfer from '../../../utils/EditorStateTransfer';
+import {MatrixClient} from 'matrix-js-sdk';
import classNames from 'classnames';
export default class MessageEditor extends React.Component {
static propTypes = {
// the message event being edited
- event: PropTypes.instanceOf(MatrixEvent).isRequired,
+ editState: PropTypes.instanceOf(EditorStateTransfer).isRequired,
};
static contextTypes = {
@@ -44,16 +45,7 @@ export default class MessageEditor extends React.Component {
constructor(props, context) {
super(props, context);
const room = this._getRoom();
- const partCreator = new PartCreator(
- () => this._autocompleteRef,
- query => this.setState({query}),
- room,
- );
- this.model = new EditorModel(
- parseEvent(this.props.event, room),
- partCreator,
- this._updateEditorState,
- );
+ this.model = null;
this.state = {
autoComplete: null,
room,
@@ -64,7 +56,7 @@ export default class MessageEditor extends React.Component {
}
_getRoom() {
- return this.context.matrixClient.getRoom(this.props.event.getRoomId());
+ return this.context.matrixClient.getRoom(this.props.editState.getEvent().getRoomId());
}
_updateEditorState = (caret) => {
@@ -133,7 +125,7 @@ export default class MessageEditor extends React.Component {
if (this._hasModifications || !this._isCaretAtStart()) {
return;
}
- const previousEvent = findEditableEvent(this._getRoom(), false, this.props.event.getId());
+ const previousEvent = findEditableEvent(this._getRoom(), false, this.props.editState.getEvent().getId());
if (previousEvent) {
dis.dispatch({action: 'edit_event', event: previousEvent});
event.preventDefault();
@@ -142,7 +134,7 @@ export default class MessageEditor extends React.Component {
if (this._hasModifications || !this._isCaretAtEnd()) {
return;
}
- const nextEvent = findEditableEvent(this._getRoom(), true, this.props.event.getId());
+ const nextEvent = findEditableEvent(this._getRoom(), true, this.props.editState.getEvent().getId());
if (nextEvent) {
dis.dispatch({action: 'edit_event', event: nextEvent});
} else {
@@ -178,11 +170,11 @@ export default class MessageEditor extends React.Component {
"m.new_content": newContent,
"m.relates_to": {
"rel_type": "m.replace",
- "event_id": this.props.event.getId(),
+ "event_id": this.props.editState.getEvent().getId(),
},
}, contentBody);
- const roomId = this.props.event.getRoomId();
+ const roomId = this.props.editState.getEvent().getRoomId();
this.context.matrixClient.sendMessage(roomId, content);
dis.dispatch({action: "edit_event", event: null});
@@ -197,12 +189,63 @@ export default class MessageEditor extends React.Component {
this.model.autoComplete.onComponentSelectionChange(completion);
}
+ componentWillUnmount() {
+ const sel = document.getSelection();
+ const {caret} = getCaretOffsetAndText(this._editorRef, sel);
+ const parts = this.model.serializeParts();
+ this.props.editState.setEditorState(caret, parts);
+ }
+
componentDidMount() {
+ this.model = this._createEditorModel();
+ // initial render of model
this._updateEditorState();
- setCaretPosition(this._editorRef, this.model, this.model.getPositionAtEnd());
+ // initial caret position
+ this._initializeCaret();
this._editorRef.focus();
}
+ _createEditorModel() {
+ const {editState} = this.props;
+ const room = this._getRoom();
+ const partCreator = new PartCreator(
+ () => this._autocompleteRef,
+ query => this.setState({query}),
+ room,
+ this.context.matrixClient,
+ );
+ let parts;
+ if (editState.hasEditorState()) {
+ // if restoring state from a previous editor,
+ // restore serialized parts from the state
+ parts = editState.getSerializedParts().map(p => partCreator.deserializePart(p));
+ } else {
+ // otherwise, parse the body of the event
+ parts = parseEvent(editState.getEvent(), room, this.context.matrixClient);
+ }
+
+ return new EditorModel(
+ parts,
+ partCreator,
+ this._updateEditorState,
+ );
+ }
+
+ _initializeCaret() {
+ const {editState} = this.props;
+ let caretPosition;
+ if (editState.hasEditorState()) {
+ // if restoring state from a previous editor,
+ // restore caret position from the state
+ const caret = editState.getCaret();
+ caretPosition = this.model.positionForOffset(caret.offset, caret.atNodeEnd);
+ } else {
+ // otherwise, set it at the end
+ caretPosition = this.model.getPositionAtEnd();
+ }
+ setCaretPosition(this._editorRef, this.model, caretPosition);
+ }
+
render() {
let autoComplete;
if (this.state.autoComplete) {
diff --git a/src/components/views/messages/MessageEvent.js b/src/components/views/messages/MessageEvent.js
index 8c90ec5a46..6d7aada542 100644
--- a/src/components/views/messages/MessageEvent.js
+++ b/src/components/views/messages/MessageEvent.js
@@ -90,7 +90,7 @@ module.exports = React.createClass({
tileShape={this.props.tileShape}
maxImageHeight={this.props.maxImageHeight}
replacingEventId={this.props.replacingEventId}
- isEditing={this.props.isEditing}
+ editState={this.props.editState}
onHeightChanged={this.props.onHeightChanged} />;
},
});
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 1fc16d6a53..6f480b8d3c 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -90,7 +90,7 @@ module.exports = React.createClass({
componentDidMount: function() {
this._unmounted = false;
- if (!this.props.isEditing) {
+ if (!this.props.editState) {
this._applyFormatting();
}
},
@@ -131,8 +131,8 @@ module.exports = React.createClass({
},
componentDidUpdate: function(prevProps) {
- if (!this.props.isEditing) {
- const stoppedEditing = prevProps.isEditing && !this.props.isEditing;
+ if (!this.props.editState) {
+ const stoppedEditing = prevProps.editState && !this.props.editState;
const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId;
if (messageWasEdited || stoppedEditing) {
this._applyFormatting();
@@ -153,7 +153,7 @@ module.exports = React.createClass({
nextProps.replacingEventId !== this.props.replacingEventId ||
nextProps.highlightLink !== this.props.highlightLink ||
nextProps.showUrlPreview !== this.props.showUrlPreview ||
- nextProps.isEditing !== this.props.isEditing ||
+ nextProps.editState !== this.props.editState ||
nextState.links !== this.state.links ||
nextState.editedMarkerHovered !== this.state.editedMarkerHovered ||
nextState.widgetHidden !== this.state.widgetHidden);
@@ -469,9 +469,9 @@ module.exports = React.createClass({
},
render: function() {
- if (this.props.isEditing) {
+ if (this.props.editState) {
const MessageEditor = sdk.getComponent('elements.MessageEditor');
- return ;
+ return ;
}
const mxEvent = this.props.mxEvent;
const content = mxEvent.getContent();
diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js
index 850f496e24..9837b4a029 100644
--- a/src/components/views/rooms/EventTile.js
+++ b/src/components/views/rooms/EventTile.js
@@ -552,13 +552,14 @@ module.exports = withMatrixClient(React.createClass({
const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted;
const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure();
+ const isEditing = !!this.props.editState;
const classes = classNames({
mx_EventTile: true,
- mx_EventTile_isEditing: this.props.isEditing,
+ mx_EventTile_isEditing: isEditing,
mx_EventTile_info: isInfoMessage,
mx_EventTile_12hr: this.props.isTwelveHour,
mx_EventTile_encrypting: this.props.eventSendStatus === 'encrypting',
- mx_EventTile_sending: isSending,
+ mx_EventTile_sending: !isEditing && isSending,
mx_EventTile_notSent: this.props.eventSendStatus === 'not_sent',
mx_EventTile_highlight: this.props.tileShape === 'notif' ? false : this.shouldHighlight(),
mx_EventTile_selected: this.props.isSelectedEvent,
@@ -632,7 +633,7 @@ module.exports = withMatrixClient(React.createClass({
}
const MessageActionBar = sdk.getComponent('messages.MessageActionBar');
- const actionBar = !this.props.isEditing ? {
const partLen = part.text.length;