use EditorStateTransfer to pass on state to newly mounted editor
This commit is contained in:
parent
e674f39e3b
commit
41e41269dc
7 changed files with 83 additions and 36 deletions
|
@ -517,7 +517,8 @@ module.exports = React.createClass({
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
const ret = [];
|
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?
|
// is this a continuation of the previous message?
|
||||||
let continuation = false;
|
let continuation = false;
|
||||||
|
|
||||||
|
@ -585,7 +586,7 @@ module.exports = React.createClass({
|
||||||
continuation={continuation}
|
continuation={continuation}
|
||||||
isRedacted={mxEv.isRedacted()}
|
isRedacted={mxEv.isRedacted()}
|
||||||
replacingEventId={mxEv.replacingEventId()}
|
replacingEventId={mxEv.replacingEventId()}
|
||||||
isEditing={isEditing}
|
editState={isEditing && this.props.editState}
|
||||||
onHeightChanged={this._onHeightChanged}
|
onHeightChanged={this._onHeightChanged}
|
||||||
readReceipts={readReceipts}
|
readReceipts={readReceipts}
|
||||||
readReceiptMap={this._readReceiptMap}
|
readReceiptMap={this._readReceiptMap}
|
||||||
|
|
|
@ -35,6 +35,7 @@ const Modal = require("../../Modal");
|
||||||
const UserActivity = require("../../UserActivity");
|
const UserActivity = require("../../UserActivity");
|
||||||
import { KeyCode } from '../../Keyboard';
|
import { KeyCode } from '../../Keyboard';
|
||||||
import Timer from '../../utils/Timer';
|
import Timer from '../../utils/Timer';
|
||||||
|
import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
||||||
|
|
||||||
const PAGINATE_SIZE = 20;
|
const PAGINATE_SIZE = 20;
|
||||||
const INITIAL_SIZE = 20;
|
const INITIAL_SIZE = 20;
|
||||||
|
@ -411,7 +412,8 @@ const TimelinePanel = React.createClass({
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
if (payload.action === "edit_event") {
|
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) {
|
if (payload.event && this.refs.messagePanel) {
|
||||||
this.refs.messagePanel.scrollToEventIfNeeded(
|
this.refs.messagePanel.scrollToEventIfNeeded(
|
||||||
payload.event.getId(),
|
payload.event.getId(),
|
||||||
|
@ -1306,7 +1308,7 @@ const TimelinePanel = React.createClass({
|
||||||
tileShape={this.props.tileShape}
|
tileShape={this.props.tileShape}
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
getRelationsForEvent={this.getRelationsForEvent}
|
getRelationsForEvent={this.getRelationsForEvent}
|
||||||
editEvent={this.state.editEvent}
|
editState={this.state.editState}
|
||||||
showReactions={this.props.showReactions}
|
showReactions={this.props.showReactions}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -28,13 +28,14 @@ import {parseEvent} from '../../../editor/deserialize';
|
||||||
import Autocomplete from '../rooms/Autocomplete';
|
import Autocomplete from '../rooms/Autocomplete';
|
||||||
import {PartCreator} from '../../../editor/parts';
|
import {PartCreator} from '../../../editor/parts';
|
||||||
import {renderModel} from '../../../editor/render';
|
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';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export default class MessageEditor extends React.Component {
|
export default class MessageEditor extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// the message event being edited
|
// the message event being edited
|
||||||
event: PropTypes.instanceOf(MatrixEvent).isRequired,
|
editState: PropTypes.instanceOf(EditorStateTransfer).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
@ -44,16 +45,7 @@ export default class MessageEditor extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
const room = this._getRoom();
|
const room = this._getRoom();
|
||||||
const partCreator = new PartCreator(
|
this.model = null;
|
||||||
() => this._autocompleteRef,
|
|
||||||
query => this.setState({query}),
|
|
||||||
room,
|
|
||||||
);
|
|
||||||
this.model = new EditorModel(
|
|
||||||
parseEvent(this.props.event, room),
|
|
||||||
partCreator,
|
|
||||||
this._updateEditorState,
|
|
||||||
);
|
|
||||||
this.state = {
|
this.state = {
|
||||||
autoComplete: null,
|
autoComplete: null,
|
||||||
room,
|
room,
|
||||||
|
@ -64,7 +56,7 @@ export default class MessageEditor extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_getRoom() {
|
_getRoom() {
|
||||||
return this.context.matrixClient.getRoom(this.props.event.getRoomId());
|
return this.context.matrixClient.getRoom(this.props.editState.getEvent().getRoomId());
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateEditorState = (caret) => {
|
_updateEditorState = (caret) => {
|
||||||
|
@ -133,7 +125,7 @@ export default class MessageEditor extends React.Component {
|
||||||
if (this._hasModifications || !this._isCaretAtStart()) {
|
if (this._hasModifications || !this._isCaretAtStart()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const previousEvent = findEditableEvent(this._getRoom(), false, this.props.event.getId());
|
const previousEvent = findEditableEvent(this._getRoom(), false, this.props.editState.getEvent().getId());
|
||||||
if (previousEvent) {
|
if (previousEvent) {
|
||||||
dis.dispatch({action: 'edit_event', event: previousEvent});
|
dis.dispatch({action: 'edit_event', event: previousEvent});
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -142,7 +134,7 @@ export default class MessageEditor extends React.Component {
|
||||||
if (this._hasModifications || !this._isCaretAtEnd()) {
|
if (this._hasModifications || !this._isCaretAtEnd()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nextEvent = findEditableEvent(this._getRoom(), true, this.props.event.getId());
|
const nextEvent = findEditableEvent(this._getRoom(), true, this.props.editState.getEvent().getId());
|
||||||
if (nextEvent) {
|
if (nextEvent) {
|
||||||
dis.dispatch({action: 'edit_event', event: nextEvent});
|
dis.dispatch({action: 'edit_event', event: nextEvent});
|
||||||
} else {
|
} else {
|
||||||
|
@ -178,11 +170,11 @@ export default class MessageEditor extends React.Component {
|
||||||
"m.new_content": newContent,
|
"m.new_content": newContent,
|
||||||
"m.relates_to": {
|
"m.relates_to": {
|
||||||
"rel_type": "m.replace",
|
"rel_type": "m.replace",
|
||||||
"event_id": this.props.event.getId(),
|
"event_id": this.props.editState.getEvent().getId(),
|
||||||
},
|
},
|
||||||
}, contentBody);
|
}, contentBody);
|
||||||
|
|
||||||
const roomId = this.props.event.getRoomId();
|
const roomId = this.props.editState.getEvent().getRoomId();
|
||||||
this.context.matrixClient.sendMessage(roomId, content);
|
this.context.matrixClient.sendMessage(roomId, content);
|
||||||
|
|
||||||
dis.dispatch({action: "edit_event", event: null});
|
dis.dispatch({action: "edit_event", event: null});
|
||||||
|
@ -197,12 +189,63 @@ export default class MessageEditor extends React.Component {
|
||||||
this.model.autoComplete.onComponentSelectionChange(completion);
|
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() {
|
componentDidMount() {
|
||||||
|
this.model = this._createEditorModel();
|
||||||
|
// initial render of model
|
||||||
this._updateEditorState();
|
this._updateEditorState();
|
||||||
setCaretPosition(this._editorRef, this.model, this.model.getPositionAtEnd());
|
// initial caret position
|
||||||
|
this._initializeCaret();
|
||||||
this._editorRef.focus();
|
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() {
|
render() {
|
||||||
let autoComplete;
|
let autoComplete;
|
||||||
if (this.state.autoComplete) {
|
if (this.state.autoComplete) {
|
||||||
|
|
|
@ -90,7 +90,7 @@ module.exports = React.createClass({
|
||||||
tileShape={this.props.tileShape}
|
tileShape={this.props.tileShape}
|
||||||
maxImageHeight={this.props.maxImageHeight}
|
maxImageHeight={this.props.maxImageHeight}
|
||||||
replacingEventId={this.props.replacingEventId}
|
replacingEventId={this.props.replacingEventId}
|
||||||
isEditing={this.props.isEditing}
|
editState={this.props.editState}
|
||||||
onHeightChanged={this.props.onHeightChanged} />;
|
onHeightChanged={this.props.onHeightChanged} />;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -90,7 +90,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
this._unmounted = false;
|
this._unmounted = false;
|
||||||
if (!this.props.isEditing) {
|
if (!this.props.editState) {
|
||||||
this._applyFormatting();
|
this._applyFormatting();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -131,8 +131,8 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidUpdate: function(prevProps) {
|
componentDidUpdate: function(prevProps) {
|
||||||
if (!this.props.isEditing) {
|
if (!this.props.editState) {
|
||||||
const stoppedEditing = prevProps.isEditing && !this.props.isEditing;
|
const stoppedEditing = prevProps.editState && !this.props.editState;
|
||||||
const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId;
|
const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId;
|
||||||
if (messageWasEdited || stoppedEditing) {
|
if (messageWasEdited || stoppedEditing) {
|
||||||
this._applyFormatting();
|
this._applyFormatting();
|
||||||
|
@ -153,7 +153,7 @@ module.exports = React.createClass({
|
||||||
nextProps.replacingEventId !== this.props.replacingEventId ||
|
nextProps.replacingEventId !== this.props.replacingEventId ||
|
||||||
nextProps.highlightLink !== this.props.highlightLink ||
|
nextProps.highlightLink !== this.props.highlightLink ||
|
||||||
nextProps.showUrlPreview !== this.props.showUrlPreview ||
|
nextProps.showUrlPreview !== this.props.showUrlPreview ||
|
||||||
nextProps.isEditing !== this.props.isEditing ||
|
nextProps.editState !== this.props.editState ||
|
||||||
nextState.links !== this.state.links ||
|
nextState.links !== this.state.links ||
|
||||||
nextState.editedMarkerHovered !== this.state.editedMarkerHovered ||
|
nextState.editedMarkerHovered !== this.state.editedMarkerHovered ||
|
||||||
nextState.widgetHidden !== this.state.widgetHidden);
|
nextState.widgetHidden !== this.state.widgetHidden);
|
||||||
|
@ -469,9 +469,9 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
if (this.props.isEditing) {
|
if (this.props.editState) {
|
||||||
const MessageEditor = sdk.getComponent('elements.MessageEditor');
|
const MessageEditor = sdk.getComponent('elements.MessageEditor');
|
||||||
return <MessageEditor event={this.props.mxEvent} className="mx_EventTile_content" />;
|
return <MessageEditor editState={this.props.editState} className="mx_EventTile_content" />;
|
||||||
}
|
}
|
||||||
const mxEvent = this.props.mxEvent;
|
const mxEvent = this.props.mxEvent;
|
||||||
const content = mxEvent.getContent();
|
const content = mxEvent.getContent();
|
||||||
|
|
|
@ -552,13 +552,14 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted;
|
const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted;
|
||||||
const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure();
|
const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure();
|
||||||
|
|
||||||
|
const isEditing = !!this.props.editState;
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
mx_EventTile: true,
|
mx_EventTile: true,
|
||||||
mx_EventTile_isEditing: this.props.isEditing,
|
mx_EventTile_isEditing: isEditing,
|
||||||
mx_EventTile_info: isInfoMessage,
|
mx_EventTile_info: isInfoMessage,
|
||||||
mx_EventTile_12hr: this.props.isTwelveHour,
|
mx_EventTile_12hr: this.props.isTwelveHour,
|
||||||
mx_EventTile_encrypting: this.props.eventSendStatus === 'encrypting',
|
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_notSent: this.props.eventSendStatus === 'not_sent',
|
||||||
mx_EventTile_highlight: this.props.tileShape === 'notif' ? false : this.shouldHighlight(),
|
mx_EventTile_highlight: this.props.tileShape === 'notif' ? false : this.shouldHighlight(),
|
||||||
mx_EventTile_selected: this.props.isSelectedEvent,
|
mx_EventTile_selected: this.props.isSelectedEvent,
|
||||||
|
@ -632,7 +633,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageActionBar = sdk.getComponent('messages.MessageActionBar');
|
const MessageActionBar = sdk.getComponent('messages.MessageActionBar');
|
||||||
const actionBar = !this.props.isEditing ? <MessageActionBar
|
const actionBar = !isEditing ? <MessageActionBar
|
||||||
mxEvent={this.props.mxEvent}
|
mxEvent={this.props.mxEvent}
|
||||||
reactions={this.state.reactions}
|
reactions={this.state.reactions}
|
||||||
permalinkCreator={this.props.permalinkCreator}
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
|
@ -794,7 +795,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
<EventTileType ref="tile"
|
<EventTileType ref="tile"
|
||||||
mxEvent={this.props.mxEvent}
|
mxEvent={this.props.mxEvent}
|
||||||
replacingEventId={this.props.replacingEventId}
|
replacingEventId={this.props.replacingEventId}
|
||||||
isEditing={this.props.isEditing}
|
editState={this.props.editState}
|
||||||
highlights={this.props.highlights}
|
highlights={this.props.highlights}
|
||||||
highlightLink={this.props.highlightLink}
|
highlightLink={this.props.highlightLink}
|
||||||
showUrlPreview={this.props.showUrlPreview}
|
showUrlPreview={this.props.showUrlPreview}
|
||||||
|
|
|
@ -88,7 +88,7 @@ export default class EditorModel {
|
||||||
|
|
||||||
update(newValue, inputType, caret) {
|
update(newValue, inputType, caret) {
|
||||||
const diff = this._diff(newValue, inputType, caret);
|
const diff = this._diff(newValue, inputType, caret);
|
||||||
const position = this._positionForOffset(diff.at, caret.atNodeEnd);
|
const position = this.positionForOffset(diff.at, caret.atNodeEnd);
|
||||||
let removedOffsetDecrease = 0;
|
let removedOffsetDecrease = 0;
|
||||||
if (diff.removed) {
|
if (diff.removed) {
|
||||||
removedOffsetDecrease = this._removeText(position, diff.removed.length);
|
removedOffsetDecrease = this._removeText(position, diff.removed.length);
|
||||||
|
@ -99,7 +99,7 @@ export default class EditorModel {
|
||||||
}
|
}
|
||||||
this._mergeAdjacentParts();
|
this._mergeAdjacentParts();
|
||||||
const caretOffset = diff.at - removedOffsetDecrease + addedLen;
|
const caretOffset = diff.at - removedOffsetDecrease + addedLen;
|
||||||
let newPosition = this._positionForOffset(caretOffset, true);
|
let newPosition = this.positionForOffset(caretOffset, true);
|
||||||
newPosition = newPosition.skipUneditableParts(this._parts);
|
newPosition = newPosition.skipUneditableParts(this._parts);
|
||||||
this._setActivePart(newPosition);
|
this._setActivePart(newPosition);
|
||||||
this._updateCallback(newPosition);
|
this._updateCallback(newPosition);
|
||||||
|
@ -248,7 +248,7 @@ export default class EditorModel {
|
||||||
return addLen;
|
return addLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
_positionForOffset(totalOffset, atPartEnd) {
|
positionForOffset(totalOffset, atPartEnd) {
|
||||||
let currentOffset = 0;
|
let currentOffset = 0;
|
||||||
const index = this._parts.findIndex(part => {
|
const index = this._parts.findIndex(part => {
|
||||||
const partLen = part.text.length;
|
const partLen = part.text.length;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue