move between editable events with arrow keys

pull/21833/head
Bruno Windels 2019-05-24 14:42:33 +02:00
parent 8926fcb3a6
commit 3591eedcfa
2 changed files with 47 additions and 8 deletions

View File

@ -23,6 +23,7 @@ import EditorModel from '../../../editor/model';
import {setCaretPosition} from '../../../editor/caret'; import {setCaretPosition} from '../../../editor/caret';
import {getCaretOffsetAndText} from '../../../editor/dom'; import {getCaretOffsetAndText} from '../../../editor/dom';
import {htmlSerializeIfNeeded, textSerialize} from '../../../editor/serialize'; import {htmlSerializeIfNeeded, textSerialize} from '../../../editor/serialize';
import {findPreviousEditableEvent, findNextEditableEvent} from '../../../utils/EventUtils';
import {parseEvent} from '../../../editor/deserialize'; 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';
@ -42,7 +43,7 @@ export default class MessageEditor extends React.Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
const room = this.context.matrixClient.getRoom(this.props.event.getRoomId()); const room = this._getRoom();
const partCreator = new PartCreator( const partCreator = new PartCreator(
() => this._autocompleteRef, () => this._autocompleteRef,
query => this.setState({query}), query => this.setState({query}),
@ -61,6 +62,10 @@ export default class MessageEditor extends React.Component {
this._autocompleteRef = null; this._autocompleteRef = null;
} }
_getRoom() {
return this.context.matrixClient.getRoom(this.props.event.getRoomId());
}
_updateEditorState = (caret) => { _updateEditorState = (caret) => {
renderModel(this._editorRef, this.model); renderModel(this._editorRef, this.model);
if (caret) { if (caret) {
@ -79,6 +84,16 @@ export default class MessageEditor extends React.Component {
this.model.update(text, event.inputType, caret); this.model.update(text, event.inputType, caret);
} }
_isCaretAtStart() {
const {caret} = getCaretOffsetAndText(this._editorRef, document.getSelection());
return caret.offset === 0;
}
_isCaretAtEnd() {
const {caret, text} = getCaretOffsetAndText(this._editorRef, document.getSelection());
return caret.offset === text.length;
}
_onKeyDown = (event) => { _onKeyDown = (event) => {
// insert newline on Shift+Enter // insert newline on Shift+Enter
if (event.shiftKey && event.key === "Enter") { if (event.shiftKey && event.key === "Enter") {
@ -112,6 +127,27 @@ export default class MessageEditor extends React.Component {
event.preventDefault(); event.preventDefault();
} else if (event.key === "Escape") { } else if (event.key === "Escape") {
this._cancelEdit(); this._cancelEdit();
} else if (event.key === "ArrowUp") {
if (!this._isCaretAtStart()) {
return;
}
const previousEvent = findPreviousEditableEvent(this._getRoom(), this.props.event.getId());
if (previousEvent) {
dis.dispatch({action: 'edit_event', event: previousEvent});
event.preventDefault();
}
} else if (event.key === "ArrowDown") {
if (!this._isCaretAtEnd()) {
return;
}
const nextEvent = findNextEditableEvent(this._getRoom(), this.props.event.getId());
if (nextEvent) {
dis.dispatch({action: 'edit_event', event: nextEvent});
} else {
dis.dispatch({action: 'edit_event', event: null});
dis.dispatch({action: 'focus_composer'});
}
event.preventDefault();
} }
} }

View File

@ -60,6 +60,7 @@ import RoomViewStore from '../../../stores/RoomViewStore';
import ReplyThread from "../elements/ReplyThread"; import ReplyThread from "../elements/ReplyThread";
import {ContentHelpers} from 'matrix-js-sdk'; import {ContentHelpers} from 'matrix-js-sdk';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import { findPreviousEditableEvent } from '../../../utils/EventUtils';
const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$'); const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
@ -1188,14 +1189,16 @@ export default class MessageComposerInput extends React.Component {
// and we must be at the edge of the document (up=start, down=end) // and we must be at the edge of the document (up=start, down=end)
if (up) { if (up) {
if (!selection.anchor.isAtStartOfNode(document)) return; if (!selection.anchor.isAtStartOfNode(document)) return;
} else {
if (!selection.anchor.isAtEndOfNode(document)) return;
}
const selected = this.selectHistory(up); const editEvent = findPreviousEditableEvent(this.props.room);
if (selected) { if (editEvent) {
// We're selecting history, so prevent the key event from doing anything else // We're selecting history, so prevent the key event from doing anything else
e.preventDefault(); e.preventDefault();
dis.dispatch({
action: 'edit_event',
event: editEvent,
});
}
} }
} else { } else {
this.moveAutocompleteSelection(up); this.moveAutocompleteSelection(up);