From 277dbdbdb007ac45a39227e6a9e130a208d37ea6 Mon Sep 17 00:00:00 2001 From: zamfofex Date: Fri, 6 Sep 2024 16:05:46 -0300 Subject: [PATCH 1/9] use 'const' more often --- codejar.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/codejar.ts b/codejar.ts index 322add1..6244188 100644 --- a/codejar.ts +++ b/codejar.ts @@ -313,7 +313,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P const before = beforeCursor() const after = afterCursor() - let [padding] = findPadding(before) + const [padding] = findPadding(before) let newLinePadding = padding // If last symbol is "{" ident new line @@ -376,7 +376,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P preventDefault(event) if (event.shiftKey) { const before = beforeCursor() - let [padding, start] = findPadding(before) + const [padding, start] = findPadding(before) if (padding.length > 0) { const pos = save() // Remove full length tab or just remaining padding @@ -499,7 +499,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P } function getKeyCode(event: KeyboardEvent): string | undefined { - let key = event.key || event.keyCode || event.which + const key = event.key || event.keyCode || event.which if (!key) return undefined return (typeof key === 'string' ? key : String.fromCharCode(key)).toUpperCase() } @@ -563,7 +563,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P restore, recordHistory, destroy() { - for (let [type, fn] of listeners) { + for (const [type, fn] of listeners) { editor.removeEventListener(type, fn) } }, From cc6b48b12bc61007a8077d5590eb52000d9f9499 Mon Sep 17 00:00:00 2001 From: zamfofex Date: Fri, 6 Sep 2024 16:31:33 -0300 Subject: [PATCH 2/9] avoid using 'any' --- codejar.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/codejar.ts b/codejar.ts index 6244188..7badc08 100644 --- a/codejar.ts +++ b/codejar.ts @@ -50,7 +50,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P const window = options.window const document = window.document - const listeners: [string, any][] = [] + const listeners: [keyof HTMLElementEventMap, (event: Event) => void][] = [] const history: HistoryRecord[] = [] let at = -1 let focus = false @@ -94,7 +94,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P }, 300) const on = (type: K, fn: (event: HTMLElementEventMap[K]) => void) => { - listeners.push([type, fn]) + listeners.push([type, fn as (event: Event) => void]) editor.addEventListener(type, fn) } @@ -443,8 +443,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P function handlePaste(event: ClipboardEvent) { if (event.defaultPrevented) return preventDefault(event) - const originalEvent = (event as any).originalEvent ?? event - const text = originalEvent.clipboardData.getData('text/plain').replace(/\r\n?/g, '\n') + const text = event.clipboardData?.getData('text/plain').replace(/\r\n?/g, '\n') ?? '' const pos = save() insert(text) doHighlight(editor) @@ -458,8 +457,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P function handleCut(event: ClipboardEvent) { const pos = save() const selection = getSelection() - const originalEvent = (event as any).originalEvent ?? event - originalEvent.clipboardData.setData('text/plain', selection.toString()) + event.clipboardData?.setData('text/plain', selection.toString()) document.execCommand('delete') doHighlight(editor) restore({ @@ -514,9 +512,9 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P document.execCommand('insertHTML', false, text) } - function debounce(cb: any, wait: number) { + function debounce(cb: (...args: Args) => void, wait: number) { let timeout = 0 - return (...args: any) => { + return (...args: Args) => { clearTimeout(timeout) timeout = window.setTimeout(() => cb(...args), wait) } From 22cf4bf3f8f3c300881f3888c723c93bac198252 Mon Sep 17 00:00:00 2001 From: zamfofex Date: Fri, 6 Sep 2024 16:59:42 -0300 Subject: [PATCH 3/9] make miscellaneous improvements --- codejar.ts | 82 ++++++++++++++++++++++-------------------------------- 1 file changed, 33 insertions(+), 49 deletions(-) diff --git a/codejar.ts b/codejar.ts index 7badc08..a9acbae 100644 --- a/codejar.ts +++ b/codejar.ts @@ -54,7 +54,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P const history: HistoryRecord[] = [] let at = -1 let focus = false - let onUpdate: (code: string) => void | undefined = () => void 0 + let onUpdate: (code: string) => void = () => { } let prev: string // code content prior keydown event editor.setAttribute('contenteditable', 'plaintext-only') @@ -64,17 +64,13 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P editor.style.overflowY = 'auto' editor.style.whiteSpace = 'pre-wrap' - const doHighlight = (editor: HTMLElement, pos?: Position) => { - highlight(editor, pos) - } - let isLegacy = false // true if plaintext-only is not supported if (editor.contentEditable !== 'plaintext-only') isLegacy = true if (isLegacy) editor.setAttribute('contenteditable', 'true') const debounceHighlight = debounce(() => { const pos = save() - doHighlight(editor, pos) + highlight(editor, pos) restore(pos) }, 30) @@ -125,13 +121,8 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P onUpdate(toString()) }) - on('focus', _event => { - focus = true - }) - - on('blur', _event => { - focus = false - }) + on('focus', () => focus = true) + on('blur', () => focus = false) on('paste', event => { recordHistory() @@ -203,8 +194,8 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P } if (el.nodeType === Node.TEXT_NODE) { - if (pos.dir != '->') pos.start += el.nodeValue!.length - if (pos.dir != '<-') pos.end += el.nodeValue!.length + if (pos.dir !== '->') pos.start += el.nodeValue!.length + if (pos.dir !== '<-') pos.end += el.nodeValue!.length } }) @@ -222,7 +213,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P if (pos.end < 0) pos.end = 0 // Flip start and end if the direction reversed - if (pos.dir == '<-') { + if (pos.dir === '<-') { const {start, end} = pos pos.start = end pos.end = start @@ -233,7 +224,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P visit(editor, el => { if (el.nodeType !== Node.TEXT_NODE) return - const len = (el.nodeValue || '').length + const len = el.nodeValue?.length ?? 0 if (current + len > pos.start) { if (!startNode) { startNode = el @@ -252,7 +243,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P if (!endNode) endNode = editor, endOffset = editor.childNodes.length // Flip back the selection - if (pos.dir == '<-') { + if (pos.dir === '<-') { [startNode, startOffset, endNode, endOffset] = [endNode, endOffset, startNode, startOffset] } @@ -282,7 +273,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P while (node && node !== editor) { if (node.nodeType === Node.ELEMENT_NODE) { const el = node as Element - if (el.getAttribute('contenteditable') == 'false') { + if (el.getAttribute('contenteditable') === 'false') { return el } } @@ -323,7 +314,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P // Preserve padding if (newLinePadding.length > 0) { - preventDefault(event) + event.preventDefault() event.stopPropagation() insert('\n' + newLinePadding) } else { @@ -343,9 +334,9 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P // Firefox does not support plaintext-only mode // and puts

on Enter. Let's help. if (isLegacy && event.key === 'Enter') { - preventDefault(event) + event.preventDefault() event.stopPropagation() - if (afterCursor() == '') { + if (afterCursor() === '') { insert('\n ') const pos = save() pos.start = --pos.end @@ -360,9 +351,9 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P const open = options.autoclose.open; const close = options.autoclose.close; if (open.includes(event.key)) { - preventDefault(event) + event.preventDefault() const pos = save() - const wrapText = pos.start == pos.end ? '' : getSelection().toString() + const wrapText = pos.start === pos.end ? '' : getSelection().toString() const text = event.key + wrapText + (close[open.indexOf(event.key)] ?? "") insert(text) pos.start++ @@ -373,7 +364,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P function handleTabCharacters(event: KeyboardEvent) { if (event.key === 'Tab') { - preventDefault(event) + event.preventDefault() if (event.shiftKey) { const before = beforeCursor() const [padding, start] = findPadding(before) @@ -395,7 +386,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P function handleUndoRedo(event: KeyboardEvent) { if (isUndo(event)) { - preventDefault(event) + event.preventDefault() at-- const record = history[at] if (record) { @@ -405,7 +396,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P if (at < 0) at = 0 } if (isRedo(event)) { - preventDefault(event) + event.preventDefault() at++ const record = history[at] if (record) { @@ -442,11 +433,11 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P function handlePaste(event: ClipboardEvent) { if (event.defaultPrevented) return - preventDefault(event) + event.preventDefault() const text = event.clipboardData?.getData('text/plain').replace(/\r\n?/g, '\n') ?? '' const pos = save() insert(text) - doHighlight(editor) + highlight(editor) restore({ start: Math.min(pos.start, pos.end) + text.length, end: Math.min(pos.start, pos.end) + text.length, @@ -459,13 +450,13 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P const selection = getSelection() event.clipboardData?.setData('text/plain', selection.toString()) document.execCommand('delete') - doHighlight(editor) + highlight(editor) restore({ start: Math.min(pos.start, pos.end), end: Math.min(pos.start, pos.end), dir: '<-', }) - preventDefault(event) + event.preventDefault() } function visit(editor: HTMLElement, visitor: (el: Node) => 'stop' | undefined) { @@ -485,21 +476,19 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P } function isUndo(event: KeyboardEvent) { - return isCtrl(event) && !event.shiftKey && getKeyCode(event) === 'Z' + return isCtrl(event) && !event.shiftKey && getKey(event) === 'Z' } function isRedo(event: KeyboardEvent) { - return isCtrl(event) && event.shiftKey && getKeyCode(event) === 'Z' + return isCtrl(event) && event.shiftKey && getKey(event) === 'Z' } function isCopy(event: KeyboardEvent) { - return isCtrl(event) && getKeyCode(event) === 'C' + return isCtrl(event) && getKey(event) === 'C' } - function getKeyCode(event: KeyboardEvent): string | undefined { - const key = event.key || event.keyCode || event.which - if (!key) return undefined - return (typeof key === 'string' ? key : String.fromCharCode(key)).toUpperCase() + function getKey(event: KeyboardEvent): string | undefined { + return event.key.toUpperCase() } function insert(text: string) { @@ -516,7 +505,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P let timeout = 0 return (...args: Args) => { clearTimeout(timeout) - timeout = window.setTimeout(() => cb(...args), wait) + timeout = setTimeout(() => cb(...args), wait) } } @@ -528,20 +517,15 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P // Find padding of the line. let j = i while (j < text.length && /[ \t]/.test(text[j])) j++ - return [text.substring(i, j) || '', i, j] + return [text.substring(i, j), i, j] } function toString() { - return editor.textContent || '' - } - - function preventDefault(event: Event) { - event.preventDefault() + return editor.textContent ?? '' } function getSelection() { - // @ts-ignore - return editor.getRootNode().getSelection() as Selection + return (editor.getRootNode() as Document).getSelection()! } return { @@ -550,8 +534,8 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P }, updateCode(code: string, callOnUpdate: boolean = true) { editor.textContent = code - doHighlight(editor) - callOnUpdate && onUpdate(code) + highlight(editor) + if (callOnUpdate) onUpdate(code) }, onUpdate(callback: (code: string) => void) { onUpdate = callback From 7dbdbe2c897946f1c8b4a40ff7c2ebb15986a548 Mon Sep 17 00:00:00 2001 From: zamfofex Date: Fri, 6 Sep 2024 18:17:18 -0300 Subject: [PATCH 4/9] remove usages of deprecated 'document.execCommand' --- codejar.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/codejar.ts b/codejar.ts index a9acbae..4bbb079 100644 --- a/codejar.ts +++ b/codejar.ts @@ -373,7 +373,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P // Remove full length tab or just remaining padding const len = Math.min(options.tab.length, padding.length) restore({start, end: start + len}) - document.execCommand('delete') + insert('') // deletes the selection pos.start -= len pos.end -= len restore(pos) @@ -449,7 +449,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P const pos = save() const selection = getSelection() event.clipboardData?.setData('text/plain', selection.toString()) - document.execCommand('delete') + insert('') // deletes the selection highlight(editor) restore({ start: Math.min(pos.start, pos.end), @@ -491,14 +491,14 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P return event.key.toUpperCase() } - function insert(text: string) { - text = text - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, ''') - document.execCommand('insertHTML', false, text) + function insert(inserted: string) { + let {start, end} = save() + const text = toString() + const before = text.slice(0, start) + const after = text.slice(end) + editor.textContent = before + inserted + after + start += inserted.length + restore({start, end: start}) } function debounce(cb: (...args: Args) => void, wait: number) { From d7a11264ac6743414933a403e267ff7f084bfd29 Mon Sep 17 00:00:00 2001 From: zamfofex Date: Fri, 6 Sep 2024 18:40:55 -0300 Subject: [PATCH 5/9] simplify event handling (by using 'input' instead of 'keydown') --- codejar.ts | 93 +++++++----------------------------------------------- 1 file changed, 12 insertions(+), 81 deletions(-) diff --git a/codejar.ts b/codejar.ts index 4bbb079..ef7b74b 100644 --- a/codejar.ts +++ b/codejar.ts @@ -53,9 +53,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P const listeners: [keyof HTMLElementEventMap, (event: Event) => void][] = [] const history: HistoryRecord[] = [] let at = -1 - let focus = false let onUpdate: (code: string) => void = () => { } - let prev: string // code content prior keydown event editor.setAttribute('contenteditable', 'plaintext-only') editor.setAttribute('spellcheck', options.spellcheck ? 'true' : 'false') @@ -74,20 +72,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P restore(pos) }, 30) - let recording = false - const shouldRecord = (event: KeyboardEvent): boolean => { - return !isUndo(event) && !isRedo(event) - && event.key !== 'Meta' - && event.key !== 'Control' - && event.key !== 'Alt' - && !event.key.startsWith('Arrow') - } - const debounceRecordHistory = debounce((event: KeyboardEvent) => { - if (shouldRecord(event)) { - recordHistory() - recording = false - } - }, 300) + const debounceRecordHistory = debounce(recordHistory, 300) const on = (type: K, fn: (event: HTMLElementEventMap[K]) => void) => { listeners.push([type, fn as (event: Event) => void]) @@ -97,45 +82,25 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P on('keydown', event => { if (event.defaultPrevented) return - prev = toString() if (options.preserveIdent) handleNewLine(event) else legacyNewLineFix(event) if (options.catchTab) handleTabCharacters(event) if (options.addClosing) handleSelfClosingCharacters(event) - if (options.history) { - handleUndoRedo(event) - if (shouldRecord(event) && !recording) { - recordHistory() - recording = true - } + if (options.history) handleUndoRedo(event) + + // 'defaultPrevented' means a change was made + if (event.defaultPrevented) { + const pos = save() + highlight(editor, pos) + restore(pos) } - if (isLegacy && !isCopy(event)) restore(save()) }) - on('keyup', event => { + on('input', event => { if (event.defaultPrevented) return - if (event.isComposing) return - - if (prev !== toString()) debounceHighlight() - debounceRecordHistory(event) - onUpdate(toString()) - }) - - on('focus', () => focus = true) - on('blur', () => focus = false) - - on('paste', event => { - recordHistory() - handlePaste(event) - recordHistory() - onUpdate(toString()) - }) - - on('cut', event => { - recordHistory() - handleCut(event) - recordHistory() - onUpdate(toString()) + if ((event as InputEvent).isComposing) return + if (options.history) debounceRecordHistory() + debounceHighlight() }) function save(): Position { @@ -408,8 +373,6 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P } function recordHistory() { - if (!focus) return - const html = editor.innerHTML const pos = save() @@ -431,34 +394,6 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P } } - function handlePaste(event: ClipboardEvent) { - if (event.defaultPrevented) return - event.preventDefault() - const text = event.clipboardData?.getData('text/plain').replace(/\r\n?/g, '\n') ?? '' - const pos = save() - insert(text) - highlight(editor) - restore({ - start: Math.min(pos.start, pos.end) + text.length, - end: Math.min(pos.start, pos.end) + text.length, - dir: '<-', - }) - } - - function handleCut(event: ClipboardEvent) { - const pos = save() - const selection = getSelection() - event.clipboardData?.setData('text/plain', selection.toString()) - insert('') // deletes the selection - highlight(editor) - restore({ - start: Math.min(pos.start, pos.end), - end: Math.min(pos.start, pos.end), - dir: '<-', - }) - event.preventDefault() - } - function visit(editor: HTMLElement, visitor: (el: Node) => 'stop' | undefined) { const queue: Node[] = [] if (editor.firstChild) queue.push(editor.firstChild) @@ -483,10 +418,6 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P return isCtrl(event) && event.shiftKey && getKey(event) === 'Z' } - function isCopy(event: KeyboardEvent) { - return isCtrl(event) && getKey(event) === 'C' - } - function getKey(event: KeyboardEvent): string | undefined { return event.key.toUpperCase() } From 98641963ce6ab6d0c1219e570108cf59b616a8f5 Mon Sep 17 00:00:00 2001 From: zamfofex Date: Fri, 6 Sep 2024 18:45:47 -0300 Subject: [PATCH 6/9] fix cursor issue in Firefox closes #111 closes #115 --- codejar.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/codejar.ts b/codejar.ts index ef7b74b..1ef5afa 100644 --- a/codejar.ts +++ b/codejar.ts @@ -110,15 +110,6 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P let {anchorNode, anchorOffset, focusNode, focusOffset} = s if (!anchorNode || !focusNode) throw 'error1' - // If the anchor and focus are the editor element, return either a full - // highlight or a start/end cursor position depending on the selection - if (anchorNode === editor && focusNode === editor) { - pos.start = (anchorOffset > 0 && editor.textContent) ? editor.textContent.length : 0 - pos.end = (focusOffset > 0 && editor.textContent) ? editor.textContent.length : 0 - pos.dir = (focusOffset >= anchorOffset) ? '->' : '<-' - return pos - } - // Selection anchor and focus are expected to be text nodes, // so normalize them. if (anchorNode.nodeType === Node.ELEMENT_NODE) { From e212f2e51ccc24690a3bacb5365c347cabca0c3a Mon Sep 17 00:00:00 2001 From: zamfofex Date: Fri, 6 Sep 2024 19:04:44 -0300 Subject: [PATCH 7/9] simplify '[before|after]Cursor' as 'aroundCursor' --- codejar.ts | 44 +++++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/codejar.ts b/codejar.ts index 1ef5afa..8a1a303 100644 --- a/codejar.ts +++ b/codejar.ts @@ -237,28 +237,20 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P } } - function beforeCursor() { - const s = getSelection() - const r0 = s.getRangeAt(0) - const r = document.createRange() - r.selectNodeContents(editor) - r.setEnd(r0.startContainer, r0.startOffset) - return r.toString() - } - - function afterCursor() { - const s = getSelection() - const r0 = s.getRangeAt(0) - const r = document.createRange() - r.selectNodeContents(editor) - r.setStart(r0.endContainer, r0.endOffset) - return r.toString() + function aroundCursor() { + let {start, end, dir} = save() + if (dir === '<-') { + [start, end] = [end, start] + } + const text = toString() + const before = text.slice(0, start) + const after = text.slice(end) + return {before, after} } function handleNewLine(event: KeyboardEvent) { if (event.key === 'Enter') { - const before = beforeCursor() - const after = afterCursor() + const {before, after} = aroundCursor() const [padding] = findPadding(before) let newLinePadding = padding @@ -292,7 +284,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P if (isLegacy && event.key === 'Enter') { event.preventDefault() event.stopPropagation() - if (afterCursor() === '') { + if (aroundCursor().after === '') { insert('\n ') const pos = save() pos.start = --pos.end @@ -322,7 +314,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P if (event.key === 'Tab') { event.preventDefault() if (event.shiftKey) { - const before = beforeCursor() + const {before} = aroundCursor() const [padding, start] = findPadding(before) if (padding.length > 0) { const pos = save() @@ -413,13 +405,11 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P return event.key.toUpperCase() } - function insert(inserted: string) { - let {start, end} = save() - const text = toString() - const before = text.slice(0, start) - const after = text.slice(end) - editor.textContent = before + inserted + after - start += inserted.length + function insert(text: string) { + let {start} = save() + const {before, after} = aroundCursor() + editor.textContent = before + text + after + start += text.length restore({start, end: start}) } From 5669e222099fdb3f832422498d2f6bdbe3dee230 Mon Sep 17 00:00:00 2001 From: zamfofex Date: Fri, 6 Sep 2024 19:57:20 -0300 Subject: [PATCH 8/9] add multiline tab editing closes #97 --- codejar.ts | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/codejar.ts b/codejar.ts index 8a1a303..2fa5eea 100644 --- a/codejar.ts +++ b/codejar.ts @@ -245,7 +245,8 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P const text = toString() const before = text.slice(0, start) const after = text.slice(end) - return {before, after} + const within = text.slice(start, end) + return {before, after, within} } function handleNewLine(event: KeyboardEvent) { @@ -311,25 +312,31 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P } function handleTabCharacters(event: KeyboardEvent) { - if (event.key === 'Tab') { - event.preventDefault() - if (event.shiftKey) { - const {before} = aroundCursor() - const [padding, start] = findPadding(before) - if (padding.length > 0) { - const pos = save() - // Remove full length tab or just remaining padding - const len = Math.min(options.tab.length, padding.length) - restore({start, end: start + len}) - insert('') // deletes the selection - pos.start -= len - pos.end -= len - restore(pos) - } - } else { - insert(options.tab) - } + if (event.key !== 'Tab') return + event.preventDefault() + const pos = save() + if (!event.shiftKey && pos.start === pos.end) { + insert(options.tab) + return } + + let {before, after, within} = aroundCursor() + + const i = Math.max(0, before.lastIndexOf('\n')) + const j = Math.min(Infinity, after.indexOf('\n')) + within = before.slice(i) + within + after.slice(0, j) + before = before.slice(0, i) + after = after.slice(j) + + const replaced = event.shiftKey + ? within.replace(new RegExp(`^[\\t ]{0,${options.tab.length}}`, 'gm'), '') + : within.replace(/^/gm, options.tab) + editor.textContent = before + replaced + after + + const len = replaced.length - within.length + pos.start += len + pos.end += len + restore(pos) } function handleUndoRedo(event: KeyboardEvent) { From c30c958a05f98b9bb26fdac8a0080c33afaeb64e Mon Sep 17 00:00:00 2001 From: zamfofex Date: Sat, 7 Sep 2024 01:50:33 -0300 Subject: [PATCH 9/9] make small stability improvements --- codejar.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/codejar.ts b/codejar.ts index 2fa5eea..7ce5eaa 100644 --- a/codejar.ts +++ b/codejar.ts @@ -66,6 +66,8 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P if (editor.contentEditable !== 'plaintext-only') isLegacy = true if (isLegacy) editor.setAttribute('contenteditable', 'true') + recordHistory() + const debounceHighlight = debounce(() => { const pos = save() highlight(editor, pos) @@ -105,10 +107,10 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P function save(): Position { const s = getSelection() - const pos: Position = {start: 0, end: 0, dir: undefined} + const pos: Position = {start: 0, end: 0} let {anchorNode, anchorOffset, focusNode, focusOffset} = s - if (!anchorNode || !focusNode) throw 'error1' + if (!anchorNode || !focusNode) return history[at]?.pos ?? pos // Selection anchor and focus are expected to be text nodes, // so normalize them. @@ -285,14 +287,7 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P if (isLegacy && event.key === 'Enter') { event.preventDefault() event.stopPropagation() - if (aroundCursor().after === '') { - insert('\n ') - const pos = save() - pos.start = --pos.end - restore(pos) - } else { - insert('\n') - } + insert('\n') } } @@ -415,8 +410,13 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P function insert(text: string) { let {start} = save() const {before, after} = aroundCursor() - editor.textContent = before + text + after start += text.length + + // the last line break isn't shown and it can cause editing issues + // so, add an extra line break in order to avoid those issues + if (after === '' && text.endsWith('\n')) text += '\n' + + editor.textContent = before + text + after restore({start, end: start}) } @@ -452,7 +452,9 @@ export function CodeJar(editor: HTMLElement, highlight: (e: HTMLElement, pos?: P Object.assign(options, newOptions) }, updateCode(code: string, callOnUpdate: boolean = true) { - editor.textContent = code + recordHistory() + editor.textContent = '' + insert(code) highlight(editor) if (callOnUpdate) onUpdate(code) },