Connecting search apis

This commit is contained in:
mustafa-rezae 2025-03-18 16:02:10 +03:30
parent 832f5dbf52
commit 3800df1b40
9 changed files with 912 additions and 477 deletions
apis
assets/haditha/font-icons
components/haditha
pages/haditha/search
types

View File

@ -1,4 +1,6 @@
export default {
search:
"repo/monir/search/@index_key/@search_type/@type_key/@listkey/@field_collapsed/@offset/@limit/@q=none",
search: {
list: "repo/monir/search/@index_key/@search_type/@type_key/@listkey/@field_collapsed/@offset/@limit/@q=none",
show: "repo/public/get/byid/@index_key/@id",
},
};

View File

@ -0,0 +1,5 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12.7158" r="10" fill="white"/>
<path d="M9 15.7158L15 9.71582" stroke="#8A92A8" stroke-linecap="round"/>
<path d="M9 9.71582L15 15.7158" stroke="#8A92A8" stroke-linecap="round"/>
</svg>

After

(image error) Size: 302 B

View File

@ -0,0 +1,17 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12.7158" r="10" fill="white"/>
<path d="M12.2796 9.32295L8.52883 13.0737C8.3805 13.222 8.24277 13.4975 8.21099 13.6988L8.00968 15.1291C7.93551 15.6482 8.29576 16.0085 8.81492 15.9343L10.2452 15.733C10.4465 15.7012 10.7326 15.5635 10.8704 15.4152L14.6211 11.6645C15.2674 11.0182 15.5746 10.2659 14.6211 9.31235C13.6781 8.36938 12.9259 8.67665 12.2796 9.32295Z" stroke="url(#paint0_linear_67_4557)" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.7395 9.86206C12.0574 11.0063 12.9473 11.8963 14.0916 12.2142" stroke="url(#paint1_linear_67_4557)" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.2796 9.32295L8.52883 13.0737C8.3805 13.222 8.24277 13.4975 8.21099 13.6988L8.00968 15.1291C7.93551 15.6482 8.29576 16.0085 8.81492 15.9343L10.2452 15.733C10.4465 15.7012 10.7326 15.5635 10.8704 15.4152L14.6211 11.6645C15.2674 11.0182 15.5746 10.2659 14.6211 9.31235C13.6781 8.36938 12.9259 8.67665 12.2796 9.32295Z" stroke="#8A92A8" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.7395 9.86206C12.0574 11.0063 12.9473 11.8963 14.0916 12.2142" stroke="#8A92A8" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_67_4557" x1="15.274" y1="8.71582" x2="7.53191" y2="8.85851" gradientUnits="userSpaceOnUse">
<stop stop-color="#D284FF"/>
<stop offset="1" stop-color="#4D00FF"/>
</linearGradient>
<linearGradient id="paint1_linear_67_4557" x1="14.1071" y1="9.86206" x2="11.5871" y2="9.90852" gradientUnits="userSpaceOnUse">
<stop stop-color="#D284FF"/>
<stop offset="1" stop-color="#4D00FF"/>
</linearGradient>
</defs>
</svg>

After

(image error) Size: 1.8 KiB

View File

@ -3,7 +3,8 @@ import type { InputMenuItem } from "@nuxt/ui";
import hadithaApi from "../../apis/hadithaApi";
import type { HadithResponseModel } from "../../types/hadithType";
import { useStorage } from "@vueuse/core";
import type { MaybeArrayOfArray } from "@nuxt/ui/runtime/types/utils.js";
import * as z from "zod";
import type { FormSubmitEvent } from "@nuxt/ui";
// #region props
const props = defineProps({
@ -25,10 +26,15 @@ const userSearchHistory = useStorage(
"userSearchHistory",
new Set() // Initial value
);
const searchTerm = ref("");
const searchTerm = useStorage<string>("searchPhrase", "");
const open = ref(false);
const loading = ref(false);
const httpService = useNuxtApp()["$http"];
const search_type = ref("normal");
const type_key = ref("hadith");
const synonymOne = ref(false);
// If you want to share state across multiple components,
// you can use the same key in useState. Nuxt will ensure
@ -42,82 +48,97 @@ const doneTypingInterval = ref<number>(1000);
// #region reactive
const state = reactive({
list: [],
filters: [
{
label: "معنایی",
items: [],
},
{
label: "ترجمه",
items: [],
},
{
label: "مترادف",
items: [
{
label: "جستجو در همه",
vector: {
label: "معنایی",
value: "vector",
icon: "i-haditha-robot-indicator",
},
type: {
icon: "",
value: "normal",
label: "نوع",
items: [
{
label: "جستجو در همه",
onSelect(e: Event) {
console.info(e);
search_type.value = "normal";
},
{
label: "فقط در متن عربی حدیث",
},
{
label: "فقط در متن عربی حدیث",
onSelect(e: Event) {
search_type.value = "arabic";
},
{
label: "فقط در ترجمه ها",
},
{
label: "فقط در ترجمه ها",
onSelect(e: Event) {
search_type.value = "translations";
},
{
label: "فقط در شروح",
},
{
label: "فقط در شروح",
onSelect(e: Event) {
search_type.value = "descriptions";
},
],
},
{
label: "نوع",
items: [
{
label: "جستجو در همه",
},
{
label: "فقط در متن عربی حدیث",
},
{
label: "فقط در ترجمه ها",
},
{
label: "فقط در شروح",
},
],
},
{
label: "عین عبارت",
items: [
{
label: "جستجو در همه",
},
{
label: "فقط در متن عربی حدیث",
},
{
label: "فقط در ترجمه ها",
},
{
label: "فقط در شروح",
},
],
},
],
},
],
},
synonym: {
value: "synonym",
label: "مترادف",
icon: "i-haditha-chevron-down",
items: [
{
label: "جستجو در همه",
slot: "arabic",
},
{
label: "فقط در متن عربی حدیث",
slot: "arabic",
},
{
label: "فقط در ترجمه ها",
slot: "arabic",
},
{
label: "فقط در شروح",
slot: "arabic",
},
],
},
type_key: {
label: "ترجمه",
value: "hadith",
},
phrase: {
label: "عین عبارت",
value: "normal",
},
});
// #endregion reactive
// #region watch
// watch(
// () => route,
// (newRoute, oldRoute) => {
// console.info(newRoute.query);
// if (newRoute.query) searchTerm.value = <string>newRoute.query.q;
// },
// { immediate: true }
// );
// #endregion emits
onMounted(() => {
if (searchTerm.value.length) sendQuery();
});
// #region methods
const clearSimilar = () => {
console.info("clearSimilar");
};
const onBlur = () => {
console.info("onBlur");
};
const onChange = () => {
console.info("onChange");
sendQuery();
};
const onUpdateModel = (newVal: boolean | InputMenuItem | any) => {
console.info("onUpdateModel", newVal);
};
@ -132,15 +153,24 @@ const onKeyUp = () => {
}, doneTypingInterval.value);
};
const setType = (type: string) => {
search_type.value = type;
sendQuery();
};
const setKey = (type: string) => {
type_key.value = type;
sendQuery();
};
const sendQuery = async () => {
if (loading.value) return;
loading.value = true;
let url = hadithaApi.search;
let url = hadithaApi.search.list;
url = url.replace("@index_key", "dhparag");
url = url.replace("@search_type", "normal");
url = url.replace("@type_key", "hadith");
url = url.replace("@search_type", search_type.value); // normal, and , phrase, vector, synonym
url = url.replace("@type_key", type_key.value); // hadith, hadith_fa, hadith_ar, hadith_shr
url = url.replace("@offset", "0");
url = url.replace("@limit", "10");
url = url.replace("@listkey", "normal");
@ -150,34 +180,69 @@ const sendQuery = async () => {
searchTerm.value.length ? `q=${searchTerm.value}` : "q=none"
);
// const baseURL =
// config.public.NUXT_PUBLIC_BASE_URL + config.public.NUXT_PUBLIC_API_NAME;
// fetch search list from backend(ssr)
const { data, status, error, refresh, clear } =
await useHadithaSearchComposable<HadithResponseModel>(url, {
method: "post",
return await httpService
.postRequest(url)
.then((res) => {
// pass res and search query to the parent.
emit("response-ready", {
res: res,
searchQuery: searchTerm.value,
});
loading.value = false;
// check if search term is not empty
if (searchTerm.value) userSearchHistory.value.add(searchTerm.value); // Add the value to the Set
// close the history dropdown menu
open.value = false;
// store search phrase
useStorage("searchPhrase", searchTerm.value);
})
.finally(() => {
loading.value = false;
});
if (status.value == "success") {
loading.value = false;
}
// pass res and search query to the parent.
emit("response-ready", {
res: data.value,
searchQuery: searchTerm.value,
});
// check if search term is not empty
if (searchTerm.value) userSearchHistory.value.add(searchTerm.value); // Add the value to the Set
// close the history dropdown menu
open.value = false;
};
// ------------------- form -------------------
type Schema = z.output<typeof schema>;
const toast = useToast();
const schema = z.object({
name: z.string(),
});
const showForm = ref(false);
const isSynonymPopupOpen = ref(false);
const Formstate = reactive({
name: "",
});
async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({
title: "Success",
description: "The form has been submitted.",
color: "success",
});
console.log(event.data);
}
// get synonyms
async function openSynonymPopup(type: string) {
search_type.value = type;
console.info("openSynonymPopup");
sendQuery().then(() => {
isSynonymPopupOpen.value = true;
});
}
// #endregion methods
</script>
<template>
<div class="haditha-search-root-wrapper">
<div class="haditha-search-root">
<div class="haditha-search-root" :class="{ 'no-backdrop': showPrevSearch }">
<!-- وقتی کاربر در صفحه نمایش بر روی مشابه کلیک میکند و به صفحه جستجو وارد میشود. -->
<div v-if="showPrevSearch" class="prev-search-item flex items-center">
<span class="total">۴۷ مشابه </span>
<span class="text me-auto">
@ -191,6 +256,8 @@ const sendQuery = async () => {
@click="clearSimilar"
/>
</div>
<!-- <client-only> -->
<div class="search-input">
<UInputMenu
class="w-full focus:placeholder-gray-800"
@ -212,7 +279,7 @@ const sendQuery = async () => {
highlightOnHover
@focus="open = true"
@blur="open = false"
@change="onChange"
@change="sendQuery"
@update:modelValue="onUpdateModel"
@update:searchTerm="onUpdateModel"
@keydown="onKeyDown"
@ -228,26 +295,209 @@ const sendQuery = async () => {
>
<!-- <UIcon name="i-lucide-search" /> -->
</UButton>
<!-- </client-only> -->
</div>
<div class="search-filter my-3 space-x-2" v-if="props.showFilter">
<UDropdownMenu
v-for="(filter, index) in state.filters"
:items="filter.items"
:content="{
align: 'start',
side: 'bottom',
sideOffset: 8,
}"
:ui="{
content: 'w-48',
}"
>
<div
class="search-filter flex items-center my-3 justify-between"
v-if="props.showFilter && searchTerm.length"
>
<div class="flex items-center space-x-2">
<!-- معنایی -->
<!-- @click.self="search_type = 'vector'" -->
<UButton
@click.self="setType('vector')"
:class="{ active: search_type == 'vector' }"
type="button"
class="filter-item"
:label="filter.label"
:trailingIcon="filter.items?.length ? 'i-haditha-chevron-down' : ''"
/>
</UDropdownMenu>
:icon="state.vector.icon"
>
{{ state.vector.label }}
<UIcon
v-if="search_type == 'vector'"
@click.self="search_type = 'normal'"
name="i-haditha-close-bg-circle"
size="20px"
>
</UIcon>
</UButton>
<!-- مترادف -->
<UPopover
:content="{
align: 'start',
side: 'bottom',
sideOffset: 8,
}"
:ui="{
content: 'popover-root-content',
}"
v-model:open="isSynonymPopupOpen"
>
<UButton
@click="openSynonymPopup('synonym')"
class="filter-item"
:label="state.synonym.label"
trailingIcon="i-haditha-dropdown-chevron-down"
/>
<template #content>
<div class="synonymItem px-2 py-4">
<div class="flex justify-between items-center p-3 mb-2">
<span class="title"> نماز </span>
<USwitch dir="ltr" v-model="synonymOne" />
</div>
<div class="flex items-center px-2">
<UButton
type="button"
@click="true"
class="me-2.5 mb-3.5 promotion-item"
:class="{ active: false }"
>
صلات
<!-- <UIcon name="i-haditha-close-bg-circle" size="12px"> </UIcon> -->
</UButton>
<UButton
type="button"
@click="true"
class="me-2.5 mb-3.5 promotion-item"
:class="{ active: false }"
>
عبادت
<!-- <UIcon name="i-haditha-close-bg-circle" size="12px"> </UIcon> -->
</UButton>
<UButton
type="button"
@click="true"
class="me-2.5 mb-3.5 add-button"
:class="{ active: false }"
icon="i-haditha-add"
>
</UButton>
</div>
</div>
<USeparator class="px-2" color="neutral" type="solid" size="xs" />
<div class="synonymItem px-2 py-4">
<div class="flex justify-between items-center p-3 mb-2">
<span class="title"> نماز </span>
<USwitch dir="ltr" v-model="synonymOne" />
</div>
<div class="flex items-center px-2">
<UButton
type="button"
@click="true"
class="me-2.5 mb-3.5 promotion-item"
:class="{ active: false }"
>
صلات
<!-- <UIcon name="i-haditha-close-bg-circle" size="12px"> </UIcon> -->
</UButton>
<UButton
type="button"
@click="true"
class="me-2.5 mb-3.5 promotion-item"
:class="{ active: false }"
>
عبادت
<!-- <UIcon name="i-haditha-close-bg-circle" size="12px"> </UIcon> -->
</UButton>
<UButton
v-if="!showForm"
type="button"
@click="showForm = true"
class="me-2.5 mb-3.5 add-button"
:class="{ active: false }"
icon="i-haditha-add"
>
</UButton>
<UForm
v-else
:schema="schema"
:state="state"
class="w-25 me-2.5 mb-3.5"
>
<UFormField name="name" required size="md">
<UInput
v-model="Formstate.name"
placeholder="بنویسید ..."
/>
</UFormField>
</UForm>
</div>
</div>
<USeparator class="px-2" color="neutral" type="solid" size="xs" />
</template>
</UPopover>
<!-- ترجمه -->
<UButton
@click.self="setKey('hadith_fa')"
:class="{ active: type_key == 'hadith_fa' }"
type="button"
class="filter-item"
>
{{ state.type_key.label }}
<UIcon
v-if="type_key == 'hadith_fa'"
@click.self="setKey('hadith')"
name="i-haditha-close-bg-circle"
size="20px"
>
</UIcon>
</UButton>
<!-- عین عبارت -->
<UButton
@click.self="setType('phrase')"
:class="{ active: search_type == 'phrase' }"
class="filter-item"
>
{{ state.phrase.label }}
<UIcon
v-if="search_type == 'phrase'"
@click.self="search_type = 'normal'"
name="i-haditha-close-bg-circle"
size="20px"
>
</UIcon>
</UButton>
</div>
<div>
<!-- نوع -->
<UDropdownMenu
:items="state.type.items"
:content="{
align: 'start',
side: 'bottom',
sideOffset: 8,
}"
:ui="{
content: 'w-48',
}"
:class="{ active: state.type.value == 'hadith' }"
>
<UButton
class="filter-item"
:label="state.type.label"
trailingIcon="i-haditha-dropdown-chevron-down"
>
{{ state.type.label }}
<UIcon
v-if="state.type.value != 'normal'"
@click.self="state.type.value = 'normal'"
name="i-haditha-close-bg-circle"
size="20px"
>
</UIcon>
</UButton>
<!-- <template #item> item </template> -->
<!-- <template #item-label> item label </template> -->
</UDropdownMenu>
</div>
</div>
</div>
</template>
@ -339,50 +589,6 @@ const sendQuery = async () => {
.search-input {
position: relative;
}
.search-filter {
.filter-item {
/* width: 81px; */
height: 40px;
border-radius: 12px;
border-width: 0.3px;
padding-top: 8px;
padding-right: 12px;
padding-bottom: 8px;
padding-left: 12px;
gap: 4px;
background-color: #fff;
border: 0.3px solid #e0e0e0;
box-shadow: 0px 1px 4px 0px #0000000d;
color: #8a92a8;
font-family: IRANSansX;
font-weight: 400;
font-size: 13px;
line-height: 20px;
letter-spacing: 0%;
text-align: right;
&.active {
color: linear-gradient(102.02deg, #4be8ae 7.38%, #00a762 91.78%);
* {
color: #fff;
}
}
}
}
&.search-page {
&::before {
content: none;
}
.my-trailing-button {
/* width: 32px; */
/* height: 32px; */
}
.haditha-search-input {
height: 56px;
}
}
}
}
</style>
@ -429,7 +635,7 @@ const sendQuery = async () => {
height: 72px;
justify-content: space-between;
padding-top: 12px;
padding-right: 12px;
/* padding-right: 12px; */
padding-bottom: 12px;
padding-left: 12px;
border-radius: 12px;
@ -447,5 +653,129 @@ const sendQuery = async () => {
text-align: right;
color: #a7acbe;
}
.search-filter {
.filter-item {
/* width: 81px; */
height: 40px;
border-radius: 12px;
border-width: 0.3px;
padding-top: 8px;
padding-right: 12px;
padding-bottom: 8px;
padding-left: 12px;
gap: 4px;
background-color: #fff;
border: 0.3px solid #e0e0e0;
box-shadow: 0px 1px 4px 0px #0000000d;
color: #8a92a8;
font-family: IRANSansX;
font-weight: 400;
font-size: 13px;
line-height: 20px;
letter-spacing: 0%;
text-align: right;
&.active {
background-image: linear-gradient(
102.02deg,
#4be8ae 7.38%,
#00a762 91.78%
);
border-color: #4be8ae;
color: #fff;
box-shadow: none;
}
}
}
&.search-page {
.haditha-search-root {
&::before {
content: none;
}
.my-trailing-button {
width: 40px;
height: 40px;
span.iconify {
width: 25px;
height: 25px;
}
}
.haditha-search-input {
height: 56px;
}
}
}
}
.popover-root-content {
width: 20.5em;
height: 17.75em;
/* gap: 8px; */
/* border-radius: 16px; */
border-width: 0.3px;
/* padding-top: 16px; */
/* padding-right: 8px; */
/* padding-bottom: 16px; */
/* padding-left: 8px; */
background: #ffffff;
border: 0.3px solid #e0e0e0;
box-shadow: 0px 8px 20px 0px #0000001a;
.synonymItem {
.title {
font-family: IRANSansX;
font-weight: 400;
font-size: 14px;
line-height: 100%;
letter-spacing: 0%;
text-align: center;
vertical-align: middle;
color: #8a92a8;
}
/* border-bottom: 1px solid #d9d9d9; */
.promotion-item {
width: 52.599998474121094;
height: 36;
gap: 4px;
border-radius: 8px;
border-width: 0.3px;
padding-top: 11px;
padding-right: 12px;
padding-bottom: 11px;
padding-left: 12px;
border: 0.3px solid #d9d9d9;
background: #f0f1f4;
font-family: IRANSansX;
font-weight: 400;
font-size: 12px;
line-height: 100%;
letter-spacing: 0%;
text-align: right;
color: #626b84;
&.active {
background: linear-gradient(320.71deg, #b9fde0 6.56%, #e4f9f0 69.69%);
border: 0.3px solid #29d985;
color: #626b84;
}
}
.add-button {
width: 48;
height: 36;
gap: 4px;
border-radius: 8px;
border-width: 1px;
padding-right: 11px;
padding-left: 11px;
background: #ffffff;
border: 1px solid #f0f1f4;
color: #8a92a8;
}
}
}
</style>

View File

@ -15,29 +15,36 @@ const props = defineProps({
default: "/img/haditha/no-data.png",
},
});
const route = useRoute();
const router = useRouter();
const modal = useModal();
const isModalOpen = ref(false);
// const modal = useModal();
// const isModalOpen = ref(false);
function openModal() {
function openModal(selectedItem) {
// modal.open(SearchShow, { title: "Welcome" });
isModalOpen.value = true;
}
async function closeModal() {
await modal.close();
}
function resetModal() {
modal.reset();
}
function updateModalTitle() {
modal.patch({ title: "Updated Title" });
// isModalOpen.value = true;
router.push({
name: "hadithaSearchShow",
params: {
id: selectedItem._id,
slug: "no-slug",
},
});
}
// async function closeModal() {
// await modal.close();
// }
// function resetModal() {
// modal.reset();
// }
// function updateModalTitle() {
// modal.patch({ title: "Updated Title" });
// }
// components declaration
const SearchShow = defineAsyncComponent(() =>
import("@haditha/components/haditha/search-page/SearchShow.vue")
);
// const SearchShow = defineAsyncComponent(() =>
// import("@haditha/components/haditha/search-page/SearchShow.vue")
// );
</script>
<template>
@ -64,7 +71,8 @@ const SearchShow = defineAsyncComponent(() =>
></p>
<div class="flex justify-end">
<p class="reference">
{{ item._source.address.vol_title }}، صفحه {{ item._source.address.page_num }}
{{ item._source.address.vol_title }}، صفحه
{{ item._source.address.page_num }}
</p>
</div>
</div>
@ -78,7 +86,7 @@ const SearchShow = defineAsyncComponent(() =>
</no-data>
</div>
<UModal
<!-- <UModal
v-model:open="isModalOpen"
:dismissible="false"
:ui="{
@ -93,13 +101,13 @@ const SearchShow = defineAsyncComponent(() =>
close: 'modal-close',
}"
>
<!-- <template #header><div class="hidden"></div></template> -->
<!-- <template #content></template> -->
<template #header><div class="hidden"></div></template>
<template #content></template>
<template #body>
<search-show @close="isModalOpen = !isModalOpen"></search-show>
</template>
<!-- <template #footer></template> -->
</UModal>
<<template #footer></template>
</UModal> -->
</div>
</template>

View File

@ -1,13 +1,27 @@
<script setup>
<script setup lang="ts">
// 1. Imports
// 2. Metas
// 3. Props
// 2. Reactive State
// 3. Computed Properties
// 4. Functions / Methods
// 5. Lifecycle Hooks
// 6. Watchers
// #region imports
import hadithaApi from "@haditha/apis/hadithaApi";
import type { HadithResponseModel } from "@haditha/types/hadithType";
import type { HadithResponseShowModel } from "~/systems/hadith_ui/types/hadithType";
// #endregion imports
// #region meta
definePageMeta({
layout: false,
name: "hadithaSearchShow",
});
useHead({
name: "hadithaSearchShow",
title: `${import.meta.env.VITE_HADITH_PAGE_TITLE} | ${
props.selectedItem._source.meta.hadith_masoum ?? "بدون عنوان"
}`,
title: `${import.meta.env.VITE_HADITH_PAGE_TITLE}`,
meta: [
{ name: "description", content: "کاوش با هوش مصنوعی در احادیث اسلامی" },
],
@ -15,23 +29,52 @@ useHead({
class: import.meta.env.VITE_HADITH_SYSTEM,
},
});
// #endregion imports
const props = defineProps({
selectedItem: {
type: Object,
default() {
return {};
},
},
});
// #region props
// const props = defineProps({
// selectedItem: {
// type: Object,
// default() {
// return {};
// },
// },
// });
// #endregion imports
// #region refs and reactives
const emit = defineEmits(["close"]);
const route = useRoute();
const router = useRouter();
const loading = ref(false);
// const open = ref(false);
const closeModal = () => {
emit("close");
const state = reactive({
selectedItem: {} as HadithResponseShowModel,
});
// #endregion refs and reactives
// #region methods
const fetchData = async () => {
if (loading.value) return;
loading.value = true;
let url = hadithaApi.search.show;
url = url.replace("@index_key", "dhparag");
url = url.replace("@id", route.params.id);
// fetch search list from backend(ssr)
const { data, status, error, refresh, clear } =
await useHadithaSearchComposable<HadithResponseShowModel>(url, {
method: "get",
});
if (status.value == "success") {
loading.value = false;
state.selectedItem = <HadithResponseShowModel>data.value;
}
};
const goToTheSearch = () => {
const goToTheSearch = (type: string) => {
router.push({
name: "hadithaSearch",
});
@ -41,136 +84,157 @@ const goToTheChatbot = () => {
name: "hadithaChatbot",
});
};
// #endregion methods
fetchData();
</script>
<template>
<div class="search-show-modal">
<div class="body-header">
<span class="top-left-bgi z-0"></span>
<div class="modal-title flex justify-between">
<NuxtImg
fit="auto"
quality="80"
placeholder
src="/img/haditha/haditha-title.svg"
/>
<UContainer
ui="{
base: 'sm:px-6 lg:px-4',
}"
class="page-inner-container sm:px-6 lg:px-4 py-8"
>
<div class="search-show-page">
<div class="body-header">
<span class="top-left-bgi z-0"></span>
<div class="modal-title flex justify-between">
<NuxtImg
fit="auto"
quality="80"
placeholder
src="/img/haditha/haditha-title.svg"
/>
<UButton
icon="i-haditha-close"
color="neutral"
variant="ghost"
class="close-btn"
@click="closeModal"
/>
</div>
</div>
<div class="body-content">
<div class="h-full flex flex-col justify-center z-2">
<div class="bg-container h-full">
<div class="header flex">
<UButton variant="ghost" class="bookmark-btn" icon="i-haditha-tag">
</UButton>
<div class="referene">
<span> نشانی: </span>
{{ props.selectedItem._source.address.vol_title }}، صفحه
{{ props.selectedItem._source.address.page_num }}
</div>
</div>
<div class="content">
<div class="search-item">
<div class="text-arabic-section">
<div class="section-header">
<span class="section-title">
{{ props.selectedItem._source.meta.hadith_masoum ?? "بدون عنوان" }}
</span>
<UButton variant="ghost" class="copy-btn" label="کپی" />
</div>
<div class="arabic-text">
<p>بدون متن عربی</p>
</div>
</div>
<div class="separator"></div>
<div class="text-persian-section">
<div class="section-header">
<span class="section-title"> ترجمه </span>
<UButton variant="ghost" class="copy-btn" label="کپی" />
</div>
<p class="from">
{{ props.selectedItem._source.meta.hadith_masoum ?? "بدون عنوان" }}:
</p>
<p
class="persian-text"
v-html="props.selectedItem.highlight['content.fa'] ?? props.selectedItem._source.content"
></p>
</div>
<div class="separator"></div>
<div class="text-description-section">
<div class="section-header">
<span class="section-title"> شرح </span>
<UButton variant="ghost" class="copy-btn" label="کپی" />
</div>
<p
class="description-item"
v-html="props.selectedItem.highlight['content.fa'] ?? props.selectedItem._source.content"
></p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="body-footer">
<div class="mt-5 z-2">
<div class="flex justify-between actions">
<UButton
class="similar-btn"
icon="i-haditha-search-3"
label="مشابه"
icon="i-haditha-close"
color="neutral"
variant="outline"
@click.prevent="goToTheSearch"
/>
<UButton
class="explore-btn"
trailing-icon="i-haditha-explore"
label="کاوش"
variant="solid"
@click.prevent="goToTheChatbot"
/>
</div>
<div class="flex justify-between pagination">
<UButton
class="prev-haditha"
label="حدیث قبل"
color=""
variant="soft"
icon="i-haditha-chevron-right"
/>
<UButton
class="next-haditha"
label="حدیث بعد"
color=""
variant="soft"
trailing-icon="i-haditha-chevron-left"
variant="ghost"
class="close-btn"
@click="goToTheSearch('normal')"
/>
</div>
</div>
<div class="body-content">
<div class="h-full flex flex-col justify-center z-2">
<div class="bg-container h-full">
<div class="header flex">
<UButton
variant="ghost"
class="bookmark-btn"
icon="i-haditha-tag"
>
</UButton>
<div class="referene">
<span> نشانی: </span>
{{ state.selectedItem?._source?.address?.vol_title }}، صفحه
{{ state.selectedItem?._source?.address?.page_num }}
</div>
</div>
<div class="content">
<div class="search-item">
<div class="text-arabic-section">
<div class="section-header">
<span class="section-title">
{{
state.selectedItem?._source?.meta?.hadith_masoum ??
"بدون عنوان"
}}
</span>
<UButton variant="ghost" class="copy-btn" label="کپی" />
</div>
<div class="arabic-text">
<p>بدون متن عربی</p>
</div>
</div>
<div class="separator"></div>
<div class="text-persian-section">
<div class="section-header">
<span class="section-title"> ترجمه </span>
<UButton variant="ghost" class="copy-btn" label="کپی" />
</div>
<p class="from">
{{
state.selectedItem?._source?.meta?.hadith_masoum ??
"بدون عنوان"
}}:
</p>
<p
class="persian-text"
v-html="state.selectedItem?._source?.content"
></p>
</div>
<div class="separator"></div>
<div class="text-description-section">
<div class="section-header">
<span class="section-title"> شرح </span>
<UButton variant="ghost" class="copy-btn" label="کپی" />
</div>
<p
class="description-item"
v-html="state.selectedItem?._source?.content"
></p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="body-footer">
<div class="mt-5 z-2">
<div class="flex justify-between actions">
<UButton
class="similar-btn"
icon="i-haditha-search-3"
label="مشابه"
color="neutral"
variant="outline"
@click.prevent="goToTheSearch('similar')"
/>
<UButton
class="explore-btn"
trailing-icon="i-haditha-explore"
label="کاوش"
variant="solid"
@click.prevent="goToTheChatbot"
/>
</div>
<div class="flex justify-between pagination">
<UButton
class="prev-haditha"
label="حدیث قبل"
color=""
variant="soft"
icon="i-haditha-chevron-right"
/>
<UButton
class="next-haditha"
label="حدیث بعد"
color=""
variant="soft"
trailing-icon="i-haditha-chevron-left"
/>
</div>
</div>
</div>
</div>
</div>
</UContainer>
</template>
<!-- because of the buttons, using without scoped. -->
<style>
.search-show-modal {
.search-show-page {
.body-header {
.modal-title {
padding: 0 0.5em 1.5em;
@ -248,8 +312,8 @@ const goToTheChatbot = () => {
}
}
.content {
height: calc(100dvh - 29em);
overflow-y: auto;
/* height: calc(100dvh - 29em); */
/* overflow-y: auto; */
.search-item {
padding: 1em 0 1em 0.1em;

View File

@ -1,5 +1,4 @@
<script setup>
definePageMeta({
layout: false,
name: "hadithaSearch",
@ -15,7 +14,6 @@ useHead({
},
});
const img = useImage();
const searchQuery = ref("");
const total = ref(0);
@ -47,11 +45,9 @@ const backgroundImageStyle = computed(() => {
const renderContent = (payload) => {
console.info(payload);
total.value = payload.res.hits.total.value;
state.searchList = payload.res.hits.hits;
searchQuery.value = payload.searchQuery;
total.value = payload.res?.hits.total.value ?? 0;
state.searchList = payload.res?.hits.hits ?? [];
searchQuery.value = payload.searchQuery ?? "";
};
// components declaration

View File

@ -1,339 +1,352 @@
export interface HadithResponseModel {
status: number;
message: string;
postion: number;
meta: any;
took: number;
timed_out: boolean;
_shards: Shards;
hits: Hits;
aggregations: Aggregations;
params: Params;
}
export interface HadithResponseShowModel {
status: number
message: string
postion: number
meta: any
took: number
timed_out: boolean
_shards: Shards
hits: Hits
aggregations: Aggregations
params: Params
_index: string
_id: string
_version: number
_seq_no: number
_primary_term: number
found: boolean
_source: Source
}
export interface Shards {
total: number
successful: number
skipped: number
failed: number
total: number;
successful: number;
skipped: number;
failed: number;
}
export interface Hits {
total: Total
max_score: number
hits: Hit[]
total: Total;
max_score: number;
hits: Hit[];
}
export interface Total {
value: number
relation: string
value: number;
relation: string;
}
export interface Hit {
_index: string
_id: string
_score: number
_source: Source
highlight: Highlight
_index: string;
_id: string;
_score: number;
_source: Source;
highlight?: Highlight;
}
export interface Source {
id: string
address: Address
content: string
meta: Meta
parag_order: number
style_tag: string
heading_level: number
sub_ayehs: any[]
sub_hadithes: any[]
type_key: string
type_title: string
lang: string
tocs: string[]
xml: string
ai_embeddings: number[]
ai_classes: AiClass[]
id: string;
address: Address;
content: string;
meta: Meta;
parag_order: number;
style_tag: string;
heading_level: number;
sub_ayehs: any[];
sub_hadithes: any[];
type_key: string;
type_title: string;
lang: string;
tocs: string[];
xml: string;
ai_embeddings: number[];
ai_classes: AiClass[];
}
export interface Address {
book_title: string
page_end: number
page_num: number
vol_id: string
vol_num: string
vol_title: string
book_title: string;
page_end: number;
page_num: number;
vol_id: string;
vol_num: string;
vol_title: string;
}
export interface Meta {
hadith_description: string
hadith_sanad: string
hadith_references: any[]
hadith_description: string;
hadith_sanad: string;
hadith_references: any[];
}
export interface AiClass {
score: number
label: string
score: number;
label: string;
}
export interface Highlight {
"content.fa": string[]
xml: string[]
"ai_classes.label"?: string[]
"content.ph": string[]
content: string[]
type_key: string[]
"content.fa": string[];
xml: string[];
"ai_classes.label"?: string[];
"content.ph": string[];
content: string[];
type_key: string[];
}
export interface Aggregations {
book_title: BookTitle
ai_classes: AiClasses
ai_keywords: AiKeywords
vol_title: VolTitle
type_title: TypeTitle
lang: Lang
book_title: BookTitle;
ai_classes: AiClasses;
ai_keywords: AiKeywords;
vol_title: VolTitle;
type_title: TypeTitle;
lang: Lang;
}
export interface BookTitle {
doc_count_error_upper_bound: number
sum_other_doc_count: number
buckets: Bucket[]
doc_count_error_upper_bound: number;
sum_other_doc_count: number;
buckets: Bucket[];
}
export interface Bucket {
key: string
doc_count: number
key: string;
doc_count: number;
}
export interface AiClasses {
doc_count_error_upper_bound: number
sum_other_doc_count: number
buckets: Bucket2[]
doc_count_error_upper_bound: number;
sum_other_doc_count: number;
buckets: Bucket2[];
}
export interface Bucket2 {
key: string
doc_count: number
key: string;
doc_count: number;
}
export interface AiKeywords {
doc_count_error_upper_bound: number
sum_other_doc_count: number
buckets: Bucket3[]
doc_count_error_upper_bound: number;
sum_other_doc_count: number;
buckets: Bucket3[];
}
export interface Bucket3 {
key: string
doc_count: number
key: string;
doc_count: number;
}
export interface VolTitle {
doc_count_error_upper_bound: number
sum_other_doc_count: number
buckets: Bucket4[]
doc_count_error_upper_bound: number;
sum_other_doc_count: number;
buckets: Bucket4[];
}
export interface Bucket4 {
key: string
doc_count: number
key: string;
doc_count: number;
}
export interface TypeTitle {
doc_count_error_upper_bound: number
sum_other_doc_count: number
buckets: Bucket5[]
doc_count_error_upper_bound: number;
sum_other_doc_count: number;
buckets: Bucket5[];
}
export interface Bucket5 {
key: string
doc_count: number
key: string;
doc_count: number;
}
export interface Lang {
doc_count_error_upper_bound: number
sum_other_doc_count: number
buckets: Bucket6[]
doc_count_error_upper_bound: number;
sum_other_doc_count: number;
buckets: Bucket6[];
}
export interface Bucket6 {
key: string
doc_count: number
key: string;
doc_count: number;
}
export interface Params {
index: string
body: Body
index: string;
body: Body;
}
export interface Body {
query: Query
from: string
size: string
aggs: Aggs
highlight: Highlight2
query: Query;
from: string;
size: string;
aggs: Aggs;
highlight: Highlight2;
}
export interface Query {
bool: Bool
bool: Bool;
}
export interface Bool {
must: Must[]
must: Must[];
}
export interface Must {
bool: Bool2
bool: Bool2;
}
export interface Bool2 {
should?: Should[]
filter?: Filter
should?: Should[];
filter?: Filter;
}
export interface Should {
match_phrase?: MatchPhrase
match?: Match
bool?: Bool3
match_phrase?: MatchPhrase;
match?: Match;
bool?: Bool3;
}
export interface MatchPhrase {
"content.ph": ContentPh
"content.ph": ContentPh;
}
export interface ContentPh {
query: string
boost: number
query: string;
boost: number;
}
export interface Match {
"content.fa"?: ContentFa
content?: Content
"content.ar"?: ContentAr
"content.fa"?: ContentFa;
content?: Content;
"content.ar"?: ContentAr;
}
export interface ContentFa {
query: string
boost: number
query: string;
boost: number;
}
export interface Content {
query: string
boost: number
query: string;
boost: number;
}
export interface ContentAr {
query: string
boost: number
query: string;
boost: number;
}
export interface Bool3 {
must: Must2[]
must: Must2[];
}
export interface Must2 {
match: Match2
match: Match2;
}
export interface Match2 {
book_title: BookTitle2
book_title: BookTitle2;
}
export interface BookTitle2 {
query: string
boost: number
query: string;
boost: number;
}
export interface Filter {
bool: Bool4
bool: Bool4;
}
export interface Bool4 {
must: Must3[]
must: Must3[];
}
export interface Must3 {
term: Term
term: Term;
}
export interface Term {
type_key: string
type_key: string;
}
export interface Aggs {
book_title: BookTitle3
ai_classes: AiClasses2
ai_keywords: AiKeywords2
vol_title: VolTitle2
type_title: TypeTitle2
lang: Lang2
book_title: BookTitle3;
ai_classes: AiClasses2;
ai_keywords: AiKeywords2;
vol_title: VolTitle2;
type_title: TypeTitle2;
lang: Lang2;
}
export interface BookTitle3 {
terms: Terms
terms: Terms;
}
export interface Terms {
field: string
size: number
field: string;
size: number;
}
export interface AiClasses2 {
terms: Terms2
terms: Terms2;
}
export interface Terms2 {
field: string
size: number
field: string;
size: number;
}
export interface AiKeywords2 {
terms: Terms3
terms: Terms3;
}
export interface Terms3 {
field: string
size: number
field: string;
size: number;
}
export interface VolTitle2 {
terms: Terms4
terms: Terms4;
}
export interface Terms4 {
field: string
size: number
field: string;
size: number;
}
export interface TypeTitle2 {
terms: Terms5
terms: Terms5;
}
export interface Terms5 {
field: string
size: number
field: string;
size: number;
}
export interface Lang2 {
terms: Terms6
terms: Terms6;
}
export interface Terms6 {
field: string
size: number
field: string;
size: number;
}
export interface Highlight2 {
pre_tags: string[]
post_tags: string[]
fields: Fields
require_field_match: boolean
fragment_size: number
number_of_fragments: number
boundary_scanner: string
pre_tags: string[];
post_tags: string[];
fields: Fields;
require_field_match: boolean;
fragment_size: number;
number_of_fragments: number;
boundary_scanner: string;
}
export interface Fields {
"*": GeneratedType
"*": GeneratedType;
}
export interface GeneratedType {}