Apply prettier formatting
This commit is contained in:
parent
1cac306093
commit
526645c791
1576 changed files with 65385 additions and 62478 deletions
|
@ -39,8 +39,7 @@ export default class AutocompleteWrapperModel {
|
|||
private getAutocompleterComponent: GetAutocompleterComponent,
|
||||
private updateQuery: UpdateQuery,
|
||||
private partCreator: PartCreator | CommandPartCreator,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public onEscape(e: KeyboardEvent): void {
|
||||
this.getAutocompleterComponent().onEscape(e);
|
||||
|
|
|
@ -34,8 +34,11 @@ export function isSlashCommand(model: EditorModel): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")
|
||||
&& (firstPart.type === Type.Plain || firstPart.type === Type.PillCandidate)) {
|
||||
if (
|
||||
firstPart.text.startsWith("/") &&
|
||||
!firstPart.text.startsWith("//") &&
|
||||
(firstPart.type === Type.Plain || firstPart.type === Type.PillCandidate)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +84,7 @@ export async function runSlashCommand(
|
|||
const title = isServerError ? _td("Server error") : _td("Command error");
|
||||
|
||||
let errText;
|
||||
if (typeof error === 'string') {
|
||||
if (typeof error === "string") {
|
||||
errText = error;
|
||||
} else if ((error as ITranslatableError).translatedMessage) {
|
||||
// Check for translatable errors (newTranslatableError)
|
||||
|
@ -107,23 +110,31 @@ export async function shouldSendAnyway(commandText: string): Promise<boolean> {
|
|||
// ask the user if their unknown command should be sent as a message
|
||||
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||
title: _t("Unknown Command"),
|
||||
description: <div>
|
||||
<p>
|
||||
{ _t("Unrecognised command: %(commandText)s", { commandText }) }
|
||||
</p>
|
||||
<p>
|
||||
{ _t("You can use <code>/help</code> to list available commands. " +
|
||||
"Did you mean to send this as a message?", {}, {
|
||||
code: t => <code>{ t }</code>,
|
||||
}) }
|
||||
</p>
|
||||
<p>
|
||||
{ _t("Hint: Begin your message with <code>//</code> to start it with a slash.", {}, {
|
||||
code: t => <code>{ t }</code>,
|
||||
}) }
|
||||
</p>
|
||||
</div>,
|
||||
button: _t('Send as message'),
|
||||
description: (
|
||||
<div>
|
||||
<p>{_t("Unrecognised command: %(commandText)s", { commandText })}</p>
|
||||
<p>
|
||||
{_t(
|
||||
"You can use <code>/help</code> to list available commands. " +
|
||||
"Did you mean to send this as a message?",
|
||||
{},
|
||||
{
|
||||
code: (t) => <code>{t}</code>,
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{_t(
|
||||
"Hint: Begin your message with <code>//</code> to start it with a slash.",
|
||||
{},
|
||||
{
|
||||
code: (t) => <code>{t}</code>,
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
button: _t("Send as message"),
|
||||
});
|
||||
const [sendAnyway] = await finished;
|
||||
return sendAnyway;
|
||||
|
|
|
@ -29,7 +29,7 @@ const LIST_TYPES = ["UL", "OL", "LI"];
|
|||
|
||||
// Escapes all markup in the given text
|
||||
function escape(text: string): string {
|
||||
return text.replace(/[\\*_[\]`<]|^>/g, match => `\\${match}`);
|
||||
return text.replace(/[\\*_[\]`<]|^>/g, (match) => `\\${match}`);
|
||||
}
|
||||
|
||||
// Finds the length of the longest backtick sequence in the given text, used for
|
||||
|
@ -77,12 +77,14 @@ function parseLink(n: Node, pc: PartCreator, opts: IParseOptions): Part[] {
|
|||
const resourceId = getPrimaryPermalinkEntity(href); // The room/user ID
|
||||
|
||||
switch (resourceId?.[0]) {
|
||||
case "@": return [pc.userPill(n.textContent, resourceId)];
|
||||
case "#": return [pc.roomPill(resourceId)];
|
||||
case "@":
|
||||
return [pc.userPill(n.textContent, resourceId)];
|
||||
case "#":
|
||||
return [pc.roomPill(resourceId)];
|
||||
}
|
||||
|
||||
const children = Array.from(n.childNodes);
|
||||
if (href === n.textContent && children.every(c => c.nodeType === Node.TEXT_NODE)) {
|
||||
if (href === n.textContent && children.every((c) => c.nodeType === Node.TEXT_NODE)) {
|
||||
return parseAtRoomMentions(n.textContent, pc, opts);
|
||||
} else {
|
||||
return [pc.plain("["), ...parseChildren(n, pc, opts), pc.plain(`](${href})`)];
|
||||
|
@ -110,7 +112,7 @@ function parseCodeBlock(n: Node, pc: PartCreator, opts: IParseOptions): Part[] {
|
|||
const fence = "`".repeat(Math.max(3, longestBacktickSequence(text) + 1));
|
||||
const parts: Part[] = [...pc.plainWithEmoji(fence + language), pc.newline()];
|
||||
|
||||
text.split("\n").forEach(line => {
|
||||
text.split("\n").forEach((line) => {
|
||||
parts.push(...pc.plainWithEmoji(line));
|
||||
parts.push(pc.newline());
|
||||
});
|
||||
|
@ -148,7 +150,7 @@ function prefixLines(parts: Part[], prefix: string, pc: PartCreator) {
|
|||
|
||||
function parseChildren(n: Node, pc: PartCreator, opts: IParseOptions, mkListItem?: (li: Node) => Part[]): Part[] {
|
||||
let prev;
|
||||
return Array.from(n.childNodes).flatMap(c => {
|
||||
return Array.from(n.childNodes).flatMap((c) => {
|
||||
const parsed = parseNode(c, pc, opts, mkListItem);
|
||||
if (parsed.length && prev && (checkBlockNode(prev) || checkBlockNode(c))) {
|
||||
if (isListChild(c)) {
|
||||
|
@ -213,7 +215,7 @@ function parseNode(n: Node, pc: PartCreator, opts: IParseOptions, mkListItem?: (
|
|||
case "LI":
|
||||
return mkListItem?.(n) ?? parseChildren(n, pc, opts);
|
||||
case "UL": {
|
||||
const parts = parseChildren(n, pc, opts, li => [pc.plain("- "), ...parseChildren(li, pc, opts)]);
|
||||
const parts = parseChildren(n, pc, opts, (li) => [pc.plain("- "), ...parseChildren(li, pc, opts)]);
|
||||
if (isListChild(n)) {
|
||||
prefixLines(parts, " ", pc);
|
||||
}
|
||||
|
@ -221,7 +223,7 @@ function parseNode(n: Node, pc: PartCreator, opts: IParseOptions, mkListItem?: (
|
|||
}
|
||||
case "OL": {
|
||||
let counter = (n as HTMLOListElement).start ?? 1;
|
||||
const parts = parseChildren(n, pc, opts, li => {
|
||||
const parts = parseChildren(n, pc, opts, (li) => {
|
||||
const parts = [pc.plain(`${counter}. `), ...parseChildren(li, pc, opts)];
|
||||
counter++;
|
||||
return parts;
|
||||
|
@ -236,12 +238,10 @@ function parseNode(n: Node, pc: PartCreator, opts: IParseOptions, mkListItem?: (
|
|||
// Math nodes are translated back into delimited latex strings
|
||||
if ((n as Element).hasAttribute("data-mx-maths")) {
|
||||
const delims = SdkConfig.get().latex_maths_delims;
|
||||
const delimLeft = (n.nodeName === "SPAN") ?
|
||||
delims?.inline?.left ?? "\\(" :
|
||||
delims?.display?.left ?? "\\[";
|
||||
const delimRight = (n.nodeName === "SPAN") ?
|
||||
delims?.inline?.right ?? "\\)" :
|
||||
delims?.display?.right ?? "\\]";
|
||||
const delimLeft =
|
||||
n.nodeName === "SPAN" ? delims?.inline?.left ?? "\\(" : delims?.display?.left ?? "\\[";
|
||||
const delimRight =
|
||||
n.nodeName === "SPAN" ? delims?.inline?.right ?? "\\)" : delims?.display?.right ?? "\\]";
|
||||
const tex = (n as Element).getAttribute("data-mx-maths");
|
||||
|
||||
return pc.plainWithEmoji(`${delimLeft}${tex}${delimRight}`);
|
||||
|
@ -268,11 +268,7 @@ function parseHtmlMessage(html: string, pc: PartCreator, opts: IParseOptions): P
|
|||
return parts;
|
||||
}
|
||||
|
||||
export function parsePlainTextMessage(
|
||||
body: string,
|
||||
pc: PartCreator,
|
||||
opts: IParseOptions,
|
||||
): Part[] {
|
||||
export function parsePlainTextMessage(body: string, pc: PartCreator, opts: IParseOptions): Part[] {
|
||||
const lines = body.split(/\r\n|\r|\n/g); // split on any new-line combination not just \n, collapses \r\n
|
||||
return lines.reduce((parts, line, i) => {
|
||||
if (opts.isQuotedMessage) {
|
||||
|
|
|
@ -187,16 +187,8 @@ function getTextNodeValue(node: Node): string {
|
|||
}
|
||||
|
||||
export function getRangeForSelection(editor: HTMLDivElement, model: EditorModel, selection: Selection): Range {
|
||||
const focusOffset = getSelectionOffsetAndText(
|
||||
editor,
|
||||
selection.focusNode,
|
||||
selection.focusOffset,
|
||||
).offset;
|
||||
const anchorOffset = getSelectionOffsetAndText(
|
||||
editor,
|
||||
selection.anchorNode,
|
||||
selection.anchorOffset,
|
||||
).offset;
|
||||
const focusOffset = getSelectionOffsetAndText(editor, selection.focusNode, selection.focusOffset).offset;
|
||||
const anchorOffset = getSelectionOffsetAndText(editor, selection.anchorNode, selection.anchorOffset).offset;
|
||||
const focusPosition = focusOffset.asPosition(model);
|
||||
const anchorPosition = anchorOffset.asPosition(model);
|
||||
return model.startRange(focusPosition, anchorPosition);
|
||||
|
|
|
@ -53,9 +53,8 @@ export default class HistoryManager {
|
|||
// so we can't push the state before something happened.
|
||||
// not ideal but changing this would be harder to fit cleanly into
|
||||
// the editor model.
|
||||
const isNonBulkInput = inputType === "insertText" ||
|
||||
inputType === "deleteContentForward" ||
|
||||
inputType === "deleteContentBackward";
|
||||
const isNonBulkInput =
|
||||
inputType === "insertText" || inputType === "deleteContentForward" || inputType === "deleteContentBackward";
|
||||
if (diff && isNonBulkInput) {
|
||||
if (diff.added) {
|
||||
this.addedSinceLastPush = true;
|
||||
|
@ -88,7 +87,7 @@ export default class HistoryManager {
|
|||
|
||||
private pushState(model: EditorModel, caret: Caret) {
|
||||
// remove all steps after current step
|
||||
while (this.currentIndex < (this.stack.length - 1)) {
|
||||
while (this.currentIndex < this.stack.length - 1) {
|
||||
this.stack.pop();
|
||||
}
|
||||
const parts = model.serializeParts();
|
||||
|
@ -132,7 +131,7 @@ export default class HistoryManager {
|
|||
}
|
||||
|
||||
public canRedo(): boolean {
|
||||
return this.currentIndex < (this.stack.length - 1);
|
||||
return this.currentIndex < this.stack.length - 1;
|
||||
}
|
||||
|
||||
// returns state that should be applied to model
|
||||
|
|
|
@ -91,7 +91,7 @@ export default class EditorModel {
|
|||
}
|
||||
|
||||
public clone(): EditorModel {
|
||||
const clonedParts = this.parts.map(p => this.partCreator.deserializePart(p.serialize()));
|
||||
const clonedParts = this.parts.map((p) => this.partCreator.deserializePart(p.serialize()));
|
||||
return new EditorModel(clonedParts, this._partCreator, this.updateCallback);
|
||||
}
|
||||
|
||||
|
@ -146,7 +146,7 @@ export default class EditorModel {
|
|||
}
|
||||
|
||||
public serializeParts(): SerializedPart[] {
|
||||
return this._parts.map(p => p.serialize());
|
||||
return this._parts.map((p) => p.serialize());
|
||||
}
|
||||
|
||||
private diff(newValue: string, inputType: string, caret: DocumentOffset): IDiff {
|
||||
|
@ -160,7 +160,7 @@ export default class EditorModel {
|
|||
}
|
||||
|
||||
public reset(serializedParts: SerializedPart[], caret?: Caret, inputType?: string): void {
|
||||
this._parts = serializedParts.map(p => this._partCreator.deserializePart(p));
|
||||
this._parts = serializedParts.map((p) => this._partCreator.deserializePart(p));
|
||||
if (!caret) {
|
||||
caret = this.getPositionAtEnd();
|
||||
}
|
||||
|
@ -218,7 +218,7 @@ export default class EditorModel {
|
|||
|
||||
private getTransformAddedLen(newPosition: DocumentPosition, inputType: string, diff: IDiff): number {
|
||||
const result = this.transformCallback(newPosition, inputType, diff);
|
||||
return Number.isFinite(result) ? result as number : 0;
|
||||
return Number.isFinite(result) ? (result as number) : 0;
|
||||
}
|
||||
|
||||
private setActivePart(pos: DocumentPosition, canOpenAutoComplete: boolean): Promise<void> {
|
||||
|
@ -397,11 +397,11 @@ export default class EditorModel {
|
|||
|
||||
public positionForOffset(totalOffset: number, atPartEnd = false): DocumentPosition {
|
||||
let currentOffset = 0;
|
||||
const index = this._parts.findIndex(part => {
|
||||
const index = this._parts.findIndex((part) => {
|
||||
const partLen = part.text.length;
|
||||
if (
|
||||
(atPartEnd && (currentOffset + partLen) >= totalOffset) ||
|
||||
(!atPartEnd && (currentOffset + partLen) > totalOffset)
|
||||
(atPartEnd && currentOffset + partLen >= totalOffset) ||
|
||||
(!atPartEnd && currentOffset + partLen > totalOffset)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -18,8 +18,7 @@ import EditorModel from "./model";
|
|||
import DocumentPosition from "./position";
|
||||
|
||||
export default class DocumentOffset {
|
||||
constructor(public offset: number, public readonly atNodeEnd: boolean) {
|
||||
}
|
||||
constructor(public offset: number, public readonly atNodeEnd: boolean) {}
|
||||
|
||||
public asPosition(model: EditorModel): DocumentPosition {
|
||||
return model.positionForOffset(this.offset, this.atNodeEnd);
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import Range from "./range";
|
||||
import { Part, Type } from "./parts";
|
||||
import { Formatting } from "../components/views/rooms/MessageComposerFormatBar";
|
||||
import { longestBacktickSequence } from './deserialize';
|
||||
import { longestBacktickSequence } from "./deserialize";
|
||||
|
||||
/**
|
||||
* Some common queries and transformations on the editor model
|
||||
|
@ -109,11 +109,13 @@ export function replaceRangeAndAutoAdjustCaret(
|
|||
const distanceFromEnd = range.length - relativeOffset;
|
||||
// Handle edge case where the caret is located within the suffix or prefix
|
||||
if (rangeHasFormatting) {
|
||||
if (relativeOffset < prefixLength) { // Was the caret at the left format string?
|
||||
if (relativeOffset < prefixLength) {
|
||||
// Was the caret at the left format string?
|
||||
replaceRangeAndMoveCaret(range, newParts, -(range.length - 2 * suffixLength));
|
||||
return;
|
||||
}
|
||||
if (distanceFromEnd < suffixLength) { // Was the caret at the right format string?
|
||||
if (distanceFromEnd < suffixLength) {
|
||||
// Was the caret at the right format string?
|
||||
replaceRangeAndMoveCaret(range, newParts, 0, true);
|
||||
return;
|
||||
}
|
||||
|
@ -122,7 +124,10 @@ export function replaceRangeAndAutoAdjustCaret(
|
|||
model.transform(() => {
|
||||
const offsetDirection = Math.sign(range.replace(newParts)); // Compensates for shrinkage or expansion
|
||||
const atEnd = distanceFromEnd === suffixLength;
|
||||
return lastStartingPosition.asOffset(model).add(offsetDirection * prefixLength, atEnd).asPosition(model);
|
||||
return lastStartingPosition
|
||||
.asOffset(model)
|
||||
.add(offsetDirection * prefixLength, atEnd)
|
||||
.asPosition(model);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -180,12 +185,10 @@ export function formatRangeAsCode(range: Range): void {
|
|||
const { model, parts } = range;
|
||||
const { partCreator } = model;
|
||||
|
||||
const hasBlockFormatting = (range.length > 0)
|
||||
&& range.text.startsWith("```")
|
||||
&& range.text.endsWith("```")
|
||||
&& range.text.includes('\n');
|
||||
const hasBlockFormatting =
|
||||
range.length > 0 && range.text.startsWith("```") && range.text.endsWith("```") && range.text.includes("\n");
|
||||
|
||||
const needsBlockFormatting = parts.some(p => p.type === Type.Newline);
|
||||
const needsBlockFormatting = parts.some((p) => p.type === Type.Newline);
|
||||
|
||||
if (hasBlockFormatting) {
|
||||
parts.shift();
|
||||
|
@ -199,9 +202,7 @@ export function formatRangeAsCode(range: Range): void {
|
|||
if (!rangeStartsAtBeginningOfLine(range)) {
|
||||
parts.unshift(partCreator.newline());
|
||||
}
|
||||
parts.push(
|
||||
partCreator.newline(),
|
||||
partCreator.plain("```"));
|
||||
parts.push(partCreator.newline(), partCreator.plain("```"));
|
||||
if (!rangeEndsAtEndOfLine(range)) {
|
||||
parts.push(partCreator.newline());
|
||||
}
|
||||
|
@ -232,8 +233,8 @@ export function formatRangeAsLink(range: Range, text?: string) {
|
|||
}
|
||||
|
||||
// parts helper methods
|
||||
const isBlank = part => !part.text || !/\S/.test(part.text);
|
||||
const isNL = part => part.type === Type.Newline;
|
||||
const isBlank = (part) => !part.text || !/\S/.test(part.text);
|
||||
const isNL = (part) => part.type === Type.Newline;
|
||||
|
||||
export function toggleInlineFormat(range: Range, prefix: string, suffix = prefix): void {
|
||||
const { model, parts } = range;
|
||||
|
@ -277,9 +278,8 @@ export function toggleInlineFormat(range: Range, prefix: string, suffix = prefix
|
|||
const base = startIdx + offset;
|
||||
const index = endIdx + offset;
|
||||
|
||||
const isFormatted = (index - base > 0) &&
|
||||
parts[base].text.startsWith(prefix) &&
|
||||
parts[index - 1].text.endsWith(suffix);
|
||||
const isFormatted =
|
||||
index - base > 0 && parts[base].text.startsWith(prefix) && parts[index - 1].text.endsWith(suffix);
|
||||
|
||||
if (isFormatted) {
|
||||
// remove prefix and suffix formatting string
|
||||
|
|
|
@ -21,11 +21,7 @@ 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,
|
||||
UpdateCallback,
|
||||
UpdateQuery,
|
||||
} from "./autocomplete";
|
||||
import AutocompleteWrapperModel, { GetAutocompleterComponent, UpdateCallback, UpdateQuery } from "./autocomplete";
|
||||
import { unicodeToShortcode } from "../HtmlUtils";
|
||||
import * as Avatar from "../Avatar";
|
||||
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||
|
@ -117,7 +113,7 @@ abstract class BasePart {
|
|||
public remove(offset: number, len: number): string | undefined {
|
||||
// validate
|
||||
const strWithRemoval = this.text.slice(0, offset) + this.text.slice(offset + len);
|
||||
for (let i = offset; i < (len + offset); ++i) {
|
||||
for (let i = offset; i < len + offset; ++i) {
|
||||
const chr = this.text.charAt(i);
|
||||
if (!this.acceptsRemoval(i, chr)) {
|
||||
return strWithRemoval;
|
||||
|
@ -216,8 +212,7 @@ abstract class PlainBasePart extends BasePart {
|
|||
|
||||
// or split if the previous character is a space
|
||||
// or if it is a + and this is a :
|
||||
return this._text[offset - 1] !== " " &&
|
||||
(this._text[offset - 1] !== "+" || chr !== ":");
|
||||
return this._text[offset - 1] !== " " && (this._text[offset - 1] !== "+" || chr !== ":");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -262,7 +257,7 @@ export abstract class PillPart extends BasePart implements IPillPart {
|
|||
}
|
||||
|
||||
protected acceptsRemoval(position: number, chr: string): boolean {
|
||||
return position !== 0; //if you remove initial # or @, pill should become plain
|
||||
return position !== 0; //if you remove initial # or @, pill should become plain
|
||||
}
|
||||
|
||||
public toDOMNode(): Node {
|
||||
|
@ -291,10 +286,12 @@ export abstract class PillPart extends BasePart implements IPillPart {
|
|||
}
|
||||
|
||||
public canUpdateDOMNode(node: HTMLElement): boolean {
|
||||
return node.nodeType === Node.ELEMENT_NODE &&
|
||||
node.nodeName === "SPAN" &&
|
||||
node.childNodes.length === 1 &&
|
||||
node.childNodes[0].nodeType === Node.TEXT_NODE;
|
||||
return (
|
||||
node.nodeType === Node.ELEMENT_NODE &&
|
||||
node.nodeName === "SPAN" &&
|
||||
node.childNodes.length === 1 &&
|
||||
node.childNodes[0].nodeType === Node.TEXT_NODE
|
||||
);
|
||||
}
|
||||
|
||||
// helper method for subclasses
|
||||
|
@ -519,12 +516,7 @@ class PillCandidatePart extends PlainBasePart implements IPillCandidatePart {
|
|||
export function getAutoCompleteCreator(getAutocompleterComponent: GetAutocompleterComponent, updateQuery: UpdateQuery) {
|
||||
return (partCreator: PartCreator) => {
|
||||
return (updateCallback: UpdateCallback) => {
|
||||
return new AutocompleteWrapperModel(
|
||||
updateCallback,
|
||||
getAutocompleterComponent,
|
||||
updateQuery,
|
||||
partCreator,
|
||||
);
|
||||
return new AutocompleteWrapperModel(updateCallback, getAutocompleterComponent, updateQuery, partCreator);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -615,8 +607,7 @@ export class PartCreator {
|
|||
room = this.client.getRoom(roomId || alias);
|
||||
} else {
|
||||
room = this.client.getRooms().find((r) => {
|
||||
return r.getCanonicalAlias() === alias ||
|
||||
r.getAltAliases().includes(alias);
|
||||
return r.getCanonicalAlias() === alias || r.getAltAliases().includes(alias);
|
||||
});
|
||||
}
|
||||
return new RoomPillPart(alias, room ? room.name : alias, room);
|
||||
|
|
|
@ -27,8 +27,7 @@ type Callback = (part: Part, startIdx: number, endIdx: number) => void;
|
|||
export type Predicate = (index: number, offset: number, part: Part) => boolean;
|
||||
|
||||
export default class DocumentPosition implements IPosition {
|
||||
constructor(public readonly index: number, public readonly offset: number) {
|
||||
}
|
||||
constructor(public readonly index: number, public readonly offset: number) {}
|
||||
|
||||
public compare(otherPos: DocumentPosition): number {
|
||||
if (this.index === otherPos.index) {
|
||||
|
@ -73,7 +72,7 @@ export default class DocumentPosition implements IPosition {
|
|||
offset += 1;
|
||||
}
|
||||
// end reached
|
||||
if (index === (parts.length - 1)) {
|
||||
if (index === parts.length - 1) {
|
||||
return new DocumentPosition(index, offset);
|
||||
} else {
|
||||
index += 1;
|
||||
|
|
|
@ -150,15 +150,18 @@ function reconcileEmptyLine(lineContainer: HTMLElement): void {
|
|||
}
|
||||
|
||||
export function renderModel(editor: HTMLDivElement, model: EditorModel): void {
|
||||
const lines = model.parts.reduce((linesArr, part) => {
|
||||
if (part.type === Type.Newline) {
|
||||
linesArr.push([]);
|
||||
} else {
|
||||
const lastLine = linesArr[linesArr.length - 1];
|
||||
lastLine.push(part);
|
||||
}
|
||||
return linesArr;
|
||||
}, [[]]);
|
||||
const lines = model.parts.reduce(
|
||||
(linesArr, part) => {
|
||||
if (part.type === Type.Newline) {
|
||||
linesArr.push([]);
|
||||
} else {
|
||||
const lastLine = linesArr[linesArr.length - 1];
|
||||
lastLine.push(part);
|
||||
}
|
||||
return linesArr;
|
||||
},
|
||||
[[]],
|
||||
);
|
||||
lines.forEach((parts, i) => {
|
||||
// find first (and remove anything else) div without className
|
||||
// (as browsers insert these in contenteditable) line container
|
||||
|
|
|
@ -15,16 +15,16 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { encode } from 'html-entities';
|
||||
import cheerio from 'cheerio';
|
||||
import { encode } from "html-entities";
|
||||
import cheerio from "cheerio";
|
||||
import escapeHtml from "escape-html";
|
||||
|
||||
import Markdown from '../Markdown';
|
||||
import Markdown from "../Markdown";
|
||||
import { makeGenericPermalink } from "../utils/permalinks/Permalinks";
|
||||
import EditorModel from "./model";
|
||||
import SettingsStore from '../settings/SettingsStore';
|
||||
import SdkConfig from '../SdkConfig';
|
||||
import { Type } from './parts';
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import SdkConfig from "../SdkConfig";
|
||||
import { Type } from "./parts";
|
||||
|
||||
export function mdSerialize(model: EditorModel): string {
|
||||
return model.parts.reduce((html, part) => {
|
||||
|
@ -40,11 +40,17 @@ export function mdSerialize(model: EditorModel): string {
|
|||
case Type.RoomPill:
|
||||
// Here we use the resourceId for compatibility with non-rich text clients
|
||||
// See https://github.com/vector-im/element-web/issues/16660
|
||||
return html +
|
||||
`[${part.resourceId.replace(/[[\\\]]/g, c => "\\" + c)}](${makeGenericPermalink(part.resourceId)})`;
|
||||
return (
|
||||
html +
|
||||
`[${part.resourceId.replace(/[[\\\]]/g, (c) => "\\" + c)}](${makeGenericPermalink(
|
||||
part.resourceId,
|
||||
)})`
|
||||
);
|
||||
case Type.UserPill:
|
||||
return html +
|
||||
`[${part.text.replace(/[[\\\]]/g, c => "\\" + c)}](${makeGenericPermalink(part.resourceId)})`;
|
||||
return (
|
||||
html +
|
||||
`[${part.text.replace(/[[\\\]]/g, (c) => "\\" + c)}](${makeGenericPermalink(part.resourceId)})`
|
||||
);
|
||||
}
|
||||
}, "");
|
||||
}
|
||||
|
@ -59,7 +65,7 @@ export function htmlSerializeIfNeeded(
|
|||
{ forceHTML = false, useMarkdown = true }: ISerializeOpts = {},
|
||||
): string {
|
||||
if (!useMarkdown) {
|
||||
return escapeHtml(textSerialize(model)).replace(/\n/g, '<br/>');
|
||||
return escapeHtml(textSerialize(model)).replace(/\n/g, "<br/>");
|
||||
}
|
||||
|
||||
const md = mdSerialize(model);
|
||||
|
@ -71,10 +77,10 @@ export function htmlSerializeFromMdIfNeeded(md: string, { forceHTML = false } =
|
|||
const orig = md;
|
||||
|
||||
if (SettingsStore.getValue("feature_latex_maths")) {
|
||||
const patternNames = ['tex', 'latex'];
|
||||
const patternTypes = ['display', 'inline'];
|
||||
const patternNames = ["tex", "latex"];
|
||||
const patternTypes = ["display", "inline"];
|
||||
const patternDefaults = {
|
||||
"tex": {
|
||||
tex: {
|
||||
// detect math with tex delimiters, inline: $...$, display $$...$$
|
||||
// preferably use negative lookbehinds, not supported in all major browsers:
|
||||
// const displayPattern = "^(?<!\\\\)\\$\\$(?![ \\t])(([^$]|\\\\\\$)+?)\\$\\$$";
|
||||
|
@ -83,7 +89,7 @@ export function htmlSerializeFromMdIfNeeded(md: string, { forceHTML = false } =
|
|||
// conditions for display math detection $$...$$:
|
||||
// - pattern starts and ends on a new line
|
||||
// - left delimiter ($$) is not escaped by backslash
|
||||
"display": "(^)\\$\\$(([^$]|\\\\\\$)+?)\\$\\$$",
|
||||
display: "(^)\\$\\$(([^$]|\\\\\\$)+?)\\$\\$$",
|
||||
|
||||
// conditions for inline math detection $...$:
|
||||
// - pattern starts at beginning of line, follows whitespace character or punctuation
|
||||
|
@ -91,32 +97,31 @@ export function htmlSerializeFromMdIfNeeded(md: string, { forceHTML = false } =
|
|||
// - left and right delimiters ($) are not escaped by backslashes
|
||||
// - left delimiter is not followed by whitespace character
|
||||
// - right delimiter is not prefixed with whitespace character
|
||||
"inline":
|
||||
"(^|\\s|[.,!?:;])(?!\\\\)\\$(?!\\s)(([^$\\n]|\\\\\\$)*([^\\\\\\s\\$]|\\\\\\$)(?:\\\\\\$)?)\\$",
|
||||
inline: "(^|\\s|[.,!?:;])(?!\\\\)\\$(?!\\s)(([^$\\n]|\\\\\\$)*([^\\\\\\s\\$]|\\\\\\$)(?:\\\\\\$)?)\\$",
|
||||
},
|
||||
"latex": {
|
||||
latex: {
|
||||
// detect math with latex delimiters, inline: \(...\), display \[...\]
|
||||
|
||||
// conditions for display math detection \[...\]:
|
||||
// - pattern starts and ends on a new line
|
||||
// - pattern is not empty
|
||||
"display": "(^)\\\\\\[(?!\\\\\\])(.*?)\\\\\\]$",
|
||||
display: "(^)\\\\\\[(?!\\\\\\])(.*?)\\\\\\]$",
|
||||
|
||||
// conditions for inline math detection \(...\):
|
||||
// - pattern starts at beginning of line or is not prefixed with backslash
|
||||
// - pattern is not empty
|
||||
"inline": "(^|[^\\\\])\\\\\\((?!\\\\\\))(.*?)\\\\\\)",
|
||||
inline: "(^|[^\\\\])\\\\\\((?!\\\\\\))(.*?)\\\\\\)",
|
||||
},
|
||||
};
|
||||
|
||||
patternNames.forEach(function(patternName) {
|
||||
patternTypes.forEach(function(patternType) {
|
||||
patternNames.forEach(function (patternName) {
|
||||
patternTypes.forEach(function (patternType) {
|
||||
// get the regex replace pattern from config or use the default
|
||||
const pattern = (((SdkConfig.get("latex_maths_delims") ||
|
||||
{})[patternType] || {})["pattern"] || {})[patternName] ||
|
||||
const pattern =
|
||||
(((SdkConfig.get("latex_maths_delims") || {})[patternType] || {})["pattern"] || {})[patternName] ||
|
||||
patternDefaults[patternName][patternType];
|
||||
|
||||
md = md.replace(RegExp(pattern, "gms"), function(m, p1, p2) {
|
||||
md = md.replace(RegExp(pattern, "gms"), function (m, p1, p2) {
|
||||
const p2e = encode(p2);
|
||||
switch (patternType) {
|
||||
case "display":
|
||||
|
@ -130,7 +135,9 @@ export function htmlSerializeFromMdIfNeeded(md: string, { forceHTML = false } =
|
|||
|
||||
// make sure div tags always start on a new line, otherwise it will confuse
|
||||
// the markdown parser
|
||||
md = md.replace(/(.)<div/g, function(m, p1) { return `${p1}\n<div`; });
|
||||
md = md.replace(/(.)<div/g, function (m, p1) {
|
||||
return `${p1}\n<div`;
|
||||
});
|
||||
}
|
||||
|
||||
const parser = new Markdown(md);
|
||||
|
@ -156,13 +163,13 @@ export function htmlSerializeFromMdIfNeeded(md: string, { forceHTML = false } =
|
|||
// since maths delimiters are handled before Markdown,
|
||||
// code blocks could contain mangled content.
|
||||
// replace code blocks with original content
|
||||
phtmlOrig('code').each(function(i) {
|
||||
phtml('code').eq(i).text(phtmlOrig('code').eq(i).text());
|
||||
phtmlOrig("code").each(function (i) {
|
||||
phtml("code").eq(i).text(phtmlOrig("code").eq(i).text());
|
||||
});
|
||||
|
||||
// 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');
|
||||
phtml("div, span").each(function (i, e) {
|
||||
const tex = phtml(e).attr("data-mx-maths");
|
||||
if (tex) {
|
||||
phtml(e).html(`<code>${tex}</code>`);
|
||||
}
|
||||
|
@ -207,7 +214,7 @@ export function startsWith(model: EditorModel, prefix: string, caseSensitive = t
|
|||
const firstPart = model.parts[0];
|
||||
// part type will be "plain" while editing,
|
||||
// and "command" while composing a message.
|
||||
let text = firstPart?.text || '';
|
||||
let text = firstPart?.text || "";
|
||||
if (!caseSensitive) {
|
||||
prefix = prefix.toLowerCase();
|
||||
text = text.toLowerCase();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue