support auto complete for /commands

This commit is contained in:
Bruno Windels 2019-08-21 15:27:50 +02:00
parent 88cc1c428d
commit c5cd8b943a
4 changed files with 41 additions and 9 deletions

View file

@ -20,7 +20,7 @@ import dis from '../../../dispatcher';
import EditorModel from '../../../editor/model'; import EditorModel from '../../../editor/model';
import {getCaretOffsetAndText} from '../../../editor/dom'; import {getCaretOffsetAndText} from '../../../editor/dom';
import {htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand} from '../../../editor/serialize'; import {htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand} from '../../../editor/serialize';
import {PartCreator} from '../../../editor/parts'; import {CommandPartCreator} from '../../../editor/parts';
import {MatrixClient} from 'matrix-js-sdk'; import {MatrixClient} from 'matrix-js-sdk';
import BasicMessageComposer from "./BasicMessageComposer"; import BasicMessageComposer from "./BasicMessageComposer";
import ReplyPreview from "./ReplyPreview"; import ReplyPreview from "./ReplyPreview";
@ -164,7 +164,7 @@ export default class SendMessageComposer extends React.Component {
_isSlashCommand() { _isSlashCommand() {
const parts = this.model.parts; const parts = this.model.parts;
const isPlain = parts.reduce((isPlain, part) => { const isPlain = parts.reduce((isPlain, part) => {
return isPlain && (part.type === "plain" || part.type === "newline"); return isPlain && (part.type === "command" || part.type === "plain" || part.type === "newline");
}, true); }, true);
return isPlain && parts.length > 0 && parts[0].text.startsWith("/"); return isPlain && parts.length > 0 && parts[0].text.startsWith("/");
} }
@ -227,15 +227,11 @@ export default class SendMessageComposer extends React.Component {
} }
componentWillUnmount() { componentWillUnmount() {
const sel = document.getSelection();
const {caret} = getCaretOffsetAndText(this._editorRef, sel);
const parts = this.model.serializeParts();
this.props.editState.setEditorState(caret, parts);
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
} }
componentWillMount() { componentWillMount() {
const partCreator = new PartCreator(this.props.room, this.context.matrixClient); const partCreator = new CommandPartCreator(this.props.room, this.context.matrixClient);
this.model = new EditorModel([], partCreator); this.model = new EditorModel([], partCreator);
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
this.sendHistoryManager = new ComposerHistoryManager(this.props.room.roomId, 'mx_slate_composer_history_'); this.sendHistoryManager = new ComposerHistoryManager(this.props.room.roomId, 'mx_slate_composer_history_');

View file

@ -303,7 +303,7 @@ export default class EditorModel {
index = 0; index = 0;
} }
while (str) { while (str) {
const newPart = this._partCreator.createPartForInput(str); const newPart = this._partCreator.createPartForInput(str, index);
if (validate) { if (validate) {
str = newPart.appendUntilRejected(str); str = newPart.appendUntilRejected(str);
} else { } else {

View file

@ -441,3 +441,33 @@ export class PartCreator {
} }
} }
// part creator that support auto complete for /commands,
// used in SendMessageComposer
export class CommandPartCreator extends PartCreator {
createPartForInput(text, partIndex) {
// at beginning and starts with /? create
if (partIndex === 0 && text[0] === "/") {
return new CommandPart("", this._autoCompleteCreator);
} else {
return super.createPartForInput(text, partIndex);
}
}
deserializePart(part) {
if (part.type === "command") {
return new CommandPart(part.text, this._autoCompleteCreator);
} else {
return super.deserializePart(part);
}
}
}
class CommandPart extends PillCandidatePart {
acceptsInsertion(chr, i) {
return PlainPart.prototype.acceptsInsertion.call(this, chr, i);
}
get type() {
return "command";
}
}

View file

@ -23,6 +23,7 @@ export function mdSerialize(model) {
case "newline": case "newline":
return html + "\n"; return html + "\n";
case "plain": case "plain":
case "command":
case "pill-candidate": case "pill-candidate":
case "at-room-pill": case "at-room-pill":
return html + part.text; return html + part.text;
@ -47,6 +48,7 @@ export function textSerialize(model) {
case "newline": case "newline":
return text + "\n"; return text + "\n";
case "plain": case "plain":
case "command":
case "pill-candidate": case "pill-candidate":
case "at-room-pill": case "at-room-pill":
return text + part.text; return text + part.text;
@ -59,7 +61,11 @@ export function textSerialize(model) {
export function containsEmote(model) { export function containsEmote(model) {
const firstPart = model.parts[0]; const firstPart = model.parts[0];
return firstPart && firstPart.type === "plain" && firstPart.text.startsWith("/me "); // part type will be "plain" while editing,
// and "command" while composing a message.
return firstPart &&
(firstPart.type === "plain" || firstPart.type === "command") &&
firstPart.text.startsWith("/me ");
} }
export function stripEmoteCommand(model) { export function stripEmoteCommand(model) {