From 1b00dac21c7221d2c1d9bce8db44089d0f0b8272 Mon Sep 17 00:00:00 2001 From: zenn Date: Mon, 11 May 2026 16:26:21 +0900 Subject: [PATCH] =?UTF-8?q?fix(search):=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EB=A7=A4=EC=B9=AD=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 본문 검색에서 /uploads 경로와 마크다운 이미지 토큰을 제거해 파일명/해시 노이즈를 제외. Co-authored-by: Cursor --- components/site/SiteSearchModal.vue | 32 ++++++++++++++++++++++- docs/update.md | 4 +++ package.json | 2 +- server/repositories/content-repository.js | 30 ++++++++++++++++++--- 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/components/site/SiteSearchModal.vue b/components/site/SiteSearchModal.vue index 1506472..f41ec5a 100644 --- a/components/site/SiteSearchModal.vue +++ b/components/site/SiteSearchModal.vue @@ -14,15 +14,28 @@ const isComposing = ref(false) /** @type {ReturnType | null} */ let debounceTimer = null -watch(query, (value) => { +/** + * 입력값을 디바운스로 검색어에 반영한다. + * @param {string} value - 현재 입력값 + * @returns {void} + */ +const scheduleDebouncedQuery = (value) => { clearTimeout(debounceTimer) debounceTimer = setTimeout(() => { debouncedQuery.value = value }, 200) +} + +watch(query, (value) => { + scheduleDebouncedQuery(value) }) +const fetchKey = computed(() => `public-search:${debouncedQuery.value}`) + const { data, pending } = await useFetch('/api/search', { + key: fetchKey, query: { q: debouncedQuery }, + watch: [debouncedQuery], default: () => ({ tags: /** @type {SearchTagHit[]} */ ([]), posts: /** @type {SearchPostHit[]} */ ([]) }) }) @@ -99,6 +112,22 @@ const onCompositionStart = () => { isComposing.value = true } +/** + * IME 조합 중에도 입력창 value를 기반으로 검색어를 갱신한다. + * 일부 환경에서는 v-model 업데이트가 조합 종료까지 지연될 수 있다. + * @param {CompositionEvent} event - 조합 업데이트 이벤트 + * @returns {void} + */ +const onCompositionUpdate = (event) => { + const target = event.target + if (!(target instanceof HTMLInputElement)) { + return + } + const value = target.value + query.value = value + scheduleDebouncedQuery(value) +} + /** * 한글/일본어 등 IME 조합 종료 처리(종료 시점에만 검색 갱신) * @returns {void} @@ -179,6 +208,7 @@ onBeforeUnmount(() => { class="site-search-modal__input min-w-0 flex-1 bg-transparent py-2 text-lg outline-none placeholder:text-[var(--site-soft)] focus-visible:ring-0 sm:text-[1.35rem]" placeholder="글 제목, 본문, 태그 검색" @compositionstart="onCompositionStart" + @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd" />