diff options
Diffstat (limited to 'editor/editor.ts')
| -rw-r--r-- | editor/editor.ts | 436 |
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; } |
