diff --git a/docs/history.md b/docs/history.md index 04ce7fa..7730a71 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,9 @@ # 의사결정 이력 +## 2026-04-03 v1.4.69 +- 아이템 검색 실패가 라벨 누락이나 이벤트 문제처럼 보일 수도 있지만, 코드상 필터링 조건 자체는 단순했으므로 한글 입력/저장 문자열의 유니코드 정규형 차이까지 먼저 흡수하는 편이 더 안전하다고 판단했다. +- 검색 시점에만 임시 보정하는 것보다, 검색어와 저장 라벨 비교를 같은 정규화 함수로 통일하고 커스텀 파일명 기반 기본 라벨 생성도 `NFC`로 맞춰 이후 신규 업로드 항목까지 같은 규칙을 타게 정리했다. + ## 2026-04-03 v1.4.68 - 우클릭 복제 UX는 카드 영역과 썸네일 이미지 중 어디를 눌러도 같은 동작이어야 하므로, 개별 카드의 버블링 이벤트만 믿기보다 전역 캡처 단계에서 아이템 우클릭을 먼저 가로채는 방식이 더 안전하다고 판단했다. - 편집기에서는 아이템 이미지를 브라우저 기본 이미지처럼 드래그하거나 저장 메뉴로 여는 것보다 보드 조작이 우선이므로, 썸네일 이미지의 기본 드래그도 명시적으로 꺼두는 편이 맞다고 정리했다. diff --git a/docs/update.md b/docs/update.md index f9fb84b..643a120 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,10 @@ # 업데이트 로그 +## 2026-04-03 v1.4.69 +- 티어표 편집 화면의 아이템 검색에서 한글 아이템명이 검색어와 눈으로는 같아 보여도 내부 유니코드 정규형 차이 때문에 일부 항목이 매칭되지 않을 수 있던 문제를 보강했다. +- 검색어와 아이템 라벨을 비교하기 전에 `NFC`로 정규화하도록 바꾸고, 커스텀 이미지 파일명에서 기본 라벨을 만들 때도 같은 정규화를 거쳐 한글 조합형 차이로 검색이 빗나가는 상황을 줄였다. +- 프런트 프로덕션 빌드(`npm run build`)까지 통과하는 것을 확인했다. + ## 2026-04-03 v1.4.68 - 티어표 편집 화면에서 아이템을 우클릭해도 브라우저 기본 컨텍스트 메뉴가 먼저 떠서 `아이템 복제` 메뉴를 누르기 어려울 수 있던 부분을 보강했다. - 기존에는 각 아이템 카드의 `@contextmenu.prevent`에 주로 의존했지만, 이제는 `window` 캡처 단계에서 `[data-item-id]` 대상 우클릭을 먼저 잡아 기본 메뉴를 막고 커스텀 복제 메뉴를 열도록 바꿨다. diff --git a/frontend/src/views/TierEditorView.vue b/frontend/src/views/TierEditorView.vue index bac88d3..89e72c9 100644 --- a/frontend/src/views/TierEditorView.vue +++ b/frontend/src/views/TierEditorView.vue @@ -141,7 +141,7 @@ const copiedFromLabel = computed(() => { return parts.join(' · ') || '복사해 온 티어표' }) const customItems = computed(() => getOrderedItems().filter((item) => item?.origin === 'custom')) -const normalizedPoolSearchQuery = computed(() => poolSearchQuery.value.trim().toLowerCase()) +const normalizedPoolSearchQuery = computed(() => normalizeSearchText(poolSearchQuery.value)) const hasSavedTierList = computed(() => !!(persistedTierListId.value || (tierListId.value && tierListId.value !== 'new'))) const canRequestTemplateCreate = computed( () => canEdit.value && hasSavedTierList.value && templateId.value === 'freeform' && customItems.value.length > 0 @@ -164,6 +164,13 @@ watch(error, (message) => { error.value = '' }) +function normalizeSearchText(text) { + return String(text || '') + .normalize('NFC') + .trim() + .toLowerCase() +} + function createAutoTierListTitle() { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' const pick = (size) => Array.from({ length: size }, () => chars[Math.floor(Math.random() * chars.length)]).join('') @@ -229,7 +236,7 @@ function isPoolItemVisible(itemId) { const query = normalizedPoolSearchQuery.value if (!query) return true const item = itemsById.value[itemId] - const label = String(item?.label || itemId || '').toLowerCase() + const label = normalizeSearchText(item?.label || itemId || '') return label.includes(query) } @@ -618,6 +625,7 @@ function createColumnName(index = columns.value.length) { function createCustomItemLabel(fileName = '') { const normalized = String(fileName || '') + .normalize('NFC') .replace(/\.[^.]+$/, '') .replace(/[_-]+/g, ' ') .replace(/\s+/g, ' ')