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 shortcuts, { keyMatches, matchesShortcut } from "./shortcuts.js"; | ||||
| import shortcuts, { keyMatches, matchesShortcut, isIMEComposing } from "./shortcuts.js"; | ||||
|  | ||||
| // Mock utils module | ||||
| vi.mock("./utils.js", () => ({ | ||||
| @@ -320,4 +320,36 @@ describe("shortcuts", () => { | ||||
|             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}`]; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 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) { | ||||
|     bindGlobalShortcut("", null, namespace); | ||||
| } | ||||
| @@ -68,6 +86,13 @@ function bindElShortcut($el: JQuery<ElementType | Element>, keyboardShortcut: st | ||||
|                 } | ||||
|  | ||||
|                 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)) { | ||||
|                     e.preventDefault(); | ||||
|                     e.stopPropagation(); | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import NoteContextAwareWidget from "./note_context_aware_widget.js"; | ||||
| import attributeService from "../services/attributes.js"; | ||||
| import FindInText from "./find_in_text.js"; | ||||
| import FindInCode from "./find_in_code.js"; | ||||
| import { isIMEComposing } from "../services/shortcuts.js"; | ||||
| import FindInHtml from "./find_in_html.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.$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 ctrl+f is pressed when the findbox is shown, select the | ||||
|                 // whole input to find | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import "./note_title.css"; | ||||
| import { isLaunchBarConfig } from "../services/utils"; | ||||
| import appContext from "../components/app_context"; | ||||
| import branches from "../services/branches"; | ||||
| import { isIMEComposing } from "../services/shortcuts"; | ||||
|  | ||||
| export default function NoteTitleWidget() { | ||||
|     const { note, noteId, componentId, viewScope, noteContext, parentComponent } = useNoteContext();     | ||||
| @@ -78,6 +79,12 @@ export default function NoteTitleWidget() { | ||||
|                     spacedUpdate.scheduleUpdate(); | ||||
|                 }} | ||||
|                 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. | ||||
|                     if (e.key === "Enter") { | ||||
|                         e.preventDefault(); | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import linkService from "../services/link.js"; | ||||
| import froca from "../services/froca.js"; | ||||
| import utils from "../services/utils.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 { Dropdown, Tooltip } from "bootstrap"; | ||||
|  | ||||
| @@ -172,6 +172,14 @@ export default class QuickSearchWidget extends BasicWidget { | ||||
|  | ||||
|         if (utils.isMobile()) { | ||||
|             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 (this.$dropdownMenu.is(":visible")) { | ||||
|                         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 { escapeQuotes, getErrorMessage } from "../../../services/utils"; | ||||
| import link from "../../../services/link"; | ||||
| import { isIMEComposing } from "../../../services/shortcuts"; | ||||
| import froca from "../../../services/froca"; | ||||
| import contextMenu from "../../../menus/context_menu"; | ||||
| import type { CommandData, FilteredCommandNames } from "../../../components/app_context"; | ||||
| @@ -287,6 +288,11 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI | ||||
|                 ref={wrapperRef} | ||||
|                 style="position: relative; padding-top: 10px; padding-bottom: 10px" | ||||
|                 onKeyDown={(e) => { | ||||
|                     // Skip processing during IME composition | ||||
|                     if (isIMEComposing(e)) { | ||||
|                         return; | ||||
|                     } | ||||
|                      | ||||
|                     if (e.key === "Enter") { | ||||
|                         // allow autocomplete to fill the result textarea | ||||
|                         setTimeout(() => save(), 100); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user