aboutsummaryrefslogtreecommitdiff
path: root/editor/editor.ts
diff options
context:
space:
mode:
Diffstat (limited to 'editor/editor.ts')
-rw-r--r--editor/editor.ts436
1 files changed, 230 insertions, 206 deletions
diff --git a/editor/editor.ts b/editor/editor.ts
index b8fd42a..0e919ab 100644
--- a/editor/editor.ts
+++ b/editor/editor.ts
@@ -13,10 +13,10 @@ export interface WuiEditorOptions {
is_editable: boolean;
// Handler that will be called when user select lines.
- OnSelection(begin: number, end: number): void;
+ onSelection(begin: number, end: number): void;
// Handler that will be called when user press CTRL+S.
- OnSave(content: string): void;
+ onSave(content: string): void;
}
export class WuiEditor {
@@ -25,8 +25,6 @@ export class WuiEditor {
lines: WuiEditorLine[] = [];
private el!: HTMLElement;
private sel!: Selection;
- private active_file: WuiVfsNodeInterface | null = null;
- private active_text: HTMLElement | null = null;
private range_begin: number = -1;
private range_end: number = -1;
private raw_lines: string[] = [];
@@ -38,7 +36,7 @@ export class WuiEditor {
this.id = opts.id;
this.is_editable = opts.is_editable;
- let el = document.getElementById(opts.id);
+ const el = document.getElementById(opts.id);
if (!el) {
console.error("WuiEditor: element ID not found:", opts.id);
return;
@@ -49,7 +47,7 @@ export class WuiEditor {
this.el.classList.add(WUI_EDITOR_CLASS);
- let sel = window.getSelection();
+ const sel = window.getSelection();
if (!sel) {
console.error("WuiEditor: cannot get window selection", opts.id);
return;
@@ -62,35 +60,37 @@ export class WuiEditor {
};
}
- // GetContent return content of file.
- GetContent(): string {
+ // getContent return content of file.
+ getContent(): string {
let content = "";
for (let x = 0; x < this.lines.length; x++) {
if (x > 0) {
content += "\n";
}
- content += this.lines[x].el_text.innerText;
+ content += this.lines[x]!.elText.innerText;
}
return content;
}
- GetSelectionRange(): WuiEditorSelectionRangeInterface {
+ getSelectionRange(): RangeInterface {
return {
begin_at: this.range_begin,
end_at: this.range_end,
- } as WuiEditorSelectionRangeInterface;
+ } as RangeInterface;
}
- OnClickText(text: HTMLElement) {
- let sel = window.getSelection();
+ onClickText() {
+ const sel = window.getSelection();
if (sel) {
this.sel = sel;
}
}
- OnKeyup(x: number, text: HTMLElement, ev: KeyboardEvent) {
- let text_before: string;
- let text_after: string;
+ onKeyup(x: number, ev: KeyboardEvent) {
+ let elTextCurr: HTMLElement;
+ let elTextPrev: HTMLElement;
+ let textBefore: string;
+ let textAfter: string;
let off: number;
switch (ev.key) {
@@ -116,32 +116,31 @@ export class WuiEditor {
case "Backspace":
ev.preventDefault();
- text_before = this.raw_lines[x];
- let el_text_curr = this.lines[x].el_text;
- text_after = el_text_curr.innerText;
+ textBefore = this.raw_lines[x]!;
+ elTextCurr = this.lines[x]!.elText;
+ textAfter = elTextCurr.innerText;
off = this.sel.focusOffset;
if (off > 0) {
- this.unre.DoUpdate(x, text_before, text_after);
+ this.unre.doUpdate(x, textBefore, textAfter);
- this.raw_lines[x] = text_after;
- this.setCaret(el_text_curr, off);
+ this.raw_lines[x] = textAfter;
+ this.setCaret(elTextCurr, off);
return false;
}
// Join current line with previous.
- let el_text_prev = this.lines[x - 1].el_text;
+ elTextPrev = this.lines[x - 1]!.elText;
- this.unre.DoJoin(x - 1, el_text_prev.innerText, el_text_curr.innerText);
+ this.unre.doJoin(x - 1, elTextPrev.innerText, elTextCurr.innerText);
- off = el_text_prev.innerText.length;
- el_text_prev.innerText =
- el_text_prev.innerText + el_text_curr.innerText;
- this.raw_lines[x - 1] = el_text_prev.innerText;
+ off = elTextPrev.innerText.length;
+ elTextPrev.innerText = elTextPrev.innerText + elTextCurr.innerText;
+ this.raw_lines[x - 1] = elTextPrev.innerText;
// Remove the current line
this.deleteLine(x);
- this.setCaret(el_text_prev, off);
+ this.setCaret(elTextPrev, off);
return false;
case "Control":
@@ -170,20 +169,24 @@ export class WuiEditor {
if (this.is_key_control) {
break;
}
- this.unre.DoUpdate(
+ this.unre.doUpdate(
x,
- this.raw_lines[x],
- this.lines[x].el_text.innerText,
+ this.raw_lines[x]!,
+ this.lines[x]!.elText.innerText,
);
- this.raw_lines[x] = this.lines[x].el_text.innerText;
+ this.raw_lines[x] = this.lines[x]!.elText.innerText;
}
return true;
}
- OnKeydownOnLine(x: number, el_text: HTMLElement, ev: KeyboardEvent) {
- let text_before: string;
- let text_after: string;
+ onKeydownOnLine(x: number, ev: KeyboardEvent) {
+ let textBefore: string;
+ let textAfter: string;
let off: number;
+ let elText: HTMLElement | undefined;
+ let elTextCurrent: HTMLElement;
+ let text: string;
+ let isJoinLineAfter: boolean;
switch (ev.key) {
case "ArrowUp":
@@ -192,12 +195,12 @@ export class WuiEditor {
}
ev.preventDefault();
- let el_text = this.lines[x - 1].el_text;
- let off = this.sel.focusOffset;
- if (off > el_text.innerText.length) {
- off = el_text.innerText.length;
+ elText = this.lines[x - 1]!.elText;
+ off = this.sel.focusOffset;
+ if (off > elText.innerText.length) {
+ off = elText.innerText.length;
}
- this.setCaret(el_text, off);
+ this.setCaret(elText, off);
if (x == 1) {
this.el.scrollTop = 0;
@@ -212,12 +215,12 @@ export class WuiEditor {
}
ev.preventDefault();
- el_text = this.lines[x + 1].el_text;
+ elText = this.lines[x + 1]!.elText;
off = this.sel.focusOffset;
- if (off > el_text.innerText.length) {
- off = el_text.innerText.length;
+ if (off > elText.innerText.length) {
+ off = elText.innerText.length;
}
- this.setCaret(el_text, off);
+ this.setCaret(elText, off);
x += 2;
if (x * 25 >= this.el.clientHeight + this.el.scrollTop) {
@@ -232,59 +235,60 @@ export class WuiEditor {
case "Delete":
ev.preventDefault();
- let is_join_line_after = false;
- let el_text_current = this.lines[x].el_text;
+ isJoinLineAfter = false;
+ elTextCurrent = this.lines[x]!.elText;
off = this.sel.focusOffset;
- text_before = el_text_current.innerText;
- text_after = "";
+ textBefore = elTextCurrent.innerText;
+ textAfter = "";
- if (text_before.length === 0 || off === text_before.length) {
+ if (textBefore.length === 0 || off === textBefore.length) {
// Current line is empty, join the next line to current
// line; or
// Current offset is at the end of text, join the next
// line to current line.
- is_join_line_after = true;
+ isJoinLineAfter = true;
}
- if (is_join_line_after) {
+ if (isJoinLineAfter) {
if (x + 1 < this.lines.length) {
- let el_text_after = this.lines[x + 1].el_text;
- text_after = el_text_after.innerText;
- el_text_after.innerText = "";
+ const elTextAfter = this.lines[x + 1]!.elText;
+ textAfter = elTextAfter.innerText;
+ elTextAfter.innerText = "";
- this.unre.DoJoin(x, text_before, text_after);
+ this.unre.doJoin(x, textBefore, textAfter);
this.deleteLine(x + 1);
- text_after = text_before + text_after;
+ textAfter = textBefore + textAfter;
}
} else {
- text_after =
- text_before.slice(0, off) +
- text_before.slice(off + 1, text_before.length);
+ textAfter =
+ textBefore.slice(0, off) +
+ textBefore.slice(off + 1, textBefore.length);
- this.unre.DoUpdate(x, text_before, text_after);
+ this.unre.doUpdate(x, textBefore, textAfter);
}
- this.lines[x].el_text.innerText = text_after;
- this.raw_lines[x] = text_after;
- this.setCaret(el_text_current, off);
+ this.lines[x]!.elText.innerText = textAfter;
+ this.raw_lines[x] = textAfter;
+ this.setCaret(elTextCurrent, off);
break;
case "Enter":
ev.preventDefault();
+ elText = this.lines[x]!.elText;
off = this.sel.focusOffset;
- let text = this.lines[x].el_text.innerText;
- text_before = text.slice(0, off);
- text_after = text.slice(off, text.length);
+ text = elText.innerText;
+ textBefore = text.slice(0, off);
+ textAfter = text.slice(off, text.length);
- this.unre.DoSplit(x, text_before, text_after);
+ this.unre.doSplit(x, textBefore, textAfter);
- this.lines[x].el_text.innerText = text_before;
- this.raw_lines[x] = text_before;
+ elText.innerText = textBefore;
+ this.raw_lines[x] = textBefore;
- this.insertNewline(x + 1, text_after);
+ this.insertNewline(x + 1, textAfter);
if (x + 3 >= this.raw_lines.length) {
this.el.scrollTop = this.el.scrollHeight;
}
@@ -293,19 +297,23 @@ export class WuiEditor {
case "Tab":
ev.preventDefault();
- el_text = this.lines[x].el_text;
+ elText = this.lines[x]?.elText;
+ if (!elText) {
+ break;
+ }
+
off = this.sel.focusOffset;
- text_before = el_text.innerText;
- text_after =
- text_before.slice(0, off) +
+ textBefore = elText.innerText;
+ textAfter =
+ textBefore.slice(0, off) +
"\t" +
- text_before.slice(off, text_before.length);
+ textBefore.slice(off, textBefore.length);
- this.unre.DoUpdate(x, text_before, text_after);
- el_text.innerText = text_after;
- this.raw_lines[x] = text_after;
+ this.unre.doUpdate(x, textBefore, textAfter);
+ elText.innerText = textAfter;
+ this.raw_lines[x] = textAfter;
- this.setCaret(el_text, off + 1);
+ this.setCaret(elText, off + 1);
break;
case "r":
@@ -320,8 +328,8 @@ export class WuiEditor {
if (this.is_key_control) {
ev.preventDefault();
ev.stopPropagation();
- if (this.opts.OnSave) {
- this.opts.OnSave(this.GetContent());
+ if (this.opts.onSave) {
+ this.opts.onSave(this.getContent());
}
return false;
}
@@ -335,82 +343,80 @@ export class WuiEditor {
}
break;
}
+ return true;
}
- OnMouseDownAtLine(x: number) {
+ onMouseDownAtLine(x: number) {
this.range_begin = x;
}
- OnMouseUpAtLine(x: number) {
+ onMouseUpAtLine(x: number) {
this.range_end = x;
if (this.range_end < this.range_begin) {
return;
}
let y = 0;
for (; y < this.range_begin; y++) {
- this.el.children[y].setAttribute("style", "");
+ this.el.children[y]?.setAttribute("style", "");
}
for (; y <= this.range_end; y++) {
- this.el.children[y].setAttribute("style", "background-color:lightsalmon");
+ this.el.children[y]?.setAttribute(
+ "style",
+ "background-color:lightsalmon",
+ );
}
for (; y < this.el.children.length; y++) {
- this.el.children[y].setAttribute("style", "");
+ this.el.children[y]?.setAttribute("style", "");
}
- if (this.opts.OnSelection) {
- this.opts.OnSelection(this.range_begin, this.range_end);
+ if (this.opts.onSelection) {
+ this.opts.onSelection(this.range_begin, this.range_end);
}
}
- //
- // SetEditOff make the content not editable.
- //
- SetEditOff() {
- for (let x = 0; x < this.lines.length; x++) {
- this.lines[x].SetEditOff();
- }
+ // setEditOff make the content not editable.
+ setEditOff() {
+ this.lines.forEach((line) => {
+ line.setEditOff();
+ });
}
- //
- // SetEditOn make the content to be editable.
- //
- SetEditOn() {
- for (let x = 0; x < this.lines.length; x++) {
- this.lines[x].SetEditOn();
- }
+ // setEditOn make the content to be editable.
+ setEditOn() {
+ this.lines.forEach((line) => {
+ line.setEditOn();
+ });
}
- // Open the node for editing.
+ // open the node for editing.
// The content MUST be encoded in base64.
- Open(node: WuiVfsNodeInterface): void {
- this.active_file = node;
-
+ open(node: WuiVfsNodeInterface): void {
let content = atob(node.content);
content = content.replace("\r\n", "\n");
this.raw_lines = content.split("\n");
this.lines = [];
- for (let x = 0; x < this.raw_lines.length; x++) {
- let line = new WuiEditorLine(x, this.raw_lines[x], this);
+ this.raw_lines.forEach((rawLine, x) => {
+ const line = new WuiEditorLine(x, rawLine, this);
this.lines.push(line);
- }
+ });
this.render();
}
- // ClearSelection clear selection range indicator.
- ClearSelection() {
+ // clearSelection clear selection range indicator.
+ clearSelection() {
if (this.range_begin < 0 || this.range_end == 0) {
return;
}
for (let x = this.range_begin; x <= this.range_end; x++) {
- this.el.children[x].setAttribute("style", "");
+ this.el.children[x]?.setAttribute("style", "");
}
this.range_begin = -1;
this.range_end = -1;
}
private initStyle() {
- let style = document.createElement("style");
+ const style = document.createElement("style");
style.type = "text/css";
style.innerText = `
[contenteditable] {
@@ -452,24 +458,36 @@ export class WuiEditor {
document.head.appendChild(style);
}
- private doJoin(changes: WuiEditorActionChangesInterface) {
- this.lines[changes.curr_line].el_text.innerText = changes.curr_text;
- this.deleteLine(changes.next_line);
- this.setCaret(this.lines[changes.curr_line].el_text, 0);
+ private doJoin(changes: ChangesInterface) {
+ const line = this.lines[changes.currLine];
+ if (!line) {
+ return;
+ }
+ line.elText.innerText = changes.currText;
+ this.deleteLine(changes.nextLine);
+ this.setCaret(line.elText, 0);
}
- private doSplit(changes: WuiEditorActionChangesInterface) {
- this.lines[changes.curr_line].el_text.innerText = changes.curr_text;
- this.insertNewline(changes.next_line, changes.next_text);
+ private doSplit(changes: ChangesInterface) {
+ const line = this.lines[changes.currLine];
+ if (!line) {
+ return;
+ }
+ line.elText.innerText = changes.currText;
+ this.insertNewline(changes.nextLine, changes.nextText);
}
- private doUpdate(changes: WuiEditorActionChangesInterface) {
- this.lines[changes.curr_line].el_text.innerText = changes.curr_text;
- this.setCaret(this.lines[changes.curr_line].el_text, 0);
+ private doUpdate(changes: ChangesInterface) {
+ const line = this.lines[changes.currLine];
+ if (!line) {
+ return;
+ }
+ line.elText.innerText = changes.currText;
+ this.setCaret(line.elText, 0);
}
private doRedo() {
- const act = this.unre.Redo();
+ const act = this.unre.redo();
if (!act) {
return;
}
@@ -487,7 +505,7 @@ export class WuiEditor {
}
private doUndo() {
- const act = this.unre.Undo();
+ const act = this.unre.undo();
if (!act) {
return;
}
@@ -510,29 +528,29 @@ export class WuiEditor {
// Reset the line numbers.
for (; x < this.lines.length; x++) {
- this.lines[x].SetNumber(x);
+ this.lines[x]?.setNumber(x);
}
this.render();
}
private insertNewline(x: number, text: string) {
- let newline = new WuiEditorLine(x, text, this);
+ const newline = new WuiEditorLine(x, text, this);
for (let y = x; y < this.lines.length; y++) {
- this.lines[y].SetNumber(y + 1);
+ this.lines[y]?.setNumber(y + 1);
}
this.lines.splice(x, 0, newline);
this.raw_lines.splice(x, 0, text);
this.render();
- this.setCaret(newline.el_text, 0);
+ this.setCaret(newline.elText, 0);
}
private onKeyupDocument(ed: WuiEditor, ev: KeyboardEvent) {
switch (ev.key) {
case "Escape":
ev.preventDefault();
- ed.ClearSelection();
+ ed.clearSelection();
break;
}
return true;
@@ -545,11 +563,11 @@ export class WuiEditor {
}
}
- private setCaret(el_text: HTMLElement, off: number) {
- if (el_text.firstChild) {
- this.range.setStart(el_text.firstChild, off);
+ private setCaret(elText: HTMLElement, off: number) {
+ if (elText.firstChild) {
+ this.range.setStart(elText.firstChild, off);
} else {
- this.range.setStart(el_text, off);
+ this.range.setStart(elText, off);
}
this.range.collapse(true);
this.sel.removeAllRanges();
@@ -558,48 +576,48 @@ export class WuiEditor {
}
class WuiEditorLine {
- private line_num: number = 0;
+ private lineNum: number = 0;
el!: HTMLElement;
el_number!: HTMLElement;
- el_text!: HTMLElement;
+ elText!: HTMLElement;
constructor(
public x: number,
public text: string,
ed: WuiEditor,
) {
- this.line_num = x;
+ this.lineNum = x;
this.el = document.createElement("div");
this.el.classList.add(WUI_EDITOR_CLASS_LINE);
this.el_number = document.createElement("span");
this.el_number.classList.add(WUI_EDITOR_CLASS_LINE_NUMBER);
- this.el_number.innerText = this.line_num + 1 + "";
+ this.el_number.innerText = this.lineNum + 1 + "";
- this.el_number.onmousedown = (ev: MouseEvent) => {
- ed.OnMouseDownAtLine(this.line_num);
+ this.el_number.onmousedown = () => {
+ ed.onMouseDownAtLine(this.lineNum);
};
- this.el_number.onmouseup = (ev: MouseEvent) => {
- ed.OnMouseUpAtLine(this.line_num);
+ this.el_number.onmouseup = () => {
+ ed.onMouseUpAtLine(this.lineNum);
};
- this.el_text = document.createElement("span");
- this.el_text.classList.add(WUI_EDITOR_CLASS_LINE_TEXT);
- this.el_text.innerText = text;
- this.el_text.contentEditable = "true";
+ this.elText = document.createElement("span");
+ this.elText.classList.add(WUI_EDITOR_CLASS_LINE_TEXT);
+ this.elText.innerText = text;
+ this.elText.contentEditable = "true";
- this.el_text.onclick = (ev: MouseEvent) => {
- ed.OnClickText(this.el_text);
+ this.elText.onclick = () => {
+ ed.onClickText();
};
- this.el_text.onkeydown = (ev: KeyboardEvent) => {
- return ed.OnKeydownOnLine(this.line_num, this.el_text, ev);
+ this.elText.onkeydown = (ev: KeyboardEvent) => {
+ return ed.onKeydownOnLine(this.lineNum, ev);
};
- this.el_text.onkeyup = (ev: KeyboardEvent) => {
- return ed.OnKeyup(this.line_num, this.el_text, ev);
+ this.elText.onkeyup = (ev: KeyboardEvent) => {
+ return ed.onKeyup(this.lineNum, ev);
};
- this.el_text.addEventListener("paste", (ev: ClipboardEvent) => {
+ this.elText.addEventListener("paste", (ev: ClipboardEvent) => {
if (!ev.clipboardData) {
return;
}
@@ -609,20 +627,20 @@ class WuiEditorLine {
});
this.el.appendChild(this.el_number);
- this.el.appendChild(this.el_text);
+ this.el.appendChild(this.elText);
}
- SetNumber(x: number) {
- this.line_num = x;
+ setNumber(x: number) {
+ this.lineNum = x;
this.el_number.innerText = x + 1 + "";
}
- SetEditOn() {
- this.el_text.contentEditable = "true";
+ setEditOn() {
+ this.elText.contentEditable = "true";
}
- SetEditOff() {
- this.el_text.contentEditable = "false";
+ setEditOff() {
+ this.elText.contentEditable = "false";
}
}
@@ -631,23 +649,22 @@ class WuiEditorLine {
//
class WuiEditorUndoRedo {
private idx: number = 0;
- private actions: WuiEditorActionInterface[] = [];
+ private actions: ActionInterface[] = [];
- DoJoin(prevLine: number, prevText: string, curr_text: string) {
- let curr_line = prevLine + 1;
- let action: WuiEditorActionInterface = {
+ doJoin(prevLine: number, prevText: string, currText: string) {
+ const action: ActionInterface = {
kind: "join",
before: {
- curr_line: prevLine,
- curr_text: prevText,
- next_line: prevLine + 1,
- next_text: curr_text,
+ currLine: prevLine,
+ currText: prevText,
+ nextLine: prevLine + 1,
+ nextText: currText,
},
after: {
- curr_line: prevLine,
- curr_text: prevText + curr_text,
- next_line: prevLine + 1,
- next_text: "",
+ currLine: prevLine,
+ currText: prevText + currText,
+ nextLine: prevLine + 1,
+ nextText: "",
},
};
if (this.actions.length > 0) {
@@ -657,20 +674,20 @@ class WuiEditorUndoRedo {
this.idx++;
}
- DoSplit(curr_line: number, curr_text: string, next_text: string) {
- let action = {
+ doSplit(currLine: number, currText: string, nextText: string) {
+ const action = {
kind: "split",
before: {
- curr_line: curr_line,
- curr_text: curr_text + next_text,
- next_line: curr_line + 1,
- next_text: "",
+ currLine: currLine,
+ currText: currText + nextText,
+ nextLine: currLine + 1,
+ nextText: "",
},
after: {
- curr_line: curr_line,
- curr_text: curr_text,
- next_line: curr_line + 1,
- next_text: next_text,
+ currLine: currLine,
+ currText: currText,
+ nextLine: currLine + 1,
+ nextText: nextText,
},
};
if (this.actions.length > 0) {
@@ -680,20 +697,20 @@ class WuiEditorUndoRedo {
this.idx++;
}
- DoUpdate(line_num: number, text_before: string, text_after: string) {
- const action: WuiEditorActionInterface = {
+ doUpdate(lineNum: number, textBefore: string, textAfter: string) {
+ const action: ActionInterface = {
kind: "update",
before: {
- curr_line: line_num,
- curr_text: text_before,
- next_line: 0,
- next_text: "",
+ currLine: lineNum,
+ currText: textBefore,
+ nextLine: 0,
+ nextText: "",
},
after: {
- curr_line: line_num,
- curr_text: text_after,
- next_line: 0,
- next_text: "",
+ currLine: lineNum,
+ currText: textAfter,
+ nextLine: 0,
+ nextText: "",
},
};
@@ -704,19 +721,26 @@ class WuiEditorUndoRedo {
this.idx++;
}
- Undo(): WuiEditorActionInterface | null {
+ undo(): ActionInterface | null {
if (this.idx == 0) {
return null;
}
this.idx--;
- return this.actions[this.idx];
+ const action = this.actions[this.idx];
+ if (!action) {
+ return null;
+ }
+ return action;
}
- Redo(): WuiEditorActionInterface | null {
+ redo(): ActionInterface | null {
if (this.idx == this.actions.length) {
return null;
}
- let action = this.actions[this.idx];
+ const action = this.actions[this.idx];
+ if (!action) {
+ return null;
+ }
this.idx++;
return action;
}
@@ -728,20 +752,20 @@ class WuiEditorUndoRedo {
// * split: split line using enter
// * join: join line using backspace.
//
-interface WuiEditorActionInterface {
+interface ActionInterface {
kind: string;
- before: WuiEditorActionChangesInterface;
- after: WuiEditorActionChangesInterface;
+ before: ChangesInterface;
+ after: ChangesInterface;
}
-interface WuiEditorActionChangesInterface {
- curr_line: number;
- curr_text: string;
- next_line: number;
- next_text: string;
+interface ChangesInterface {
+ currLine: number;
+ currText: string;
+ nextLine: number;
+ nextText: string;
}
-interface WuiEditorSelectionRangeInterface {
+interface RangeInterface {
begin_at: number;
end_at: number;
}