550 lines
15 KiB
Vue
550 lines
15 KiB
Vue
<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>
|