mirror of
https://github.com/zadam/trilium.git
synced 2025-10-21 07:38:53 +02:00
fix(shortcuts): try to fix ime composition checks (#6851)
This commit is contained in:
commit
145f89eded
@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||||
import shortcuts, { keyMatches, matchesShortcut } from "./shortcuts.js";
|
import shortcuts, { keyMatches, matchesShortcut, isIMEComposing } from "./shortcuts.js";
|
||||||
|
|
||||||
// Mock utils module
|
// Mock utils module
|
||||||
vi.mock("./utils.js", () => ({
|
vi.mock("./utils.js", () => ({
|
||||||
@ -320,4 +320,36 @@ describe("shortcuts", () => {
|
|||||||
expect(event.preventDefault).not.toHaveBeenCalled();
|
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isIMEComposing', () => {
|
||||||
|
it('should return true when event.isComposing is true', () => {
|
||||||
|
const event = { isComposing: true, keyCode: 65 } as KeyboardEvent;
|
||||||
|
expect(isIMEComposing(event)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when keyCode is 229', () => {
|
||||||
|
const event = { isComposing: false, keyCode: 229 } as KeyboardEvent;
|
||||||
|
expect(isIMEComposing(event)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when both isComposing is true and keyCode is 229', () => {
|
||||||
|
const event = { isComposing: true, keyCode: 229 } as KeyboardEvent;
|
||||||
|
expect(isIMEComposing(event)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for normal keys', () => {
|
||||||
|
const event = { isComposing: false, keyCode: 65 } as KeyboardEvent;
|
||||||
|
expect(isIMEComposing(event)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when isComposing is undefined and keyCode is not 229', () => {
|
||||||
|
const event = { keyCode: 13 } as KeyboardEvent;
|
||||||
|
expect(isIMEComposing(event)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle null/undefined events gracefully', () => {
|
||||||
|
expect(isIMEComposing(null as any)).toBe(false);
|
||||||
|
expect(isIMEComposing(undefined as any)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -40,6 +40,24 @@ for (let i = 1; i <= 19; i++) {
|
|||||||
keyMap[`f${i}`] = [`F${i}`];
|
keyMap[`f${i}`] = [`F${i}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if IME (Input Method Editor) is composing
|
||||||
|
* This is used to prevent keyboard shortcuts from firing during IME composition
|
||||||
|
* @param e - The keyboard event to check
|
||||||
|
* @returns true if IME is currently composing, false otherwise
|
||||||
|
*/
|
||||||
|
export function isIMEComposing(e: KeyboardEvent): boolean {
|
||||||
|
// Handle null/undefined events gracefully
|
||||||
|
if (!e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard check for composition state
|
||||||
|
// e.isComposing is true when IME is actively composing
|
||||||
|
// e.keyCode === 229 is a fallback for older browsers where 229 indicates IME processing
|
||||||
|
return e.isComposing || e.keyCode === 229;
|
||||||
|
}
|
||||||
|
|
||||||
function removeGlobalShortcut(namespace: string) {
|
function removeGlobalShortcut(namespace: string) {
|
||||||
bindGlobalShortcut("", null, namespace);
|
bindGlobalShortcut("", null, namespace);
|
||||||
}
|
}
|
||||||
@ -68,6 +86,13 @@ function bindElShortcut($el: JQuery<ElementType | Element>, keyboardShortcut: st
|
|||||||
}
|
}
|
||||||
|
|
||||||
const e = evt as KeyboardEvent;
|
const e = evt as KeyboardEvent;
|
||||||
|
|
||||||
|
// Skip processing if IME is composing to prevent shortcuts from
|
||||||
|
// interfering with text input in CJK languages
|
||||||
|
if (isIMEComposing(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (matchesShortcut(e, keyboardShortcut)) {
|
if (matchesShortcut(e, keyboardShortcut)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -8,6 +8,7 @@ import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
|||||||
import attributeService from "../services/attributes.js";
|
import attributeService from "../services/attributes.js";
|
||||||
import FindInText from "./find_in_text.js";
|
import FindInText from "./find_in_text.js";
|
||||||
import FindInCode from "./find_in_code.js";
|
import FindInCode from "./find_in_code.js";
|
||||||
|
import { isIMEComposing } from "../services/shortcuts.js";
|
||||||
import FindInHtml from "./find_in_html.js";
|
import FindInHtml from "./find_in_html.js";
|
||||||
import type { EventData } from "../components/app_context.js";
|
import type { EventData } from "../components/app_context.js";
|
||||||
|
|
||||||
@ -162,6 +163,11 @@ export default class FindWidget extends NoteContextAwareWidget {
|
|||||||
this.$replaceButton.on("click", () => this.replace());
|
this.$replaceButton.on("click", () => this.replace());
|
||||||
|
|
||||||
this.$input.on("keydown", async (e) => {
|
this.$input.on("keydown", async (e) => {
|
||||||
|
// Skip processing during IME composition
|
||||||
|
if (isIMEComposing(e.originalEvent as KeyboardEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ((e.metaKey || e.ctrlKey) && (e.key === "F" || e.key === "f")) {
|
if ((e.metaKey || e.ctrlKey) && (e.key === "F" || e.key === "f")) {
|
||||||
// If ctrl+f is pressed when the findbox is shown, select the
|
// If ctrl+f is pressed when the findbox is shown, select the
|
||||||
// whole input to find
|
// whole input to find
|
||||||
|
@ -8,6 +8,7 @@ import "./note_title.css";
|
|||||||
import { isLaunchBarConfig } from "../services/utils";
|
import { isLaunchBarConfig } from "../services/utils";
|
||||||
import appContext from "../components/app_context";
|
import appContext from "../components/app_context";
|
||||||
import branches from "../services/branches";
|
import branches from "../services/branches";
|
||||||
|
import { isIMEComposing } from "../services/shortcuts";
|
||||||
|
|
||||||
export default function NoteTitleWidget() {
|
export default function NoteTitleWidget() {
|
||||||
const { note, noteId, componentId, viewScope, noteContext, parentComponent } = useNoteContext();
|
const { note, noteId, componentId, viewScope, noteContext, parentComponent } = useNoteContext();
|
||||||
@ -78,6 +79,12 @@ export default function NoteTitleWidget() {
|
|||||||
spacedUpdate.scheduleUpdate();
|
spacedUpdate.scheduleUpdate();
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
|
// Skip processing if IME is composing to prevent interference
|
||||||
|
// with text input in CJK languages
|
||||||
|
if (isIMEComposing(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Focus on the note content when pressing enter.
|
// Focus on the note content when pressing enter.
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -4,7 +4,7 @@ import linkService from "../services/link.js";
|
|||||||
import froca from "../services/froca.js";
|
import froca from "../services/froca.js";
|
||||||
import utils from "../services/utils.js";
|
import utils from "../services/utils.js";
|
||||||
import appContext from "../components/app_context.js";
|
import appContext from "../components/app_context.js";
|
||||||
import shortcutService from "../services/shortcuts.js";
|
import shortcutService, { isIMEComposing } from "../services/shortcuts.js";
|
||||||
import { t } from "../services/i18n.js";
|
import { t } from "../services/i18n.js";
|
||||||
import { Dropdown, Tooltip } from "bootstrap";
|
import { Dropdown, Tooltip } from "bootstrap";
|
||||||
|
|
||||||
@ -180,6 +180,14 @@ export default class QuickSearchWidget extends BasicWidget {
|
|||||||
|
|
||||||
if (utils.isMobile()) {
|
if (utils.isMobile()) {
|
||||||
this.$searchString.keydown((e) => {
|
this.$searchString.keydown((e) => {
|
||||||
|
// Skip processing if IME is composing to prevent interference
|
||||||
|
// with text input in CJK languages
|
||||||
|
// Note: jQuery wraps the native event, so we access originalEvent
|
||||||
|
const originalEvent = e.originalEvent as KeyboardEvent;
|
||||||
|
if (originalEvent && isIMEComposing(originalEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.which === 13) {
|
if (e.which === 13) {
|
||||||
if (this.$dropdownMenu.is(":visible")) {
|
if (this.$dropdownMenu.is(":visible")) {
|
||||||
this.search(); // just update already visible dropdown
|
this.search(); // just update already visible dropdown
|
||||||
|
@ -13,6 +13,7 @@ import attribute_parser, { Attribute } from "../../../services/attribute_parser"
|
|||||||
import ActionButton from "../../react/ActionButton";
|
import ActionButton from "../../react/ActionButton";
|
||||||
import { escapeQuotes, getErrorMessage } from "../../../services/utils";
|
import { escapeQuotes, getErrorMessage } from "../../../services/utils";
|
||||||
import link from "../../../services/link";
|
import link from "../../../services/link";
|
||||||
|
import { isIMEComposing } from "../../../services/shortcuts";
|
||||||
import froca from "../../../services/froca";
|
import froca from "../../../services/froca";
|
||||||
import contextMenu from "../../../menus/context_menu";
|
import contextMenu from "../../../menus/context_menu";
|
||||||
import type { CommandData, FilteredCommandNames } from "../../../components/app_context";
|
import type { CommandData, FilteredCommandNames } from "../../../components/app_context";
|
||||||
@ -287,6 +288,11 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI
|
|||||||
ref={wrapperRef}
|
ref={wrapperRef}
|
||||||
style="position: relative; padding-top: 10px; padding-bottom: 10px"
|
style="position: relative; padding-top: 10px; padding-bottom: 10px"
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
|
// Skip processing during IME composition
|
||||||
|
if (isIMEComposing(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
// allow autocomplete to fill the result textarea
|
// allow autocomplete to fill the result textarea
|
||||||
setTimeout(() => save(), 100);
|
setTimeout(() => save(), 100);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user