<template> <div class="input-group" @keyup="keyupdiv" :style="{ showAppend: { 'box-shadow': '0 1px 3px rgba(23, 23, 23, 0.24)' }, }" > <div class="input-group-prepend"> <button v-if="showPrepend" dir="rtl" class="btn d-flex align-items-center" type="button" id="button-addon2" @click="searchNavigateList()" > جستجو <span class="tavasi tavasi-Component-198--1"></span> </button> <span v-else style="opacity: 0">xxx</span> </div> <input type="text" v-model="localTextSearch" @click="showHisory()" @keyup.enter="prevSearchStart" @keyup="toggleAutocomplete" @keydown="onKeyDown()" class="form-control" id="search" :placeholder="placeholder" autocomplete="off" ref="searchinput" @focus="setInputFocus()" @blur="inputfocused = false" v-focus /> <div :class="[showAppend ? 'input-group-text' : 'input-group-append']"> <client-only> <template v-if="showAppend"> <button v-tooltip="'جستجو در خاصیت(فیلد) ویژه'" class="btn dropdown-toggle dropdown-toggle-color rounded-0" type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" id="dropdownMenuButton12" > <span class="navItemlabel"> {{ domainActiveGetter?.label }} </span> </button> <div class="dropdown-menu" aria-labelledby="dropdownMenuButton12"> <button v-for="(navItem, index) in searchDomain" :key="index" type="button" class="dropdown-item" @click.prevent="setDomainField(navItem)" > {{ navItem.label }} </button> </div> </template> </client-only> <button v-if="showAppendSearchButton" v-tooltip="'جستجو در خاصیت(فیلد) ویژه'" @click.prevent="prevSearchStart()" class="btn btn-primary search-icon" > <svg class="icon icon-Component-198--1"> <use xlink:href="#icon-Component-198--1"></use> </svg> <!-- <NuxtImg src="@assets/common/img/searchmajles.svg" alt="" /> --> </button> </div> <div v-if="inputPopupState == 3 && localListAutocomplate.length" class="search-page__result firefox-scrollbar" :class="{ show: localListAutocomplate.length, }" > <!-- v-click-outside="onClickOutside" --> <client-only> <ul ref="auto_complate_ul"> <li v-for="(item, i) in localListAutocomplate" :key="i" :value="i"> <a @click.prevent="selectAutocomplate2(item)"> <span class="text__14" v-html="getHighlightAutocomplate(item)" ></span> </a> </li> </ul> </client-only> </div> <div v-if="inputPopupState == 1" class="search-page__result show"> <!-- v-click-outside="onClickOutside" --> <client-only> <template v-if="historySearch.length"> <div> <div class="scrollbar"> <ul ref="his_ul"> <li v-for="(item, i) in historySearch" :key="i" :data-key="i"> <a @click.prevent="selectHistorySearch(item)"> <NuxtImg src="assets/common/img/Component 359 – 2.svg" alt="" /> <span class="text__14">{{ item }}</span> </a> <a @click="removeHistorySearch(i)" class="close"> <svg class="icon icon-Component-294--1"> <use xlink:href="#icon-Component-294--1"></use> </svg> </a> </li> </ul> </div> </div> </template> <template v-else> <div class="scrollbar"> <ul ref=""> <li> <p class="m-0 text__light"> <NuxtImg src="assets/common/img/Component 359 – 2.svg" alt="" /> <span class="text__14"> تاریخچه جستجو خالی می باشد </span> </p> </li> </ul> </div> </template> </client-only> </div> </div> </template> <script> import { mapState } from "pinia"; import searchApi from "@search/apis/searchApi.js"; import { useSearchStore } from "@search/stores/searchStore"; // Vue.directive("click-outside", { // bind(el, binding, vnode) { // el.clickOutsideEvent = (event) => { // if (!(el === event.target || el.contains(event.target))) { // vnode.context[binding.expression](event); // } // }; // document.body.addEventListener("click", el.clickOutsideEvent); // }, // unbind(el) { // document.body.removeEventListener("click", el.clickOutsideEvent); // }, // }); export default { props: { placeholder: { default: "جستجو در هزاران محتوای قوانین و مقرارت", }, contentKey: { default: "qasection", }, entityTheme: { default: false, }, showAppendSearchButton: { default: true, }, showPrepend: { default: false, }, showAppend: { default: true, }, textSearch: { default: "", }, modeInit: { default: 0, }, searchDomain: { default() { return []; }, }, listAutocomplate: { default() { return []; }, }, }, emits: ["getAutoComplateList"], watch: { modeInit(newVal = 1) { this.mode = newVal; }, textSearch(newVal) { this.localTextSearch = newVal; }, }, beforeMount() { // this.inputPopupState = this.inputPopupState; }, mounted() { this.localTextSearch = this.textSearch; if (window.localStorage.getItem([this.historySearchRecent])) { try { this.historySearch = JSON.parse( window.localStorage.getItem([this.historySearchRecent]) ); } catch (e) { window.localStorage.removeItem([this.historySearchRecent]); } } }, beforeDestroy() { window.removeEventListener("resize", this.handleResize); window.removeEventListener("load", this.handleResize); document.removeEventListener("click", this.handleClickOutside); }, data() { return { localListAutocomplate: [], typingTimer: undefined, doneTypingInterval: 800, localTextSearch: "", historySearch: [], inputPopupState: 0, mode: 1, tagLiSelected: null, inputfocused: false, isLastKeyCodeArrow: false, historySearchRecent: "historysearchrecent", }; }, computed: { ...mapState(useSearchStore, [ "domainActiveGetter", "searchActiveTabGetter", "searchSchemaGetter", "searchSynonymTitleGetter", ]), }, methods: { /** * تنظیم آیتم انتخاب شده و شروع جستجو. * @param {Object} navItem - آیتم انتخابشده. */ setDomainField(navItem) { this.$emit("onSetDomainField", navItem); // this.prevSearchStart(); }, async getAutoComplateList() { this.localListAutocomplate = []; if (this.localTextSearch == "") return; let index_key = this.contentKey; if (!index_key) return; let url = searchApi.search.autoComplate; url = url.replace("{{appname}}", buildName()); url = url.replace("{{index_key}}", index_key); url = url.replace("{{filter}}", "q=" + this.localTextSearch); try { const { $api } = useNuxtApp(); const response = await $api(url, { baseURL: repoUrl(), }); this.localListAutocomplate = response.hits?.hits; this.inputPopupState = 3; } catch (err) {} }, onKeyDown() { clearTimeout(this.typingTimer); this.typingTimer = undefined; }, /** * کنترل تایمر تکمیل خودکار و ارسال کوئری. * تایمر فعلی را پاک میکند و یک تایمر جدید با یک تاخیر مشخص تنظیم میکند تا کوئری را ارسال کند. */ toggleAutocomplete(event) { // برای کلید حرکت پایین درخواست اضافی نرود if ( event.keyCode === 40 || event.keyCode === 38 || event.keyCode === 13 ) { this.isLastKeyCodeArrow = event.keyCode === 40 || event.keyCode === 38; return; } this.isLastKeyCodeArrow = false; if (!this.inputfocused && this.inputPopupState === 3) { return; } if (this.typingTimer) { clearTimeout(this.typingTimer); this.typingTimer = undefined; } else { this.typingTimer = setTimeout(() => { this.getAutoComplateList(); this.typingTimer = undefined; }, this.doneTypingInterval); } }, setInputFocus() { this.inputfocused = true; // this.inputPopupfocused=false }, /** * شروع جستجو را کنترل میکند. * @event prevSearchStart */ prevSearchStart(event) { if ( this.isLastKeyCodeArrow && event.keyCode === 13 && this.inputPopupState != 0 ) { this.selectAutocomplate2(); this.isLastKeyCodeArrow = false; return; } this.tagLiSelected = null; this.inputPopupState = 0; this.addHistorySearch(this.localTextSearch); let tt = myEncodeQuery(this.localTextSearch); this.$emit("onSearchStart", tt); // this.searchStart(tt); }, /** *نمایش سابقه جستجو. */ showHisory() { setTimeout(() => { if (this.localTextSearch == "") this.inputPopupState = 1; else { this.inputPopupState = 0; this.tagLiSelected = null; } }, 100); }, /** * کنترل کلیکهای خارج از المان. */ keyupdiv(event) { // 13:enter // 8:backspace if (this.inputfocused == false) return; if (this.inputPopupState == 0 || this.inputPopupState == 2) return; var el = ""; el = this.inputPopupState == 1 ? this.$refs["his_ul"] : this.$refs["auto_complate_ul"]; if (!el || !el.firstChild) return; const selectItem = (item) => { this.tagLiSelected = item; this.tagLiSelected?.classList.add("selected"); this.tagLiSelected?.focus(); }; // 40: arrow down // 38: arrow up if (this.inputfocused == true && event.keyCode === 40) { if (this.tagLiSelected) { this.tagLiSelected?.classList.remove("selected"); const next = this.tagLiSelected?.nextSibling || el.firstChild; selectItem(next); } else { selectItem(el?.firstChild); } } else if (this.inputfocused == true && event.keyCode === 38) { if (this.tagLiSelected) { this.tagLiSelected?.classList.remove("selected"); const prev = this.tagLiSelected?.previousSibling || el.lastChild; selectItem(prev); } else { selectItem(el?.lastChild); } } // 46: delete else if (this.inputfocused == true && event.keyCode === 46) { let index = this.tagLiSelected?.getAttribute("data-key"); this.removeHistorySearch(index); } }, onClickOutside() { if (this.inputPopupState == 1 || this.inputPopupState == 3) { this.inputPopupState = 0; this.tagLiSelected = null; } }, selectAutocomplate(item) { this.tagLiSelected = null; this.inputPopupState = 0; this.localTextSearch = item; this.$emit("onSearchStart", this.localTextSearch); // this.$emit("onSearchStart", { // textSearch: this.localTextSearch, // searchItem: item, // }); }, selectHistorySearch(item) { this.localTextSearch = item; this.$emit("onSearchStart", this.localTextSearch); }, removeHistorySearch(x) { this.tagLiSelected = null; this.historySearch.splice(x, 1); this.saveHistorySearch(); }, saveHistorySearch() { const parsed = JSON.stringify(this.historySearch); window.localStorage.setItem([this.historySearchRecent], parsed); }, getHighlightAutocomplate(item) { if (!item) return ""; let html = ""; let key = "title"; if (this.contentKey == "qasection" || this.contentKey == "rgsection") { key = "qanon_title"; if (item?.highlight[key]) html = item.highlight[key][0]; if (!html) { let key1 = key + ".ph"; if (item?.highlight[key1]) html = item.highlight[key1][0]; } if (!html) { let key1 = key + ".fa"; if (item?.highlight[key1]) html = item.highlight[key1][0]; } let key2 = "ts_date"; if (item?._source[key2]) { html += `<span style="color: #a7a098;"> - تاریخ: ${item._source[key2]}</span>`; } } if (this.contentKey == "sanad") { Object.values(item?.highlight).forEach((element, index) => { html += element[0]; }); } return html; }, addHistorySearch(newSearch = "") { if (newSearch == "") { return; } var index = this.historySearch.indexOf(newSearch); if (index != -1) { this.historySearch.splice(index, 1); } this.historySearch.unshift(newSearch); this.saveHistorySearch(); }, saveHistorySearch() { const parsed = JSON.stringify(this.historySearch); window.localStorage.setItem([this.historySearchRecent], parsed); }, selectAutocomplate2(item) { if (!item) { if (this.localListAutocomplate.length) { item = this.localListAutocomplate[this.tagLiSelected?.value]; } } let key = "title"; if (this.contentKey == "qasection" || this.contentKey == "rgsection") key = "qanon_title"; this.inputfocused = false; this.searchinput?.blur(); this.localTextSearch = item?._source[key]; this.$emit("onSearchStart", this.localTextSearch); // this.$emit("onSearchStart", { // textSearch: this.localTextSearch, // searchItem: item, // }); if (!item) { this.localTextSearch = this.tagLiSelected?.innerText; this.addHistorySearch(this.localTextSearch); let tt = myEncodeQuery(this.localTextSearch); this.$emit("onSearchStart", tt); // this.$emit("onSearchStart", { textSearch: tt, searchItem: item }); } this.inputPopupState = 0; this.tagLiSelected = null; }, }, }; </script> <style lang="scss" scoped> .entityTheme { .btn { border-radius: 0.5rem 0 0 0.5rem !important; } } .close { svg { font-size: 0.5em; &:hover { color: #ef4444 !important; } } } .search-page__result.show { border-radius: 0.5em !important; } .input-group-text { background-color: #fff !important; } </style>