mirror of
https://github.com/element-hq/element-web
synced 2024-11-28 04:21:57 +03:00
Switch to a discriminated unions
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
2bf5e4b142
commit
ed0d9973b7
9 changed files with 130 additions and 74 deletions
|
@ -17,13 +17,13 @@ limitations under the License.
|
||||||
|
|
||||||
import {KeyboardEvent} from "react";
|
import {KeyboardEvent} from "react";
|
||||||
|
|
||||||
import {BasePart, CommandPartCreator, PartCreator} from "./parts";
|
import {Part, CommandPartCreator, PartCreator} from "./parts";
|
||||||
import DocumentPosition from "./position";
|
import DocumentPosition from "./position";
|
||||||
import {ICompletion} from "../autocomplete/Autocompleter";
|
import {ICompletion} from "../autocomplete/Autocompleter";
|
||||||
import Autocomplete from "../components/views/rooms/Autocomplete";
|
import Autocomplete from "../components/views/rooms/Autocomplete";
|
||||||
|
|
||||||
export interface ICallback {
|
export interface ICallback {
|
||||||
replaceParts?: BasePart[];
|
replaceParts?: Part[];
|
||||||
close?: boolean;
|
close?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ export type GetAutocompleterComponent = () => Autocomplete;
|
||||||
export type UpdateQuery = (test: string) => Promise<void>;
|
export type UpdateQuery = (test: string) => Promise<void>;
|
||||||
|
|
||||||
export default class AutocompleteWrapperModel {
|
export default class AutocompleteWrapperModel {
|
||||||
private queryPart: BasePart;
|
private queryPart: Part;
|
||||||
private partIndex: number;
|
private partIndex: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -89,7 +89,7 @@ export default class AutocompleteWrapperModel {
|
||||||
this.getAutocompleterComponent().moveSelection(+1);
|
this.getAutocompleterComponent().moveSelection(+1);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPartUpdate(part: BasePart, pos: DocumentPosition) {
|
onPartUpdate(part: Part, pos: DocumentPosition) {
|
||||||
// cache the typed value and caret here
|
// cache the typed value and caret here
|
||||||
// so we can restore it in onComponentSelectionChange when the value is undefined (meaning it should be the typed text)
|
// so we can restore it in onComponentSelectionChange when the value is undefined (meaning it should be the typed text)
|
||||||
this.queryPart = part;
|
this.queryPart = part;
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {needsCaretNodeBefore, needsCaretNodeAfter} from "./render";
|
||||||
import Range from "./range";
|
import Range from "./range";
|
||||||
import EditorModel from "./model";
|
import EditorModel from "./model";
|
||||||
import DocumentPosition, {IPosition} from "./position";
|
import DocumentPosition, {IPosition} from "./position";
|
||||||
import {BasePart} from "./parts";
|
import {Part} from "./parts";
|
||||||
|
|
||||||
export type Caret = Range | DocumentPosition;
|
export type Caret = Range | DocumentPosition;
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ export function getLineAndNodePosition(model: EditorModel, caretPosition: IPosit
|
||||||
return {lineIndex, nodeIndex, offset};
|
return {lineIndex, nodeIndex, offset};
|
||||||
}
|
}
|
||||||
|
|
||||||
function findNodeInLineForPart(parts: BasePart[], partIndex: number) {
|
function findNodeInLineForPart(parts: Part[], partIndex: number) {
|
||||||
let lineIndex = 0;
|
let lineIndex = 0;
|
||||||
let nodeIndex = -1;
|
let nodeIndex = -1;
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ function findNodeInLineForPart(parts: BasePart[], partIndex: number) {
|
||||||
return {lineIndex, nodeIndex};
|
return {lineIndex, nodeIndex};
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveOutOfUneditablePart(parts: BasePart[], partIndex: number, nodeIndex: number, offset: number) {
|
function moveOutOfUneditablePart(parts: Part[], partIndex: number, nodeIndex: number, offset: number) {
|
||||||
// move caret before or after uneditable part
|
// move caret before or after uneditable part
|
||||||
const part = parts[partIndex];
|
const part = parts[partIndex];
|
||||||
if (part && !part.canEdit) {
|
if (part && !part.canEdit) {
|
||||||
|
|
|
@ -16,12 +16,11 @@ limitations under the License.
|
||||||
|
|
||||||
import EditorModel from "./model";
|
import EditorModel from "./model";
|
||||||
import {IDiff} from "./diff";
|
import {IDiff} from "./diff";
|
||||||
import {ISerializedPart} from "./parts";
|
import {SerializedPart} from "./parts";
|
||||||
import Range from "./range";
|
|
||||||
import {Caret} from "./caret";
|
import {Caret} from "./caret";
|
||||||
|
|
||||||
interface IHistory {
|
interface IHistory {
|
||||||
parts: ISerializedPart[];
|
parts: SerializedPart[];
|
||||||
caret: Caret;
|
caret: Caret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
import {diffAtCaret, diffDeletion, IDiff} from "./diff";
|
import {diffAtCaret, diffDeletion, IDiff} from "./diff";
|
||||||
import DocumentPosition, {IPosition} from "./position";
|
import DocumentPosition, {IPosition} from "./position";
|
||||||
import Range from "./range";
|
import Range from "./range";
|
||||||
import {BasePart, ISerializedPart, PartCreator} from "./parts";
|
import {SerializedPart, Part, PartCreator} from "./parts";
|
||||||
import AutocompleteWrapperModel, {ICallback} from "./autocomplete";
|
import AutocompleteWrapperModel, {ICallback} from "./autocomplete";
|
||||||
import DocumentOffset from "./offset";
|
import DocumentOffset from "./offset";
|
||||||
import {Caret} from "./caret";
|
import {Caret} from "./caret";
|
||||||
|
@ -49,7 +49,7 @@ type UpdateCallback = (caret: Caret, inputType?: string, diff?: IDiff) => void;
|
||||||
type ManualTransformCallback = () => Caret;
|
type ManualTransformCallback = () => Caret;
|
||||||
|
|
||||||
export default class EditorModel {
|
export default class EditorModel {
|
||||||
private _parts: BasePart[];
|
private _parts: Part[];
|
||||||
private readonly _partCreator: PartCreator;
|
private readonly _partCreator: PartCreator;
|
||||||
private activePartIdx: number = null;
|
private activePartIdx: number = null;
|
||||||
private _autoComplete: AutocompleteWrapperModel = null;
|
private _autoComplete: AutocompleteWrapperModel = null;
|
||||||
|
@ -57,7 +57,7 @@ export default class EditorModel {
|
||||||
private autoCompletePartCount = 0;
|
private autoCompletePartCount = 0;
|
||||||
private transformCallback: TransformCallback = null;
|
private transformCallback: TransformCallback = null;
|
||||||
|
|
||||||
constructor(parts: BasePart[], partCreator: PartCreator, private updateCallback: UpdateCallback = null) {
|
constructor(parts: Part[], partCreator: PartCreator, private updateCallback: UpdateCallback = null) {
|
||||||
this._parts = parts;
|
this._parts = parts;
|
||||||
this._partCreator = partCreator;
|
this._partCreator = partCreator;
|
||||||
this.transformCallback = null;
|
this.transformCallback = null;
|
||||||
|
@ -94,7 +94,7 @@ export default class EditorModel {
|
||||||
return new EditorModel(this._parts, this._partCreator, this.updateCallback);
|
return new EditorModel(this._parts, this._partCreator, this.updateCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
private insertPart(index: number, part: BasePart) {
|
private insertPart(index: number, part: Part) {
|
||||||
this._parts.splice(index, 0, part);
|
this._parts.splice(index, 0, part);
|
||||||
if (this.activePartIdx >= index) {
|
if (this.activePartIdx >= index) {
|
||||||
++this.activePartIdx;
|
++this.activePartIdx;
|
||||||
|
@ -118,7 +118,7 @@ export default class EditorModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private replacePart(index: number, part: BasePart) {
|
private replacePart(index: number, part: Part) {
|
||||||
this._parts.splice(index, 1, part);
|
this._parts.splice(index, 1, part);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ export default class EditorModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset(serializedParts: ISerializedPart[], caret: Caret, inputType: string) {
|
reset(serializedParts: SerializedPart[], caret: Caret, inputType: string) {
|
||||||
this._parts = serializedParts.map(p => this._partCreator.deserializePart(p));
|
this._parts = serializedParts.map(p => this._partCreator.deserializePart(p));
|
||||||
if (!caret) {
|
if (!caret) {
|
||||||
caret = this.getPositionAtEnd();
|
caret = this.getPositionAtEnd();
|
||||||
|
@ -180,7 +180,7 @@ export default class EditorModel {
|
||||||
* @param {DocumentPosition} position the position to start inserting at
|
* @param {DocumentPosition} position the position to start inserting at
|
||||||
* @return {Number} the amount of characters added
|
* @return {Number} the amount of characters added
|
||||||
*/
|
*/
|
||||||
insert(parts: BasePart[], position: IPosition) {
|
insert(parts: Part[], position: IPosition) {
|
||||||
const insertIndex = this.splitAt(position);
|
const insertIndex = this.splitAt(position);
|
||||||
let newTextLength = 0;
|
let newTextLength = 0;
|
||||||
for (let i = 0; i < parts.length; ++i) {
|
for (let i = 0; i < parts.length; ++i) {
|
||||||
|
@ -420,7 +420,7 @@ export default class EditorModel {
|
||||||
return new Range(this, positionA, positionB);
|
return new Range(this, positionA, positionB);
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceRange(startPosition: DocumentPosition, endPosition: DocumentPosition, parts: BasePart[]) {
|
replaceRange(startPosition: DocumentPosition, endPosition: DocumentPosition, parts: Part[]) {
|
||||||
// convert end position to offset, so it is independent of how the document is split into parts
|
// 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
|
// which we'll change when splitting up at the start position
|
||||||
const endOffset = endPosition.asOffset(this);
|
const endOffset = endPosition.asOffset(this);
|
||||||
|
|
|
@ -15,13 +15,13 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Range from "./range";
|
import Range from "./range";
|
||||||
import {BasePart} from "./parts";
|
import {Part} from "./parts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some common queries and transformations on the editor model
|
* Some common queries and transformations on the editor model
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function replaceRangeAndExpandSelection(range: Range, newParts: BasePart[]) {
|
export function replaceRangeAndExpandSelection(range: Range, newParts: Part[]) {
|
||||||
const {model} = range;
|
const {model} = range;
|
||||||
model.transform(() => {
|
model.transform(() => {
|
||||||
const oldLen = range.length;
|
const oldLen = range.length;
|
||||||
|
@ -32,7 +32,7 @@ export function replaceRangeAndExpandSelection(range: Range, newParts: BasePart[
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function replaceRangeAndMoveCaret(range: Range, newParts: BasePart[]) {
|
export function replaceRangeAndMoveCaret(range: Range, newParts: Part[]) {
|
||||||
const {model} = range;
|
const {model} = range;
|
||||||
model.transform(() => {
|
model.transform(() => {
|
||||||
const oldLen = range.length;
|
const oldLen = range.length;
|
||||||
|
|
|
@ -19,15 +19,66 @@ import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
||||||
import {Room} from "matrix-js-sdk/src/models/room";
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import AutocompleteWrapperModel, {GetAutocompleterComponent, UpdateCallback, UpdateQuery} from "./autocomplete";
|
import AutocompleteWrapperModel, {
|
||||||
|
GetAutocompleterComponent,
|
||||||
|
UpdateCallback,
|
||||||
|
UpdateQuery
|
||||||
|
} from "./autocomplete";
|
||||||
import * as Avatar from "../Avatar";
|
import * as Avatar from "../Avatar";
|
||||||
|
|
||||||
export interface ISerializedPart {
|
interface ISerializedPart {
|
||||||
type: string;
|
type: Type.Plain | Type.Newline | Type.Command | Type.PillCandidate;
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class BasePart {
|
interface ISerializedPillPart {
|
||||||
|
type: Type.AtRoomPill | Type.RoomPill | Type.UserPill;
|
||||||
|
text: string;
|
||||||
|
resourceId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SerializedPart = ISerializedPart | ISerializedPillPart;
|
||||||
|
|
||||||
|
enum Type {
|
||||||
|
Plain = "plain",
|
||||||
|
Newline = "newline",
|
||||||
|
Command = "command",
|
||||||
|
UserPill = "user-pill",
|
||||||
|
RoomPill = "room-pill",
|
||||||
|
AtRoomPill = "at-room-pill",
|
||||||
|
PillCandidate = "pill-candidate",
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IBasePart {
|
||||||
|
text: string;
|
||||||
|
type: Type.Plain | Type.Newline;
|
||||||
|
canEdit: boolean;
|
||||||
|
|
||||||
|
createAutoComplete(updateCallback: UpdateCallback): void;
|
||||||
|
|
||||||
|
serialize(): SerializedPart;
|
||||||
|
remove(offset: number, len: number): string;
|
||||||
|
split(offset: number): IBasePart;
|
||||||
|
validateAndInsert(offset: number, str: string, inputType: string): boolean;
|
||||||
|
appendUntilRejected(str: string, inputType: string): string;
|
||||||
|
updateDOMNode(node: Node);
|
||||||
|
canUpdateDOMNode(node: Node);
|
||||||
|
toDOMNode(): Node;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPillCandidatePart extends Omit<IBasePart, "type" | "createAutoComplete"> {
|
||||||
|
type: Type.PillCandidate | Type.Command;
|
||||||
|
createAutoComplete(updateCallback: UpdateCallback): AutocompleteWrapperModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPillPart extends Omit<IBasePart, "type" | "resourceId"> {
|
||||||
|
type: Type.AtRoomPill | Type.RoomPill | Type.UserPill;
|
||||||
|
resourceId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Part = IBasePart | IPillCandidatePart | IPillPart;
|
||||||
|
|
||||||
|
abstract class BasePart {
|
||||||
protected _text: string;
|
protected _text: string;
|
||||||
|
|
||||||
constructor(text = "") {
|
constructor(text = "") {
|
||||||
|
@ -42,7 +93,7 @@ export abstract class BasePart {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
merge(part: BasePart) {
|
merge(part: Part) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +145,7 @@ export abstract class BasePart {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
createAutoComplete(updateCallback): AutocompleteWrapperModel | void {}
|
createAutoComplete(updateCallback: UpdateCallback): void {}
|
||||||
|
|
||||||
trim(len: number) {
|
trim(len: number) {
|
||||||
const remaining = this._text.substr(len);
|
const remaining = this._text.substr(len);
|
||||||
|
@ -106,7 +157,7 @@ export abstract class BasePart {
|
||||||
return this._text;
|
return this._text;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract get type(): string;
|
abstract get type(): Type;
|
||||||
|
|
||||||
get canEdit() {
|
get canEdit() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -116,8 +167,11 @@ export abstract class BasePart {
|
||||||
return `${this.type}(${this.text})`;
|
return `${this.type}(${this.text})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(): ISerializedPart {
|
serialize(): SerializedPart {
|
||||||
return {type: this.type, text: this.text};
|
return {
|
||||||
|
type: this.type as ISerializedPart["type"],
|
||||||
|
text: this.text,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract updateDOMNode(node: Node);
|
abstract updateDOMNode(node: Node);
|
||||||
|
@ -125,8 +179,7 @@ export abstract class BasePart {
|
||||||
abstract toDOMNode(): Node;
|
abstract toDOMNode(): Node;
|
||||||
}
|
}
|
||||||
|
|
||||||
// exported for unit tests, should otherwise only be used through PartCreator
|
abstract class PlainBasePart extends BasePart {
|
||||||
export class PlainPart extends BasePart {
|
|
||||||
acceptsInsertion(chr: string, offset: number, inputType: string) {
|
acceptsInsertion(chr: string, offset: number, inputType: string) {
|
||||||
if (chr === "\n") {
|
if (chr === "\n") {
|
||||||
return false;
|
return false;
|
||||||
|
@ -150,10 +203,6 @@ export class PlainPart extends BasePart {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get type() {
|
|
||||||
return "plain";
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDOMNode(node: Node) {
|
updateDOMNode(node: Node) {
|
||||||
if (node.textContent !== this.text) {
|
if (node.textContent !== this.text) {
|
||||||
node.textContent = this.text;
|
node.textContent = this.text;
|
||||||
|
@ -165,7 +214,14 @@ export class PlainPart extends BasePart {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class PillPart extends BasePart {
|
// exported for unit tests, should otherwise only be used through PartCreator
|
||||||
|
export class PlainPart extends PlainBasePart implements IBasePart {
|
||||||
|
get type(): IBasePart["type"] {
|
||||||
|
return Type.Plain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class PillPart extends BasePart implements IPillPart {
|
||||||
constructor(public resourceId: string, label) {
|
constructor(public resourceId: string, label) {
|
||||||
super(label);
|
super(label);
|
||||||
}
|
}
|
||||||
|
@ -223,12 +279,14 @@ export abstract class PillPart extends BasePart {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract get type(): IPillPart["type"];
|
||||||
|
|
||||||
abstract get className(): string;
|
abstract get className(): string;
|
||||||
|
|
||||||
abstract setAvatar(node: HTMLElement): void;
|
abstract setAvatar(node: HTMLElement): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NewlinePart extends BasePart {
|
class NewlinePart extends BasePart implements IBasePart {
|
||||||
acceptsInsertion(chr: string, offset: number) {
|
acceptsInsertion(chr: string, offset: number) {
|
||||||
return offset === 0 && chr === "\n";
|
return offset === 0 && chr === "\n";
|
||||||
}
|
}
|
||||||
|
@ -251,8 +309,8 @@ class NewlinePart extends BasePart {
|
||||||
return node.tagName === "BR";
|
return node.tagName === "BR";
|
||||||
}
|
}
|
||||||
|
|
||||||
get type() {
|
get type(): IBasePart["type"] {
|
||||||
return "newline";
|
return Type.Newline;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this makes the cursor skip this part when it is inserted
|
// this makes the cursor skip this part when it is inserted
|
||||||
|
@ -283,8 +341,8 @@ class RoomPillPart extends PillPart {
|
||||||
this._setAvatarVars(node, avatarUrl, initialLetter);
|
this._setAvatarVars(node, avatarUrl, initialLetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
get type() {
|
get type(): IPillPart["type"] {
|
||||||
return "room-pill";
|
return Type.RoomPill;
|
||||||
}
|
}
|
||||||
|
|
||||||
get className() {
|
get className() {
|
||||||
|
@ -293,8 +351,8 @@ class RoomPillPart extends PillPart {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AtRoomPillPart extends RoomPillPart {
|
class AtRoomPillPart extends RoomPillPart {
|
||||||
get type() {
|
get type(): IPillPart["type"] {
|
||||||
return "at-room-pill";
|
return Type.AtRoomPill;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,28 +379,29 @@ class UserPillPart extends PillPart {
|
||||||
this._setAvatarVars(node, avatarUrl, initialLetter);
|
this._setAvatarVars(node, avatarUrl, initialLetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
get type() {
|
get type(): IPillPart["type"] {
|
||||||
return "user-pill";
|
return Type.UserPill;
|
||||||
}
|
}
|
||||||
|
|
||||||
get className() {
|
get className() {
|
||||||
return "mx_UserPill mx_Pill";
|
return "mx_UserPill mx_Pill";
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize() {
|
serialize(): ISerializedPillPart {
|
||||||
return {
|
return {
|
||||||
...super.serialize(),
|
type: this.type,
|
||||||
|
text: this.text,
|
||||||
resourceId: this.resourceId,
|
resourceId: this.resourceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PillCandidatePart extends PlainPart {
|
class PillCandidatePart extends PlainBasePart implements IPillCandidatePart {
|
||||||
constructor(text: string, private autoCompleteCreator: IAutocompleteCreator) {
|
constructor(text: string, private autoCompleteCreator: IAutocompleteCreator) {
|
||||||
super(text);
|
super(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
createAutoComplete(updateCallback): AutocompleteWrapperModel {
|
createAutoComplete(updateCallback: UpdateCallback): AutocompleteWrapperModel {
|
||||||
return this.autoCompleteCreator.create(updateCallback);
|
return this.autoCompleteCreator.create(updateCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,8 +421,8 @@ class PillCandidatePart extends PlainPart {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
get type() {
|
get type(): IPillCandidatePart["type"] {
|
||||||
return "pill-candidate";
|
return Type.PillCandidate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,7 +458,7 @@ export class PartCreator {
|
||||||
this.autoCompleteCreator.create = autoCompleteCreator(this);
|
this.autoCompleteCreator.create = autoCompleteCreator(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
createPartForInput(input: string, partIndex: number, inputType?: string): BasePart {
|
createPartForInput(input: string, partIndex: number, inputType?: string): Part {
|
||||||
switch (input[0]) {
|
switch (input[0]) {
|
||||||
case "#":
|
case "#":
|
||||||
case "@":
|
case "@":
|
||||||
|
@ -416,20 +475,20 @@ export class PartCreator {
|
||||||
return this.plain(text);
|
return this.plain(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
deserializePart(part: ISerializedPart) {
|
deserializePart(part: SerializedPart): Part {
|
||||||
switch (part.type) {
|
switch (part.type) {
|
||||||
case "plain":
|
case Type.Plain:
|
||||||
return this.plain(part.text);
|
return this.plain(part.text);
|
||||||
case "newline":
|
case Type.Newline:
|
||||||
return this.newline();
|
return this.newline();
|
||||||
case "at-room-pill":
|
case Type.AtRoomPill:
|
||||||
return this.atRoomPill(part.text);
|
return this.atRoomPill(part.text);
|
||||||
case "pill-candidate":
|
case Type.PillCandidate:
|
||||||
return this.pillCandidate(part.text);
|
return this.pillCandidate(part.text);
|
||||||
case "room-pill":
|
case Type.RoomPill:
|
||||||
return this.roomPill(part.text);
|
return this.roomPill(part.text);
|
||||||
case "user-pill":
|
case Type.UserPill:
|
||||||
return this.userPill(part.text, (part as PillPart).resourceId);
|
return this.userPill(part.text, part.resourceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,7 +550,7 @@ export class CommandPartCreator extends PartCreator {
|
||||||
return new CommandPart(text, this.autoCompleteCreator);
|
return new CommandPart(text, this.autoCompleteCreator);
|
||||||
}
|
}
|
||||||
|
|
||||||
deserializePart(part: BasePart) {
|
deserializePart(part: Part): Part {
|
||||||
if (part.type === "command") {
|
if (part.type === "command") {
|
||||||
return this.command(part.text);
|
return this.command(part.text);
|
||||||
} else {
|
} else {
|
||||||
|
@ -501,7 +560,7 @@ export class CommandPartCreator extends PartCreator {
|
||||||
}
|
}
|
||||||
|
|
||||||
class CommandPart extends PillCandidatePart {
|
class CommandPart extends PillCandidatePart {
|
||||||
get type() {
|
get type(): IPillCandidatePart["type"] {
|
||||||
return "command";
|
return Type.Command;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,15 @@ limitations under the License.
|
||||||
|
|
||||||
import DocumentOffset from "./offset";
|
import DocumentOffset from "./offset";
|
||||||
import EditorModel from "./model";
|
import EditorModel from "./model";
|
||||||
import {BasePart} from "./parts";
|
import {Part} from "./parts";
|
||||||
|
|
||||||
export interface IPosition {
|
export interface IPosition {
|
||||||
index: number;
|
index: number;
|
||||||
offset: number;
|
offset: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Callback = (part: BasePart, startIdx: number, endIdx: number) => void;
|
type Callback = (part: Part, startIdx: number, endIdx: number) => void;
|
||||||
type Predicate = (index: number, offset: number, part: BasePart) => boolean;
|
type Predicate = (index: number, offset: number, part: Part) => boolean;
|
||||||
|
|
||||||
export default class DocumentPosition implements IPosition {
|
export default class DocumentPosition implements IPosition {
|
||||||
constructor(public readonly index: number, public readonly offset: number) {
|
constructor(public readonly index: number, public readonly offset: number) {
|
||||||
|
|
|
@ -15,16 +15,15 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BasePart} from "./parts";
|
import {Part} from "./parts";
|
||||||
import EditorModel from "./model";
|
import EditorModel from "./model";
|
||||||
import {instanceOf} from "prop-types";
|
|
||||||
|
|
||||||
export function needsCaretNodeBefore(part: BasePart, prevPart: BasePart) {
|
export function needsCaretNodeBefore(part: Part, prevPart: Part) {
|
||||||
const isFirst = !prevPart || prevPart.type === "newline";
|
const isFirst = !prevPart || prevPart.type === "newline";
|
||||||
return !part.canEdit && (isFirst || !prevPart.canEdit);
|
return !part.canEdit && (isFirst || !prevPart.canEdit);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function needsCaretNodeAfter(part: BasePart, isLastOfLine: boolean) {
|
export function needsCaretNodeAfter(part: Part, isLastOfLine: boolean) {
|
||||||
return !part.canEdit && isLastOfLine;
|
return !part.canEdit && isLastOfLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +82,7 @@ function removeChildren(parent: HTMLElement) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function reconcileLine(lineContainer: ChildNode, parts: BasePart[]) {
|
function reconcileLine(lineContainer: ChildNode, parts: Part[]) {
|
||||||
let currentNode;
|
let currentNode;
|
||||||
let prevPart;
|
let prevPart;
|
||||||
const lastPart = parts[parts.length - 1];
|
const lastPart = parts[parts.length - 1];
|
||||||
|
|
|
@ -18,7 +18,6 @@ limitations under the License.
|
||||||
import Markdown from '../Markdown';
|
import Markdown from '../Markdown';
|
||||||
import {makeGenericPermalink} from "../utils/permalinks/Permalinks";
|
import {makeGenericPermalink} from "../utils/permalinks/Permalinks";
|
||||||
import EditorModel from "./model";
|
import EditorModel from "./model";
|
||||||
import {PillPart} from "./parts";
|
|
||||||
|
|
||||||
export function mdSerialize(model: EditorModel) {
|
export function mdSerialize(model: EditorModel) {
|
||||||
return model.parts.reduce((html, part) => {
|
return model.parts.reduce((html, part) => {
|
||||||
|
@ -32,7 +31,7 @@ export function mdSerialize(model: EditorModel) {
|
||||||
return html + part.text;
|
return html + part.text;
|
||||||
case "room-pill":
|
case "room-pill":
|
||||||
case "user-pill":
|
case "user-pill":
|
||||||
return html + `[${part.text.replace(/[[\\\]]/g, c => "\\" + c)}](${makeGenericPermalink((part as PillPart).resourceId)})`;
|
return html + `[${part.text.replace(/[[\\\]]/g, c => "\\" + c)}](${makeGenericPermalink(part.resourceId)})`;
|
||||||
}
|
}
|
||||||
}, "");
|
}, "");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue