diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownEditor/BitMarkdownEditor.ts b/src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownEditor/BitMarkdownEditor.ts index 7387f44c6f..a13f3f7983 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownEditor/BitMarkdownEditor.ts +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownEditor/BitMarkdownEditor.ts @@ -1,14 +1,10 @@ namespace BitBlazorUI { - type Content = { - value: string; - type: "inline" | "block" | "wrap" | "init"; - }; export class MarkdownEditor { - private static _editors: { [key: string]: MarkdownEditor } = {}; + private static _editors: { [key: string]: Editor } = {}; public static init(id: string, textArea: HTMLTextAreaElement, dotnetObj?: DotNetObject, defaultValue?: string) { - const editor = new MarkdownEditor(textArea, dotnetObj); + const editor = new Editor(textArea, dotnetObj); if (defaultValue) { editor.value = defaultValue; @@ -27,134 +23,123 @@ namespace BitBlazorUI { public static dispose(id: string) { if (!MarkdownEditor._editors[id]) return; - MarkdownEditor._editors[id].#dispose(); + MarkdownEditor._editors[id].dispose(); delete MarkdownEditor._editors[id]; } + } - #opens: string[] = []; - - #pairs: { [key: string]: string } = { - "(": ")", - "{": "}", - "[": "]", - "<": ">", + class Editor { + _opens: string[] = []; + _pairs: { [key: string]: string } = { + '(': ')', + '{': '}', + '[': ']', + '<': '>', '"': '"', - "`": "`", - "*": "*", - "**": "**", + '`': '`', + '*': '*', + '**': '**', }; - #textArea: HTMLTextAreaElement; - #dotnetObj: DotNetObject | undefined | null; + private textArea: HTMLTextAreaElement; + private dotnetObj: DotNetObject | undefined | null; - #clickHandler: (e: MouseEvent) => void; - #changeHandler: (e: Event) => void; - #dblClickHandler: (e: MouseEvent) => void; - #keydownHandler: (e: KeyboardEvent) => Promise; + private clickHandler: (e: MouseEvent) => void; + private changeHandler: (e: Event) => void; + private dblClickHandler: (e: MouseEvent) => void; + private keydownHandler: (e: KeyboardEvent) => Promise; constructor(textArea: HTMLTextAreaElement, dotnetObj?: DotNetObject) { - this.#textArea = textArea; - this.#dotnetObj = dotnetObj; - - this.#clickHandler = this.#click.bind(this); - this.#changeHandler = this.#change.bind(this); - this.#dblClickHandler = this.#dblClick.bind(this); - this.#keydownHandler = this.#keydown.bind(this); - - this.#init(); + this.textArea = textArea; + this.dotnetObj = dotnetObj; + + this.clickHandler = this.click.bind(this); + this.changeHandler = this.change.bind(this); + this.dblClickHandler = this.dblClick.bind(this); + this.keydownHandler = this.keydown.bind(this); + + this.textArea.addEventListener('click', this.clickHandler); + this.textArea.addEventListener('change', this.changeHandler); + this.textArea.addEventListener('input', this.changeHandler); + this.textArea.addEventListener('dblclick', this.dblClickHandler); + this.textArea.addEventListener('keydown', this.keydownHandler); } get value() { - return this.#textArea.value; + return this.textArea.value; } set value(value) { - this.#textArea.value = value; + this.textArea.value = value; } - #init() { - this.#textArea.addEventListener("click", this.#clickHandler); - this.#textArea.addEventListener("change", this.#changeHandler); - this.#textArea.addEventListener("input", this.#changeHandler); - this.#textArea.addEventListener("dblclick", this.#dblClickHandler); - this.#textArea.addEventListener("keydown", this.#keydownHandler); - } - - get #block() { - const codeBlocks = this.value.split("```"); + get block() { + const codeBlocks = this.value.split('```'); let total = 0; for (const [i, b] of codeBlocks.entries()) { total += b.length + 3; - if (this.#start < total) { + if (this.start < total) { return i; } } return 0; } - get #end() { - return this.#textArea.selectionEnd; + get end() { + return this.textArea.selectionEnd; } - get #start() { - return this.#textArea.selectionStart; + get start() { + return this.textArea.selectionStart; } - #set(start: number, end: number) { - this.#textArea.setSelectionRange(start, end); + set(start: number, end: number) { + this.textArea.setSelectionRange(start, end); } - #getLine() { - const total = this.value.split("\n"); + getLine() { + const total = this.value.split('\n'); let count = 0; for (let i = 0; i < total.length; i++) { const length = total.at(i)?.length ?? 0; count++; count += length; - if (count > this.#end) { + if (count > this.end) { return { total, num: i, - col: this.#end - (count - length - 1), + col: this.end - (count - length - 1), }; } } return { total, num: 0, col: 0 }; } - #insert( - content: Content, - start: number, - end: number, - ) { - if (content.type === "inline") { + insert(content: Content, start: number, end: number) { + if (content.type === 'inline') { this.value = `${this.value.slice(0, end)}${content.value}${this.value.slice(end)}`; - } else if (content.type === "wrap") { + } else if (content.type === 'wrap') { this.value = insert(this.value, content.value, start); this.value = insert( this.value, - this.#pairs[content.value] as string, + this._pairs[content.value] as string, end + content.value.length, ); - if (content.value.length < 2) this.#opens.push(content.value); - } else if (content.type === "block") { - const { total, num } = this.#getLine(); + if (content.value.length < 2) this._opens.push(content.value); + } else if (content.type === 'block') { + const { total, num } = this.getLine(); const first = content.value.at(0); if (first && total[num]?.startsWith(first)) { total[num] = content.value.trim() + total[num]; } else { total[num] = content.value + total[num]; } - this.value = total.join("\n"); - } else if (content.type === "init") { } + this.value = total.join('\n'); + } else if (content.type === 'init') { } } - #setCaret( - text: string, - start: number, - end: number, - ) { + setCaret(text: string, start: number, end: number) { let startPos = 0; let endPos = 0; if (/[a-z]/i.test(text)) { @@ -174,19 +159,19 @@ namespace BitBlazorUI { endPos = end + text.length; } - this.#set(startPos, endPos); - this.#textArea.focus(); + this.set(startPos, endPos); + this.textArea.focus(); } - #add(content: Content) { - const end = this.#end; - const start = this.#start; + add(content: Content) { + const end = this.end; + const start = this.start; - this.#insert(content, start, end); - this.#setCaret(content.value, start, end); + this.insert(content, start, end); + this.setCaret(content.value, start, end); } - #getLists(str: string | undefined) { + getLists(str: string | undefined) { if (!str) return; if (startsWithDash(str)) { @@ -197,8 +182,8 @@ namespace BitBlazorUI { if (listNum) return `${listNum}. `; } - #correct(cur: number, isDec = false) { - const { total } = this.#getLine(); + correct(cur: number, isDec = false) { + const { total } = this.getLine(); for (let i = cur + 1; i < total.length; i++) { const l = total[i]; if (!l) continue; @@ -229,50 +214,62 @@ namespace BitBlazorUI { } } } - this.value = total.join("\n"); + this.value = total.join('\n'); + } + + dispose() { + this.textArea.removeEventListener('click', this.clickHandler); + this.textArea.removeEventListener('change', this.changeHandler); + this.textArea.removeEventListener('input', this.changeHandler); + this.textArea.removeEventListener('dblclick', this.dblClickHandler); + this.textArea.removeEventListener('keydown', this.keydownHandler); + + this.dotnetObj = undefined; } - async #keydown(e: KeyboardEvent) { - const reseters = ["Delete", "ArrowUp", "ArrowDown"]; - const next = this.value[this.#end] ?? ""; + // ========================================================== + + async keydown(e: KeyboardEvent) { + const reseters = ['Delete', 'ArrowUp', 'ArrowDown']; + const next = this.value[this.end] ?? ''; if (reseters.includes(e.key)) { - this.#opens = []; - } else if (e.key === "Backspace") { - const prev = this.value[this.#start - 1]; + this._opens = []; + } else if (e.key === 'Backspace') { + const prev = this.value[this.start - 1]; if ( prev && - prev in this.#pairs && - next === this.#pairs[prev] + prev in this._pairs && + next === this._pairs[prev] ) { e.preventDefault(); - const start = this.#start - 1; - const end = this.#end - 1; + const start = this.start - 1; + const end = this.end - 1; this.value = remove(this.value, start); this.value = remove(this.value, end); setTimeout(() => { - this.#set(start, end); + this.set(start, end); }, 0); - this.#opens.pop(); + this._opens.pop(); } - if (prev === "\n" && this.#start === this.#end) { + if (prev === '\n' && this.start === this.end) { e.preventDefault(); - const pos = this.#start - 1; - const { num } = this.#getLine(); - this.#correct(num, true); + const pos = this.start - 1; + const { num } = this.getLine(); + this.correct(num, true); this.value = remove(this.value, pos); setTimeout(async () => { - this.#set(pos, pos); + this.set(pos, pos); }, 0); } - } else if (e.key === "Tab") { - if (this.#block % 2 !== 0) { + } else if (e.key === 'Tab') { + if (this.block % 2 !== 0) { e.preventDefault(); - this.#add({ type: "inline", value: "\t" }); + this.add({ type: 'inline', value: '\t' }); } - } else if (e.key === "Enter") { - const { total, num, col } = this.#getLine(); + } else if (e.key === 'Enter') { + const { total, num, col } = this.getLine(); const line = total.at(num); - let rep = this.#getLists(line); + let rep = this.getLists(line); const orig = rep; const n = startsWithNumber(rep); @@ -280,12 +277,12 @@ namespace BitBlazorUI { if (rep && (orig && orig.length < col)) { e.preventDefault(); - if (n) this.#correct(num); - this.#add({ type: "inline", value: `\n${rep}` }); + if (n) this.correct(num); + this.add({ type: 'inline', value: `\n${rep}` }); } else if (rep && (orig && orig.length === col)) { e.preventDefault(); - const origEnd = this.#end; + const origEnd = this.end; const pos = origEnd - orig.length; for (let i = 0; i < orig.length; i++) { @@ -293,27 +290,27 @@ namespace BitBlazorUI { } setTimeout(async () => { - this.#set(pos, pos); - this.#textArea.focus(); - this.#add({ type: "inline", value: `\n` }); + this.set(pos, pos); + this.textArea.focus(); + this.add({ type: 'inline', value: `\n` }); }, 0); } } else { - const nextIsPaired = Object.values(this.#pairs).includes(next); - const isSelected = this.#start !== this.#end; + const nextIsPaired = Object.values(this._pairs).includes(next); + const isSelected = this.start !== this.end; if (e.ctrlKey || e.metaKey) { - if (this.#start === this.#end) { - if (e.key === "c" || e.key === "x") { + if (this.start === this.end) { + if (e.key === 'c' || e.key === 'x') { e.preventDefault(); - const { total, num, col } = this.#getLine(); + const { total, num, col } = this.getLine(); - await navigator.clipboard.writeText(`${num === 0 && e.key === "x" ? "" : "\n"}${total[num]}`); + await navigator.clipboard.writeText(`${num === 0 && e.key === 'x' ? '' : '\n'}${total[num]}`); - if (e.key === "x") { - const pos = this.#start - col; + if (e.key === 'x') { + const pos = this.start - col; total.splice(num, 1); - this.value = total.join("\n"); - setTimeout(() => this.#set(pos, pos), 0); + this.value = total.join('\n'); + setTimeout(() => this.set(pos, pos), 0); } } } @@ -322,68 +319,63 @@ namespace BitBlazorUI { if ((e.ctrlKey || e.metaKey) && e.key) { let content: Content | undefined; - if (e.key === "h") { + if (e.key === 'h') { content = { type: 'block', value: '# ' }; } - if (e.key === "b") { + if (e.key === 'b') { content = { type: 'wrap', value: '**' }; } - if (e.key === "i") { + if (e.key === 'i') { content = { type: 'wrap', value: '*' }; } if (content) { - this.#add(content); + this.add(content); e.preventDefault(); } } else if ( nextIsPaired && - (next === e.key || e.key === "ArrowRight") && - this.#opens.length && + (next === e.key || e.key === 'ArrowRight') && + this._opens.length && !isSelected ) { e.preventDefault(); - this.#set(this.#start + 1, this.#end + 1); - this.#opens.pop(); - } else if (e.key in this.#pairs) { + this.set(this.start + 1, this.end + 1); + this._opens.pop(); + } else if (e.key in this._pairs) { e.preventDefault(); - this.#add({ type: "wrap", value: e.key }); - this.#opens.push(e.key); + this.add({ type: 'wrap', value: e.key }); + this._opens.push(e.key); } } } - #dblClick(e: MouseEvent) { - if (this.#start !== this.#end) { - if (this.value[this.#start] === " ") { - this.#set(this.#start + 1, this.#end); + dblClick(e: MouseEvent) { + if (this.start !== this.end) { + if (this.value[this.start] === ' ') { + this.set(this.start + 1, this.end); } - if (this.value[this.#end - 1] === " ") { - this.#set(this.#start, this.#end - 1); + if (this.value[this.end - 1] === ' ') { + this.set(this.start, this.end - 1); } } } - #click(e: MouseEvent) { - this.#opens = []; - } - - #change(e: Event) { - this.#dotnetObj?.invokeMethodAsync("OnChange", this.value); + click(e: MouseEvent) { + this._opens = []; } - #dispose() { - this.#textArea.removeEventListener("click", this.#clickHandler); - this.#textArea.removeEventListener("change", this.#changeHandler); - this.#textArea.removeEventListener("input", this.#changeHandler); - this.#textArea.removeEventListener("dblclick", this.#dblClickHandler); - this.#textArea.removeEventListener("keydown", this.#keydownHandler); - - this.#dotnetObj = undefined; + change(e: Event) { + this.dotnetObj?.invokeMethodAsync('OnChange', this.value); } } + type Content = { + value: string; + type: 'inline' | 'block' | 'wrap' | 'init'; + }; + const startsWithDash = (str: string | undefined) => { return !!(str?.startsWith('- ')); };