Merge pull request #6353 from SimonBrandner/feature/improved-composer

Improve handling of pills in the composer
This commit is contained in:
Travis Ralston 2021-08-11 10:55:13 -06:00 committed by GitHub
commit a149108a7d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 247 additions and 189 deletions

View file

@ -31,7 +31,7 @@ import {
} from '../../../editor/operations';
import { getCaretOffsetAndText, getRangeForSelection } from '../../../editor/dom';
import Autocomplete, { generateCompletionDomId } from '../rooms/Autocomplete';
import { getAutoCompleteCreator } from '../../../editor/parts';
import { getAutoCompleteCreator, Type } from '../../../editor/parts';
import { parseEvent, parsePlainTextMessage } from '../../../editor/deserialize';
import { renderModel } from '../../../editor/render';
import TypingStore from "../../../stores/TypingStore";
@ -169,7 +169,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
range.expandBackwardsWhile((index, offset) => {
const part = model.parts[index];
n -= 1;
return n >= 0 && (part.type === "plain" || part.type === "pill-candidate");
return n >= 0 && (part.type === Type.Plain || part.type === Type.PillCandidate);
});
const emoticonMatch = REGEX_EMOTICON_WHITESPACE.exec(range.text);
if (emoticonMatch) {
@ -541,6 +541,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
handled = true;
} else if (event.key === Key.BACKSPACE || event.key === Key.DELETE) {
this.formatBarRef.current.hide();
handled = this.fakeDeletion(event.key === Key.BACKSPACE);
}
if (handled) {
@ -549,6 +550,29 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
}
};
/**
* Because pills have contentEditable="false" there is no event emitted when
* the user tries to delete them. Therefore we need to fake what would
* normally happen
* @param direction in which to delete
* @returns handled
*/
private fakeDeletion(backward: boolean): boolean {
const selection = document.getSelection();
// Use the default handling for ranges
if (selection.type === "Range") return false;
this.modifiedFlag = true;
const { caret, text } = getCaretOffsetAndText(this.editorRef.current, selection);
// Do the deletion itself
if (backward) caret.offset--;
const newText = text.slice(0, caret.offset) + text.slice(caret.offset + 1);
this.props.model.update(newText, backward ? "deleteContentBackward" : "deleteContentForward", caret);
return true;
}
private async tabCompleteName(): Promise<void> {
try {
await new Promise<void>(resolve => this.setState({ showVisualBell: false }, resolve));
@ -558,9 +582,9 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
const range = model.startRange(position);
range.expandBackwardsWhile((index, offset, part) => {
return part.text[offset] !== " " && part.text[offset] !== "+" && (
part.type === "plain" ||
part.type === "pill-candidate" ||
part.type === "command"
part.type === Type.Plain ||
part.type === Type.PillCandidate ||
part.type === Type.Command
);
});
const { partCreator } = model;

View file

@ -25,7 +25,7 @@ import { getCaretOffsetAndText } from '../../../editor/dom';
import { htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand } from '../../../editor/serialize';
import { findEditableEvent } from '../../../utils/EventUtils';
import { parseEvent } from '../../../editor/deserialize';
import { CommandPartCreator, Part, PartCreator } from '../../../editor/parts';
import { CommandPartCreator, Part, PartCreator, Type } from '../../../editor/parts';
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
import BasicMessageComposer from "./BasicMessageComposer";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
@ -242,12 +242,12 @@ export default class EditMessageComposer extends React.Component<IProps, IState>
const parts = this.model.parts;
const firstPart = parts[0];
if (firstPart) {
if (firstPart.type === "command" && firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")) {
if (firstPart.type === Type.Command && firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")) {
return true;
}
if (firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")
&& (firstPart.type === "plain" || firstPart.type === "pill-candidate")) {
&& (firstPart.type === Type.Plain || firstPart.type === Type.PillCandidate)) {
return true;
}
}
@ -268,7 +268,7 @@ export default class EditMessageComposer extends React.Component<IProps, IState>
private getSlashCommand(): [Command, string, string] {
const commandText = this.model.parts.reduce((text, part) => {
// use mxid to textify user pills in a command
if (part.type === "user-pill") {
if (part.type === Type.UserPill) {
return text + part.resourceId;
}
return text + part.text;

View file

@ -31,7 +31,7 @@ import {
textSerialize,
unescapeMessage,
} from '../../../editor/serialize';
import { CommandPartCreator, Part, PartCreator, SerializedPart } from '../../../editor/parts';
import { CommandPartCreator, Part, PartCreator, SerializedPart, Type } from '../../../editor/parts';
import BasicMessageComposer from "./BasicMessageComposer";
import ReplyThread from "../elements/ReplyThread";
import { findEditableEvent } from '../../../utils/EventUtils';
@ -240,14 +240,14 @@ export default class SendMessageComposer extends React.Component<IProps> {
const parts = this.model.parts;
const firstPart = parts[0];
if (firstPart) {
if (firstPart.type === "command" && firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")) {
if (firstPart.type === Type.Command && firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")) {
return true;
}
// be extra resilient when somehow the AutocompleteWrapperModel or
// CommandPartCreator fails to insert a command part, so we don't send
// a command as a message
if (firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")
&& (firstPart.type === "plain" || firstPart.type === "pill-candidate")) {
&& (firstPart.type === Type.Plain || firstPart.type === Type.PillCandidate)) {
return true;
}
}