You've already forked matrix-react-sdk
mirror of
https://github.com/matrix-org/matrix-react-sdk.git
synced 2025-11-19 05:22:13 +03:00
Merge branches 'develop' and 't3chguy/restore_composer_history' of github.com:matrix-org/matrix-react-sdk into t3chguy/restore_composer_history
# Conflicts: # src/components/views/rooms/MessageComposerInput.js
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -28,6 +28,7 @@ import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
|
||||
import * as ServerType from '../../views/auth/ServerTypeSelector';
|
||||
import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||
import classNames from "classnames";
|
||||
import * as Lifecycle from '../../../Lifecycle';
|
||||
|
||||
// Phases
|
||||
// Show controls to configure server details
|
||||
@@ -80,6 +81,9 @@ module.exports = React.createClass({
|
||||
// Phase of the overall registration dialog.
|
||||
phase: PHASE_REGISTRATION,
|
||||
flows: null,
|
||||
// If set, we've registered but are not going to log
|
||||
// the user in to their new account automatically.
|
||||
completedNoSignin: false,
|
||||
|
||||
// We perform liveliness checks later, but for now suppress the errors.
|
||||
// We also track the server dead errors independently of the regular errors so
|
||||
@@ -209,6 +213,7 @@ module.exports = React.createClass({
|
||||
errorText: _t("Registration has been disabled on this homeserver."),
|
||||
});
|
||||
} else {
|
||||
console.log("Unable to query for supported registration methods.", e);
|
||||
this.setState({
|
||||
errorText: _t("Unable to query for supported registration methods."),
|
||||
});
|
||||
@@ -282,21 +287,27 @@ module.exports = React.createClass({
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
// we're still busy until we get unmounted: don't show the registration form again
|
||||
busy: true,
|
||||
const newState = {
|
||||
doingUIAuth: false,
|
||||
});
|
||||
};
|
||||
if (response.access_token) {
|
||||
const cli = await this.props.onLoggedIn({
|
||||
userId: response.user_id,
|
||||
deviceId: response.device_id,
|
||||
homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
|
||||
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
|
||||
accessToken: response.access_token,
|
||||
});
|
||||
|
||||
const cli = await this.props.onLoggedIn({
|
||||
userId: response.user_id,
|
||||
deviceId: response.device_id,
|
||||
homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
|
||||
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
|
||||
accessToken: response.access_token,
|
||||
});
|
||||
this._setupPushers(cli);
|
||||
// we're still busy until we get unmounted: don't show the registration form again
|
||||
newState.busy = true;
|
||||
} else {
|
||||
newState.busy = false;
|
||||
newState.completedNoSignin = true;
|
||||
}
|
||||
|
||||
this._setupPushers(cli);
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
_setupPushers: function(matrixClient) {
|
||||
@@ -353,6 +364,12 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
_makeRegisterRequest: function(auth) {
|
||||
// We inhibit login if we're trying to register with an email address: this
|
||||
// avoids a lot of complex race conditions that can occur if we try to log
|
||||
// the user in one one or both of the tabs they might end up with after
|
||||
// clicking the email link.
|
||||
let inhibitLogin = Boolean(this.state.formVals.email);
|
||||
|
||||
// Only send the bind params if we're sending username / pw params
|
||||
// (Since we need to send no params at all to use the ones saved in the
|
||||
// session).
|
||||
@@ -360,6 +377,8 @@ module.exports = React.createClass({
|
||||
email: true,
|
||||
msisdn: true,
|
||||
} : {};
|
||||
// Likewise inhibitLogin
|
||||
if (!this.state.formVals.password) inhibitLogin = null;
|
||||
|
||||
return this.state.matrixClient.register(
|
||||
this.state.formVals.username,
|
||||
@@ -368,6 +387,7 @@ module.exports = React.createClass({
|
||||
auth,
|
||||
bindThreepids,
|
||||
null,
|
||||
inhibitLogin,
|
||||
);
|
||||
},
|
||||
|
||||
@@ -379,6 +399,19 @@ module.exports = React.createClass({
|
||||
};
|
||||
},
|
||||
|
||||
// Links to the login page shown after registration is completed are routed through this
|
||||
// which checks the user hasn't already logged in somewhere else (perhaps we should do
|
||||
// this more generally?)
|
||||
_onLoginClickWithCheck: async function(ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
const sessionLoaded = await Lifecycle.loadSession({});
|
||||
if (!sessionLoaded) {
|
||||
// ok fine, there's still no session: really go to the login page
|
||||
this.props.onLoginClick();
|
||||
}
|
||||
},
|
||||
|
||||
renderServerComponent() {
|
||||
const ServerTypeSelector = sdk.getComponent("auth.ServerTypeSelector");
|
||||
const ServerConfig = sdk.getComponent("auth.ServerConfig");
|
||||
@@ -528,17 +561,49 @@ module.exports = React.createClass({
|
||||
</a>;
|
||||
}
|
||||
|
||||
let body;
|
||||
if (this.state.completedNoSignin) {
|
||||
let regDoneText;
|
||||
if (this.state.formVals.password) {
|
||||
// We're the client that started the registration
|
||||
regDoneText = _t(
|
||||
"<a>Log in</a> to your new account.", {},
|
||||
{
|
||||
a: (sub) => <a href="#/login" onClick={this._onLoginClickWithCheck}>{sub}</a>,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// We're not the original client: the user probably got to us by clicking the
|
||||
// email validation link. We can't offer a 'go straight to your account' link
|
||||
// as we don't have the original creds.
|
||||
regDoneText = _t(
|
||||
"You can now close this window or <a>log in</a> to your new account.", {},
|
||||
{
|
||||
a: (sub) => <a href="#/login" onClick={this._onLoginClickWithCheck}>{sub}</a>,
|
||||
},
|
||||
);
|
||||
}
|
||||
body = <div>
|
||||
<h2>{_t("Registration Successful")}</h2>
|
||||
<h3>{ regDoneText }</h3>
|
||||
</div>;
|
||||
} else {
|
||||
body = <div>
|
||||
<h2>{ _t('Create your account') }</h2>
|
||||
{ errorText }
|
||||
{ serverDeadSection }
|
||||
{ this.renderServerComponent() }
|
||||
{ this.renderRegisterComponent() }
|
||||
{ goBack }
|
||||
{ signIn }
|
||||
</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthPage>
|
||||
<AuthHeader />
|
||||
<AuthBody>
|
||||
<h2>{ _t('Create your account') }</h2>
|
||||
{ errorText }
|
||||
{ serverDeadSection }
|
||||
{ this.renderServerComponent() }
|
||||
{ this.renderRegisterComponent() }
|
||||
{ goBack }
|
||||
{ signIn }
|
||||
{ body }
|
||||
</AuthBody>
|
||||
</AuthPage>
|
||||
);
|
||||
|
||||
@@ -101,16 +101,25 @@ export default class ServerConfig extends React.PureComponent {
|
||||
return result;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
let message = _t("Unable to validate homeserver/identity server");
|
||||
if (e.translatedMessage) {
|
||||
message = e.translatedMessage;
|
||||
}
|
||||
this.setState({
|
||||
busy: false,
|
||||
errorText: message,
|
||||
});
|
||||
|
||||
return null;
|
||||
const stateForError = AutoDiscoveryUtils.authComponentStateForError(e);
|
||||
if (!stateForError.isFatalError) {
|
||||
// carry on anyway
|
||||
const result = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl, true);
|
||||
this.props.onServerConfigChange(result);
|
||||
return result;
|
||||
} else {
|
||||
let message = _t("Unable to validate homeserver/identity server");
|
||||
if (e.translatedMessage) {
|
||||
message = e.translatedMessage;
|
||||
}
|
||||
this.setState({
|
||||
busy: false,
|
||||
errorText: message,
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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} />;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 <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 content = mxEvent.getContent();
|
||||
|
||||
@@ -171,26 +171,13 @@ export default class Autocomplete extends React.Component {
|
||||
}
|
||||
|
||||
// called from MessageComposerInput
|
||||
onUpArrow(): ?Completion {
|
||||
moveSelection(delta): ?Completion {
|
||||
const completionCount = this.countCompletions();
|
||||
// completionCount + 1, since 0 means composer is selected
|
||||
const selectionOffset = (completionCount + 1 + this.state.selectionOffset - 1)
|
||||
% (completionCount + 1);
|
||||
if (!completionCount) {
|
||||
return null;
|
||||
}
|
||||
this.setSelection(selectionOffset);
|
||||
}
|
||||
if (completionCount === 0) return; // there are no items to move the selection through
|
||||
|
||||
// called from MessageComposerInput
|
||||
onDownArrow(): ?Completion {
|
||||
const completionCount = this.countCompletions();
|
||||
// completionCount + 1, since 0 means composer is selected
|
||||
const selectionOffset = (this.state.selectionOffset + 1) % (completionCount + 1);
|
||||
if (!completionCount) {
|
||||
return null;
|
||||
}
|
||||
this.setSelection(selectionOffset);
|
||||
// Note: selectionOffset 0 represents the unsubstituted text, while 1 means first pill selected
|
||||
const index = (this.state.selectionOffset + delta + completionCount + 1) % (completionCount + 1);
|
||||
this.setSelection(index);
|
||||
}
|
||||
|
||||
onEscape(e): boolean {
|
||||
|
||||
@@ -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 ? <MessageActionBar
|
||||
const actionBar = !isEditing ? <MessageActionBar
|
||||
mxEvent={this.props.mxEvent}
|
||||
reactions={this.state.reactions}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
@@ -794,7 +795,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||
<EventTileType ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
replacingEventId={this.props.replacingEventId}
|
||||
isEditing={this.props.isEditing}
|
||||
editState={this.props.editState}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
|
||||
@@ -676,6 +676,31 @@ export default class MessageComposerInput extends React.Component {
|
||||
|
||||
onKeyDown = (ev: KeyboardEvent, change: Change, editor: Editor) => {
|
||||
this.suppressAutoComplete = false;
|
||||
this.direction = '';
|
||||
|
||||
// Navigate autocomplete list with arrow keys
|
||||
if (this.autocomplete.countCompletions() > 0) {
|
||||
if (!(ev.ctrlKey || ev.shiftKey || ev.altKey || ev.metaKey)) {
|
||||
switch (ev.keyCode) {
|
||||
case KeyCode.LEFT:
|
||||
this.autocomplete.moveSelection(-1);
|
||||
ev.preventDefault();
|
||||
return true;
|
||||
case KeyCode.RIGHT:
|
||||
this.autocomplete.moveSelection(+1);
|
||||
ev.preventDefault();
|
||||
return true;
|
||||
case KeyCode.UP:
|
||||
this.autocomplete.moveSelection(-1);
|
||||
ev.preventDefault();
|
||||
return true;
|
||||
case KeyCode.DOWN:
|
||||
this.autocomplete.moveSelection(+1);
|
||||
ev.preventDefault();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// skip void nodes - see
|
||||
// https://github.com/ianstormtaylor/slate/issues/762#issuecomment-304855095
|
||||
@@ -683,8 +708,6 @@ export default class MessageComposerInput extends React.Component {
|
||||
this.direction = 'Previous';
|
||||
} else if (ev.keyCode === KeyCode.RIGHT) {
|
||||
this.direction = 'Next';
|
||||
} else {
|
||||
this.direction = '';
|
||||
}
|
||||
|
||||
switch (ev.keyCode) {
|
||||
@@ -1181,45 +1204,36 @@ export default class MessageComposerInput extends React.Component {
|
||||
};
|
||||
|
||||
onVerticalArrow = (e, up) => {
|
||||
if (e.ctrlKey || e.altKey || e.metaKey) {
|
||||
return;
|
||||
if (e.ctrlKey || e.shiftKey || e.metaKey) return;
|
||||
|
||||
// Select history
|
||||
const selection = this.state.editorState.selection;
|
||||
|
||||
// selection must be collapsed
|
||||
if (!selection.isCollapsed) return;
|
||||
const document = this.state.editorState.document;
|
||||
|
||||
// and we must be at the edge of the document (up=start, down=end)
|
||||
if (up) {
|
||||
if (!selection.anchor.isAtStartOfNode(document)) return;
|
||||
|
||||
if (!e.altKey) {
|
||||
const editEvent = findEditableEvent(this.props.room, false);
|
||||
if (editEvent) {
|
||||
// We're selecting history, so prevent the key event from doing anything else
|
||||
e.preventDefault();
|
||||
dis.dispatch({
|
||||
action: 'edit_event',
|
||||
event: editEvent,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Select history only if we are not currently auto-completing
|
||||
if (this.autocomplete.state.completionList.length === 0) {
|
||||
const selection = this.state.editorState.selection;
|
||||
|
||||
// selection must be collapsed
|
||||
if (!selection.isCollapsed) return;
|
||||
const document = this.state.editorState.document;
|
||||
|
||||
// and we must be at the edge of the document (up=start, down=end)
|
||||
if (up) {
|
||||
if (!selection.anchor.isAtStartOfNode(document)) return;
|
||||
|
||||
if (!e.shiftKey) {
|
||||
const editEvent = findEditableEvent(this.props.room, false);
|
||||
if (editEvent) {
|
||||
// We're selecting history, so prevent the key event from doing anything else
|
||||
e.preventDefault();
|
||||
dis.dispatch({
|
||||
action: 'edit_event',
|
||||
event: editEvent,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!selection.anchor.isAtEndOfNode(document)) return;
|
||||
}
|
||||
|
||||
const selected = this.selectHistory(up);
|
||||
if (selected) {
|
||||
// We're selecting history, so prevent the key event from doing anything else
|
||||
e.preventDefault();
|
||||
}
|
||||
} else {
|
||||
this.moveAutocompleteSelection(up);
|
||||
const selected = this.selectHistory(up);
|
||||
if (selected) {
|
||||
// We're selecting history, so prevent the key event from doing anything else
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
@@ -1277,23 +1291,19 @@ export default class MessageComposerInput extends React.Component {
|
||||
someCompletions: null,
|
||||
});
|
||||
e.preventDefault();
|
||||
if (this.autocomplete.state.completionList.length === 0) {
|
||||
if (this.autocomplete.countCompletions() === 0) {
|
||||
// Force completions to show for the text currently entered
|
||||
const completionCount = await this.autocomplete.forceComplete();
|
||||
this.setState({
|
||||
someCompletions: completionCount > 0,
|
||||
});
|
||||
// Select the first item by moving "down"
|
||||
await this.moveAutocompleteSelection(false);
|
||||
await this.autocomplete.moveSelection(+1);
|
||||
} else {
|
||||
await this.moveAutocompleteSelection(e.shiftKey);
|
||||
await this.autocomplete.moveSelection(e.shiftKey ? -1 : +1);
|
||||
}
|
||||
};
|
||||
|
||||
moveAutocompleteSelection = (up) => {
|
||||
up ? this.autocomplete.onUpArrow() : this.autocomplete.onDownArrow();
|
||||
};
|
||||
|
||||
onEscape = async (e) => {
|
||||
e.preventDefault();
|
||||
if (this.autocomplete) {
|
||||
|
||||
Reference in New Issue
Block a user