hadith_ui/components/hadith/AutoComplation.vue

558 lines
15 KiB
Vue
Raw Normal View History

2025-02-11 10:17:22 +00:00
<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 "../../apis/searchApi.js";
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>