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" />