From 20e50156a264ab8b22c1e04bcd7a4b9be17c65e0 Mon Sep 17 00:00:00 2001 From: pixelpaws Date: Fri, 27 Mar 2026 19:28:58 +0800 Subject: [PATCH 1/2] fix(recipes): allow Enter to add import tags --- .../js/managers/import/RecipeDataManager.js | 19 +++++++ .../RecipeDataManager.tagInput.test.js | 56 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 tests/frontend/managers/RecipeDataManager.tagInput.test.js diff --git a/static/js/managers/import/RecipeDataManager.js b/static/js/managers/import/RecipeDataManager.js index 63668410..319d3419 100644 --- a/static/js/managers/import/RecipeDataManager.js +++ b/static/js/managers/import/RecipeDataManager.js @@ -6,8 +6,27 @@ export class RecipeDataManager { this.importManager = importManager; } + setupTagInputEnterHandler() { + const tagInput = document.getElementById('tagInput'); + if (!tagInput || tagInput.hasEnterAddTagHandler) { + return; + } + + tagInput.addEventListener('keydown', (event) => { + if (event.key !== 'Enter') { + return; + } + + event.preventDefault(); + this.addTag(); + }); + + tagInput.hasEnterAddTagHandler = true; + } + showRecipeDetailsStep() { this.importManager.stepManager.showStep('detailsStep'); + this.setupTagInputEnterHandler(); // Set default recipe name from prompt or image filename const recipeName = document.getElementById('recipeName'); diff --git a/tests/frontend/managers/RecipeDataManager.tagInput.test.js b/tests/frontend/managers/RecipeDataManager.tagInput.test.js new file mode 100644 index 00000000..329def6d --- /dev/null +++ b/tests/frontend/managers/RecipeDataManager.tagInput.test.js @@ -0,0 +1,56 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('../../../static/js/utils/uiHelpers.js', () => ({ + showToast: vi.fn(), +})); + +vi.mock('../../../static/js/utils/i18nHelpers.js', () => ({ + translate: (_key, _params, fallback) => fallback ?? '', +})); + +describe('RecipeDataManager tag input Enter behavior', () => { + beforeEach(() => { + vi.resetModules(); + document.body.innerHTML = ` + +
+ `; + }); + + it('adds a tag when pressing Enter in tag input', async () => { + const { RecipeDataManager } = await import('../../../static/js/managers/import/RecipeDataManager.js'); + const importManager = { + recipeTags: [], + stepManager: { showStep: vi.fn() }, + }; + const manager = new RecipeDataManager(importManager); + + manager.setupTagInputEnterHandler(); + + const tagInput = document.getElementById('tagInput'); + tagInput.value = 'portrait'; + tagInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); + + expect(importManager.recipeTags).toEqual(['portrait']); + expect(tagInput.value).toBe(''); + expect(document.getElementById('tagsContainer').textContent).toContain('portrait'); + }); + + it('does not register duplicate Enter handlers when setup runs multiple times', async () => { + const { RecipeDataManager } = await import('../../../static/js/managers/import/RecipeDataManager.js'); + const importManager = { + recipeTags: [], + stepManager: { showStep: vi.fn() }, + }; + const manager = new RecipeDataManager(importManager); + + manager.setupTagInputEnterHandler(); + manager.setupTagInputEnterHandler(); + + const tagInput = document.getElementById('tagInput'); + tagInput.value = 'anime'; + tagInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); + + expect(importManager.recipeTags).toEqual(['anime']); + }); +}); From 9121306b06ee1dac5e9325e10c29c49575fb5f48 Mon Sep 17 00:00:00 2001 From: pixelpaws Date: Fri, 27 Mar 2026 19:52:53 +0800 Subject: [PATCH 2/2] Guard Enter tag add during IME composition --- .../js/managers/import/RecipeDataManager.js | 4 ++ .../RecipeDataManager.tagInput.test.js | 50 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/static/js/managers/import/RecipeDataManager.js b/static/js/managers/import/RecipeDataManager.js index 319d3419..bc49399c 100644 --- a/static/js/managers/import/RecipeDataManager.js +++ b/static/js/managers/import/RecipeDataManager.js @@ -17,6 +17,10 @@ export class RecipeDataManager { return; } + if (event.isComposing || event.keyCode === 229) { + return; + } + event.preventDefault(); this.addTag(); }); diff --git a/tests/frontend/managers/RecipeDataManager.tagInput.test.js b/tests/frontend/managers/RecipeDataManager.tagInput.test.js index 329def6d..9e0320ef 100644 --- a/tests/frontend/managers/RecipeDataManager.tagInput.test.js +++ b/tests/frontend/managers/RecipeDataManager.tagInput.test.js @@ -53,4 +53,54 @@ describe('RecipeDataManager tag input Enter behavior', () => { expect(importManager.recipeTags).toEqual(['anime']); }); + + it('ignores Enter while IME composition is active', async () => { + const { RecipeDataManager } = await import('../../../static/js/managers/import/RecipeDataManager.js'); + const importManager = { + recipeTags: [], + stepManager: { showStep: vi.fn() }, + }; + const manager = new RecipeDataManager(importManager); + + manager.setupTagInputEnterHandler(); + + const tagInput = document.getElementById('tagInput'); + tagInput.value = '未確定'; + const event = new KeyboardEvent('keydown', { + key: 'Enter', + bubbles: true, + cancelable: true, + }); + Object.defineProperty(event, 'isComposing', { value: true }); + tagInput.dispatchEvent(event); + + expect(importManager.recipeTags).toEqual([]); + expect(tagInput.value).toBe('未確定'); + expect(event.defaultPrevented).toBe(false); + }); + + it('ignores keyCode 229 fallback during composition', async () => { + const { RecipeDataManager } = await import('../../../static/js/managers/import/RecipeDataManager.js'); + const importManager = { + recipeTags: [], + stepManager: { showStep: vi.fn() }, + }; + const manager = new RecipeDataManager(importManager); + + manager.setupTagInputEnterHandler(); + + const tagInput = document.getElementById('tagInput'); + tagInput.value = '候補'; + const event = new KeyboardEvent('keydown', { + key: 'Enter', + bubbles: true, + cancelable: true, + }); + Object.defineProperty(event, 'keyCode', { value: 229 }); + tagInput.dispatchEvent(event); + + expect(importManager.recipeTags).toEqual([]); + expect(tagInput.value).toBe('候補'); + expect(event.defaultPrevented).toBe(false); + }); });