mirror of
https://github.com/ankitects/anki.git
synced 2025-11-10 22:57:11 -05:00
Merge pull request #1060 from hgiesel/focusplus
Fix focus handling by moving caretToEnd logic
This commit is contained in:
commit
d3fa8621cb
6 changed files with 71 additions and 49 deletions
|
|
@ -6,16 +6,12 @@ import type { EditingArea } from ".";
|
||||||
import { getCurrentField } from ".";
|
import { getCurrentField } from ".";
|
||||||
import { bridgeCommand } from "./lib";
|
import { bridgeCommand } from "./lib";
|
||||||
import { getNoteId } from "./noteId";
|
import { getNoteId } from "./noteId";
|
||||||
import { updateButtonState } from "./toolbar";
|
|
||||||
|
|
||||||
let changeTimer: number | null = null;
|
let changeTimer: number | null = null;
|
||||||
|
|
||||||
export function triggerChangeTimer(currentField: EditingArea): void {
|
export function triggerChangeTimer(currentField: EditingArea): void {
|
||||||
clearChangeTimer();
|
clearChangeTimer();
|
||||||
changeTimer = setTimeout(function () {
|
changeTimer = setTimeout(() => saveField(currentField, "key"), 600);
|
||||||
updateButtonState();
|
|
||||||
saveField(currentField, "key");
|
|
||||||
}, 600);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearChangeTimer(): void {
|
function clearChangeTimer(): void {
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,23 @@
|
||||||
/* Copyright: Ankitects Pty Ltd and contributors
|
/* Copyright: Ankitects Pty Ltd and contributors
|
||||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
* 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 { bridgeCommand } from "./lib";
|
||||||
import { enableButtons, disableButtons } from "./toolbar";
|
import { enableButtons, disableButtons } from "./toolbar";
|
||||||
import { saveField } from "./changeTimer";
|
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) {
|
|
||||||
field.focusEditable();
|
|
||||||
bridgeCommand(`focus:${field.ord}`);
|
|
||||||
enableButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
// For distinguishing focus by refocusing window from deliberate focus
|
|
||||||
let previousActiveElement: EditingArea | null = null;
|
|
||||||
|
|
||||||
export function onFocus(evt: FocusEvent): void {
|
export function onFocus(evt: FocusEvent): void {
|
||||||
const currentField = evt.currentTarget as EditingArea;
|
const currentField = evt.currentTarget as EditingArea;
|
||||||
const previousFocus = evt.relatedTarget as EditingArea;
|
currentField.focusEditable();
|
||||||
|
bridgeCommand(`focus:${currentField.ord}`);
|
||||||
if (
|
enableButtons();
|
||||||
!(previousFocus instanceof EditingArea) ||
|
|
||||||
previousFocus === previousActiveElement
|
|
||||||
) {
|
|
||||||
focusField(currentField);
|
|
||||||
|
|
||||||
if (previousFocus) {
|
|
||||||
caretToEnd(currentField);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onBlur(evt: FocusEvent): void {
|
export function onBlur(evt: FocusEvent): void {
|
||||||
const previousFocus = evt.currentTarget as EditingArea;
|
const previousFocus = evt.currentTarget as EditingArea;
|
||||||
|
const currentFieldUnchanged = previousFocus === document.activeElement;
|
||||||
|
|
||||||
saveField(previousFocus, previousFocus === document.activeElement ? "key" : "blur");
|
saveField(previousFocus, currentFieldUnchanged ? "key" : "blur");
|
||||||
// other widget or window focused; current field unchanged
|
|
||||||
previousActiveElement = previousFocus;
|
|
||||||
disableButtons();
|
disableButtons();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
/* Copyright: Ankitects Pty Ltd and contributors
|
/* Copyright: Ankitects Pty Ltd and contributors
|
||||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
* 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 {
|
export function nodeIsElement(node: Node): node is Element {
|
||||||
return node.nodeType === Node.ELEMENT_NODE;
|
return node.nodeType === Node.ELEMENT_NODE;
|
||||||
}
|
}
|
||||||
|
|
@ -66,3 +68,12 @@ const INLINE_TAGS = [
|
||||||
export function nodeIsInline(node: Node): boolean {
|
export function nodeIsInline(node: Node): boolean {
|
||||||
return !nodeIsElement(node) || INLINE_TAGS.includes(node.tagName);
|
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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/* Copyright: Ankitects Pty Ltd and contributors
|
/* Copyright: Ankitects Pty Ltd and contributors
|
||||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
* 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 { bridgeCommand } from "./lib";
|
||||||
import { saveField } from "./changeTimer";
|
import { saveField } from "./changeTimer";
|
||||||
import { filterHTML } from "./htmlFilter";
|
import { filterHTML } from "./htmlFilter";
|
||||||
|
|
@ -34,6 +34,8 @@ export function focusField(n: number): void {
|
||||||
|
|
||||||
if (field) {
|
if (field) {
|
||||||
field.editingArea.focusEditable();
|
field.editingArea.focusEditable();
|
||||||
|
caretToEnd(field.editingArea);
|
||||||
|
updateButtonState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
||||||
|
|
||||||
import { EditingArea } from ".";
|
import { EditingArea } from ".";
|
||||||
import { nodeIsElement } from "./helpers";
|
import { caretToEnd, nodeIsElement } from "./helpers";
|
||||||
import { triggerChangeTimer } from "./changeTimer";
|
import { triggerChangeTimer } from "./changeTimer";
|
||||||
|
import { updateButtonState } from "./toolbar";
|
||||||
|
|
||||||
function inListItem(currentField: EditingArea): boolean {
|
function inListItem(currentField: EditingArea): boolean {
|
||||||
const anchor = currentField.getSelection()!.anchorNode!;
|
const anchor = currentField.getSelection()!.anchorNode!;
|
||||||
|
|
@ -21,6 +22,7 @@ function inListItem(currentField: EditingArea): boolean {
|
||||||
export function onInput(event: Event): void {
|
export function onInput(event: Event): void {
|
||||||
// make sure IME changes get saved
|
// make sure IME changes get saved
|
||||||
triggerChangeTimer(event.currentTarget as EditingArea);
|
triggerChangeTimer(event.currentTarget as EditingArea);
|
||||||
|
updateButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onKey(evt: KeyboardEvent): void {
|
export function onKey(evt: KeyboardEvent): void {
|
||||||
|
|
@ -59,6 +61,22 @@ export function onKey(evt: KeyboardEvent): void {
|
||||||
triggerChangeTimer(currentField);
|
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 {
|
export function onKeyUp(evt: KeyboardEvent): void {
|
||||||
const currentField = evt.currentTarget as EditingArea;
|
const currentField = evt.currentTarget as EditingArea;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
/* Copyright: Ankitects Pty Ltd and contributors
|
/* Copyright: Ankitects Pty Ltd and contributors
|
||||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
* 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 {
|
export function updateButtonState(): void {
|
||||||
const buts = ["bold", "italic", "underline", "superscript", "subscript"];
|
for (const name of highlightButtons) {
|
||||||
for (const name of buts) {
|
|
||||||
const elem = document.querySelector(`#${name}`) as HTMLElement;
|
const elem = document.querySelector(`#${name}`) as HTMLElement;
|
||||||
elem.classList.toggle("highlighted", document.queryCommandState(name));
|
elem.classList.toggle("highlighted", document.queryCommandState(name));
|
||||||
}
|
}
|
||||||
|
|
@ -12,6 +13,13 @@ export function updateButtonState(): void {
|
||||||
// 'col': document.queryCommandValue("forecolor")
|
// '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 {
|
export function preventButtonFocus(): void {
|
||||||
for (const element of document.querySelectorAll("button.linkb")) {
|
for (const element of document.querySelectorAll("button.linkb")) {
|
||||||
element.addEventListener("mousedown", (evt: Event) => {
|
element.addEventListener("mousedown", (evt: Event) => {
|
||||||
|
|
@ -20,19 +28,34 @@ export function preventButtonFocus(): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function disableButtons(): void {
|
export function enableButtons(): void {
|
||||||
$("button.linkb:not(.perm)").prop("disabled", true);
|
const buttons = document.querySelectorAll(
|
||||||
|
"button.linkb"
|
||||||
|
) as NodeListOf<HTMLButtonElement>;
|
||||||
|
buttons.forEach((elem: HTMLButtonElement): void => {
|
||||||
|
elem.disabled = false;
|
||||||
|
});
|
||||||
|
updateButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function enableButtons(): void {
|
export function disableButtons(): void {
|
||||||
$("button.linkb").prop("disabled", false);
|
const buttons = document.querySelectorAll(
|
||||||
|
"button.linkb:not(.perm)"
|
||||||
|
) as NodeListOf<HTMLButtonElement>;
|
||||||
|
buttons.forEach((elem: HTMLButtonElement): void => {
|
||||||
|
elem.disabled = true;
|
||||||
|
});
|
||||||
|
clearButtonHighlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setFGButton(col: string): void {
|
export function setFGButton(col: string): void {
|
||||||
document.getElementById("forecolor")!.style.backgroundColor = col;
|
document.getElementById("forecolor")!.style.backgroundColor = col;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toggleEditorButton(buttonid: string): void {
|
export function toggleEditorButton(buttonOrId: string | HTMLElement): void {
|
||||||
const button = $(buttonid)[0];
|
const button =
|
||||||
|
typeof buttonOrId === "string"
|
||||||
|
? (document.getElementById(buttonOrId) as HTMLElement)
|
||||||
|
: buttonOrId;
|
||||||
button.classList.toggle("highlighted");
|
button.classList.toggle("highlighted");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue