From 6db78976013c2919142f63dd4fedfa342516aa4d Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Mon, 8 Mar 2021 14:20:06 +0100 Subject: [PATCH 1/3] Improve focus handling * Ported from #1046: * disabling buttons will clear button highlight * enabling button will set button highlight * move caret to end executed before enabling buttons (so button highlight will be for actual position of caret) * move caret to end will also be executed if previousActiveElement is null, which will only be the case before the first onBlur was executed: * so that caret will be moved to end on opening editor --- ts/editor/focusHandlers.ts | 14 +++++++++----- ts/editor/toolbar.ts | 39 ++++++++++++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/ts/editor/focusHandlers.ts b/ts/editor/focusHandlers.ts index abc7095bb..cd0b630f4 100644 --- a/ts/editor/focusHandlers.ts +++ b/ts/editor/focusHandlers.ts @@ -16,9 +16,14 @@ function caretToEnd(currentField: EditingArea): void { selection.addRange(range); } -function focusField(field: EditingArea) { +function focusField(field: EditingArea, moveCaretToEnd: boolean): void { field.focusEditable(); bridgeCommand(`focus:${field.ord}`); + + if (moveCaretToEnd) { + caretToEnd(field); + } + enableButtons(); } @@ -33,11 +38,10 @@ export function onFocus(evt: FocusEvent): void { !(previousFocus instanceof EditingArea) || previousFocus === previousActiveElement ) { - focusField(currentField); + const moveCaretToEnd = + Boolean(previousFocus) || !Boolean(previousActiveElement); - if (previousFocus) { - caretToEnd(currentField); - } + focusField(currentField, moveCaretToEnd); } } diff --git a/ts/editor/toolbar.ts b/ts/editor/toolbar.ts index aa0b7ad5c..a4d87819b 100644 --- a/ts/editor/toolbar.ts +++ b/ts/editor/toolbar.ts @@ -1,9 +1,10 @@ /* Copyright: Ankitects Pty Ltd and contributors * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ +const highlightButtons = ["bold", "italic", "underline", "superscript", "subscript"]; + export function updateButtonState(): void { - const buts = ["bold", "italic", "underline", "superscript", "subscript"]; - for (const name of buts) { + for (const name of highlightButtons) { const elem = document.querySelector(`#${name}`) as HTMLElement; elem.classList.toggle("highlighted", document.queryCommandState(name)); } @@ -12,6 +13,13 @@ export function updateButtonState(): void { // 'col': document.queryCommandValue("forecolor") } +function clearButtonHighlight(): void { + for (const name of highlightButtons) { + const elem = document.querySelector(`#${name}`) as HTMLElement; + elem.classList.remove("highlighted"); + } +} + export function preventButtonFocus(): void { for (const element of document.querySelectorAll("button.linkb")) { element.addEventListener("mousedown", (evt: Event) => { @@ -20,19 +28,34 @@ export function preventButtonFocus(): void { } } -export function disableButtons(): void { - $("button.linkb:not(.perm)").prop("disabled", true); +export function enableButtons(): void { + const buttons = document.querySelectorAll( + "button.linkb" + ) as NodeListOf; + buttons.forEach((elem: HTMLButtonElement): void => { + elem.disabled = false; + }); + updateButtonState(); } -export function enableButtons(): void { - $("button.linkb").prop("disabled", false); +export function disableButtons(): void { + const buttons = document.querySelectorAll( + "button.linkb:not(.perm)" + ) as NodeListOf; + buttons.forEach((elem: HTMLButtonElement): void => { + elem.disabled = true; + }); + clearButtonHighlight(); } export function setFGButton(col: string): void { document.getElementById("forecolor")!.style.backgroundColor = col; } -export function toggleEditorButton(buttonid: string): void { - const button = $(buttonid)[0]; +export function toggleEditorButton(buttonOrId: string | HTMLElement): void { + const button = + typeof buttonOrId === "string" + ? (document.getElementById(buttonOrId) as HTMLElement) + : buttonOrId; button.classList.toggle("highlighted"); } From 972993b42e9754ad011c4a82de71d7d86d4021ed Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Mon, 8 Mar 2021 20:40:23 +0100 Subject: [PATCH 2/3] Move caretToEnd logic out of focus handling --- ts/editor/changeTimer.ts | 6 +----- ts/editor/focusHandlers.ts | 44 ++++++-------------------------------- ts/editor/helpers.ts | 11 ++++++++++ ts/editor/index.ts | 4 +++- ts/editor/inputHandlers.ts | 16 +++++++++++++- 5 files changed, 36 insertions(+), 45 deletions(-) diff --git a/ts/editor/changeTimer.ts b/ts/editor/changeTimer.ts index 5b7ce8153..7a123773b 100644 --- a/ts/editor/changeTimer.ts +++ b/ts/editor/changeTimer.ts @@ -6,16 +6,12 @@ import type { EditingArea } from "."; import { getCurrentField } from "."; import { bridgeCommand } from "./lib"; import { getNoteId } from "./noteId"; -import { updateButtonState } from "./toolbar"; let changeTimer: number | null = null; export function triggerChangeTimer(currentField: EditingArea): void { clearChangeTimer(); - changeTimer = setTimeout(function () { - updateButtonState(); - saveField(currentField, "key"); - }, 600); + changeTimer = setTimeout(() => saveField(currentField, "key"), 600); } function clearChangeTimer(): void { diff --git a/ts/editor/focusHandlers.ts b/ts/editor/focusHandlers.ts index cd0b630f4..1828d957d 100644 --- a/ts/editor/focusHandlers.ts +++ b/ts/editor/focusHandlers.ts @@ -1,55 +1,23 @@ /* Copyright: Ankitects Pty Ltd and contributors * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ -import { EditingArea } from "."; +import type { EditingArea } from "."; import { bridgeCommand } from "./lib"; import { enableButtons, disableButtons } from "./toolbar"; import { saveField } from "./changeTimer"; -function caretToEnd(currentField: EditingArea): void { - const range = document.createRange(); - range.selectNodeContents(currentField.editable); - range.collapse(false); - const selection = currentField.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); -} - -function focusField(field: EditingArea, moveCaretToEnd: boolean): void { - field.focusEditable(); - bridgeCommand(`focus:${field.ord}`); - - if (moveCaretToEnd) { - caretToEnd(field); - } - - enableButtons(); -} - -// For distinguishing focus by refocusing window from deliberate focus -let previousActiveElement: EditingArea | null = null; - export function onFocus(evt: FocusEvent): void { const currentField = evt.currentTarget as EditingArea; - const previousFocus = evt.relatedTarget as EditingArea; - - if ( - !(previousFocus instanceof EditingArea) || - previousFocus === previousActiveElement - ) { - const moveCaretToEnd = - Boolean(previousFocus) || !Boolean(previousActiveElement); - - focusField(currentField, moveCaretToEnd); - } + currentField.focusEditable(); + bridgeCommand(`focus:${currentField.ord}`); + enableButtons(); } export function onBlur(evt: FocusEvent): void { const previousFocus = evt.currentTarget as EditingArea; + const currentFieldUnchanged = previousFocus === document.activeElement; - saveField(previousFocus, previousFocus === document.activeElement ? "key" : "blur"); - // other widget or window focused; current field unchanged - previousActiveElement = previousFocus; + saveField(previousFocus, currentFieldUnchanged ? "key" : "blur"); disableButtons(); } diff --git a/ts/editor/helpers.ts b/ts/editor/helpers.ts index da7cb5d27..80a9897ce 100644 --- a/ts/editor/helpers.ts +++ b/ts/editor/helpers.ts @@ -1,6 +1,8 @@ /* Copyright: Ankitects Pty Ltd and contributors * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ +import type { EditingArea } from "."; + export function nodeIsElement(node: Node): node is Element { return node.nodeType === Node.ELEMENT_NODE; } @@ -66,3 +68,12 @@ const INLINE_TAGS = [ export function nodeIsInline(node: Node): boolean { return !nodeIsElement(node) || INLINE_TAGS.includes(node.tagName); } + +export function caretToEnd(currentField: EditingArea): void { + const range = document.createRange(); + range.selectNodeContents(currentField.editable); + range.collapse(false); + const selection = currentField.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); +} diff --git a/ts/editor/index.ts b/ts/editor/index.ts index 5861b3da4..341657026 100644 --- a/ts/editor/index.ts +++ b/ts/editor/index.ts @@ -1,7 +1,7 @@ /* Copyright: Ankitects Pty Ltd and contributors * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ -import { nodeIsInline } from "./helpers"; +import { nodeIsInline, caretToEnd } from "./helpers"; import { bridgeCommand } from "./lib"; import { saveField } from "./changeTimer"; import { filterHTML } from "./htmlFilter"; @@ -34,6 +34,8 @@ export function focusField(n: number): void { if (field) { field.editingArea.focusEditable(); + caretToEnd(field.editingArea); + updateButtonState(); } } diff --git a/ts/editor/inputHandlers.ts b/ts/editor/inputHandlers.ts index f153d1968..bed09d66c 100644 --- a/ts/editor/inputHandlers.ts +++ b/ts/editor/inputHandlers.ts @@ -2,8 +2,9 @@ * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ import { EditingArea } from "."; -import { nodeIsElement } from "./helpers"; +import { caretToEnd, nodeIsElement } from "./helpers"; import { triggerChangeTimer } from "./changeTimer"; +import { updateButtonState } from "./toolbar"; function inListItem(currentField: EditingArea): boolean { const anchor = currentField.getSelection()!.anchorNode!; @@ -21,6 +22,7 @@ function inListItem(currentField: EditingArea): boolean { export function onInput(event: Event): void { // make sure IME changes get saved triggerChangeTimer(event.currentTarget as EditingArea); + updateButtonState(); } export function onKey(evt: KeyboardEvent): void { @@ -59,6 +61,18 @@ export function onKey(evt: KeyboardEvent): void { triggerChangeTimer(currentField); } +globalThis.addEventListener("keydown", (evt: KeyboardEvent) => { + if (evt.code === "Tab") { + globalThis.addEventListener("focusin", (evt: FocusEvent) => { + const newFocusTarget = evt.target; + if (newFocusTarget instanceof EditingArea) { + caretToEnd(newFocusTarget); + updateButtonState(); + } + }, { once: true }) + } +}) + export function onKeyUp(evt: KeyboardEvent): void { const currentField = evt.currentTarget as EditingArea; From 76260c3f8d1b3ce590b7cc04c0191903783c5d46 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Mon, 8 Mar 2021 20:55:04 +0100 Subject: [PATCH 3/3] Satisfy formatter --- ts/editor/inputHandlers.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ts/editor/inputHandlers.ts b/ts/editor/inputHandlers.ts index bed09d66c..052575614 100644 --- a/ts/editor/inputHandlers.ts +++ b/ts/editor/inputHandlers.ts @@ -63,15 +63,19 @@ export function onKey(evt: KeyboardEvent): void { globalThis.addEventListener("keydown", (evt: KeyboardEvent) => { if (evt.code === "Tab") { - globalThis.addEventListener("focusin", (evt: FocusEvent) => { - const newFocusTarget = evt.target; - if (newFocusTarget instanceof EditingArea) { - caretToEnd(newFocusTarget); - updateButtonState(); - } - }, { once: true }) + globalThis.addEventListener( + "focusin", + (evt: FocusEvent) => { + const newFocusTarget = evt.target; + if (newFocusTarget instanceof EditingArea) { + caretToEnd(newFocusTarget); + updateButtonState(); + } + }, + { once: true } + ); } -}) +}); export function onKeyUp(evt: KeyboardEvent): void { const currentField = evt.currentTarget as EditingArea;