Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/a11y/composer-list-autocomplete

 Conflicts:
	src/components/views/rooms/BasicMessageComposer.tsx
	src/editor/autocomplete.ts
This commit is contained in:
Michael Telatynski 2021-07-15 09:59:40 +01:00
commit ebfe38dc4a
910 changed files with 29171 additions and 17758 deletions

View file

@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {KeyboardEvent} from "react";
import { KeyboardEvent } from "react";
import {Part, CommandPartCreator, PartCreator} from "./parts";
import { Part, CommandPartCreator, PartCreator } from "./parts";
import DocumentPosition from "./position";
import {ICompletion} from "../autocomplete/Autocompleter";
import { ICompletion } from "../autocomplete/Autocompleter";
import Autocomplete from "../components/views/rooms/Autocomplete";
export interface ICallback {
@ -47,7 +47,7 @@ export default class AutocompleteWrapperModel {
}
public close() {
this.updateCallback({close: true});
this.updateCallback({ close: true });
}
public hasSelection() {
@ -61,7 +61,7 @@ export default class AutocompleteWrapperModel {
public async confirmCompletion() {
await this.getAutocompleterComponent().onConfirmCompletion();
this.updateCallback({close: true});
this.updateCallback({ close: true });
}
/**
@ -96,7 +96,7 @@ export default class AutocompleteWrapperModel {
}
private partForCompletion(completion: ICompletion) {
const {completionId} = completion;
const { completionId } = completion;
const text = completion.completion;
switch (completion.type) {
case "room":

View file

@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {needsCaretNodeBefore, needsCaretNodeAfter} from "./render";
import { needsCaretNodeBefore, needsCaretNodeAfter } from "./render";
import Range from "./range";
import EditorModel from "./model";
import DocumentPosition, {IPosition} from "./position";
import {Part} from "./parts";
import DocumentPosition, { IPosition } from "./position";
import { Part } from "./parts";
export type Caret = Range | DocumentPosition;
@ -44,7 +44,7 @@ function setDocumentRangeSelection(editor: HTMLDivElement, model: EditorModel, r
export function setCaretPosition(editor: HTMLDivElement, model: EditorModel, caretPosition: IPosition) {
const range = document.createRange();
const {node, offset} = getNodeAndOffsetForPosition(editor, model, caretPosition);
const { node, offset } = getNodeAndOffsetForPosition(editor, model, caretPosition);
range.setStart(node, offset);
range.collapse(true);
@ -68,7 +68,7 @@ export function setCaretPosition(editor: HTMLDivElement, model: EditorModel, car
}
function getNodeAndOffsetForPosition(editor: HTMLDivElement, model: EditorModel, position: IPosition) {
const {offset, lineIndex, nodeIndex} = getLineAndNodePosition(model, position);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, position);
const lineNode = editor.childNodes[lineIndex];
let focusNode;
@ -82,16 +82,16 @@ function getNodeAndOffsetForPosition(editor: HTMLDivElement, model: EditorModel,
focusNode = focusNode.firstChild;
}
}
return {node: focusNode, offset};
return { node: focusNode, offset };
}
export function getLineAndNodePosition(model: EditorModel, caretPosition: IPosition) {
const {parts} = model;
const { parts } = model;
const partIndex = caretPosition.index;
const lineResult = findNodeInLineForPart(parts, partIndex);
const {lineIndex} = lineResult;
let {nodeIndex} = lineResult;
let {offset} = caretPosition;
const { lineIndex } = lineResult;
let { nodeIndex } = lineResult;
let { offset } = caretPosition;
// we're at an empty line between a newline part
// and another newline part or end/start of parts.
// set offset to 0 so it gets set to the <br> inside the line container
@ -99,9 +99,9 @@ export function getLineAndNodePosition(model: EditorModel, caretPosition: IPosit
offset = 0;
} else {
// move caret out of uneditable part (into caret node, or empty line br) if needed
({nodeIndex, offset} = moveOutOfUneditablePart(parts, partIndex, nodeIndex, offset));
({ nodeIndex, offset } = moveOutOfUneditablePart(parts, partIndex, nodeIndex, offset));
}
return {lineIndex, nodeIndex, offset};
return { lineIndex, nodeIndex, offset };
}
function findNodeInLineForPart(parts: Part[], partIndex: number) {
@ -137,7 +137,7 @@ function findNodeInLineForPart(parts: Part[], partIndex: number) {
}
}
return {lineIndex, nodeIndex};
return { lineIndex, nodeIndex };
}
function moveOutOfUneditablePart(parts: Part[], partIndex: number, nodeIndex: number, offset: number) {
@ -159,5 +159,5 @@ function moveOutOfUneditablePart(parts: Part[], partIndex: number, nodeIndex: nu
offset = 0;
}
}
return {nodeIndex, offset};
return { nodeIndex, offset };
}

View file

@ -42,7 +42,7 @@ function parseAtRoomMentions(text: string, partCreator: PartCreator) {
}
function parseLink(a: HTMLAnchorElement, partCreator: PartCreator) {
const {href} = a;
const { href } = a;
const resourceId = getPrimaryPermalinkEntity(href); // The room/user ID
const prefix = resourceId ? resourceId[0] : undefined; // First character of ID
switch (prefix) {
@ -297,7 +297,7 @@ export function parsePlainTextMessage(body: string, partCreator: PartCreator, is
}, []);
}
export function parseEvent(event: MatrixEvent, partCreator: PartCreator, {isQuotedMessage = false} = {}) {
export function parseEvent(event: MatrixEvent, partCreator: PartCreator, { isQuotedMessage = false } = {}) {
const content = event.getContent();
let parts;
if (content.format === "org.matrix.custom.html") {

View file

@ -35,9 +35,9 @@ function diffStringsAtEnd(oldStr: string, newStr: string): IDiff {
const len = Math.min(oldStr.length, newStr.length);
const startInCommon = oldStr.substr(0, len) === newStr.substr(0, len);
if (startInCommon && oldStr.length > newStr.length) {
return {removed: oldStr.substr(len), at: len};
return { removed: oldStr.substr(len), at: len };
} else if (startInCommon && oldStr.length < newStr.length) {
return {added: newStr.substr(len), at: len};
return { added: newStr.substr(len), at: len };
} else {
const commonStartLen = firstDiff(oldStr, newStr);
return {
@ -55,7 +55,7 @@ export function diffDeletion(oldStr: string, newStr: string): IDiff {
}
const firstDiffIdx = firstDiff(oldStr, newStr);
const amount = oldStr.length - newStr.length;
return {at: firstDiffIdx, removed: oldStr.substr(firstDiffIdx, amount)};
return { at: firstDiffIdx, removed: oldStr.substr(firstDiffIdx, amount) };
}
/**

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {CARET_NODE_CHAR, isCaretNode} from "./render";
import { CARET_NODE_CHAR, isCaretNode } from "./render";
import DocumentOffset from "./offset";
type Predicate = (node: Node) => boolean;
@ -44,8 +44,8 @@ export function walkDOMDepthFirst(rootNode: Node, enterNodeCallback: Predicate,
}
export function getCaretOffsetAndText(editor: HTMLDivElement, sel: Selection) {
const {offset, text} = getSelectionOffsetAndText(editor, sel.focusNode, sel.focusOffset);
return {caret: offset, text};
const { offset, text } = getSelectionOffsetAndText(editor, sel.focusNode, sel.focusOffset);
return { caret: offset, text };
}
function tryReduceSelectionToTextNode(selectionNode: Node, selectionOffset: number) {
@ -86,10 +86,10 @@ function tryReduceSelectionToTextNode(selectionNode: Node, selectionOffset: numb
}
function getSelectionOffsetAndText(editor: HTMLDivElement, selectionNode: Node, selectionOffset: number) {
const {node, characterOffset} = tryReduceSelectionToTextNode(selectionNode, selectionOffset);
const {text, offsetToNode} = getTextAndOffsetToNode(editor, node);
const { node, characterOffset } = tryReduceSelectionToTextNode(selectionNode, selectionOffset);
const { text, offsetToNode } = getTextAndOffsetToNode(editor, node);
const offset = getCaret(node, offsetToNode, characterOffset);
return {offset, text};
return { offset, text };
}
// gets the caret position details, ignoring and adjusting to
@ -163,7 +163,7 @@ function getTextAndOffsetToNode(editor: HTMLDivElement, selectionNode: Node) {
walkDOMDepthFirst(editor, enterNodeCallback, leaveNodeCallback);
return {text, offsetToNode};
return { text, offsetToNode };
}
// get text value of text node, ignoring ZWS if it's a caret node

View file

@ -15,9 +15,9 @@ limitations under the License.
*/
import EditorModel from "./model";
import {IDiff} from "./diff";
import {SerializedPart} from "./parts";
import {Caret} from "./caret";
import { IDiff } from "./diff";
import { SerializedPart } from "./parts";
import { Caret } from "./caret";
interface IHistory {
parts: SerializedPart[];
@ -92,7 +92,7 @@ export default class HistoryManager {
this.stack.pop();
}
const parts = model.serializeParts();
this.stack.push({parts, caret});
this.stack.push({ parts, caret });
this.currentIndex = this.stack.length - 1;
this.lastCaret = null;
this.changedSinceLastPush = false;

View file

@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {diffAtCaret, diffDeletion, IDiff} from "./diff";
import DocumentPosition, {IPosition} from "./position";
import { diffAtCaret, diffDeletion, IDiff } from "./diff";
import DocumentPosition, { IPosition } from "./position";
import Range from "./range";
import {SerializedPart, Part, PartCreator} from "./parts";
import AutocompleteWrapperModel, {ICallback} from "./autocomplete";
import { SerializedPart, Part, PartCreator } from "./parts";
import AutocompleteWrapperModel, { ICallback } from "./autocomplete";
import DocumentOffset from "./offset";
import {Caret} from "./caret";
import { Caret } from "./caret";
/**
* @callback ModelCallback
@ -70,7 +70,7 @@ export default class EditorModel {
* on the model that can span multiple parts. Also see `startRange()`.
* @param {TransformCallback} transformCallback
*/
setTransformCallback(transformCallback: TransformCallback) {
public setTransformCallback(transformCallback: TransformCallback): void {
this.transformCallback = transformCallback;
}
@ -78,23 +78,23 @@ export default class EditorModel {
* Set a callback for rerendering the model after it has been updated.
* @param {ModelCallback} updateCallback
*/
setUpdateCallback(updateCallback: UpdateCallback) {
public setUpdateCallback(updateCallback: UpdateCallback): void {
this.updateCallback = updateCallback;
}
get partCreator() {
public get partCreator(): PartCreator {
return this._partCreator;
}
get isEmpty() {
public get isEmpty(): boolean {
return this._parts.reduce((len, part) => len + part.text.length, 0) === 0;
}
clone() {
public clone(): EditorModel {
return new EditorModel(this._parts, this._partCreator, this.updateCallback);
}
private insertPart(index: number, part: Part) {
private insertPart(index: number, part: Part): void {
this._parts.splice(index, 0, part);
if (this.activePartIdx >= index) {
++this.activePartIdx;
@ -104,7 +104,7 @@ export default class EditorModel {
}
}
private removePart(index: number) {
private removePart(index: number): void {
this._parts.splice(index, 1);
if (index === this.activePartIdx) {
this.activePartIdx = null;
@ -118,22 +118,22 @@ export default class EditorModel {
}
}
private replacePart(index: number, part: Part) {
private replacePart(index: number, part: Part): void {
this._parts.splice(index, 1, part);
}
get parts() {
public get parts(): Part[] {
return this._parts;
}
get autoComplete() {
public get autoComplete(): AutocompleteWrapperModel {
if (this.activePartIdx === this.autoCompletePartIdx) {
return this._autoComplete;
}
return null;
}
getPositionAtEnd() {
public getPositionAtEnd(): DocumentPosition {
if (this._parts.length) {
const index = this._parts.length - 1;
const part = this._parts[index];
@ -144,11 +144,11 @@ export default class EditorModel {
}
}
serializeParts() {
public serializeParts(): SerializedPart[] {
return this._parts.map(p => p.serialize());
}
private diff(newValue: string, inputType: string, caret: DocumentOffset) {
private diff(newValue: string, inputType: string, caret: DocumentOffset): IDiff {
const previousValue = this.parts.reduce((text, p) => text + p.text, "");
// can't use caret position with drag and drop
if (inputType === "deleteByDrag") {
@ -158,7 +158,7 @@ export default class EditorModel {
}
}
reset(serializedParts: SerializedPart[], caret?: Caret, inputType?: string) {
public reset(serializedParts: SerializedPart[], caret?: Caret, inputType?: string): void {
this._parts = serializedParts.map(p => this._partCreator.deserializePart(p));
if (!caret) {
caret = this.getPositionAtEnd();
@ -180,7 +180,7 @@ export default class EditorModel {
* @param {DocumentPosition} position the position to start inserting at
* @return {Number} the amount of characters added
*/
insert(parts: Part[], position: IPosition) {
public insert(parts: Part[], position: IPosition): number {
const insertIndex = this.splitAt(position);
let newTextLength = 0;
for (let i = 0; i < parts.length; ++i) {
@ -191,7 +191,7 @@ export default class EditorModel {
return newTextLength;
}
update(newValue: string, inputType: string, caret: DocumentOffset) {
public update(newValue: string, inputType: string, caret: DocumentOffset): Promise<void> {
const diff = this.diff(newValue, inputType, caret);
const position = this.positionForOffset(diff.at, caret.atNodeEnd);
let removedOffsetDecrease = 0;
@ -220,8 +220,8 @@ export default class EditorModel {
return Number.isFinite(result) ? result as number : 0;
}
private setActivePart(pos: DocumentPosition, canOpenAutoComplete: boolean) {
const {index} = pos;
private setActivePart(pos: DocumentPosition, canOpenAutoComplete: boolean): Promise<void> {
const { index } = pos;
const part = this._parts[index];
if (part) {
if (index !== this.activePartIdx) {
@ -250,7 +250,7 @@ export default class EditorModel {
return Promise.resolve();
}
private onAutoComplete = ({replaceParts, close}: ICallback) => {
private onAutoComplete = ({ replaceParts, close }: ICallback): void => {
let pos;
if (replaceParts) {
this._parts.splice(this.autoCompletePartIdx, this.autoCompletePartCount, ...replaceParts);
@ -270,7 +270,7 @@ export default class EditorModel {
this.updateCallback(pos);
};
private mergeAdjacentParts() {
private mergeAdjacentParts(): void {
let prevPart;
for (let i = 0; i < this._parts.length; ++i) {
let part = this._parts[i];
@ -294,8 +294,8 @@ export default class EditorModel {
* @return {Number} how many characters before pos were also removed,
* usually because of non-editable parts that can only be removed in their entirety.
*/
removeText(pos: IPosition, len: number) {
let {index, offset} = pos;
public removeText(pos: IPosition, len: number): number {
let { index, offset } = pos;
let removedOffsetDecrease = 0;
while (len > 0) {
// part might be undefined here
@ -329,7 +329,7 @@ export default class EditorModel {
}
// return part index where insertion will insert between at offset
private splitAt(pos: IPosition) {
private splitAt(pos: IPosition): number {
if (pos.index === -1) {
return 0;
}
@ -356,9 +356,9 @@ export default class EditorModel {
* @return {Number} how far from position (in characters) the insertion ended.
* This can be more than the length of `str` when crossing non-editable parts, which are skipped.
*/
private addText(pos: IPosition, str: string, inputType: string) {
let {index} = pos;
const {offset} = pos;
private addText(pos: IPosition, str: string, inputType: string): number {
let { index } = pos;
const { offset } = pos;
let addLen = str.length;
const part = this._parts[index];
if (part) {
@ -390,7 +390,7 @@ export default class EditorModel {
return addLen;
}
positionForOffset(totalOffset: number, atPartEnd: boolean) {
public positionForOffset(totalOffset: number, atPartEnd = false): DocumentPosition {
let currentOffset = 0;
const index = this._parts.findIndex(part => {
const partLen = part.text.length;
@ -416,11 +416,11 @@ export default class EditorModel {
* @param {DocumentPosition?} positionB the other boundary of the range, optional
* @return {Range}
*/
startRange(positionA: DocumentPosition, positionB = positionA) {
public startRange(positionA: DocumentPosition, positionB = positionA): Range {
return new Range(this, positionA, positionB);
}
replaceRange(startPosition: DocumentPosition, endPosition: DocumentPosition, parts: Part[]) {
public replaceRange(startPosition: DocumentPosition, endPosition: DocumentPosition, parts: Part[]): void {
// convert end position to offset, so it is independent of how the document is split into parts
// which we'll change when splitting up at the start position
const endOffset = endPosition.asOffset(this);
@ -445,9 +445,9 @@ export default class EditorModel {
* @param {ManualTransformCallback} callback to run the transformations in
* @return {Promise} a promise when auto-complete (if applicable) is done updating
*/
transform(callback: ManualTransformCallback) {
public transform(callback: ManualTransformCallback): Promise<void> {
const pos = callback();
let acPromise = null;
let acPromise: Promise<void> = null;
if (!(pos instanceof Range)) {
acPromise = this.setActivePart(pos, true);
} else {

View file

@ -15,14 +15,14 @@ limitations under the License.
*/
import Range from "./range";
import {Part} from "./parts";
import { Part } from "./parts";
/**
* Some common queries and transformations on the editor model
*/
export function replaceRangeAndExpandSelection(range: Range, newParts: Part[]) {
const {model} = range;
const { model } = range;
model.transform(() => {
const oldLen = range.length;
const addedLen = range.replace(newParts);
@ -33,7 +33,7 @@ export function replaceRangeAndExpandSelection(range: Range, newParts: Part[]) {
}
export function replaceRangeAndMoveCaret(range: Range, newParts: Part[]) {
const {model} = range;
const { model } = range;
model.transform(() => {
const oldLen = range.length;
const addedLen = range.replace(newParts);
@ -44,7 +44,7 @@ export function replaceRangeAndMoveCaret(range: Range, newParts: Part[]) {
}
export function rangeStartsAtBeginningOfLine(range: Range) {
const {model} = range;
const { model } = range;
const startsWithPartial = range.start.offset !== 0;
const isFirstPart = range.start.index === 0;
const previousIsNewline = !isFirstPart && model.parts[range.start.index - 1].type === "newline";
@ -52,7 +52,7 @@ export function rangeStartsAtBeginningOfLine(range: Range) {
}
export function rangeEndsAtEndOfLine(range: Range) {
const {model} = range;
const { model } = range;
const lastPart = model.parts[range.end.index];
const endsWithPartial = range.end.offset !== lastPart.text.length;
const isLastPart = range.end.index === model.parts.length - 1;
@ -61,8 +61,8 @@ export function rangeEndsAtEndOfLine(range: Range) {
}
export function formatRangeAsQuote(range: Range) {
const {model, parts} = range;
const {partCreator} = model;
const { model, parts } = range;
const { partCreator } = model;
for (let i = 0; i < parts.length; ++i) {
const part = parts[i];
if (part.type === "newline") {
@ -82,8 +82,8 @@ export function formatRangeAsQuote(range: Range) {
}
export function formatRangeAsCode(range: Range) {
const {model, parts} = range;
const {partCreator} = model;
const { model, parts } = range;
const { partCreator } = model;
const needsBlock = parts.some(p => p.type === "newline");
if (needsBlock) {
parts.unshift(partCreator.plain("```"), partCreator.newline());
@ -108,8 +108,8 @@ const isBlank = part => !part.text || !/\S/.test(part.text);
const isNL = part => part.type === "newline";
export function toggleInlineFormat(range: Range, prefix: string, suffix = prefix) {
const {model, parts} = range;
const {partCreator} = model;
const { model, parts } = range;
const { partCreator } = model;
// compute paragraph [start, end] indexes
const paragraphIndexes = [];

View file

@ -15,9 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {MatrixClient} from "matrix-js-sdk/src/client";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
import {Room} from "matrix-js-sdk/src/models/room";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { Room } from "matrix-js-sdk/src/models/room";
import AutocompleteWrapperModel, {
GetAutocompleterComponent,
@ -466,7 +466,7 @@ export class PartCreator {
constructor(private room: Room, private client: MatrixClient, autoCompleteCreator: AutoCompleteCreator = null) {
// pre-create the creator as an object even without callback so it can already be passed
// to PillCandidatePart (e.g. while deserializing) and set later on
this.autoCompleteCreator = {create: autoCompleteCreator && autoCompleteCreator(this)};
this.autoCompleteCreator = { create: autoCompleteCreator && autoCompleteCreator(this) };
}
setAutoCompleteCreator(autoCompleteCreator: AutoCompleteCreator) {
@ -552,7 +552,7 @@ export class PartCreator {
// part creator that support auto complete for /commands,
// used in SendMessageComposer
export class CommandPartCreator extends PartCreator {
createPartForInput(text: string, partIndex: number) {
public createPartForInput(text: string, partIndex: number): Part {
// at beginning and starts with /? create
if (partIndex === 0 && text[0] === "/") {
// text will be inserted by model, so pass empty string
@ -562,11 +562,11 @@ export class CommandPartCreator extends PartCreator {
}
}
command(text: string) {
public command(text: string): CommandPart {
return new CommandPart(text, this.autoCompleteCreator);
}
deserializePart(part: Part): Part {
public deserializePart(part: SerializedPart): Part {
if (part.type === "command") {
return this.command(part.text);
} else {
@ -576,7 +576,7 @@ export class CommandPartCreator extends PartCreator {
}
class CommandPart extends PillCandidatePart {
get type(): IPillCandidatePart["type"] {
public get type(): IPillCandidatePart["type"] {
return Type.Command;
}
}

View file

@ -16,7 +16,7 @@ limitations under the License.
import DocumentOffset from "./offset";
import EditorModel from "./model";
import {Part} from "./parts";
import { Part } from "./parts";
export interface IPosition {
index: number;
@ -62,8 +62,8 @@ export default class DocumentPosition implements IPosition {
return this;
}
let {index, offset} = this;
const {parts} = model;
let { index, offset } = this;
const { parts } = model;
while (index < parts.length) {
const part = parts[index];
while (offset < part.text.length) {
@ -87,7 +87,7 @@ export default class DocumentPosition implements IPosition {
return this;
}
let {index, offset} = this;
let { index, offset } = this;
const parts = model.parts;
while (index >= 0) {
const part = parts[index];

View file

@ -15,8 +15,8 @@ limitations under the License.
*/
import EditorModel from "./model";
import DocumentPosition, {Predicate} from "./position";
import {Part} from "./parts";
import DocumentPosition, { Predicate } from "./position";
import { Part } from "./parts";
const whitespacePredicate: Predicate = (index, offset, part) => {
return part.text[offset].trim() === "";

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {Part} from "./parts";
import { Part } from "./parts";
import EditorModel from "./model";
export function needsCaretNodeBefore(part: Part, prevPart: Part) {

View file

@ -16,7 +16,7 @@ limitations under the License.
*/
import Markdown from '../Markdown';
import {makeGenericPermalink} from "../utils/permalinks/Permalinks";
import { makeGenericPermalink } from "../utils/permalinks/Permalinks";
import EditorModel from "./model";
import { AllHtmlEntities } from 'html-entities';
import SettingsStore from '../settings/SettingsStore';
@ -45,7 +45,7 @@ export function mdSerialize(model: EditorModel) {
}, "");
}
export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) {
export function htmlSerializeIfNeeded(model: EditorModel, { forceHTML = false } = {}) {
let md = mdSerialize(model);
// copy of raw input to remove unwanted math later
const orig = md;
@ -142,9 +142,9 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} =
// add fallback output for latex math, which should not be interpreted as markdown
phtml('div, span').each(function(i, e) {
const tex = phtml(e).attr('data-mx-maths')
const tex = phtml(e).attr('data-mx-maths');
if (tex) {
phtml(e).html(`<code>${tex}</code>`)
phtml(e).html(`<code>${tex}</code>`);
}
});
}
@ -200,18 +200,18 @@ export function stripEmoteCommand(model: EditorModel) {
export function stripPrefix(model: EditorModel, prefix: string) {
model = model.clone();
model.removeText({index: 0, offset: 0}, prefix.length);
model.removeText({ index: 0, offset: 0 }, prefix.length);
return model;
}
export function unescapeMessage(model: EditorModel) {
const {parts} = model;
const { parts } = model;
if (parts.length) {
const firstPart = parts[0];
// only unescape \/ to / at start of editor
if (firstPart.type === "plain" && firstPart.text.startsWith("\\/")) {
model = model.clone();
model.removeText({index: 0, offset: 0}, 1);
model.removeText({ index: 0, offset: 0 }, 1);
}
}
return model;