base_ui/components/global/AutoComplation.vue
2025-02-01 13:04:55 +03:30

550 lines
15 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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']">
<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>
<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" -->
<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>
</div>
<div
v-if="inputPopupState == 1"
class="search-page__result show"
>
<!-- v-click-outside="onClickOutside" -->
<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>
</div>
</div>
</template>
<script>
import { mapState } from "pinia";
import searchApi from "~/apis/searchApi";
import { useSearchStore } from "~/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>