mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	fix(shortcuts): try to fix ime composition checks
This commit is contained in:
		| @@ -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"; | ||||||
|  |  | ||||||
| @@ -172,6 +172,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); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user