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